Add build-deps libcups2-dev, libxkbcommon-dev.
[sven/vym.git] / src / vymmodel.cpp
index d3ce38483e9a1943381cdeb552e06cb486793094..bf1d88974136ce4b903cdd007c2090ecaa217b85 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "attributeitem.h"
 #include "branchitem.h"
+#include "branchpropeditor.h"
 #include "confluence-agent.h"
 #include "download-agent.h"
 #include "editxlinkdialog.h"
 #include "export-firefox.h"
 #include "export-html.h"
 #include "export-impress.h"
+#include "export-impress-filedialog.h"
 #include "export-latex.h"
 #include "export-markdown.h"
 #include "export-orgmode.h"
+#include "export-taskjuggler.h"
 #include "file.h"
 #include "findresultmodel.h"
+#include "heading-container.h"
+#include "image-container.h"
 #include "jira-agent.h"
+#include "linkobj.h"
 #include "lockedfiledialog.h"
 #include "mainwindow.h"
+#include "mapdesign.h"
+#include "mapeditor.h"
 #include "misc.h"
 #include "noteeditor.h"
 #include "options.h"
 #include "taskeditor.h"
 #include "taskmodel.h"
 #include "treeitem.h"
-#include "vymprocess.h"
+#include "vymmodelwrapper.h"
+#include "vymview.h"
 #include "warningdialog.h"
+#include "xlink.h"
 #include "xlinkitem.h"
 #include "xlinkobj.h"
-#include "xml-freemind.h"
+#include "xml-freeplane.h"
+#include "xml-ithoughts.h"
 #include "xml-vym.h"
 #include "xmlobj.h"
-
-#ifdef Q_OS_WINDOWS
-#include <windows.h>
-#endif
+#include "zip-agent.h"
 
 extern bool debug;
 extern bool testmode;
 extern bool restoreMode;
 extern QStringList ignoredLockedFiles;
 
+extern BranchPropertyEditor *branchPropertyEditor;
 extern Main *mainWindow;
 
 extern QDir tmpVymDir;
 
+extern bool useActionLog;
+extern QString actionLogPath;
+
 extern NoteEditor *noteEditor;
 extern TaskEditor *taskEditor;
 extern ScriptEditor *scriptEditor;
@@ -88,12 +100,14 @@ extern QString vymVersion;
 extern QDir vymBaseDir;
 
 extern QDir lastImageDir;
-extern QDir lastMapDir;
 extern QDir lastExportDir;
 
 extern Settings settings;
 extern QTextStream vout;
 
+extern bool usingDarkTheme;
+extern QColor vymBlueColor;
+
 uint VymModel::idLast = 0; // make instance
 
 VymModel::VymModel()
@@ -106,19 +120,51 @@ VymModel::VymModel()
 
 VymModel::~VymModel()
 {
-    // out << "Destr VymModel begin this="<<this<<"  "<<mapName<<flush;
+    //qDebug() << "Destr VymModel begin this=" << this << "  " << mapName << "zipAgent=" << zipAgent;
+
     mapEditor = nullptr;
     repositionBlocked = true;
     autosaveTimer->stop();
+    filePath.clear();
     fileChangedTimer->stop();
-    stopAllAnimation();
 
-    // qApp->processEvents();  // Update view (scene()->update() is not enough)
-    // qDebug() << "Destr VymModel end   this="<<this;
+    if (zipAgent) {
+        mainWindow->statusMessage(tr("Waiting until map is completely saved and compressed..."));
+        zipAgent->waitForFinished();
+        
+        // zipAgent might be set to nullptr already in VymModel::zipFinished
+        if (zipAgent) {
+            if (zipAgent->exitStatus() != QProcess::NormalExit) {
+                QMessageBox::critical(0, QObject::tr("Critical Error"),
+                                      QObject::tr("zip didn't exit normally"));
+            }
+            else {
+                if (zipAgent->exitCode() > 0) {
+                    QMessageBox::critical(
+                        0, QObject::tr("Critical Error"),
+                        QString("zip exit code:  %1").arg(zipAgent->exitCode()));
+                }
+            }
+            zipAgent->deleteLater();
+            zipAgent = nullptr;
+        }
+    }
 
     vymLock.releaseLock();
 
+    // Delete rootItem already now, while VymModel is still around
+    // ImageItems can ask VymModel for a path in their destructor then.
+    // Delete whole tree, XLinkItems will be queude for deletion in xlinksTrash
+    delete rootItem;
+
+    emptyXLinksTrash();
+
     delete (wrapper);
+    delete mapDesignInt;
+
+    // qDebug() << "Destr VymModel end this=" << this;
+
+    logInfo("VymModel destroyed", __func__);
 }
 
 void VymModel::clear()
@@ -126,31 +172,37 @@ void VymModel::clear()
     while (rootItem->childCount() > 0) {
         // qDebug()<<"VM::clear  ri="<<rootItem<<"
         // ri->count()="<<rootItem->childCount();
-        deleteItem(rootItem->getChildNum(0));
+        deleteItem(rootItem->childItemByRow(0));
     }
+    reposition();
 }
 
 void VymModel::init()
 {
     // No MapEditor yet
-    mapEditor = NULL;
+    mapEditor = nullptr;
+
+    // No ZipAgent yet and not saving
+    zipAgent = nullptr;
+    isSavingInt = false;
 
     // Use default author
-    author =
-        settings
-            .value("/user/name", tr("unknown user",
-                                    "default name for map author in settings"))
-            .toString();
+    authorInt = settings
+            .value("/user/name",
+                    tr("unknown user", "default name for map author in settings")).toString();
+    // MapDesign
+    mapDesignInt = new MapDesign;
 
     // States and IDs
     idLast++;
-    modelID = idLast;
+    modelIdInt = idLast;
     mapChanged = false;
     mapDefault = true;
     mapUnsaved = false;
+    buildingUndoScript = false;
 
     // Selection history
-    selModel = NULL;
+    selModel = nullptr;
     selectionBlocked = false;
     resetSelectionHistory();
 
@@ -167,6 +219,7 @@ void VymModel::init()
     mapName = fileName;
     repositionBlocked = false;
     saveStateBlocked = false;
+    dataChangedBlocked = false;
 
     autosaveTimer = new QTimer(this);
     connect(autosaveTimer, SIGNAL(timeout()), this, SLOT(autosave()));
@@ -179,34 +232,13 @@ void VymModel::init()
     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;
+    hideMode = TreeItem::HideNone;
 
     // Animation in MapEditor
     zoomFactor = 1;
-    rotationAngle = 0;
+    mapRotationInt = 0;
     animDuration = 2000;
     animCurve = QEasingCurve::OutQuint;
 
@@ -214,9 +246,6 @@ void VymModel::init()
     slideModel = new SlideModel(this);
     blockSlideSelection = false;
 
-    // Avoid recursions later
-    cleaningUpLinks = false;
-
     // Network
     netstate = Offline;
 
@@ -224,7 +253,7 @@ void VymModel::init()
     // 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))
+            QString("/vymmodel_%1").arg(modelIdInt), this))
         qWarning("VymModel: Couldn't register DBUS object!");
 #endif
 }
@@ -232,14 +261,23 @@ void VymModel::init()
 void VymModel::makeTmpDirectories()
 {
     // Create unique temporary directories
-    tmpMapDirPath = tmpVymDir.path() + QString("/model-%1").arg(modelID);
+    tmpMapDirPath = tmpVymDir.path() + QString("/model-%1").arg(modelIdInt);
     histPath = tmpMapDirPath + "/history";
     QDir d;
-    d.mkdir(tmpMapDirPath);
+    if (!d.mkdir(tmpMapDirPath))
+        qWarning() << "Couldn't create tmpMapDir=" << tmpMapDirPath;
+
+    QString s = tmpMapDirPath + "/zipDir";
+    if (!d.mkpath(s))
+        qWarning() << "Couldn't create zipDirInt=" << s;
+
+    zipDirInt.setPath(s);
 }
 
 QString VymModel::tmpDirPath() { return tmpMapDirPath; }
 
+QString VymModel::zipDirPath() { return zipDirInt.path(); }
+
 MapEditor *VymModel::getMapEditor() { return mapEditor; }
 
 VymModelWrapper *VymModel::getWrapper() { return wrapper; }
@@ -270,6 +308,9 @@ void VymModel::resetUsedFlags()
 
 QString VymModel::saveToDir(const QString &tmpdir, const QString &prefix,
                             FlagRowMaster::WriteMode flagMode, const QPointF &offset,
+                            bool writeMapAttr,
+                            bool writeMapDesign,
+                            bool writeCompleteTree,
                             TreeItem *saveSel)
 {
     // tmpdir      temporary directory to which data will be written
@@ -282,140 +323,117 @@ QString VymModel::saveToDir(const QString &tmpdir, const QString &prefix,
     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;
+
+    QString mapAttr = xml.attribute("version", vymVersion);
+    // Current map version after load still might be original one, change it now.
+    mapVersionInt = vymVersion;
+
+    if (writeMapAttr) {
+        mapAttr += xml.attribute("date", toS(QDate::currentDate())) + "\n";
+
+        if (!authorInt.isEmpty())
+            mapAttr += xml.attribute("author", authorInt) + "\n";
+        if (!titleInt.isEmpty())
+            mapAttr += xml.attribute("title", titleInt) + "\n";
+        if (!commentInt.isEmpty())
+            mapAttr += xml.attribute("comment", commentInt) + "\n";
+
+        mapAttr += xml.attribute("branchCount", QString().number(branchCount()));
+        mapAttr += xml.attribute("mapZoomFactor",
+                     QString().setNum(mapEditor->zoomFactorTarget()));
+        mapAttr += xml.attribute("mapRotation",
+                     QString().setNum(mapEditor->rotationTarget()));
     }
     header += xml.beginElement("vymmap", mapAttr);
+
+    QString design;
+
+    if (writeMapDesign)
+        design = mapDesignInt->saveToDir(tmpdir, prefix);
+
     xml.incIndent();
 
     // Find the used flags while traversing the tree
     resetUsedFlags();
 
     // Temporary list of links
-    QList<Link *> tmpLinks;
+    QList<XLink *> tmpXLinks;
 
     QString tree;
     // Build xml recursivly
-    if (!saveSel) {
+    if (writeCompleteTree) {
         // Save all mapcenters as complete map, if saveSel not set
-        tree += saveTreeToDir(tmpdir, prefix, offset, tmpLinks);
+        tree += saveTreeToDir(tmpdir, prefix, offset, tmpXLinks);
 
         // Save local settings
         tree += settings.getDataXML(destPath);
 
         // Save selection
-        if (getSelectedItem() && !saveSel)
+        if (getSelectedItems().count() > 0 && !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;
+    } else
+    {
+        if (saveSel) {
+            switch (saveSel->getType()) {
+                case TreeItem::Branch:
+                case TreeItem::MapCenter:
+                    // Save Subtree
+                    tree += ((BranchItem *)saveSel)
+                                ->saveToDir(tmpdir, prefix, offset, tmpXLinks, exportBoundingBoxes);
+                    break;
+                case TreeItem::Image:
+                    tree += ((ImageItem *)saveSel)->saveToDir(tmpdir);
+                    break;
+                case TreeItem::XLinkItemType:
+                    tree += ((XLinkItem *)saveSel)->getXLink()->saveToDir();
+                    break;
+                default:
+                    // other types shouldn't be saved directly...
+                    break;
+            }
         }
     }
 
     QString flags;
 
     // Write images and definitions of used user flags
+    standardFlagsMaster->saveDataToDir(tmpdir + "/flags/standard/", FlagRowMaster::UsedFlags);
     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);
+        userFlagsMaster->saveDataToDir(tmpdir + "/flags/user/", flagMode);
     }
 
     QString footer;
     // Save XLinks
-    for (int i = 0; i < tmpLinks.count(); ++i)
-        footer += tmpLinks.at(i)->saveToDir();
+    for (int i = 0; i < tmpXLinks.count(); ++i)
+        footer += tmpXLinks.at(i)->saveToDir();
 
-    // Save slides
-    footer += slideModel->saveToDir();
+    if (writeCompleteTree)
+        // Save slides
+        footer += slideModel->saveToDir();
 
     xml.decIndent();
     footer += xml.endElement("vymmap");
 
-    return header + flags + tree + footer;
+    return header + design + flags + tree + footer;
 }
 
 QString VymModel::saveTreeToDir(const QString &tmpdir, const QString &prefix,
-                                const QPointF &offset, QList<Link *> &tmpLinks)
+                                const QPointF &offset, QList<XLink *> &tmpXLinks)
 {
     QString s;
     for (int i = 0; i < rootItem->branchCount(); i++)
-        s += rootItem->getBranchNum(i)->saveToDir(tmpdir, prefix, offset,
-                                                  tmpLinks);
+        s += rootItem->getBranchNum(i)->saveToDir(
+                tmpdir,
+                prefix,
+                offset,
+                tmpXLinks,
+                exportBoundingBoxes);
     return s;
 }
 
@@ -434,7 +452,7 @@ void VymModel::setFilePath(QString fpath, QString destname)
 
         // If fpath is not an absolute path, complete it
         filePath = QDir(fpath).absolutePath();
-        fileDir = filePath.left(1 + filePath.lastIndexOf("/"));
+        fileDir = filePath.left(filePath.lastIndexOf("/"));
 
         // Set short name, too. Search from behind:
         fileName = basename(fileName);
@@ -457,75 +475,41 @@ 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)
+bool VymModel::loadMap(QString fname, const File::LoadMode &lmode,
+                                  const File::FileType &ftype,
+                                  const int &contentFilter,
+                                  BranchItem *insertBranch,
+                                  int insertPos)
 {
-    File::ErrorCode err = File::Success;
+    bool noError = true;
 
     // Get updated zoomFactor, before applying one read from file in the end
     if (mapEditor) {
-        zoomFactor = mapEditor->getZoomFactorTarget();
-        rotationAngle = mapEditor->getAngleTarget();
+        zoomFactor = mapEditor->zoomFactorTarget();
+        mapRotationInt = mapEditor->rotationTarget();
     }
 
-    parseBaseHandler *handler;
+    BaseReader *reader;
     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;
+        case File::VymMap:
+            reader = new VymReader(this);
+            // For imports we might want to ignore slides
+            reader->setContentFilter(contentFilter);// FIXME-4 Maybe ignore slides hardcoded, when parsing and remove contentFilter?
+            break;
+        case File::FreemindMap:
+            reader = new FreeplaneReader(this);
+            break;
+        case File::IThoughtsMap:
+            reader = new IThoughtsReader(this);
+            break;
+        default:
+            QMessageBox::critical(0, tr("Critical Parse Error"),
+                                  "Unknown FileType in VymModel::load()");
+            return false;
     }
 
-    if (lmode == NewMap) {
+    if (lmode == File::NewMap) {
         // Reset timestamp to check for later updates of file
         fileChangedTime = QFileInfo(destPath).lastModified();
 
@@ -534,14 +518,14 @@ File::ErrorCode VymModel::loadMap(QString fname, const LoadMode &lmode,
 
     bool zipped_org = zipped;
 
-    // Create temporary directory for packing
+    // Create temporary directory for unzip
     bool ok;
-    QString tmpZipDir = makeTmpDir(ok, tmpDirPath(), "unzip");
+    QString tmpUnzipDir = 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;
+        return false;
     }
 
     QString xmlfile;
@@ -549,52 +533,86 @@ File::ErrorCode VymModel::loadMap(QString fname, const LoadMode &lmode,
         xmlfile = fname;
         zipped = false;
 
-        if (lmode == NewMap || lmode == DefaultMap)
+        if (lmode == File::NewMap || lmode == File::DefaultMap)
             zipped_org = false;
-    }
-    else {
+    } else {
         // Try to unzip file
-        err = unzipDir(tmpZipDir, fname);
+        QFile file(fname);
+        if (file.size() > 2000000)
+            // Inform user that unzipping might take a while.
+            // Detailed estimation about number of branches required for progress bar 
+            // is within the zipfile and cannot be used yet.
+            mainWindow->statusMessage(tr("Uncompressing %1").arg(fname));
+
+        ZipAgent zipAgent(tmpUnzipDir, fname);
+        zipAgent.setBackgroundProcess(false);
+        zipAgent.startUnzip();
+        if (zipAgent.exitStatus() != QProcess::NormalExit)
+            noError = false;
+
+        if (file.size() > 2000000)
+            // Inform user that unzipping might take a while.
+            // Detailed estimation about number of branches required for progress bar 
+            // is within the zipfile and cannot be used yet.
+            mainWindow->statusMessage(tr("Loading %1").arg(fname), 0);
     }
 
     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()) {
+        QStringList filters;
+        filters << "*.xml";
+        QStringList xmlFileList = QDir(tmpUnzipDir).entryList(filters);
+        if (xmlFileList.count() == 1) {
+            // Only one xml file in zip archive, take this one
+            xmlfile = tmpUnzipDir + "/" + xmlFileList.first();
+        } else {
+            // Multiple xml files - which one to choose?
+            // Sometimes (at least on Windows) a zipped (!) $MAPNAME.xml also ends up in archive,
+            // which prevented subsequent loading
+            QString warning = QString("Found multiple .xml files in %1: %2").arg(fname, xmlFileList.join(", "));
+            logWarning(warning, __func__);
+            QMessageBox::warning (0, "Multiple xml files found", warning + "\n\nWill try to keep only map.xml");
+
+            // mainWindow->fileLoadFromTmp (xmlFileList);
+            // returnCode = 1; // Silently forget this attempt to load
+            if (fileType == File::VymMap) {
+                // Default map name
+                QString xfile = "map.xml";
+                if (xmlFileList.contains(xfile))
+                    xmlfile = tmpUnzipDir + "/" + xfile;
+                else {
+                    // Try $MAPNAME.xml
+                    xfile = basename(fname);
+                    xfile = tmpUnzipDir + "/" + xfile.left(xfile.lastIndexOf(".", -1, Qt::CaseSensitive)) + ".xml";
+                    QFile mfile(xfile);
+                    if (!mfile.exists()) {
+                        // $MAPNAME.xml does not exist, well, ...
+                        QMessageBox::critical(
+                            0, tr("Critical Load Error"), "Multiple maps found, but no map.xml or " + xfile);
+                        noError = false;
+                    }
+                }
+            } else if (fileType == File::IThoughtsMap) {
+                if (!xmlFileList.contains("mapdata.xml")) {
+                    QMessageBox::critical(
+                        0, tr("Critical Load Error"),
+                        tr("Couldn't find %1 in map file.\n").arg("mapdata.xml"));
+                    noError = false;
+                }
+                else
+                    xmlfile = tmpUnzipDir + "/mapdata.xml";
+            } else {
                 QMessageBox::critical(
-                    0, tr("Critical Load Error"),
-                    tr("Couldn't find a map (*.xml) in .vym archive.\n"));
-                err = File::Aborted;
+                    0, tr("Critical Load Error"), "Multiple maps found in zip archive and no clue about file type");
+                noError = false;
             }
-        } // file doesn't exist
-        else
-            xmlfile = mfile.fileName();
-    }
+        }
+        if (xmlFileList.isEmpty()) {
+            QMessageBox::critical(
+                0, tr("Critical Load Error"),
+                tr("Couldn't find a map (*.xml) in .vym archive.\n"));
+            noError = false;
+        }
+    } // zipped
 
     QFile file(xmlfile);
 
@@ -603,101 +621,134 @@ File::ErrorCode VymModel::loadMap(QString fname, const LoadMode &lmode,
     if (!file.exists()) {
         QMessageBox::critical(
             0, tr("Critical Parse Error"),
-            tr(QString("Couldn't open map %1").arg(file.fileName()).toUtf8()));
-        err = File::Aborted;
+            tr(QString("Couldn't open map \"%1\"").arg(file.fileName()).toUtf8()));
+        noError = false;
     }
     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;
+            tmpdir = tmpUnzipDir;
         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);
+        reader->setTmpDir(tmpdir);
+
+        reader->setLoadMode(lmode);
+
+        if (lmode == File::ImportReplace || lmode == File::ImportAdd) 
+            reader->setInsertBranch(insertBranch);
+
+        if (lmode == File::ImportAdd)
+            reader->setInsertPos(insertPos);
+
+        bool parsedWell = false;
+
+        // Open file
+        if (!file.open(QFile::ReadOnly | QFile::Text)) {
+            QMessageBox::critical(nullptr, "VymModel::loadMap",
+                    QString("Cannot read file %1:\n%2.")
+                    .arg(QDir::toNativeSeparators(fileName),
+                        file.errorString()));
+            noError = false;
+        } else {
+            // Here we actually parse the XML file
+            parsedWell = reader->read(&file);
+
+            file.close();
+        }
 
         // 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
+        if (noError) {
+            if (parsedWell) {
+                reposition();
+
+                if (lmode == File::NewMap)  // no lockfile for default map!
+                {
+                    mapDefault = false;
+                    mapChanged = false;
+                    mapUnsaved = false;
+                    autosaveTimer->stop();
+
+                    resetHistory();
+                    resetSelectionHistory();
+
+                    if (!tryVymLock() && debug)
+                        qWarning() << "VM::loadMap  no lockfile created!";
+                }
+
+                // Recalc priorities and sort
+                taskModel->recalcPriorities();
+
+                // Log
+                logInfo(QString("Map loaded successfully from \"%1\"").arg(fname), __func__);
+            } else {
+                QMessageBox::critical(0, tr("Critical Parse Error"),
+                                        reader->errorString());
+                // returnCode=1;
+                // Still return "success": the map maybe at least
+                // partially read by the parser
+
+                setReadOnly(true);
+            }
+        } // noError so far
+    }
+
+    // If required, fix positions when importing from old versions
+    if (fileType == File::VymMap && versionLowerOrEqual(mapVersionInt, "2.9.500")) {
+        foreach (BranchItem *center, rootItem->getBranches()) {
+            foreach (BranchItem *mainBranch, center->getBranches()) {
+                BranchContainer *bc = mainBranch->getBranchContainer();
+                QRectF rb = bc->ornamentsRect();
+                QPointF offset;
+                offset.setX(rb.width() / 2);
+                offset.setY(rb.height() / 2);
+                bc->setPos(bc->x() + offset.x(), bc->y() + offset.y());
+                logInfo("Adjusting legacy position of " + mainBranch->headingPlain() + "  offset: " + toS(offset), __func__);
+            }
         }
+        reposition();
     }
 
-    // Delete tmpZipDir
-    removeDir(QDir(tmpZipDir));
+    // Cleanup
+    removeDir(QDir(tmpUnzipDir));
+    delete reader;
 
     // Restore original zip state
     zipped = zipped_org;
 
     updateActions();
 
-    if (lmode != NewMap)
+    if (lmode != File::NewMap)
         emitUpdateQueries();
 
     if (mapEditor) {
         mapEditor->setZoomFactorTarget(zoomFactor);
-        mapEditor->setAngleTarget(rotationAngle);
+        mapEditor->setRotationTarget(mapRotationInt);
     }
 
     qApp->processEvents(); // Update view (scene()->update() is not enough)
-    return err;
+    return noError;
 }
 
-File::ErrorCode VymModel::save(const SaveMode &savemode)
+bool VymModel::saveMap(const File::SaveMode &savemode)
 {
-    QString tmpZipDir;
+    // Block closing the map while saving, esp. while zipping
+    isSavingInt = true;
+
     QString mapFileName;
     QString saveFilePath;
 
-    File::ErrorCode err = File::Success;
+    bool noError = true;
 
     if (zipped)
         // save as .xml
@@ -709,34 +760,32 @@ File::ErrorCode VymModel::save(const SaveMode &savemode)
     // 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;
+        QMessageBox mb(QMessageBox::Warning, 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));
+        QPushButton *compressedButton = mb.addButton(
+            tr("compressed (vym default)"),
+            QMessageBox::AcceptRole);
+        QPushButton *uncompressedButton = mb.addButton(
+            tr("uncompressed, potentially overwrite existing data"),
+            QMessageBox::NoRole);
+        mb.addButton(
+            tr("Cancel"),
+            QMessageBox::RejectRole);
+        mb.exec();
+        if (mb.clickedButton() == compressedButton)
+            // save compressed (default file format)
+            zipped = true;
+        else if (mb.clickedButton() == uncompressedButton)
+            zipped = false; // FIXME-5 Filename suffix still could be .xml instead of .vym
+        else  {
+            // do nothing
+            isSavingInt = false;
+            return false; 
         }
     }
 
@@ -766,33 +815,38 @@ File::ErrorCode VymModel::save(const SaveMode &savemode)
 
     if (zipped) {
         // Create temporary directory for packing
-        bool ok;
-        tmpZipDir = makeTmpDir(ok, tmpDirPath(), "zip");
-        if (!ok) {
+        if (!zipDirInt.exists()) {
             QMessageBox::critical(
                 0, tr("Critical Save Error"),
-                tr("Couldn't create temporary directory before save\n"));
-            return File::Aborted;
+                tr("Couldn't access zipDir %1\n").arg(zipDirInt.path()));
+            isSavingInt = false;
+            return false;
         }
 
         saveFilePath = filePath;
-        setFilePath(tmpZipDir + "/" + mapName + ".xml", saveFilePath);
+        setFilePath(zipDirInt.path() + "/" + mapName + ".xml", saveFilePath);
     } // zipped
 
+    // Notification, that we start to save
+    mainWindow->statusMessage(tr("Saving  %1...").arg(saveFilePath));
+
     // Create mapName and fileDir
     makeSubDirs(fileDir);
 
-    QString saveFile;
-    if (savemode == CompleteMap || selModel->selection().isEmpty()) {
-        // Save complete map
+    // Real saving (not exporting) needs to save also temporary hidden parts
+    TreeItem::HideTmpMode hideModeOrg = hideMode;
+    setHideTmpMode(TreeItem::HideNone);
+
+    QString mapStringData;
+    if (savemode == File::CompleteMap || selModel->selection().isEmpty()) {
+        // Save complete map    // FIXME-3 prefix still needed? all treeItems from all models have unique number in filename already...
         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);
+            mapStringData =
+                saveToDir(fileDir, "", FlagRowMaster::UsedFlags, QPointF());
         else
-            saveFile = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags,
-                                 QPointF(), NULL);
+            mapStringData = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags, QPointF());
         mapChanged = false;
         mapUnsaved = false;
         autosaveTimer->stop();
@@ -802,101 +856,141 @@ File::ErrorCode VymModel::save(const SaveMode &savemode)
         if (selectionType() == TreeItem::Image)
             saveImage();
         else
-            saveFile = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags,
-                                 QPointF(), getSelectedBranch());
+            mapStringData = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags,
+                                 QPointF(), false, false, false, getSelectedBranch());
         // FIXME-3 take care of multiselections when saving parts
     }
 
-    bool saved;
+    // Restore original mode
+    setHideTmpMode(hideModeOrg);
+
+    QString saveFileName;
     if (zipped)
         // Use defined map name "map.xml", if zipped. Introduce in 2.6.6
-        saved = saveStringToDisk(fileDir + "map.xml", saveFile);
+        saveFileName = fileDir + "/map.xml";
     else
         // Use regular mapName, when saved as XML
-        saved = saveStringToDisk(fileDir + mapFileName, saveFile);
-    if (!saved) {
-        err = File::Aborted;
+        saveFileName = fileDir + "/" + mapFileName;
+
+    if (!saveStringToDisk(saveFileName, mapStringData)) {
         qWarning("ME::saveStringToDisk failed!");
+        noError = false;
     }
 
-    if (zipped) {
-        // zip
-        if (err == File::Success)
-            err = zipDir(tmpZipDir, destPath);
+    if (!noError)
+        mainWindow->statusMessage(tr("Couldn't save ").arg(saveFilePath));
+    else {
+        if (useActionLog) {
+            QString log = QString("Wrote unzipped map \"%1\" into zipDirInt = %2")
+                .arg(mapFileName, zipDirInt.path());
+            logInfo(log, __func__);
+        }
+
+        if (zipped) {
+            // zip
+            mainWindow->statusMessage(tr("Compressing %1").arg(destPath), 0);
 
-        // Delete tmpDir
-        removeDir(QDir(tmpZipDir));
+            zipAgent = new ZipAgent(zipDirInt, destPath);
+            connect(zipAgent, SIGNAL(zipFinished()), this, SLOT(zipFinished()));
+            QString log = QString("Starting zipAgent to compress \"%1\" in zipDirInt = %2")
+                .arg(mapFileName, zipDirInt.path());
+            logInfo(log, __func__);
+            zipAgent->startZip();
+        } else
+            mainWindow->statusMessage(tr("Saved %1").arg(saveFilePath));
 
+        logInfo("Finishing saving map " + destPath, __func__);  // FIXME-3 debugging
         // Restore original filepath outside of tmp zip dir
         setFilePath(saveFilePath);
     }
 
     updateActions();
 
+    if (!zipped)
+        isSavingInt = false;
+
+    return noError;
+}
+
+bool VymModel::isSaving()
+{
+    return isSavingInt;
+}
+
+void VymModel::zipFinished()
+{
+    // Cleanup
+    QString log = QString("Finished zipping %1 to %2").arg(zipAgent->zipDir().path(), zipAgent->zipName());
+    logInfo(log, __func__);
+
+    zipAgent->deleteLater();
+    zipAgent = nullptr;
+    isSavingInt = false;
+
+    mainWindow->statusMessage(tr("Saved %1").arg(filePath));
+
     fileChangedTime = QFileInfo(destPath).lastModified();
-    return err;
+
+    updateActions();
+
 }
 
-ImageItem* VymModel::loadImage(BranchItem *dst, const QString &fn)  // FIXME-2 better move filedialog to MainWindow
+ImageItem* VymModel::loadImage(BranchItem *parentBranch, const QStringList &imagePaths)
 {
-    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 (!parentBranch)
+        parentBranch = getSelectedBranch();
+
+    if (parentBranch) {
+        if (!imagePaths.isEmpty()) {
+           ImageItem *ii = nullptr;
 
-        if (!fns.isEmpty()) {
             lastImageDir.setPath(
-                fns.first().left(fns.first().lastIndexOf("/")));
+                imagePaths.first().left(imagePaths.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();
-                        }
-                    }
+            for (int j = 0; j < imagePaths.count(); j++) {
+                s = imagePaths.at(j);
+
+                ii = createImage(parentBranch);
+
+                QString bv = setBranchVar(parentBranch);
+                QString uc = setImageVar(ii) + "map.removeImage(i);";
+                QString rc = bv + "b.loadBranchInsert(\"REDO_PATH\", 0);";
+                QString comment = QString("Load image %1").arg(s);
+
+                logAction(rc, comment, __func__);
 
-                    // On default include image // FIXME-4 check, if we change
-                    // default settings...
-                    select(dst);
-                    setIncludeImagesHor(false);
-                    setIncludeImagesVer(true);
 
-                    reposition();
-                    return ii;
+
+                if (ii && ii->load(s)) {
+                    
+                    ImageContainer *ic = ii->getImageContainer();
+                    QPointF pos_new = parentBranch->getBranchContainer()->getPositionHintNewChild(ic);
+                    ic->setPos(pos_new);
+
+                    saveState( uc, rc, comment, nullptr, ii);
                 }
                 else {
-                    qWarning() << "vymmodel: Failed to load " + s;
+                    logWarning("Failed: " + comment, __func__);
                     deleteItem(ii);
+                    return nullptr;
                 }
             }
+
+           reposition();
+           return ii;  // When pasting we need the last added image for scaling
         }
     }
     return nullptr;
 }
 
+ImageItem* VymModel::loadImage(BranchItem *parentBranch, const QString &imagePath)
+{
+    QStringList imagePaths;
+    imagePaths << imagePath;
+    return loadImage(parentBranch, imagePaths);
+}
+
 void VymModel::saveImage(ImageItem *ii, QString fn)
 {
     if (!ii)
@@ -908,41 +1002,38 @@ void VymModel::saveImage(ImageItem *ii, QString fn)
             tr("All", "Filedialog") + " (*.*)");
         if (fn.isEmpty())
             fn = QFileDialog::getSaveFileName(
-                NULL, vymName + " - " + tr("Save image"), lastImageDir.path(),
-                filter, NULL, QFileDialog::DontConfirmOverwrite);
+                nullptr, vymName + " - " + tr("Save image"), lastImageDir.path(),
+                filter, nullptr, 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
+                QMessageBox mb(
+                   QMessageBox::Warning,
+                   vymName,
+                   tr("The file %1 exists already.\n"
+                      "Do you want to overwrite it?")
+                       .arg(fn));
+                mb.addButton(
+                    tr("Overwrite"),
+                    QMessageBox::AcceptRole);
+                mb.addButton(
+                    tr("Cancel"),
+                    QMessageBox::RejectRole);
+                mb.exec();
+                if (mb.result() != QMessageBox::AcceptRole)
                     return;
-                    break;
-                }
             }
             if (!ii->saveImage(fn))
                 QMessageBox::critical(0, tr("Critical Error"),
                                       tr("Couldn't save %1").arg(fn));
+            else
+                mainWindow->statusMessage(tr("Saved %1").arg(fn));
         }
     }
 }
 
-void VymModel::importDirInt(BranchItem *dst, QDir d)
+void VymModel::importDirInt(QDir d, BranchItem *dst)
 {
     bool oldSaveState = saveStateBlocked;
     saveStateBlocked = true;
@@ -954,13 +1045,23 @@ void VymModel::importDirInt(BranchItem *dst, QDir d)
         QFileInfoList list = d.entryInfoList();
         QFileInfo fi;
 
+        QColor dirColor;
+        QColor fileColor;
+        if (usingDarkTheme) {
+            dirColor = QColor(0, 170, 255); // #00aaff
+            fileColor = QColor(255, 255, 255);
+        } else {
+            dirColor = QColor(0, 0, 255);
+            fileColor = QColor(0, 0, 0);
+        }
+
         // 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"));
+                bi->setHeadingColor(dirColor);
                 if (debug)
                     qDebug() << "Added subdir: " << fi.fileName();
                 if (!d.cd(fi.fileName()))
@@ -969,7 +1070,7 @@ void VymModel::importDirInt(BranchItem *dst, QDir d)
                         tr("Cannot find the directory %1").arg(fi.fileName()));
                 else {
                     // Recursively add subdirs
-                    importDirInt(bi, d);
+                    importDirInt(d, bi);
                     d.cdUp();
                 }
                 emitDataChanged(bi);
@@ -981,7 +1082,7 @@ void VymModel::importDirInt(BranchItem *dst, QDir d)
             if (fi.isFile()) {
                 bi = addNewBranchInt(dst, -2);
                 bi->setHeadingPlainText(fi.fileName());
-                bi->setHeadingColor(QColor("black"));
+                bi->setHeadingColor(fileColor);
                 if (fi.fileName().right(4) == ".vym")
                     bi->setVymLink(fi.filePath());
                 emitDataChanged(bi);
@@ -995,16 +1096,21 @@ void VymModel::importDirInt(BranchItem *dst, QDir d)
     saveStateBlocked = oldSaveState;
 }
 
-void VymModel::importDir(const QString &s)
+void VymModel::importDir(const QString &dirPath, BranchItem *bi)
 {
-    BranchItem *selbi = getSelectedBranch();
+    BranchItem *selbi = getSelectedBranch(bi);
     if (selbi) {
-        saveStateChangingPart(
-            selbi, selbi, QString("importDir (\"%1\")").arg(s),
-            QString("Import directory structure from %1").arg(s));
+        QString bv = setBranchVar(selbi);
+        QString uc = bv + QString("map.loadMapReplace(\"UNDO_PATH\", b);");
+        QString rc = bv + QString("b.importDir(\"%1\");").arg(dirPath);
+        QString comment = QString("Import directory structure from \"%1\" to branch \"%2\"").arg(dirPath, selbi->headingText());
 
-        QDir d(s);
-        importDirInt(selbi, d);
+        logAction(rc, comment, __func__);
+
+        saveState(uc, rc, comment, selbi);
+
+        QDir d(dirPath);
+        importDirInt(d, selbi);
     }
 }
 
@@ -1017,13 +1123,12 @@ void VymModel::importDir()
         QFileDialog fd;
         fd.setWindowTitle(vymName + " - " +
                           tr("Choose directory structure to import"));
-        fd.setFileMode(QFileDialog::DirectoryOnly);
+        fd.setFileMode(QFileDialog::Directory);
         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();
@@ -1031,6 +1136,58 @@ void VymModel::importDir()
     }
 }
 
+bool VymModel::addMapInsert(QString fpath, int insertPos, BranchItem *bi)
+{
+    BranchItem *selbi = getSelectedBranch(bi);
+    if (!selbi) {
+        logWarning("Failed: No branch provided");
+        return false;
+    }
+
+    // Only saveState if a branch is inserted
+    // Other data like XLink is only used for undo/redo operations and currently
+    // does not need a saveState
+    QString bv = setBranchVar(selbi);
+    QString uc = bv + QString("map.loadBranchReplace(\"UNDO_PATH\", b);");
+    QString rc = bv + QString("b.loadBranchInsert(\"%1\", %2);").arg(fpath).arg(insertPos);
+    QString comment = QString("Add map %1 to \"%2\"").arg(fpath, selbi->headingText());
+
+    logAction(rc, comment, __func__);
+
+    saveState(uc, rc, comment, selbi);
+
+    if (loadMap(fpath, File::ImportAdd, File::VymMap, 0x0000, selbi, insertPos))
+        return true;
+
+    logWarning("Failed: Loading from " + fpath, __func__);
+    return false;
+}
+
+bool VymModel::addMapReplace(QString fpath, BranchItem *bi)
+{
+    BranchItem *selbi = getSelectedBranch(bi);
+    if (!selbi) {
+        logWarning("Failed: No branch provided");
+        return false;
+    }
+
+    QString bv = setBranchVar(selbi);
+    QString pbv = setBranchVar(selbi->parentBranch(), "pb");
+    QString uc = pbv + QString("map.loadBranchReplace(\"UNDO_PATH\", pb);");
+    QString rc = bv + QString("map.loadBranchReplace(\"REDO_PATH\", b);");
+    QString comment = QString("Replace \"%1\" with \"%2\"").arg(selbi->headingText(), fpath);
+
+    logAction(rc, comment, __func__);
+
+    saveState(uc, rc, comment, selbi->parentBranch(), selbi);
+
+    if (loadMap(fpath, File::ImportReplace, File::VymMap, 0x0000, selbi))
+        return true;
+
+    logWarning("Failed: " + comment, __func__);
+    return false;
+}
+
 bool VymModel::removeVymLock()
 {
     if (vymLock.removeLockForced()) {
@@ -1080,8 +1237,7 @@ bool VymModel::tryVymLock()
                            "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);
+                        .arg(a, h);
                 dia.setText(s);
                 dia.setWindowTitle(
                     tr("Warning: Map already opended", "VymModel"));
@@ -1118,6 +1274,10 @@ bool VymModel::tryVymLock()
 }
 
 bool VymModel::renameMap(const QString &newPath)
+// map is renamed before fileSaveAs() or from VymModelWrapper::saveSelection()
+// Usually renamed back to original name again. Purpose here is to adapt the lockfile 
+// new name of map.
+// Internally the paths in ImageItems pointing to zipDirInt do not need to be adapted.
 {
     QString oldPath = filePath;
     if (vymLock.getState() == VymLock::LockedByMyself || vymLock.getState() == VymLock::Undefined) {
@@ -1126,18 +1286,20 @@ bool VymModel::renameMap(const QString &newPath)
         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);
+            logWarning(QString("Failed to create lock for %1").arg(newPath), __func__);
             return false;
         }
 
         // Change lockfiles now
         if (!vymLock.releaseLock())
-            qWarning() << "VymModel::renameMap failed to release lock for " << oldPath;
+            logWarning(QString("Failed to release lock for %1").arg(oldPath), __func__);
         vymLock = newLock;
         setFilePath(newPath);
+        if (readonly)
+            setReadOnly(false);
         return true;
     }
-    qWarning() << "VymModel::renameMap failed to get lockfile. state=" << vymLock.getState();
+    logWarning("Failed to rename map.", __func__);
     return false;
 }
 
@@ -1151,24 +1313,20 @@ bool VymModel::isReadOnly() { return readonly; }
 
 void VymModel::autosave()
 {
-    // Check if autosave is disabled due to testmode
+    // Check if autosave is disabled due to testmode or current zip process
     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);
+        // qWarning() << QString("VymModel::autosave disabled globally!  Current map: %1").arg(filePath);
         return;
     }
 
-    QDateTime now = QDateTime().currentDateTime();
+    if (zipAgent || isSavingInt) {
+        //qDebug() << "VymModel::autosave blocked by zipAgent or ongoing save";
+        return;
+    }
 
     // Disable autosave, while we have gone back in history
     int redosAvail = undoSet.numValue(QString("/history/redosAvail"));
@@ -1177,61 +1335,70 @@ void VymModel::autosave()
 
     // Also disable autosave for new map without filename
     if (filePath.isEmpty()) {
+        /*
         if (debug)
-            qWarning()
-                << "VymModel::autosave rejected due to missing filePath\n";
+            qWarning() << "VymModel::autosave rejected due to missing filePath\n";
+        */
         return;
     }
 
-    if (mapUnsaved && mapChanged && mainWindow->useAutosave() && !testmode) {
-        if (QFileInfo(filePath).lastModified() <= fileChangedTime)
+    if (mapUnsaved && mapChanged && !testmode) {
+        if (QFileInfo(filePath).lastModified() <= fileChangedTime) {
+            logInfo("Autosave starting", __func__);
+
+            // Call save via MainWindow to check for filename and readonly status
             mainWindow->fileSave(this);
-        else if (debug)
-            qDebug() << "  ME::autosave  rejected, file on disk is newer than "
+        else if (debug)
+            qDebug() << "  VM::autosave  rejected, file on disk is newer than "
                         "last save.\n";
     }
+    logInfo("Autosave finished", __func__); // FIXME-3 remove after debugging
 }
 
 void VymModel::fileChanged()
 {
     // Check if file on disk has changed meanwhile
     if (!filePath.isEmpty()) {
-        if (readonly) {
+        if (readonly && vymLock.getState() != VymLock::LockedByMyself) {
             // unset readonly if lockfile is gone
+            // but only, if map was LockedByOther before
             if (vymLock.tryLock())
                 setReadOnly(false);
         }
         else {
-            // We could check, if somebody else removed/replaced lockfile
+            // FIXME-5 We could check, if somebody else removed/replaced lockfile
             // (A unique vym ID would be needed)
 
+            if (isSavingInt)
+                return;
+
             QDateTime tmod = QFileInfo(filePath).lastModified();
             if (tmod > fileChangedTime) {
-                // FIXME-4 VM switch to current mapeditor and finish
+                // FIXME-5 VM switch to current mapeditor and finish
                 // lineedits...
                 QMessageBox mb(
+                    QMessageBox::Question,
                     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:
+                        .arg(filePath));
+
+                mb.addButton(
+                    tr("Reload"),
+                    QMessageBox::AcceptRole);
+                mb.addButton(
+                    tr("Ignore"),
+                    QMessageBox::RejectRole);
+                mb.exec();
+                if (mb.result() == QMessageBox::AcceptRole) {
                     // Reload map
                     mainWindow->initProgressCounter(1);
                     loadMap(filePath);
                     mainWindow->removeProgressCounter();
-                    break;
-                case QMessageBox::Cancel:
-                    fileChangedTime =
-                        tmod; // allow autosave to overwrite newer file!
-                }
+                } else
+                    // allow autosave to overwrite newer file!
+                    fileChangedTime = tmod;
             }
         }
     }
@@ -1269,23 +1436,16 @@ void VymModel::setChanged()
     updateActions();
 }
 
-QString VymModel::getObjectName(LinkableMapObj *lmo)
-{
-    if (!lmo || !lmo->getTreeItem())
-        return QString();
-    return getObjectName(lmo->getTreeItem());
-}
-
-QString VymModel::getObjectName(TreeItem *ti)
+QString VymModel::getObjectName(TreeItem *ti)   // FIXME-3 compare with headingText - still needed?
 {
     QString s;
     if (!ti)
-        return QString("Error: NULL has no name!");
-    s = ti->getHeadingPlain();
+        return QString("Error: nullptr has no name!");
+    s = ti->headingPlain();
     if (s == "")
         s = "unnamed";
 
-    return QString("%1 (%2)").arg(ti->getTypeName()).arg(s);
+    return QString("%1 \"%2\"").arg(ti->getTypeName(), s);
 }
 
 void VymModel::redo()
@@ -1306,26 +1466,18 @@ void VymModel::redo()
         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))
+    if (!checkVersion(mapVersionInt))
     QMessageBox::warning(0,tr("Warning"),
         tr("Version %1 of saved undo/redo data\ndoes not match current vym
-    version %2.").arg(version).arg(vymVersion));
+    version %2.").arg(mapVersionInt).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;
@@ -1333,23 +1485,19 @@ void VymModel::redo()
         qDebug() << "       curStep=" << curStep;
         qDebug() << "    ---------------------------";
         qDebug() << "    comment=" << comment;
-        qDebug() << "    undoSel=" << undoSelection;
-        qDebug() << "    redoSel=" << redoSelection;
         qDebug() << "    undoCom:";
-        cout << qPrintable(undoCommand);
+        cout << qPrintable(undoCommand) << endl;
         qDebug() << "    redoCom=";
-        cout << qPrintable(redoCommand);
+        cout << qPrintable(redoCommand) << endl;
         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();
+        QString("map = vym.currentMap();%1").arg(redoCommand);
+
+    errMsg = mainWindow->runScript(redoScript).toString();
+
     saveStateBlocked = saveStateBlockedOrg;
 
     undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
@@ -1358,13 +1506,14 @@ void VymModel::redo()
     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() << "    undosAvail=" << undosAvail;
+    qDebug() << "    redosAvail=" << redosAvail;
+    qDebug() << "       curStep=" << curStep;
     qDebug() << "    ---------------------------";
     */
 }
@@ -1397,15 +1546,15 @@ QString VymModel::lastRedoCommand()
 
 QVariant VymModel::repeatLastCommand()
 {
-    QString command = "m = vym.currentMap();";
-    if (isUndoAvailable())
-        command += "m." +
-                   undoSet.value(
-                       QString("/history/step-%1/redoCommand").arg(curStep)) +
-                   ";";
+    QString command = QString("vym.gotoMap(%1); m = vym.currentMap();").arg(modelIdInt);
+    QString redoCommand = undoSet.value(
+       QString("/history/step-%1/redoCommand").arg(curStep));
+    if (isUndoAvailable() && !redoCommand.startsWith("model."))
+        // Only repeat command, if not a set of commands
+        command += "m." + redoCommand + ";";
     else
         return false;
-    return execute(command);
+    return mainWindow->runScript(command);
 }
 
 void VymModel::undo()
@@ -1421,32 +1570,18 @@ void VymModel::undo()
 
     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))
+    if (!checkVersion(mapVersionInt))
     QMessageBox::warning(0,tr("Warning"),
         tr("Version %1 of saved undo/redo data\ndoes not match current vym
-    version %2.").arg(version).arg(vymVersion));
+    version %2.").arg(mapVersionInt).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;
@@ -1454,8 +1589,6 @@ void VymModel::undo()
         qDebug() << "       curStep=" << curStep;
         cout << "    ---------------------------" << endl;
         qDebug() << "    comment=" << comment;
-        qDebug() << "    undoSel=" << undoSelection;
-        qDebug() << "    redoSel=" << redoSelection;
         cout << "    undoCom:" << endl;
         cout << qPrintable(undoCommand) << endl;
         cout << "    redoCom:" << endl;
@@ -1463,15 +1596,11 @@ void VymModel::undo()
         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();
+    QString undoScript = QString("map = vym.currentMap();%1").arg(undoCommand);
+
+    errMsg = mainWindow->runScript(undoScript).toString();
 
     undosAvail--;
     curStep--;
@@ -1495,6 +1624,7 @@ void VymModel::undo()
     undoSet.writeSettings(histPath);
 
     mainWindow->updateHistory(undoSet);
+
     updateActions();
 }
 
@@ -1534,7 +1664,7 @@ void VymModel::gotoHistoryStep(int i)
 
 QString VymModel::getHistoryPath()
 {
-    QString histName(QString("history-%1").arg(curStep));
+    QString histName(QString("history-%1").arg(dataStep));
     return (tmpMapDirPath + "/" + histName);
 }
 
@@ -1543,76 +1673,187 @@ void VymModel::resetHistory()
     curStep = 0;
     redosAvail = 0;
     undosAvail = 0;
+    dataStep = 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)
+QString VymModel::setAttributeVar(AttributeItem* ai, QString varName)
 {
-    sendData(redoCom); // FIXME-4 testing
+    // Default varName: "a"
+    QString r;
+    if (!ai)
+        qWarning() << "VM::setAttributeVar ai == nullptr";
+    else
 
-    // Main saveState
+        r = QString("%1 = map.findAttributeById(\"%2\");").arg(varName, ai->getUuid().toString());
 
-    if (saveStateBlocked)
-        return;
+    return r;
+}
 
-    if (debug)
-        qDebug() << "VM::saveState() for map " << mapName;
+QString VymModel::setBranchVar(BranchItem* bi, QString varName)
+{
+    // Default varName: "b"
+    QString r;
+    if (!bi)
+        qWarning() << "VM::setBranchVar bi == nullptr";
+    else
+        r = QString("%1 = map.findBranchById(\"%2\");").arg(varName, bi->getUuid().toString());
+
+    return r;
+}
+
+QString VymModel::setImageVar(ImageItem* ii, QString varName)
+{
+    // Default varName: "i"
+    QString r;
+    if (!ii)
+        qWarning() << "VM::setImageVar ii == nullptr";
+    else
+        r = QString("%1 = map.findImageById(\"%2\");").arg(varName, ii->getUuid().toString());
 
-    QString undoCommand = undoCom;
-    QString redoCommand = redoCom;
+    return r;
+}
 
+QString VymModel::setXLinkVar(XLink* xl, QString varName)
+{
+    QString r;
+    if (!xl)
+        qWarning() << "VM::setXLinkVar xl == nullptr";
+    else
+        r = QString("%1 = map.findXLinkById(\"%2\");").arg(varName, xl->getUuid().toString());
+
+    return r;
+}
+
+QString VymModel::saveState(
+         QString undoCommand,
+         QString redoCommand,
+         const QString &comment,
+         TreeItem *saveUndoItem,
+         TreeItem *saveRedoItem,
+         bool createHistoryDir)
+{
+    // Main saveState
+
+    // sendData(redoCom); // FIXME-5 testing network
+
+    if (saveStateBlocked)
+        return QString();
 
-    // 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);
+    if (debug) {
+        qDebug() << "VM::saveState() for map " << mapName;
+        qDebug() << "  comment: " << comment;
+        qDebug() << "  Script:   " << buildingUndoScript;
+        qDebug() << "  undoCom: " << undoCommand;
+        qDebug() << "  redoCom: " << redoCommand;
     }
-    else {
     */
-        if (undosAvail < stepsTotal)
-            undosAvail++;
 
-        curStep++;
-        if (curStep > stepsTotal)
-            curStep = 1;
-    //}
+    if (buildingUndoScript)
+        logInfo("// Building script: " + redoCommand + " " +  comment, __func__);    // FIXME-3 Use logDebug instead? Remove logging completely from saveState?
+    else
+        logInfo("saveState: " + comment + " " + redoCommand, __func__);
 
-    QString histDir = getHistoryPath();
-    QString bakMapPath = histDir + "/map.xml";
+    // Increase undo steps, but check for repeated actions
+    // like editing a vymNote - then do not increase but replace last command
+    //
+    bool repeatedCommand = false;
+
+    /* FIXME-3 Repeated command not supported yet in saveState
+    // Undo Scripts start with "model.select" - do not consider these for repeated actions
+    if (!undoCommand.startsWith("{")) {
+        if (curStep > 0 && redoSelection == lastRedoSelection()) {
+            int i = redoCommand.indexOf("(");
+            QString rcl = redoCommand.left(i-1);
+            if (i > 0 && rcl == lastRedoCommand().left(i-1)) {
+
+                // Current command is a repeated one. We only want to "squash" some of these
+                QRegularExpression re("<vymnote");
+                if (rcl.startsWith("model.parseVymText") && re.match(redoCommand).hasMatch()) {
+                    if (debug)
+                        qDebug() << "VM::saveState repeated command: " << redoCommand;
+
+                    // Do not increase undoCommand counter
+                    repeatedCommand = true;
+                    undoCommand = undoSet.value(
+                        QString("/history/step-%1/undoCommand").arg(curStep), undoCommand);
+                } else
+                    if (debug)
+                        qDebug() << "VM::saveState not repeated command: " << redoCommand;
+            }
+        }
+    }
+    */
+    QString historyPath = getHistoryPath();
 
-    // Create histDir if not available
-    QDir d(histDir);
-    if (!d.exists())
-        makeSubDirs(histDir);
+    // Create historyPath if not available and required
+    if (saveUndoItem || saveRedoItem || createHistoryDir) {
+        dataStep++;
+        QDir d(historyPath);
+        if (!d.exists())
+            makeSubDirs(historyPath);   // FIXME-3 Only create subDirs on demand, e.g. when saving imageItem
+    }
 
     // Save depending on how much needs to be saved
-    QList<Link *> tmpLinks;
-    if (saveSel)
-        dataXML = saveToDir(histDir, mapName + "-", FlagRowMaster::NoFlags, QPointF(),
-                            saveSel);
+    //
+    // FIXME-5 saveState: userFlags are not written, but still in memory. Could
+    //         lead to problem, if one day removed from userFlags toolbar AND memory
+    if (saveUndoItem) {
+        QString dataXML = saveToDir(historyPath, mapName + "-", FlagRowMaster::NoFlags, QPointF(),
+                            false, false, false, saveUndoItem);
+
+        QString xmlUndoPath = historyPath + "/undo.xml";
+        undoCommand.replace("UNDO_PATH", xmlUndoPath);
+        saveStringToDisk(xmlUndoPath, dataXML);
+    }
+    if (saveRedoItem) {
+        QString dataXML = saveToDir(historyPath, mapName + "-", FlagRowMaster::NoFlags, QPointF(),
+                            false, false, false, saveRedoItem);
+
+        QString xmlRedoPath = historyPath + "/redo.xml";
+        redoCommand.replace("REDO_PATH", xmlRedoPath);
+        saveStringToDisk(xmlRedoPath, dataXML);
+    }
 
-    if (savemode == PartOfMap) {
-        undoCommand.replace("PATH", bakMapPath);
-        redoCommand.replace("PATH", bakMapPath);
+    undoCommand.replace("HISTORY_PATH", historyPath);
+    redoCommand.replace("HISTORY_PATH", historyPath);
+
+    if (debug) {
+        qDebug() << "  undoCommand: " << undoCommand;
+        qDebug() << "  redoCommand: " << redoCommand;
+        qDebug() << "      Comment: " << comment;
+    }
+
+    if (buildingUndoScript)
+    {
+        // Build string with all commands
+        undoScript = undoCommand + undoScript;
+        redoScript = redoScript + redoCommand;
+
+        if (debug) {
+            qDebug() << "VM::saveState  building scripts:";
+            qDebug() << "  undoScript = " << undoScript;
+            qDebug() << "  redoScript = " << redoScript;
+        }
+        return historyPath + "/";
     }
 
-    if (!dataXML.isEmpty())
-        // Write XML Data to disk
-        saveStringToDisk(bakMapPath, dataXML);
+    if (!repeatedCommand) {
+        if (undosAvail < stepsTotal)
+            undosAvail++;
+
+        curStep++;
+        if (curStep > stepsTotal)
+            curStep = 1;
+    }
 
     // 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.
+    // possible redos after an action. Possible, but we are too lazy: forget
+    // about redos
     redosAvail = 0;
 
     // Write the current state to disk
@@ -1621,16 +1862,12 @@ void VymModel::saveState(const SaveMode &savemode, const QString &undoSelection,
     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
@@ -1642,126 +1879,62 @@ void VymModel::saveState(const SaveMode &savemode, const QString &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;
+        cout << "    undoCom:" <<  qPrintable(undoCommand) << "\n";
+        cout << "    redoCom:" <<  qPrintable(redoCommand) << "\n";
+        cout << "    ---------------------------\n";
     }
+    */
 
     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);
+    return historyPath + "/";
 }
 
-void VymModel::saveStateRemovingPart(TreeItem *redoSel, const QString &comment)
+QString VymModel::saveStateBranch(
+        BranchItem *bi,
+        const QString &uc,
+        const QString &rc,
+        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);
-    }
+    QString prefix = setBranchVar(bi) + "b.";
+    return saveState(prefix + uc, prefix + rc, comment);
 }
 
-void VymModel::saveState(TreeItem *undoSel, const QString &uc,
-                         TreeItem *redoSel, const QString &rc,
-                         const QString &comment)
+void VymModel::saveStateBeginScript(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);
+    if (buildingUndoScript)
+        logWarning("Nested saveState scripts found", __func__);  // FIXME-3 e.g. for setFrameAutoDesign...
+    else {
+        logDebug("Starting to build saveStateScript: '" + comment + "'", __func__);
 
-    saveState(UndoCommand, undoSelection, uc, redoSelection, rc, comment, NULL);
+        buildingUndoScript = true;
+        undoScriptComment = comment;
+        undoScript.clear();
+        redoScript.clear();
+    }
 }
 
-void VymModel::saveState(const QString &undoSel, const QString &uc,
-                         const QString &redoSel, const QString &rc,
-                         const QString &comment)
+void VymModel::saveStateEndScript()
 {
-    // "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);
-}
+    if (debug)
+        std::cout << "VM::saveStateEndScript" << endl 
+            << "  buildingScript=" << buildingUndoScript << endl
+            << "  undoScript=" << undoScript.toStdString() << endl;
 
-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);
-}
+    if (buildingUndoScript) {
+        buildingUndoScript = false;
 
-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);
+        logDebug("Finished building saveStateScript: '" + undoScriptComment + "'", __func__);
 
-    saveState(UndoCommand, undoSelection, uc, redoSelection, rc, comment, NULL);
-}
+        // Drop whole Script, if empty
+        if (undoScript.isEmpty() && redoScript.isEmpty()) return;
 
-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)));
-        }
+        saveState(
+                QString("{%1}").arg(undoScript),
+                QString("{%1}").arg(redoScript),
+                undoScriptComment, nullptr);
     }
 }
 
@@ -1770,7 +1943,7 @@ QGraphicsScene *VymModel::getScene() { return mapEditor->getScene(); }
 TreeItem *VymModel::findBySelectString(QString s)
 {
     if (s.isEmpty())
-        return NULL;
+        return nullptr;
 
     // Old maps don't have multiple mapcenters and don't save full path
     if (s.left(2) != "mc")
@@ -1794,15 +1967,15 @@ TreeItem *VymModel::findBySelectString(QString s)
         else if (typ == "xl")
             ti = ti->getXLinkItemNum(n);
         if (!ti)
-            return NULL;
+            return nullptr;
     }
     return ti;
 }
 
 TreeItem *VymModel::findID(const uint &id)
 {
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev);
     while (cur) {
         if (id == cur->getID())
@@ -1821,15 +1994,22 @@ TreeItem *VymModel::findID(const uint &id)
                 return ii;
             j++;
         }
+        j = 0;
+        while (j < cur->attributeCount()) {
+            AttributeItem *ai = cur->getAttributeNum(j);
+            if (id == ai->getID())
+                return ai;
+            j++;
+        }
         nextBranch(cur, prev);
     }
-    return NULL;
+    return nullptr;
 }
 
 TreeItem *VymModel::findUuid(const QUuid &id)
 {
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev);
     while (cur) {
         if (id == cur->getUuid())
@@ -1850,56 +2030,234 @@ TreeItem *VymModel::findUuid(const QUuid &id)
         }
         nextBranch(cur, prev);
     }
-    return NULL;
+
+    // Special case: XLinks (not XLinkItems!) are no TreeItem, if Uuid matches,
+    // return one of the XLinkItems. Used in VymModelWrapper::findXLinkById
+    foreach (auto xl, xlinks)
+        if (xl->getUuid() == id)
+            return xl->beginXLinkItem();
+
+    // For restoring MapCenters we might want to add to rootItem
+    if (rootItem->getUuid() == id)
+        return rootItem;
+
+    return nullptr;
+}
+
+BranchItem* VymModel::findBranchByAttribute(const QString &key, const QString &value)
+{
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
+    nextBranch(cur, prev);
+    while (cur) {
+        AttributeItem *ai = cur->getAttributeByKey(key); 
+        if (ai && ai->value().toString() == value)
+            return cur;
+        nextBranch(cur, prev);
+    }
+
+    return nullptr;
+}
+
+void VymModel::test()
+{
+    // Print item structure
+    foreach (TreeItem *ti, getSelectedItems()) {
+        if (ti->hasTypeBranch()) {
+            BranchContainer *bc = ((BranchItem*)ti)->getBranchContainer();
+            bc->printStructure();
+        }
+        if (ti->hasTypeImage())
+            ((ImageItem*)ti)->parentBranch()->getBranchContainer()->printStructure();
+    }
+    return;
+
+    // Do animation step. All BranchContainers
+    QList <BranchContainer*> bc_list;
+
+    qDebug() << "Calculating forces...";
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
+    nextBranch(cur, prev);
+    while (cur) {
+        //qDebug() << "Adding branch: " << cur->headingText();
+        BranchContainer *bc = cur->getBranchContainer();
+        bc->v_anim = QPointF(0,0);
+        bc_list << bc;
+        nextBranch(cur, prev);
+    }
+
+    foreach (BranchContainer *bc, bc_list) {
+        HeadingContainer *hc = bc->getHeadingContainer();
+        HeadingContainer *ohc;
+
+        // Forces pushing apart
+        /*
+        */
+        foreach (BranchContainer *obc, bc_list) {
+            if (bc != obc) {
+                
+                ohc = obc->getHeadingContainer();
+
+                QPointF vec = hc->mapFromItem(ohc, ohc->pos());
+                qreal dx = vec.x();
+                qreal dy = vec.y();
+                double l = 2.0 * (dx * dx + dy * dy);
+
+                if (l > 25) {
+                    bc->v_anim += QPointF(- (dx *150) / l, - (dy * 150) / l);
+                    qDebug() << "Push "<< hc->info() << " <- " << ohc->info() << " vec=" << toS(vec) << " l=" << l;
+                }
+            }
+        }
+
+        // Forces pulling together
+        BranchItem *bi = bc->getBranchItem();
+        double weight = (bi->branchCount() + 1) * 10;
+
+        /*
+        for (int i = 0; i < bi->branchCount(); i++) {
+            BranchItem *obi = bi->getBranchNum(i);
+            BranchContainer *obc = obi->getBranchContainer();
+            ohc = obc->getHeadingContainer();
+
+            // Parent pulled by child
+            QPointF vec = hc->mapFromItem(ohc, ohc->pos());
+            bc->v_anim += QPointF( vec.x() / weight, vec.y() / weight);
+            qDebug() << "  Child Pull  from " << obi->headingText() << " to " << bi->headingText() << toS(vec);
+
+            // Child pulled by parent
+            vec = ohc->mapFromItem(hc, ohc->pos());
+            obc->v_anim += QPointF( vec.x() / weight, vec.y() / weight);
+            qDebug() << "  Parent Pull from " << bi->headingText() << " to " << obi->headingText() << toS(vec);
+        }
+        */
+
+        // Move MapCenters towards center
+        if (bi->depth() == 0) {
+            QPointF vec = hc->mapToScene(QPointF(0,0));
+            qreal dx = vec.x();
+            qreal dy = vec.y();
+            double l = sqrt( dx * dx + dy * dy);
+            if (l > 5) {
+                bc->v_anim += QPointF(- (dx ) / l, - (dy ) / l);
+                qDebug() << "Moving to center: " << bc->info() << "l=" << l;
+            }
+        }
+
+        // Ignore too small vector
+        if (qAbs(bc->v_anim.x()) < 0.1 && qAbs(bc->v_anim.y()) < 0.1)
+            bc->v_anim = QPointF(0, 0);
+    }
+
+    foreach (BranchContainer *bc, bc_list) {
+        // Show vector
+        bc->v.setLine(0, 0, bc->v_anim.x() * 10, bc->v_anim.y() * 10);
+
+        // Now actually move items
+        bc->setPos( bc->pos() + bc->v_anim);
+    }
+
+    reposition();
+    return;
+
+
+    //mapEditor->testFunction1();
+    //return;
+
+    // Read bookmarks
+    QFile file(fileName);
+    if (!file.open(QFile::ReadOnly | QFile::Text)) {
+        QMessageBox::warning(nullptr, "QXmlStream Bookmarks",
+                QString("Cannot read file %1:\n%2.")
+                .arg(QDir::toNativeSeparators(fileName),
+                    file.errorString()));
+        return;
+    }
+
+    VymReader reader(this);
+    if (!reader.read(&file)) {
+        QMessageBox::warning(nullptr, QString("QXmlStream Bookmarks"),
+                QString("Parse error in file %1:\n\n%2")
+                .arg(QDir::toNativeSeparators(fileName),
+                    reader.errorString()));
+    } else {
+        mainWindow->statusMessage("File loaded");
+        reposition();
+    }
+
 }
 
 //////////////////////////////////////////////
 // Interface
 //////////////////////////////////////////////
-void VymModel::setVersion(const QString &s) { version = s; }
+    
+void VymModel::setMapTitle(const QString &s)
+{
+    if (titleInt != s) {
+        QString uc = QString("map.setTitle (\"%1\");").arg(titleInt);
+        QString rc = QString("map.setTitle (\"%1\");").arg(s);
+        QString comment = QString("Set title of map to \"%1\"").arg(s);
 
-QString VymModel::getVersion() { return version; }
+        logAction(rc, comment, __func__);
 
-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;
+        saveState(uc, rc, comment);
+        titleInt = s;
+    }
 }
 
-QString VymModel::getTitle() { return title; }
+QString VymModel::mapTitle() { return titleInt; }
 
-void VymModel::setAuthor(const QString &s)
+void VymModel::setMapAuthor(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;
+    if (authorInt != s) {
+        QString uc = QString("map.setAuthor (\"%1\");").arg(authorInt);
+        QString rc = QString("map.setAuthor (\"%1\");").arg(s);
+        QString comment = QString("Set author of map to \"%1\"").arg(s);
+
+        logAction(rc, comment, __func__);
+
+        saveState(uc, rc, comment);
+
+        authorInt = s;
+    }
 }
 
-QString VymModel::getAuthor() { return author; }
+QString VymModel::mapAuthor() { return authorInt; }
 
-void VymModel::setComment(const QString &s)
+void VymModel::setMapComment(const QString &s)
 {
-    saveState(QString("setMapComment (\"%1\")").arg(comment),
-              QString("setMapComment (\"%1\")").arg(s),
-              QString("Set comment of map"));
-    comment = s;
+    if (commentInt != s) {
+        QString uc = QString("map.setComment (\"%1\");").arg(commentInt);
+        QString rc = QString("map.setComment (\"%1\");").arg(s);
+        QString c = QString("Set comment of map to \"%1\"").arg(s);
+
+        logAction(rc, c, __func__);
+
+        saveState(uc, rc, c);
+
+        commentInt = s;
+    }
 }
 
-QString VymModel::getComment() { return comment; }
+QString VymModel::mapComment() { return commentInt; }
+
+void VymModel::setMapVersion(const QString &s)
+{
+    // Version stored in file
+    mapVersionInt = s;
+}
 
-QString VymModel::getDate()
+QString VymModel::mapVersion()
 {
-    return QDate::currentDate().toString("yyyy-MM-dd");
+    return mapVersionInt;
 }
 
 int VymModel::branchCount()
 {
     int c = 0;
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev);
     while (cur) {
         c++;
@@ -1913,157 +2271,168 @@ int VymModel::centerCount() { return rootItem->branchCount(); }
 void VymModel::setSortFilter(const QString &s)
 {
     sortFilter = s;
-    emit(sortFilterChanged(sortFilter));
+    emit sortFilterChanged(sortFilter);
 }
 
 QString VymModel::getSortFilter() { return sortFilter; }
 
-void VymModel::setHeading(const VymText &vt, BranchItem *bi)
+void VymModel::setHeading(const VymText &vt, TreeItem *ti)
 {
     Heading h_old;
     Heading h_new;
     h_new = vt;
     QString s = vt.getTextASCII();
-    if (!bi)
-        bi = getSelectedBranch();
-    if (bi) {
-        h_old = bi->getHeading();
+
+    TreeItem *selti = getSelectedItem(ti);
+
+    if (selti && selti->hasTypeBranchOrImage()) {
+        h_old = selti->heading();
         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);
+
+        QString tiv;    // ti variable in script
+        if (selti->hasTypeBranch())
+            tiv = setBranchVar((BranchItem*)selti) + "b.";
+        else
+            tiv = setImageVar((ImageItem*)selti) + "i.";
+
+        QString uc, rc;
+        if (h_old.isRichText())
+            uc = QString("%1setHeadingRichText(\"%2\");").arg(tiv, quoteQuotes(h_old.getText()));
+        else
+            uc = QString("%1setHeadingText(\"%2\");").arg(tiv, quoteQuotes(h_old.getText()));
+        if (h_new.isRichText())
+            rc = QString("%1setHeadingRichText(\"%2\");").arg(tiv, quoteQuotes(h_new.getText()));
+        else
+            rc = QString("%1setHeadingText(\"%2\");").arg(tiv, quoteQuotes(h_new.getText()));
+
+        QString comment = QString("Set heading of %1 to \"%2\"").arg(getObjectName(selti), s);
+
+        logAction(rc, comment, __func__);
+
+        saveState( uc, rc, comment);
+
+        // After adding branches or MapCenters interactively we might want to end an undo script
+        saveStateEndScript();
+
+        selti->setHeading(vt);
+        emitDataChanged(selti);
         emitUpdateQueries();
-        mainWindow->updateHeadingEditor(bi);    // Update HeadingEditor with new heading
+        mainWindow->updateHeadingEditor(selti);    // Update HeadingEditor with new heading (if required)
         reposition();
-    }
+    } else
+        qWarning() << "VM::setHeading has no branch or image selected!";
 }
 
-void VymModel::setHeadingPlainText(const QString &s, BranchItem *bi)
+void VymModel::setHeadingPlainText(const QString &s, TreeItem *ti)
 {
-    if (!bi)
-        bi = getSelectedBranch();
-    if (bi) {
-        VymText vt = bi->getHeading();
+    TreeItem *selti = getSelectedItem(ti);
+    if (selti && selti->hasTypeBranchOrImage()) {
+        VymText vt = selti->heading();
         vt.setPlainText(s);
-        if (bi->getHeading() == vt)
+        if (selti->heading() == vt)
             return;
-        setHeading(vt, bi);
+        setHeading(vt, selti);
 
         // Set URL
-        if ((s.startsWith("http://") || s.startsWith("https://")) &&
-            bi->getURL().isEmpty())
-            setURL(s);
+        if (selti->hasTypeBranch()) {
+            BranchItem *bi = (BranchItem*)selti;
+
+            if ((s.startsWith("http://") || s.startsWith("https://")) && !bi->hasUrl())
+                    setUrl(s, true, bi);
+        }
     }
 }
 
-Heading VymModel::getHeading()
+QString VymModel::headingText(TreeItem *ti)
 {
-    TreeItem *selti = getSelectedItem();
-    if (selti)
-        return selti->getHeading();
-    qWarning() << "VymModel::getHeading Nothing selected.";
-    return Heading();
+    // Mainly used for debugging, also works with nullptr
+    if (ti)
+        return ti->headingPlain();
+    else
+        return QString("No treeItem available");
 }
 
 void VymModel::updateNoteText(const VymText &vt)
 {
-    bool editorStateChanged = false;
+    VymNote note_new(vt);
+    setNote(note_new, nullptr, true);
+}
 
-    TreeItem *selti = getSelectedItem();
-    if (selti) {
-        VymNote note_old = selti->getNote();
-        VymNote note_new(vt);
+void VymModel::setNote(const VymNote &note_new, BranchItem *bi, bool senderIsNoteEditor)
+{
+    BranchItem *selbi = getSelectedBranch(bi);
+    //qDebug() << "VM::setNote  selbi=" << selbi->headingText() << " n=" << note_new.getText();
+    if (selbi) {
+        VymNote note_old;
+        note_old = selbi->getNote();
+        if (note_old == note_new)
+            return;
+
+        bool editorStateChanged = false;
         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);
+        //qDebug() << "VM::setNote  selbi=" << selbi->headingText() << " n=" << note_new.getText();
 
-            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)));
+        // branch variable in script
+        QString bv = setBranchVar(selbi) + "b.";
 
-            selti->setNote(vn);
-        }
+        QString uc, rc;
+        if (note_old.isRichText())
+            uc = QString("%1setNoteRichText(\"%2\");").arg(bv, quoteQuotes(note_old.getText()));
+        else
+            uc = QString("%1setNoteText(\"%2\");").arg(bv, quoteQuotes(note_old.getText()));
+        if (note_new.isRichText())
+            rc = QString("%1setNoteRichText(\"%2\");").arg(bv, quoteQuotes(note_new.getText()));
+        else
+            rc = QString("%1setNoteText(\"%2\");").arg(bv, quoteQuotes(note_new.getText()));
 
-        // Update also flags after changes in NoteEditor
-        emitDataChanged(selti);
+        QString comment = QString("Set note of %1 to \"%2\"").arg(getObjectName(selbi), note_new.getTextASCII().left(40));
+
+        logAction(rc, comment, __func__);
+
+        saveState(uc, rc, comment);
+
+        selbi->setNote(note_new);
+        if (!senderIsNoteEditor)
+            emitNoteChanged(selbi);
+
+        emitDataChanged(selbi);
 
         // 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)
+bool VymModel::loadNote(const QString &fn, BranchItem *bi)
 {
-    BranchItem *selbi = getSelectedBranch();
+    BranchItem *selbi = getSelectedBranch(bi);
     if (selbi) {
         QString n;
         if (!loadStringFromDisk(fn, n))
-            qWarning() << "VymModel::loadNote Couldn't load " << fn;
+            logWarning("Failed: Couldn't load '" + fn + "'", __func__);
         else {
             VymNote vn;
             vn.setAutoText(n);
-            setNote(vn);
+            setNote(vn, selbi);
             emitDataChanged(selbi);
             emitUpdateQueries();
             reposition();
+            return true;
         }
     }
     else
         qWarning("VymModel::loadNote no branch selected");
+    return false;
 }
 
-void VymModel::saveNote(const QString &fn)
+bool VymModel::saveNote(const QString &fn)
 {
     BranchItem *selbi = getSelectedBranch();
     if (selbi) {
@@ -2073,46 +2442,41 @@ void VymModel::saveNote(const QString &fn)
                        << fn;
         else {
             if (!saveStringToDisk(fn, n.saveToDir()))
-                qWarning() << "VymModel::saveNote Couldn't save " << fn;
+                logWarning("Failed: Couldn't save not to '" + fn + "'", __func__);
+            else
+                return true;
         }
     }
     else
         qWarning("VymModel::saveNote no branch selected");
+    return false;
 }
 
-void VymModel::findDuplicateURLs() // FIXME-3 needs GUI
+void VymModel::findDuplicateURLs() // FIXME-3 Feature needs GUI for viewing
 {
-    // Generate map containing _all_ URLs and branches
-    QString u;
-    QMultiMap<QString, BranchItem *> map;
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
+    // Generate multimap containing _all_ URLs and branches
+    QMultiMap<QString, BranchItem *> multimap;
+    QStringList urls;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev);
     while (cur) {
-        u = cur->getURL();
-        if (!u.isEmpty())
-            map.insert(u, cur);
+        QString u = cur->url();
+        if (!u.isEmpty()) {
+            multimap.insert(u, cur);
+            if (!urls.contains(u))
+                urls << u;
+        }
         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();
+    foreach (auto u, urls) {
+        if (multimap.values(u).size() > -1) {
+            qDebug() << "URL: " << u;
+            foreach(auto *bi, multimap.values(u))
+                qDebug() << " - " << bi->headingPlain();
         }
-        else
-            firstdup = i;
-
-        ++i;
     }
 }
 
@@ -2125,14 +2489,14 @@ bool VymModel::findAll(FindResultModel *rmodel, QString s,
                                // QTextDocument::FindFlag
     bool hit = false;
 
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev);
 
-    FindResultItem *lastParent = NULL;
+    FindResultItem *lastParent = nullptr;
     while (cur) {
-        lastParent = NULL;
-        if (cur->getHeading().getTextASCII().contains(s, cs)) {
+        lastParent = nullptr;
+        if (cur->heading().getTextASCII().contains(s, cs)) {
             lastParent = rmodel->addItem(cur);
             hit = true;
         }
@@ -2158,7 +2522,6 @@ bool VymModel::findAll(FindResultModel *rmodel, QString s,
                     }
 
                     // save index of occurence
-                    QString e = n.mid(i - 15, 30);
                     n.replace('\n', ' ');
                     rmodel->addSubItem(
                         lastParent,
@@ -2176,300 +2539,768 @@ bool VymModel::findAll(FindResultModel *rmodel, QString s,
     return hit;
 }
 
-void VymModel::setURL(QString url, bool updateFromCloud, BranchItem *bi)
+void VymModel::setUrl(QString url, bool updateFromCloud, BranchItem *bi)
 {
     if (!bi) bi = getSelectedBranch();
-    if (bi->getURL() == url)
+    if (bi->url() == 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();
+        QString oldurl = bi->url();
+        bi->setUrl(url);
 
-        if (updateFromCloud)    // FIXME-2 use oembed.com also for Youtube and other cloud providers
-            // Check for Confluence
-            setHeadingConfluencePageName();
-    }
-}
+        QString uc = QString("setUrl(\"%1\");").arg(oldurl);
+        QString rc = QString("setUrl(\"%1\");").arg(url);
 
-QString VymModel::getURL()
-{
-    TreeItem *selti = getSelectedItem();
-    if (selti)
-        return selti->getURL();
-    else
-        return QString();
-}
+        QString comment = QString("set URL of %1 to %2").arg(getObjectName(bi), url);
 
-QStringList VymModel::getURLs(bool ignoreScrolled)
-{
+        logAction(rc, comment, __func__);
+
+        saveStateBranch(bi, uc, rc, comment);
+
+        if (!url.isEmpty()) {
+            if (updateFromCloud) {    // FIXME-3 use oembed.com also for Youtube and other cloud providers
+                // Check for Jira
+                JiraAgent agent;
+                if (agent.setTicket(url)) {
+                    logInfo("Preparing to get data from Jira for URL: " + url, __func__);
+                    setAttribute(bi, "Jira.key", agent.key());
+
+                    // Initially set heading with ticket Id
+                    setHeading(agent.key(), bi);
+
+                    // Then try to update heading from Jira
+                    getJiraData(false, bi);
+                }
+
+                // Check for Confluence
+                if (bi->urlType() != TreeItem::JiraUrl)
+                    setConfluencePageDetails(false);
+            }
+        } else {
+            // url == ""
+            AttributeItem *ai = getAttributeByKey("Jira.issueUrl", bi);
+            if (ai)
+                deleteItem(ai);
+        }
+        updateJiraFlag(bi); // Implicitly calls setUrlType()
+
+
+        emitDataChanged(bi);
+        reposition();
+    }
+}
+
+QString VymModel::getUrl()
+{
+    TreeItem *selti = getSelectedItem();
+    if (selti)
+        return selti->url();
+    else
+        return QString();
+}
+
+QStringList VymModel::getUrls(bool ignoreScrolled)
+{
     QStringList urls;
     BranchItem *selbi = getSelectedBranch();
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev, true, selbi);
     while (cur) {
-        if (!cur->getURL().isEmpty() &&
+        if (cur->hasUrl() &&
             !(ignoreScrolled && cur->hasScrolledParent()))
-            urls.append(cur->getURL());
+            urls.append(cur->url());
         nextBranch(cur, prev, true, selbi);
     }
     return urls;
 }
 
-void VymModel::setFrameType(const FrameObj::FrameType &t)
+void VymModel::setJiraQuery(const QString &query_new, BranchItem *bi)
 {
-    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();
+    QList <BranchItem*> selbis = getSelectedBranches(bi);
+
+    foreach (BranchItem *bi, selbis)
+        if (query_new.isEmpty())
+            deleteAttribute(bi, "Jira.query");
+        else
+            setAttribute(bi, "Jira.query", query_new);
+}
+
+void VymModel::setFrameAutoDesign(const bool &useInnerFrame, const bool &b, BranchItem *bi)
+{
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+
+
+    BranchContainer *bc;
+    foreach (BranchItem *selbi, selbis) {
+        QString uif = toS(useInnerFrame);
+        QString b_undo = toS(!b);
+        QString b_redo = toS(b);
+        QString uc = QString("setFrameAutoDesign (%1, %2);").arg(uif).arg(b_undo);
+        QString rc = QString("setFrameAutoDesign (%1, %2);").arg(uif).arg(b_redo);
+
+        QString comment = QString("Set automatic design of frame to '%1'").arg(toS(b));
+
+        logAction(rc, comment, __func__);
+
+        saveStateBeginScript(comment);
+
+        bc = selbi->getBranchContainer();
+        bc->setFrameAutoDesign(useInnerFrame, b);
+        if (b) {
+            setFrameType(useInnerFrame, mapDesignInt->frameType(useInnerFrame, selbi->depth()), selbi);
+            setFramePenColor(useInnerFrame, mapDesignInt->framePenColor(useInnerFrame, selbi->depth()), selbi);
+            setFramePenWidth(useInnerFrame, mapDesignInt->framePenWidth(useInnerFrame, selbi->depth()), selbi);
+            setFrameBrushColor(useInnerFrame, mapDesignInt->frameBrushColor(useInnerFrame, selbi->depth()), selbi);
+       }
+
+        emitDataChanged(selbi);
+        branchPropertyEditor->updateControls();
+
+        saveStateBranch(selbi, uc, rc, comment);
+        saveStateEndScript();
+    }
+}
+
+void VymModel::setFrameType(const bool &useInnerFrame, const FrameContainer::FrameType &t, BranchItem *bi)
+{
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    BranchContainer *bc;
+    foreach (BranchItem *selbi, selbis) {
+        bc = selbi->getBranchContainer();
+        if (bc->frameType(useInnerFrame) == t)
+            break;
+
+        QString uif = toS(useInnerFrame);
+
+        QString oldFrameTypeName = bc->frameTypeString(bc->frameType(useInnerFrame));
+        QString newFrameTypeName = FrameContainer::frameTypeString(t);
+        QString uc = QString("setFrameType(%1, \"%2\");").arg(uif, oldFrameTypeName);
+        QString rc = QString("setFrameType(%1, \"%2\");").arg(uif, newFrameTypeName);
+        QString comment = QString("Set type of frame to %1").arg(newFrameTypeName);
+
+        logAction(rc, comment, __func__);
+
+        bool saveCompleteFrame = false;
+
+        if (t == FrameContainer::NoFrame) {
+            // Save also penWidth, colors, etc. to restore frame on undo
+            saveCompleteFrame = true;
+
+            saveStateBeginScript("Set frame parameters");
+            QString colorName = bc->framePenColor(useInnerFrame).name();
+            saveStateBranch(selbi,
+                    QString("setFramePenColor (%1, \"%2\");").arg(uif, colorName),
+                    "",
+                    QString("set pen color of frame to %1").arg(colorName));
+
+            colorName = bc->frameBrushColor(useInnerFrame).name();
+            saveStateBranch(bi,
+                    QString("setFrameBrushColor (%1, \"%2\");").arg(uif, colorName),
+                    "",
+                    QString("set background color of frame to %1").arg(colorName));
+
+            int i = bc->framePenWidth(useInnerFrame);
+            saveStateBranch(selbi,
+                    QString("setFramePenWidth (%1, \"%2\");").arg(uif).arg(i),
+                    "",
+                    QString("set pen width of frame to %1").arg(i));
+
+            i = bc->framePadding(useInnerFrame);
+            saveStateBranch(selbi,
+                    QString("setFramePadding (%1, \"%2\");").arg(uif, i),
+                    "",
+                    QString("set padding of frame to %1").arg(i));
         }
+
+
+        bc->setFrameType(useInnerFrame, t);
+
+        saveStateBranch(selbi, uc, rc, comment);
+
+        if (saveCompleteFrame)
+            saveStateEndScript();
+
+        emitDataChanged(selbi);
     }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+        reposition();
+    }
+    reposition();
 }
 
-void VymModel::setFrameType(const QString &s)
+void VymModel::setFrameType(const bool &useInnerFrame, const QString &s, BranchItem *bi)
 {
-    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();
+    setFrameType(useInnerFrame, FrameContainer::frameTypeFromString(s), bi);
+}
+
+void VymModel::setFramePenColor(const bool &useInnerFrame, const QColor &col, BranchItem *bi)
+
+{
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        BranchContainer *bc = selbi->getBranchContainer();
+        if (bc->frameType(useInnerFrame) != FrameContainer::NoFrame)  {
+            QString uif = toS(useInnerFrame);
+            QString colorNameOld = bc->framePenColor(useInnerFrame).name();
+            QString uc = QString("setFramePenColor (%1, \"%2\");").arg(uif, colorNameOld);
+            QString colorNameNew = col.name();
+            QString rc = QString("setFramePenColor (%1, \"%2\");").arg(uif, colorNameNew);
+            QString comment = QString("Set pen color of frame to %1").arg(colorNameNew);
+
+            logAction(rc, comment, __func__);
+
+            emitDataChanged(selbi);
+            branchPropertyEditor->updateControls();
+
+            saveStateBranch(selbi, uc, rc, comment);
+
+            bc->setFramePenColor(useInnerFrame, col);
         }
     }
 }
 
-void VymModel::toggleFrameIncludeChildren()
+void VymModel::setFrameBrushColor(
+    const bool &useInnerFrame, const QColor &col, BranchItem *bi)
 {
-    BranchItem *bi = getSelectedBranch();
-    if (bi) {
-        bool b = bi->getFrameIncludeChildren();
-        setFrameIncludeChildren(!b);
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        BranchContainer *bc = selbi->getBranchContainer();
+        if (bc->frameType(useInnerFrame) != FrameContainer::NoFrame)  {
+            QString uif = toS(useInnerFrame);
+            QString colorNameOld = bc->framePenColor(useInnerFrame).name();
+            QString uc = QString("setFrameBrushColor (%1, \"%2\");").arg(uif, colorNameOld);
+            QString colorNameNew = col.name();
+            QString rc = QString("setFrameBrushColor (%1, \"%2\");").arg(uif, colorNameNew);
+            QString comment = QString("Set background color of frame to %1").arg(colorNameNew);
+
+            logAction(rc, comment, __func__);
+
+            saveStateBranch(selbi, uc, rc, comment);
+
+            bc->setFrameBrushColor(useInnerFrame, col);
+        }
+        emitDataChanged(selbi);  // Notify HeadingEditor to eventually change BG color
+        branchPropertyEditor->updateControls();
     }
 }
 
-void VymModel::setFrameIncludeChildren(bool b)
+void VymModel::setFramePadding(
+    const bool &useInnerFrame, const int &i, BranchItem *bi)
 {
-    BranchItem *bi = getSelectedBranch();
-    if (bi) {
-        QString u = b ? "false" : "true";
-        QString r = !b ? "false" : "true";
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        BranchContainer *bc = selbi->getBranchContainer();
+        if (bc->frameType(useInnerFrame) != FrameContainer::NoFrame)  {
+            QString uif = toS(useInnerFrame);
+            QString uc = QString("setFramePadding (%1, \"%2\");").arg(uif).arg(bc->framePadding(useInnerFrame));
+            QString rc = QString("setFramePadding (%1, \"%2\");").arg(uif).arg(i);
+            QString comment = QString("Set padding of frame to '%1").arg(i);
 
-        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);
+            logAction(rc, comment, __func__);
+
+            saveStateBranch(selbi, uc, rc, comment);
+
+            bc->setFramePadding(useInnerFrame, i);
+            emitDataChanged(selbi);
+        }
+    }
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
         reposition();
     }
 }
+void VymModel::setFramePenWidth(
+    const bool &useInnerFrame, const int &i, BranchItem *bi)
+{
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        BranchContainer *bc = selbi->getBranchContainer();
+        if (bc->frameType(useInnerFrame) != FrameContainer::NoFrame)  {
+            QString uif = toS(useInnerFrame);
+            QString uc = QString("setFramePenWidth (%1, \"%2\");").arg(uif).arg(bc->framePenWidth(useInnerFrame));
+            QString rc = QString("setFramePenWidth (%1, \"%2\");").arg(uif).arg(i);
+            QString comment = QString("Set pen width of frame to '%1").arg(i);
+
+            logAction(rc, comment, __func__);
+
+            saveStateBranch(selbi, uc, rc, comment);
+
+            bc->setFramePenWidth(useInnerFrame, i);
+            emitDataChanged(selbi);
+        }
+    }
 
-void VymModel::setFramePenColor(
-    const QColor &c) // FIXME-4 not saved if there is no LMO
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+        reposition();
+    }
+}
 
+void VymModel::setHeadingColumnWidthAutoDesign(const bool &b, BranchItem *bi)
 {
-    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);
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    BranchContainer *bc;
+    foreach (BranchItem *selbi, selbis) {
+        bc = selbi->getBranchContainer();
+        if (bc->columnWidthAutoDesign() != b) {
+            if (b)
+                bc->setColumnWidth(mapDesignInt->headingColumnWidth(selbi->depth()));
+           QString v = b ? "Enable" : "Disable";
+           QString uc = QString("setHeadingColumnWidthAutoDesign (%1);").arg(toS(!b));
+           QString rc = QString("setHeadingColumnWidthAutoDesign (%1);").arg(toS(b));
+            QString comment = QString("%1 automatic heading width").arg(v);
+
+            logAction(rc, comment, __func__);
+
+            saveStateBranch(selbi, uc, rc, comment);
+            bc->setColumnWidthAutoDesign(b);
+            branchPropertyEditor->updateControls();
+            emitDataChanged(selbi);
         }
     }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+        reposition();
+    }
 }
 
-void VymModel::setFrameBrushColor(
-    const QColor &c) // FIXME-4 not saved if there is no LMO
+void VymModel::setHeadingColumnWidth (const int &i, BranchItem *bi)
 {
-    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
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        BranchContainer *bc = selbi->getBranchContainer();
+       if (bc->columnWidth() != i) {
+           QString uc = QString("setHeadingColumnWidth (%1);").arg(bc->columnWidth());
+           QString rc = QString("setHeadingColumnWidth (%1);").arg(i);
+            QString comment = QString("Set heading column width to %1").arg(i);
+
+            logAction(rc, comment, __func__);
+
+            saveStateBranch(selbi, uc, rc, comment);
+
+            bc->setColumnWidth(i);
+            emitDataChanged(selbi);
         }
-        emitDataChanged(bi);  // Notify HeadingEditor to eventually change BG color
+    }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+        reposition();
     }
 }
 
-void VymModel::setFramePadding(
-    const int &i)
+void VymModel::setRotationAutoDesign(const bool &b, BranchItem *bi)
 {
-    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();
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+
+    foreach (BranchItem *selbi, selbis) {
+        BranchContainer *bc = selbi->getBranchContainer();
+        if (bc->rotationsAutoDesign() != b) {
+            QString s = b ? "Enable" : "Disable";
+            QString uc = QString("setRotationAutoDesign(%1);").arg(toS(bc->rotationsAutoDesign()));
+            QString rc = QString("setRotationAutoDesign(%1);").arg(toS(b));
+            QString comment = QString("%1 automatic rotation heading and subtree").arg(s);
+
+            logAction(rc, comment, __func__);
+
+            saveStateBeginScript(comment);
+            if (b) {
+                setRotationHeading(mapDesignInt->rotationHeading(selbi->depth()));
+                setRotationSubtree(mapDesignInt->rotationSubtree(selbi->depth()));
+            }
+            saveStateBranch(selbi, uc, rc);
+
+            bc->setRotationsAutoDesign(b);
+            emitDataChanged(selbi);
+
+            saveStateEndScript();
         }
     }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+        reposition();
+    }
 }
 
-void VymModel::setFrameBorderWidth(
-    const int &i)
+void VymModel::setRotationHeading (const int &i, BranchItem* bi)
 {
-    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();
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+
+    foreach (BranchItem *selbi, selbis) {
+        BranchContainer *bc = selbi->getBranchContainer();
+       if (bc->rotationHeading() != i) {
+
+            QString uc = QString("setRotationHeading(\"%1\");").arg(toS(bc->rotationHeading(), 1));
+            QString rc = QString("setRotationHeading(\"%1\");").arg(i);
+            QString comment = QString("Set rotation angle of heading and flags to %1").arg(i);
+
+            logAction(rc, comment, __func__);
+
+            saveStateBranch(selbi, uc, rc, comment);
+
+            bc->setRotationHeading(i);
+            emitDataChanged(selbi);
         }
     }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+        reposition();
+    }
 }
 
-void VymModel::setIncludeImagesVer(bool b)
+void VymModel::setRotationSubtree (const int &i, BranchItem *bi)
 {
-    BranchItem *bi = getSelectedBranch();
-    if (bi && b != bi->getIncludeImagesVer()) {
-        QString u = b ? "false" : "true";
-        QString r = !b ? "false" : "true";
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
 
-        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);
+    foreach (BranchItem *selbi, selbis) {
+        BranchContainer *bc = selbi->getBranchContainer();
+       if (bc->rotationSubtree() != i) {
+            QString uc = QString("setRotationSubtree(\"%1\");").arg(toS(bc->rotationSubtree(), 1));
+            QString rc = QString("setRotationSubtree(\"%1\");").arg(i);
+            QString comment = QString("Set rotation angle of subtree to %1").arg(i);
+
+            logAction(rc, comment, __func__);
+
+            saveStateBranch(selbi, uc, rc, comment);
+
+            bc->setRotationSubtree(i);
+            emitDataChanged(selbi);
+       }
+    }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
         reposition();
     }
 }
 
-void VymModel::setIncludeImagesHor(bool b)
+void VymModel::setScaleAutoDesign (const bool & b, BranchItem *bi)
 {
-    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);
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+
+    BranchContainer *bc;
+    foreach (BranchItem *selbi, selbis) {
+        bc = selbi->getBranchContainer();
+        if (bc->scaleAutoDesign() != b) {
+            QString s = b ? "Enable" : "Disable";
+            QString uc = QString("setScaleAutoDesign(%1);").arg(toS(bc->scaleAutoDesign()));
+            QString rc = QString("setScaleAutoDesign(%1);").arg(toS(b));
+            QString c = QString("%1 automatic scaling").arg(s);
+
+            logAction(rc, c, __func__);
+
+            saveStateBeginScript(c);
+            if (b) {
+                setScaleHeading(mapDesignInt->scaleHeading(selbi->depth()));
+                setScaleSubtree(mapDesignInt->scaleSubtree(selbi->depth()));
+            }
+            saveStateBranch(selbi, uc, rc);
+            bc->setScaleAutoDesign(b);
+            emitDataChanged(selbi);
+
+            saveStateEndScript();
+        }
+    }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
         reposition();
     }
 }
 
-void VymModel::setChildrenLayout(
-    BranchItem::LayoutHint layoutHint) // FIXME-3 no savestate yet
+void VymModel::setScaleHeading (const qreal &f, const bool relative, BranchItem *bi)
 {
-    BranchItem *bi = getSelectedBranch();
-    if (bi) {
-        /*
-        QString u= b ? "false" : "true";
-        QString r=!b ? "false" : "true";
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
 
-        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);
+    BranchContainer *bc;
+    foreach (BranchItem *selbi, selbis) {
+        bc = selbi->getBranchContainer();
+        qreal f_old = bc->scaleHeading();
+        qreal f_new = relative ? f_old + f : f;
+
+       if (bc->scaleHeading() != f_new) {
+            QString uc = QString("setScaleHeading(%1);").arg(toS(f_old, 3));
+            QString rc = QString("setScaleHeading(%1);").arg(toS(f_new, 3));
+            QString c  = QString("Set heading scale factor to %1").arg(f_new);
+
+            logAction(rc, c, __func__);
+
+            saveStateBranch(selbi, uc, rc, c);
+
+            bc->setScaleHeading(f_new);
+            emitDataChanged(selbi);
+       }
+    }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
         reposition();
     }
 }
 
-void VymModel::setHideLinkUnselected(bool b)
+qreal VymModel::getScaleHeading ()
 {
-    TreeItem *ti = getSelectedItem();
-    if (ti && (ti->getType() == TreeItem::Image || ti->isBranchLikeType())) {
-        QString u = b ? "false" : "true";
-        QString r = !b ? "false" : "true";
+    QList<BranchItem *> selbis = getSelectedBranches();
 
-        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);
+    if (selbis.isEmpty()) return 1;
+
+    return selbis.first()->getBranchContainer()->scaleHeading();
+}
+
+
+void VymModel::setScaleSubtree (const qreal &f_new, BranchItem *bi)
+{
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+
+    BranchContainer *bc;
+    foreach (BranchItem *selbi, selbis) {
+        bc = selbi->getBranchContainer();
+        qreal f_old = bc->scaleSubtree();
+
+       if (f_new != f_old) {
+            QString uc = QString("setScaleSubtree(%1);").arg(toS(f_old, 3));
+            QString rc = QString("setScaleSubtree(%1);").arg(toS(f_new,3));
+            QString c  = QString("Set subtree scale factor to %1").arg(toS(f_new, 3));
+            logAction(rc, c, __func__);
+            saveStateBranch(selbi, uc, rc, c);
+
+            bc->setScaleSubtree(f_new);
+            branchPropertyEditor->updateControls();
+            emitDataChanged(selbi);
+       }
+    }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+        reposition();
     }
 }
 
-void VymModel::setHideExport(bool b, TreeItem *ti)
+qreal VymModel::getScaleSubtree ()
 {
-    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();
+    QList<BranchItem *> selbis = getSelectedBranches();
+
+    if (selbis.isEmpty()) return 1;
+
+    return selbis.first()->getBranchContainer()->scaleSubtree();
+}
+
+void VymModel::setScaleImage(const qreal &f, const bool relative, ImageItem *ii)
+{
+    QList<ImageItem *> seliis = getSelectedImages(ii);
+
+    foreach (ImageItem *selii, seliis) {
+        qreal f_old = selii->scale();
+        qreal f_new = relative ? f_old + f : f;
+        if (selii->scale() != f_new) {
+            QString iv = setImageVar(selii);
+            QString uc = iv + QString("i.setScale(%1);").arg(toS(f_old, 3));
+            QString rc = iv + QString("i.setScale(%1);").arg(toS(f_new,3));
+            QString c  = QString("Set image scale factor to %1").arg(toS(f_new, 3));
+            logAction(rc, c, __func__);
+            saveState(uc, rc, c);
+
+            selii->setScale(f_new);
+            emitDataChanged(selii);
+        }
+    }
+
+    if (!seliis.isEmpty()) {
+        branchPropertyEditor->updateControls();
         reposition();
     }
 }
 
-void VymModel::toggleHideExport()
+void VymModel::setScale(const qreal &f, const bool relative)
+{
+    // Grow/shring branches and/or images using shortcuts
+    setScaleAutoDesign(false);
+    setScaleHeading(f, relative);
+    setScaleImage(f, relative);
+}
+
+void VymModel::growSelectionSize()
 {
-    QList<TreeItem *> selItems = getSelectedItems();
-    if (selItems.count() > 0) {
-        foreach (TreeItem *ti, selItems) {
-            bool b = !ti->hideInExport();
-            setHideExport(b, ti);
+    setScale(0.05, true);
+}
+
+void VymModel::shrinkSelectionSize()
+{
+    setScale(- 0.05, true);
+}
+
+void VymModel::resetSelectionSize()
+{
+    setScale(1, false);
+}
+
+void VymModel::setBranchesLayout(const QString &s, BranchItem *bi)
+{
+    // qDebug() << "VM::setBranchesLayout for " << headingText(bi) << s;
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    BranchContainer *bc;
+    foreach (BranchItem *selbi, selbis) {
+        Container::Layout layout;
+        bc = selbi->getBranchContainer();
+
+        if (s == "Auto") {
+            // Set layout to "auto"
+            bc->branchesContainerAutoLayout = true;
+
+            // Get layout from mapDesign
+            layout = mapDesignInt->branchesContainerLayout(selbi->depth());
+        } else {
+            bc->branchesContainerAutoLayout = false;
+            layout = Container::layoutFromString(s);
+        }
+        if (bc->branchesContainerLayout() != layout  && layout != Container::UndefinedLayout) {
+            QString bv = setBranchVar(bi);
+            QString uc = bv + "map.loadBranchReplace(\"UNDO_PATH\", b);";
+            QString rc = bv + QString("b.setBranchesLayout (\"%1\")").arg(s);
+            QString com = QString("Set branches layout of %1 to %2").arg(getObjectName(bi), layout);
+            logAction(rc, com, __func__);
+
+            saveState(uc, rc, com, bi);
+
+            bc->setBranchesContainerLayout(layout);
+            emitDataChanged(selbi);
         }
     }
+
+    // Links might have been added or removed, Nested lists, etc...
+    foreach (BranchItem *selbi, selbis)
+        applyDesignRecursively(MapDesign::LayoutChanged, selbi);
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+
+        // Create and delete containers, update their structure
+        reposition();
+    }
+
 }
 
-void VymModel::toggleTask()
+void VymModel::setImagesLayout(const QString &s, BranchItem *bi)
 {
-    BranchItem *selbi = getSelectedBranch();
-    if (selbi) {
-        saveStateChangingPart(
-            selbi, selbi, QString("toggleTask()"),
-            QString("Toggle task of %1").arg(getObjectName(selbi)));
+    BranchContainer *bc;
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        Container::Layout layout;
+        bc = selbi->getBranchContainer();
+        if (s == "Auto") {
+            bc->imagesContainerAutoLayout = true;
+            layout = mapDesignInt->imagesContainerLayout(selbi->depth());
+        } else {
+            bc->imagesContainerAutoLayout = false;
+            layout = Container::layoutFromString(s);
+        }
+
+        if (bc->imagesContainerLayout() != layout  && layout != Container::UndefinedLayout) {
+            QString bv = setBranchVar(selbi);
+            QString uc = bv + "map.loadBranchReplace(\"UNDO_PATH\", b);";
+            QString rc = bv + QString("b.setImagesLayout (\"%1\")").arg(s);
+            QString com = QString("Set images layout of %1 to %2").arg(getObjectName(selbi), s);
+            logAction(rc, com, __func__);
+
+            saveState(uc, rc, com, selbi);
+
+            bc->setImagesContainerLayout(layout);
+            emitDataChanged(selbi);
+        }
+    }
+
+    if (!selbis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+        reposition();
+    }
+}
+
+void VymModel::setHideLinkUnselected(bool b, TreeItem *ti)
+{
+    QList <TreeItem*> seltis = getSelectedItems(ti);
+
+    foreach (TreeItem *selti, seltis) {
+        if (selti->getType() == TreeItem::Image || selti->hasTypeBranch()) {
+            QString v = b ? "Hide" : "Show";
+            QString tiv;
+            if (selti->hasTypeBranch())
+                tiv = setBranchVar((BranchItem*)selti, "ti");
+            else if (selti->hasTypeImage())
+                tiv = setImageVar((ImageItem*)selti, "ti");
+            else {
+                qWarning() << "VymModel::setHideLinkUnselected  no branch or image";
+                return;
+            }
+            QString uc = tiv + QString("ti.setHideLinkUnselected(%1);").arg(toS(!b));
+            QString rc = tiv + QString("ti.setHideLinkUnselected(%1);").arg(toS(b));
+            QString comment = QString("%1 link if item %2 is not selected").arg(v, getObjectName(selti));
+
+            logAction(rc, comment, __func__);
+
+            saveState( uc, rc, comment);
+            ((MapItem *)selti)->setHideLinkUnselected(b);
+        }
+        emitDataChanged(selti);
+    }
+
+    if (!seltis.isEmpty()) {
+        branchPropertyEditor->updateControls();
+        reposition();
+    }
+}
+
+void VymModel::setHideExport(bool b, BranchItem *bi)
+{
+    QList <BranchItem*> selbis = getSelectedBranches(bi);
+
+    foreach (BranchItem *selbi, selbis) {
+        if (selbi->hideTemporary() != b) {
+            selbi->setHideTemporary(b);
+            QString u = toS(!b);
+            QString r = toS(b);
+            QString uc = QString("setHideExport (%1)").arg(u);
+            QString rc = QString("setHideExport (%1)").arg(r);
+
+            QString comment = "Set hide export of " + getObjectName(selbi) + " to " + r;
+
+            logAction(rc, comment, __func__);
+
+            saveStateBranch(selbi, uc, uc, comment);
+
+            emitDataChanged(selbi);
+        }
+    }
+
+    if (!selbis.isEmpty())
+        reposition();
+}
+
+void VymModel::toggleHideExport()
+{
+    QList<BranchItem *> selbis = getSelectedBranches();
+    foreach (BranchItem *selbi, selbis) {
+        bool b = !selbi->hideTemporary();
+        setHideExport(b, selbi);
+    }
+}
+
+void VymModel::toggleTask(BranchItem *bi)
+{
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (auto selbi, selbis) {
+        QString uc = "toggleTask();";
+        QString comment = QString("Toggle task of %1").arg(getObjectName(selbi));
+
+        logAction(uc, comment, __func__);
+
+        saveStateBranch( selbi, uc, uc, comment);
         Task *task = selbi->getTask();
         if (!task) {
             task = taskModel->createTask(selbi);
@@ -2479,111 +3310,121 @@ void VymModel::toggleTask()
             taskModel->deleteTask(task);
 
         emitDataChanged(selbi);
-        emitSelectionChanged();
         reposition();
     }
 }
 
-bool VymModel::cycleTaskStatus(bool reverse)
+bool VymModel::cycleTaskStatus(BranchItem *bi, bool reverse)
 {
-    BranchItem *selbi = getSelectedBranch();
-    if (selbi) {
+    bool repositionRequired = false;
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
         Task *task = selbi->getTask();
         if (task) {
-            saveStateChangingPart(
-                selbi, selbi, QString("cycleTask()"),
-                QString("Toggle task of %1").arg(getObjectName(selbi)));
+            QString uc, rc;
+            if (!reverse) {
+                uc = "cycleTask(true);";
+                rc = "cycleTask();";
+            } else {
+                uc = "cycleTask();";
+                rc = "cycleTask(true);";
+            }
+            QString comment = QString("Cycle task of %1").arg(getObjectName(selbi));
+
+            logAction(rc, comment, __func__);
+
+            saveStateBranch(selbi, uc, rc, comment);
+
             task->cycleStatus(reverse);
             task->setDateModification();
 
-            // make sure task is still visible
+            // make sure task is still visible  // FIXME-3 for multi-selections?
             taskEditor->select(task);
             emitDataChanged(selbi);
-            reposition();
-            return true;
+            repositionRequired = true;
         }
     }
+    if (repositionRequired) {
+        reposition();
+        return true;
+    }
     return false;
 }
 
-bool VymModel::setTaskSleep(const QString &s)
+bool VymModel::setTaskSleep(const QString &s, BranchItem *bi) // FIXME-4 (WIP) Rename "sleep" to "alarm" in code, commands, doc
 {
     bool ok = false;
-    BranchItem *selbi = getSelectedBranch();
-    if (selbi && !s.isEmpty()) {
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (auto selbi, selbis) {
         Task *task = selbi->getTask();
         if (task) {
-            QDateTime oldSleep = task->getSleep();
+            QDateTime oldAlarmTime = task->alarmTime();
 
             // Parse the string, which could be days, hours or one of several
             // time formats
 
             if (s == "0") {
+                // Reset sleep time and wake up task
                 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());
+                static QRegularExpression re;
+
+                // Only digits considered as days
+                re.setPattern("^\\s*(\\d+)\\s*$");
+                re.setPatternOptions(QRegularExpression::InvertedGreedinessOption);
+                QRegularExpressionMatch match = re.match(s);
+                if (match.hasMatch()) {
+                    ok = task->setDaysSleep(match.captured(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());
+                    // Digit followed by "h", considered as hours
+                    re.setPattern("^\\s*(\\d+)\\s*h\\s*$");
+                    match = re.match(s);
+                    if (match.hasMatch()) {
+                        ok = task->setHoursSleep(match.captured(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());
+                        // Digits followed by "w", considered as weeks
+                        re.setPattern("^\\s*(\\d+)\\s*w\\s*$");
+                        match = re.match(s);
+                        if (match.hasMatch()) {
+                            ok = task->setDaysSleep(7 * match.captured(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());
+                            // Digits followed by "s", considered as seconds
+                            re.setPattern("^\\s*(\\d+)\\s*s\\s*$");
+                            match = re.match(s);
+                            if (match.hasMatch()) {
+                                ok = task->setSecsSleep(match.captured(1).toInt());
                             }
                             else {
-                                ok = task->setDateSleep(
-                                    s); // ISO date YYYY-MM-DDTHH:mm:ss
+                                // Try setting ISO date YYYY-MM-DDTHH:mm:ss
+                                ok = task->setDateSleep(s);
 
                                 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
+                                    // German format, e.g. "24.12.2012"
+                                    re.setPattern("(\\d+)\\.(\\d+)\\.(\\d+)");
+                                    re.setPatternOptions(QRegularExpression::NoPatternOption);
+                                    match = re.match(s);
+                                    if (match.hasMatch()) {
+                                        QDateTime d(
+                                            QDate(match.captured(3).toInt(),
+                                                  match.captured(2).toInt(),
+                                                  match.captured(1).toInt()).startOfDay());
+                                        ok = task->setDateSleep(d); 
                                     }
                                     else {
+                                        // Short German format, e.g. "24.12."
                                         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();
+                                        re.setPatternOptions(QRegularExpression::InvertedGreedinessOption);
+                                        match = re.match(s);
+                                        if (match.hasMatch()) {
+                                            int month = match.captured(2).toInt();
+                                            int day = match.captured(1).toInt();
                                             int year =
                                                 QDate::currentDate().year();
-                                            d = QDateTime(
-                                                QDate(year, month, day).startOfDay());
+                                            QDateTime d(QDate(year, month, day).startOfDay());
                                             // d = QDate(year, month,
                                             // day).startOfDay();
                                             if (QDateTime::currentDateTime()
@@ -2594,22 +3435,19 @@ bool VymModel::setTaskSleep(const QString &s)
                                                 // d = QDate(year, month,
                                                 // day).startOfDay();
                                             }
-                                            ok = task->setDateSleep(
-                                                d); // Short German format,
-                                                    // e.g. 24.12.
+                                            ok = task->setDateSleep(d);
                                         }
                                         else {
+                                            // Time HH:MM
                                             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(
+                                            match = re.match(s);
+                                            if (match.hasMatch()) {
+                                                int hour = match.captured(1).toInt();
+                                                int min = match.captured(2).toInt();
+                                                QDateTime d(
                                                     QDate::currentDate(),
                                                     QTime(hour, min));
-                                                ok = task->setDateSleep(
-                                                    d); // Time HH:MM
+                                                ok = task->setDateSleep(d);
                                             }
                                         }
                                     }
@@ -2621,57 +3459,59 @@ bool VymModel::setTaskSleep(const QString &s)
             }
 
             if (ok) {
-                QString oldSleepString;
-                if (oldSleep.isValid())
-                    oldSleepString = oldSleep.toString(Qt::ISODate);
+                QString oldAlarmTimeString;
+                if (oldAlarmTime.isValid())
+                    oldAlarmTimeString = oldAlarmTime.toString(Qt::ISODate);
                 else
-                    oldSleepString =
-                        "1970-01-26T00:00:00"; // Some date long ago
+                    oldAlarmTimeString = "1970-01-26T00:00:00"; // Some date long ago...
 
-                QString newSleepString = task->getSleep().toString(Qt::ISODate);
+                QString newAlarmTimeString = task->alarmTime().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));
+                QString bv = setBranchVar(selbi);
+                QString uc = QString("setTaskSleep (\"%1\")").arg(oldAlarmTimeString);
+                QString rc = QString("setTaskSleep (\"%1\")").arg(newAlarmTimeString);
+                QString comment = "Set sleep time for task";
+
+                logAction(rc, comment, __func__);  // FIXME-3 Logging command should be done before actual change. 
+                                                    // Would require separate checks in task, if new alarmTime is valid
+
+                saveStateBranch(selbi, uc, rc, comment);
+
                 emitDataChanged(selbi);
                 reposition();
             }
 
         } // Found task
-    }     // Found branch
+        if (!ok)
+            return false;
+    }     // Looping over selected branches
     return ok;
 }
 
 void VymModel::setTaskPriorityDelta(const int &pd, BranchItem *bi)
 {
-    QList<BranchItem *> selbis;
-    if (bi)
-        selbis << bi;
-    else
-        selbis = getSelectedBranches();
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
 
     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");
+            QString bv = setBranchVar(selbi);
+            QString uc = QString("setTaskPriorityDelta (%1)").arg(task->getPriorityDelta());
+            QString rc = QString("setTaskPriorityDelta (%1)").arg(pd);
+            QString comment = "Set delta for priority of task";
+            logAction(rc, comment, __func__);
+            saveStateBranch(selbi, uc, rc, comment);
             task->setPriorityDelta(pd);
             emitDataChanged(selbi);
         }
     }
 }
 
-int VymModel::getTaskPriorityDelta()
+int VymModel::getTaskPriorityDelta(BranchItem *bi)
 {
-    BranchItem *selbi = getSelectedBranch();
+    BranchItem *selbi = getSelectedBranch(bi);
     if (selbi) {
         Task *task = selbi->getTask();
         if (task)
@@ -2691,17 +3531,23 @@ void VymModel::updateTasksAlarm(bool force)
 
 BranchItem *VymModel::addTimestamp()
 {
-    BranchItem *selbi = addNewBranch();
+    BranchItem *selbi = getSelectedBranch();
+
     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);
+        QChar c = '0';
+        QString s = QString("%1-%2-%3")
+                       .arg(today.year(), 4, 10, c)
+                       .arg(today.month(), 2, 10, c)
+                       .arg(today.day(), 2, 10, c);
+        QString comment = "Add branch with current date as heading: " + s;
+
+        saveStateBeginScript(comment);
+        BranchItem *newbi = addNewBranch(selbi);
+        setHeadingPlainText(s, newbi);
+        saveStateEndScript();
+
+        select(newbi);
     }
     return selbi;
 }
@@ -2716,15 +3562,19 @@ void VymModel::copy()
     QStringList clipboardFiles;
 
     if (itemList.count() > 0) {
+
+        QStringList uids;
+
         uint i = 1;
         QString fn;
         foreach (TreeItem *ti, itemList) {
+            uids << QString("\"%1\"").arg(ti->getUuid().toString());
             fn = QString("%1/%2-%3.xml")
                      .arg(clipboardDir)
                      .arg(clipboardFile)
                      .arg(i);
             QString content = saveToDir(clipboardDir, clipboardFile,
-                                        FlagRowMaster::NoFlags, QPointF(), ti);
+                                        FlagRowMaster::NoFlags, QPointF(), false, false, false,  ti);
 
             if (!saveStringToDisk(fn, content))
                 qWarning() << "ME::saveStringToDisk failed: " << fn;
@@ -2737,7 +3587,19 @@ void VymModel::copy()
         QMimeData *mimeData = new QMimeData;
         mimeData->setData("application/x-vym", clipboardFiles.join(",").toLatin1());
         clipboard->setMimeData(mimeData);
+
+        QString rc = QString("map.selectUids([%1]); map.copy();").arg(uids.join(","));
+        QString comment = QString("Copy %1 selected %2 to clipboard: [%3]")
+            .arg(itemList.count())
+            .arg(pluralize(QString("item"), itemList.count()))
+            .arg(uids.join(","));
+
+        logAction(rc, "Copy selection", __func__);
+        saveState("", rc, comment);
+
     }
+
+    mainWindow->updateActions();
 }
 
 void VymModel::paste()
@@ -2745,34 +3607,57 @@ void VymModel::paste()
     if (readonly)
         return;
 
+    const QClipboard *clipboard = QApplication::clipboard();
+    const QMimeData *mimeData = clipboard->mimeData();
+
     BranchItem *selbi = getSelectedBranch();
+    ImageItem *selii = getSelectedImage();
 
-    if (selbi) {
-        const QClipboard *clipboard = QApplication::clipboard();
-        const QMimeData *mimeData = clipboard->mimeData();
+    // Special case: When image is selected and image is pasted, try to append 
+    // pasted image to current set of images in parent
+    if (!selbi && selii && mimeData->hasImage())
+        selbi = selii->parentBranch();
 
+    if (selbi) {
         if (mimeData->formats().contains("application/x-vym")) {
             QStringList clipboardFiles = QString(mimeData->data("application/x-vym")).split(",");
 
-            saveStateChangingPart(selbi, selbi, QString("paste ()"),
-                                  QString("Paste"));
+            QString bv = setBranchVar(selbi);
+            QString uc = bv + QString("map.loadBranchReplace(\"UNDO_PATH\", b);");
+            QString rc = bv + QString("b.select(); map.paste();");
+            QString comment = QString("Paste to branch \"%1\"").arg(selbi->headingText());
+
+            logAction(rc, comment, __func__);
+
+            saveState(uc, rc, comment, selbi, selbi);
 
             bool zippedOrg = zipped;
             foreach(QString fn, clipboardFiles) {
-                if (File::Success != loadMap(fn, ImportAdd, VymMap, SlideContent))
-                    qWarning() << "VM::paste Loading clipboard failed: " << fn;
+                if (!loadMap(fn,
+                            File::ImportAdd,
+                            File::VymMap,
+                            VymReader::SlideContent,
+                            selbi,
+                            selbi->branchCount()))
+                    logWarning("Failed to load clipboard from " + fn, __func__);
             }
+            select(selbi);
             zipped = zippedOrg;
-            reposition();
         } else if (mimeData->hasImage()) {
+            //qDebug() << "VM::paste  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";
+                logWarning("Could not save copy of image in system clipboard " + fn, __func__);
             else {
                 ImageItem *ii = loadImage(selbi, fn);
-                if (ii)
-                    setScaleFactor(300.0 / image.width(), ii);    // FIXME-2 Better use user-defined fixed width
+                if (ii) {
+                    setScaleImage(300.0 / image.width(), false, ii);    // FIXME-3 Better use user-defined fixed width when pasting images
+
+                    if (selii)
+                        // In case we pasted onto an existing image, select the new one
+                        select(ii);
+                }
             }
         } else if (mimeData->hasHtml()) {
             //setText(mimeData->html());
@@ -2783,7 +3668,7 @@ void VymModel::paste()
             //setTextFormat(Qt::PlainText);
             qDebug() << "VM::paste found text...";
         } else {
-            qWarning() << "VM::paste Cannot paste data, mimeData->formats=" << mimeData->formats();
+            logWarning("Cannot paste data, mimeData->formats=" + mimeData->formats().join(","), __func__);
         }
     }
 }
@@ -2797,155 +3682,306 @@ void VymModel::cut()
     deleteSelection();
 }
 
-bool VymModel::moveUp(BranchItem *bi)
+bool VymModel::canMoveUp(TreeItem *ti)
 {
-    if (readonly)
-        return false;
+    if (ti) {
+        BranchItem *pbi;
+        if (ti->hasTypeBranch())
+            pbi = ((BranchItem*)ti)->parentBranch();
+        else if (ti->hasTypeImage())
+            pbi = ((ImageItem*)ti)->parentBranch();
+        else 
+            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;
+        if (pbi == rootItem)
+            return false;
+
+        return (pbi->num(ti) > 0);
+    }
+
+    return false;
 }
 
-void VymModel::moveUp()
+bool VymModel::canMoveDown(TreeItem *ti)
 {
-    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);
+    if (ti) {
+        BranchItem *pbi;
+
+        if (ti->hasTypeBranch()) {
+            pbi = ((BranchItem*)ti)->parentBranch();
+            if (pbi == rootItem)
+                return false;
+            return (pbi->num(ti) < pbi->branchCount() - 1);
+        } else if (ti->hasTypeImage()) {
+            pbi = ((ImageItem*)ti)->parentBranch();
+            return (pbi->num(ti) < pbi->imageCount() - 1);
         }
     }
+
+    return false;
 }
 
-bool VymModel::moveDown(BranchItem *bi)
+void VymModel::moveUp(TreeItem *ti)
 {
-    if (readonly)
-        return false;
+    if (readonly) return;
 
-    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;
+
+    QList<BranchItem *> selbis = getSelectedBranches(ti);
+
+    if (!selbis.isEmpty()){
+        foreach (BranchItem *selbi, sortBranchesByNum(selbis, false)) {
+            if (canMoveUp(selbi)) {
+                logAction("", "Starting to move branch up: " + headingText(selbi), __func__);
+                relinkBranch(selbi, selbi->parentBranch(), selbi->num() - 1);
+            }
+        }
+    }
+
+    QList<ImageItem *> seliis = getSelectedImages(ti);
+
+    if (!seliis.isEmpty()){
+        foreach (ImageItem *selii, sortImagesByNum(seliis, false)) {
+            if (canMoveUp(selii)) {
+                logAction("", "Starting to move image up: " + headingText(selii), __func__);
+                relinkImage(selii, selii->parentBranch(), selii->num() - 1);
+            }
+        }
+    }
 }
 
-void VymModel::moveDown()
+void VymModel::moveDown(TreeItem *ti)
 {
-    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);
-        }
+    if (readonly) return;
+
+    QList<BranchItem *> selbis = getSelectedBranches(ti);
+    if (!selbis.isEmpty()) {
+        foreach (BranchItem *selbi, sortBranchesByNum(selbis, true))
+            if (canMoveDown(selbi)) {
+                logAction("", "Starting to move branch down: " + headingText(selbi), __func__);
+                relinkBranch(selbi, selbi->parentBranch(), selbi->num() + 1);
+            }
+    }
+
+    QList<ImageItem *> seliis = getSelectedImages(ti);
+    if (!seliis.isEmpty()) {
+        foreach (ImageItem *selii, sortImagesByNum(seliis, true))
+            if (canMoveDown(selii)) {
+                logAction("", "Starting to move image down: " + headingText(selii), __func__);
+                relinkImage(selii, selii->parentBranch(), selii->num() + 1);
+            }
     }
 }
 
 void VymModel::moveUpDiagonally()
 {
-    BranchItem *selbi = getSelectedBranch();
-    if (selbi) {
-        BranchItem *parent = selbi->parentBranch();
-        if (parent == rootItem) return;
+    if (readonly) return;   // FIXME-3 readonly needs be checked for every 
+                            // public function in model, which modifies data...
+
+    QList<BranchItem *> selbis = getSelectedBranches();
+
+    foreach (BranchItem *selbi, selbis) {
+        logAction("", "Starting to move up diagonally: " + headingText(selbi), __func__);
+        BranchItem *pbi = selbi->parentBranch();
+        if (pbi == rootItem) break;
 
         int n = selbi->num();
-        if (n == 0) return;
+        if (n == 0) break;
 
-        BranchItem *dst = parent->getBranchNum(n-1);
-        if (!dst) return;
+        BranchItem *dst = pbi->getBranchNum(n - 1);
+        if (!dst) break;
 
-        relinkBranch(selbi, dst, dst->branchCount() + 1, true);
-     }
+        relinkBranch(selbi, dst, dst->branchCount() + 1);
+    }
 }
 
 void VymModel::moveDownDiagonally()
 {
-    BranchItem *selbi = getSelectedBranch();
-    if (selbi) {
-        BranchItem *parent = selbi->parentBranch();
-        if (parent == rootItem) return;
-        BranchItem *parentParent = parent->parentBranch();
-        int n = parent->num();
+    if (readonly) return;
+
 
-        relinkBranch(selbi, parentParent, n + 1, true);
-     }
+    QList<BranchItem *> selbis = getSelectedBranches();
+    foreach (BranchItem *selbi, selbis) {
+        logAction("", "Starting to move down diagonally: " + headingText(selbi), __func__);
+        BranchItem *pbi = selbi->parentBranch();
+        if (pbi == rootItem) break;
+        BranchItem *parentParent = pbi->parentBranch();
+        int n = pbi->num();
+
+        relinkBranch(selbi, parentParent, n + 1);
+    }
 }
 
-void VymModel::detach()
+void VymModel::detach(BranchItem *bi)   // FIXME-2 Various issues
+                                        // sometines linkSpaceCont and/or reposition missing 
+                                        // -1 does not remove link for MainBranch
+                                        // does not save old position in relinkBranch()
 {
-    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();
+    QList<BranchItem *> selbis;
+    if (bi)
+        selbis << bi;
+    else
+        selbis = getSelectedBranches();
+    foreach (BranchItem *selbi, selbis) {
+        if (selbi->depth() > 0) {
+            relinkBranch(selbi, rootItem, -1);
+        }
+    }
+}
 
-        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)));
+QList <BranchItem*> VymModel::sortBranchesByNum(QList <BranchItem*> unsortedList, bool inverse)
+{
+    // Shortcut
+    if (unsortedList.count() < 2)
+        return unsortedList;
+
+    // We use QMultiMap because unsortedList might have branches 
+    // with identical depths, but different parentBranches e.g.
+    // when moving up/down. Then parts of the list would be lost.
+    QMultiMap <int, BranchItem*> multimap;
+    foreach (BranchItem *bi, unsortedList)
+        multimap.insert(bi->num(), bi);
+
+    QList <BranchItem*> sortedList;
+
+    QMultiMapIterator<int, BranchItem*> i(multimap);
+    if (inverse) {
+            i.toBack();
+            while (i.hasPrevious()) {
+                i.previous();
+                sortedList << i.value();
+            }
+    } else while (i.hasNext()) {
+        i.next();
+        sortedList << i.value();
     }
+
+    return sortedList;
 }
 
-void VymModel::sortChildren(bool inverse)
+QList <BranchItem*> VymModel::sortBranchesByHeading(QList <BranchItem*> unsortedList, bool inverse) // FIXME-4 This funcion is currently never called
 {
-    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)));
+    QMap <QString, BranchItem*> map;
+    foreach (BranchItem *bi, unsortedList)
+        map.insert(bi->headingPlain(), bi);
 
-            selbi->sortChildren(inverse);
-            select(selbi);
-            reposition();
+    QList <BranchItem*> sortedList;
+
+    if (inverse)
+        for (auto i = map.cend(), begin = map.cbegin(); i != begin; --i)
+            sortedList << i.value();
+    else
+        for (auto i = map.cbegin(), end = map.cend(); i != end; ++i)
+            sortedList << i.value();
+
+    return sortedList;
+}
+
+void VymModel::sortChildren(bool inverse, BranchItem *bi)
+{
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+
+    if (selbis.isEmpty()) return;
+
+    foreach (BranchItem *selbi, selbis) {
+        if (selbi) {
+            if (selbi->branchCount() > 1) {
+                QString bv = setBranchVar(selbi);
+                QString uc = bv + QString("map.loadBranchReplace(\"UNDO_PATH\", b);");
+                QString com;
+                QString rc;
+                if (!inverse) {
+                    rc = bv + QString("b.sortChildren(false);");
+                    com = QString("Sort children of \"%1\"").arg(getObjectName(selbi));
+                } else {
+                    bv = setBranchVar(selbi);
+                    uc = bv + QString("map.loadBranchReplace(\"UNDO_PATH\", b);");
+                    rc = bv + QString("b.sortChildren(false);");
+                    com = QString("Inverse sort children of \"%1\"").arg(getObjectName(selbi));
+                }
+                logAction(rc, com, __func__);
+                saveState(uc, rc, com, selbi);
+
+                QMultiMap <QString, BranchItem*> multimap;
+                for (int i = 0; i < selbi->branchCount(); i++)
+                    multimap.insert(selbi->getBranchNum(i)->headingPlain(), selbi->getBranchNum(i));
+
+                int n = 0;
+                QMultiMapIterator<QString, BranchItem*> i(multimap);
+                if (inverse) {
+                    i.toBack();
+                    while (i.hasPrevious()) {
+                        i.previous();
+                        if (i.value()->num() != n) {
+                            // Only relink if not already at this position
+                            // and don't saveState while relinking
+                            bool oldSaveStateBlocked = saveStateBlocked;
+                            saveStateBlocked = true;
+                            relinkBranch(i.value(), selbi, n);
+                            saveStateBlocked = oldSaveStateBlocked;
+                        }
+                        n++;
+                    }
+                } else while (i.hasNext())  {
+                    i.next();
+                    if (i.value()->num() != n) {
+                        // Only relink if not already at this position
+                        bool oldSaveStateBlocked = saveStateBlocked;
+                        saveStateBlocked = true;
+                        relinkBranch(i.value(), selbi, n);
+                        saveStateBlocked = oldSaveStateBlocked;
+                    }
+                    n++;
+                }
+            }
         }
     }
 }
 
-BranchItem *VymModel::createMapCenter()
-{
-    BranchItem *newbi = addMapCenter(QPointF(0, 0));
-    return newbi;
+QList <ImageItem*> VymModel::sortImagesByNum(QList <ImageItem*> unsortedList, bool inverse)
+{   // for moving up/down *multiple* images in lists or grid...
+
+    if (unsortedList.count() < 2)
+        return unsortedList;
+
+    // We use QMultiMap because unsortedList might have branches 
+    // with identical depths, but different parentBranches e.g.
+    // when moving up/down. Then parts of the list would be lost.
+    QMultiMap <int, ImageItem*> multimap;
+    foreach (ImageItem *ii, unsortedList)
+        multimap.insert(ii->num(), ii);
+
+    QList <ImageItem*> sortedList;
+
+    QMultiMapIterator<int, ImageItem*> i(multimap);
+    if (inverse) {
+            i.toBack();
+            while (i.hasPrevious()) {
+                i.previous();
+                sortedList << i.value();
+            }
+    } else while (i.hasNext()) {
+        i.next();
+        sortedList << i.value();
+    }
+
+    return sortedList;
 }
 
-BranchItem *VymModel::createBranch(BranchItem *dst)
+BranchItem *VymModel::createBranchWhileLoading(BranchItem *dst, int insertPos)
 {
-    if (dst)
-        return addNewBranchInt(dst, -2);
-    else
-        return NULL;
+    BranchItem *newbi;
+    if (!dst || dst == rootItem)
+        newbi = addMapCenterAtPos(QPointF(0, 0));
+    else {
+        if (insertPos < 0)
+            newbi = addNewBranchInt(dst, -2);
+        else
+            newbi = addNewBranchInt(dst, insertPos);
+    }
+
+    // Set default design styles, e.g. font
+    applyDesign(MapDesign::LoadingMap, newbi);
+    return newbi;
 }
 
 ImageItem *VymModel::createImage(BranchItem *dst)
@@ -2955,10 +3991,8 @@ ImageItem *VymModel::createImage(BranchItem *dst)
         int n;
 
         ImageItem *newii = new ImageItem();
-        // newii->setHeading (QApplication::translate("Heading of new image in
-        // map", "new image"));
 
-        emit(layoutAboutToBeChanged());
+        emit layoutAboutToBeChanged();
 
         parix = index(dst);
         if (!parix.isValid())
@@ -2968,39 +4002,38 @@ ImageItem *VymModel::createImage(BranchItem *dst)
         dst->appendChild(newii);
         endInsertRows();
 
-        emit(layoutChanged());
+        emit layoutChanged();
 
-        // save scroll state. If scrolled, automatically select
-        // new branch in order to tmp unscroll parent...
-        newii->createMapObj();
-        latestAddedItem = newii;
+        dst->addToImagesContainer(newii->createImageContainer());
+
+        latestAddedItemUuid = newii->getUuid();
         reposition();
         return newii;
     }
-    return NULL;
+    return nullptr;
 }
 
-bool VymModel::createLink(Link *link)
+bool VymModel::createXLink(XLink *xlink)
 {
-    BranchItem *begin = link->getBeginBranch();
-    BranchItem *end = link->getEndBranch();
+    BranchItem *begin = xlink->getBeginBranch();
+    BranchItem *end = xlink->getEndBranch();
 
     if (!begin || !end) {
-        qWarning() << "VM::createXLinkNew part of XLink is NULL";
+        qWarning() << "VM::createXLinkNew part of XLink is nullptr";
         return false;
     }
 
     if (begin == end) {
         if (debug)
-            qDebug() << "VymModel::createLink begin==end, aborting";
+            qDebug() << "VymModel::createXLink begin==end, aborting";
         return false;
     }
 
     // check, if link already exists
-    foreach (Link *l, xlinks) {
+    foreach (XLink *l, xlinks) {
         if ((l->getBeginBranch() == begin && l->getEndBranch() == end) ||
             (l->getBeginBranch() == end && l->getEndBranch() == begin)) {
-            qWarning() << "VymModel::createLink link exists already, aborting";
+            qWarning() << "VymModel::createXLink link exists already, aborting";
             return false;
         }
     }
@@ -3009,10 +4042,10 @@ bool VymModel::createLink(Link *link)
     int n;
 
     XLinkItem *newli = new XLinkItem();
-    newli->setLink(link);
-    link->setBeginLinkItem(newli);
+    newli->setXLink(xlink);
+    xlink->setBeginXLinkItem(newli);
 
-    emit(layoutAboutToBeChanged());
+    emit layoutAboutToBeChanged();
 
     parix = index(begin);
     n = begin->getRowNumAppend(newli);
@@ -3021,8 +4054,8 @@ bool VymModel::createLink(Link *link)
     endInsertRows();
 
     newli = new XLinkItem();
-    newli->setLink(link);
-    link->setEndLinkItem(newli);
+    newli->setXLink(xlink);
+    xlink->setEndXLinkItem(newli);
 
     parix = index(end);
     n = end->getRowNumAppend(newli);
@@ -3030,181 +4063,262 @@ bool VymModel::createLink(Link *link)
     end->appendChild(newli);
     endInsertRows();
 
-    emit(layoutChanged());
+    emit layoutChanged();
 
-    xlinks.append(link);
-    link->activate();
+    xlinks.append(xlink);
+    xlink->activate();
 
-    latestAddedItem = newli;
+    latestAddedItemUuid = newli->getUuid();
 
-    if (!link->getMO()) {
-        link->createMapObj();
+    if (!xlink->getXLinkObj()) {
+        xlink->createXLinkObj();
         reposition();
     }
     else
-        link->updateLink();
+        xlink->updateXLink();
+
+    xlink->setStyleBegin(mapDesignInt->defXLinkStyleBegin());
+    xlink->setStyleEnd(mapDesignInt->defXLinkStyleEnd());
+
+    QString xv = setXLinkVar(xlink, "xl");
+    QString uc = xv + "map.removeXLink(xl);";
+    QString rc = QString("map.loadDataInsert(\"REDO_PATH\");");
+    QString com = QString("Add XLink from \"%1\" to \"%2\"")
+        .arg(getObjectName(begin), getObjectName(end));
+    saveState(uc, rc, com, nullptr, xlink->beginXLinkItem());
 
-    link->setStyleBegin(defXLinkStyleBegin);
-    link->setStyleEnd(defXLinkStyleEnd);
     return true;
 }
 
 QColor VymModel::getXLinkColor()
 {
-    Link *l = getSelectedXLink();
-    if (l)
-        return l->getPen().color();
+    XLink *xl = getSelectedXLink();
+    if (xl)
+        return xl->getPen().color();
     else
         return QColor();
 }
 
 int VymModel::getXLinkWidth()
 {
-    Link *l = getSelectedXLink();
-    if (l)
-        return l->getPen().width();
+    XLink *xl = getSelectedXLink();
+    if (xl)
+        return xl->getPen().width();
     else
         return -1;
 }
 
 Qt::PenStyle VymModel::getXLinkStyle()
 {
-    Link *l = getSelectedXLink();
-    if (l)
-        return l->getPen().style();
+    XLink *xl = getSelectedXLink();
+    if (xl)
+        return xl->getPen().style();
     else
         return Qt::NoPen;
 }
 
 QString VymModel::getXLinkStyleBegin()
 {
-    Link *l = getSelectedXLink();
-    if (l)
-        return l->getStyleBeginString();
+    XLink *xl = getSelectedXLink();
+    if (xl)
+        return xl->getStyleBeginString();
     else
         return QString();
 }
 
 QString VymModel::getXLinkStyleEnd()
 {
-    Link *l = getSelectedXLink();
-    if (l)
-        return l->getStyleEndString();
+    XLink *xl = getSelectedXLink();
+    if (xl)
+        return xl->getStyleEndString();
     else
         return QString();
 }
-
-AttributeItem *VymModel::setAttribute() // FIXME-3 Experimental, savestate missing
-
+AttributeItem *VymModel::setAttribute( // FIXME-3 saveState( missing. For bulk changes like Jira maybe save whole branch use Script...
+        BranchItem *dst,
+        const QString &key,
+        const QVariant &value,
+        bool removeIfEmpty)
 {
-    BranchItem *selbi = getSelectedBranch();
-    if (selbi) {
-        AttributeItem *ai = new AttributeItem();
-        ai->setAttributeType(AttributeItem::String);
-        ai->setKey("Foo Attrib");
-        ai->setValue(QString("Att val"));
+    if (!dst) return nullptr;
 
-        return setAttribute(selbi, ai);
-    }
-    return nullptr;
-}
+    bool keyFound = false;
+    AttributeItem *ai;
 
-AttributeItem *VymModel::setAttribute(BranchItem *dst, AttributeItem *ai_new)
-{
-    if (dst) {
+    logAction("", "Set attribute " + key + " of " + headingText(dst) + " to '" + value.toString() + "'", __func__);
 
+    for (int i = 0; i < dst->attributeCount(); i++) {
         // 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;
+        ai = dst->getAttributeNum(i);
+        if (ai->key() == key)
+        {
+            keyFound = true;
+            if (value.toString().isEmpty() && removeIfEmpty) {
+                // Remove attribute
+                deleteAttribute(dst, key);
+                ai = nullptr;
+            } else {
+                // Set new value (if required)
+                if (value != ai->value())
+                    ai->setValue(value);
             }
+            break;
         }
-
+    }
+    if (!keyFound) {
         // Create new attribute
-        emit(layoutAboutToBeChanged());
+        ai = new AttributeItem(key, value);
+
+        emit layoutAboutToBeChanged();
 
         QModelIndex parix = index(dst);
-        int n = dst->getRowNumAppend(ai_new);
+        int n = dst->getRowNumAppend(ai);
         beginInsertRows(parix, n, n);
-        dst->appendChild(ai_new);
+        dst->appendChild(ai);
         endInsertRows();
+        emit layoutChanged();
+
+        // Special case: Jira attributes
+        if (ai->key() == "Jira.issueUrl") {
+            dst->setUrlType(TreeItem::JiraUrl);
+            updateJiraFlag(dst);
+        }
+    }
+
+    emitDataChanged(dst);
+    reposition();
+
+    return ai;
+}
+
+void VymModel::deleteAttribute(BranchItem *dst, const QString &key)
+{
+    AttributeItem *ai;
+
+    for (int i = 0; i < dst->attributeCount(); i++) {
+        // Check if there is already an attribute with same key
+        ai = dst->getAttributeNum(i);
+        if (ai->key() == key)
+        {
+            logAction("", "Delete attribute '" + key + "' of " + headingText(dst), __func__);
+
+            // Key exists, delete attribute
+            deleteItem(ai);
+            break;
+        }
+    }
+}
+
+void VymModel::deleteAttributesKeyStartingWith(BranchItem *dst, const QString &key_start)
+{
+    AttributeItem *ai;
 
-        emit(layoutChanged());
+    if (!dst) return;
+
+    logAction("", "Delete attribute starting with '" + key_start + "' of " + headingText(dst), __func__);
+    for (int i = 0; i < dst->attributeCount(); i++) {
+        // Check if there is already an attribute with same key
+        ai = dst->getAttributeNum(i);
+        if (ai->key().startsWith(key_start))
+        {
+            // Key exists, delete attribute
+            deleteItem(ai);
+            break;
+        }
+    }
+}
 
-        emitDataChanged(dst);
-        return ai_new;  // FIXME-3 Check if ai is used or deleted - deep copy here?
+AttributeItem *VymModel::getAttributeByKey(const QString &key, TreeItem *ti)
+{
+    TreeItem *selti = getSelectedItem(ti);
+    if (selti) {
+        for (int i = 0; i < selti->attributeCount(); i++) {
+            AttributeItem *ai = selti->getAttributeNum(i);
+            if (ai->key() == key)
+                return ai;
+        }
     }
-    return NULL;
+    return nullptr;
 }
 
-BranchItem *VymModel::addMapCenter(bool saveStateFlag)
+BranchItem *VymModel::addMapCenter(bool interactive)
 {
+    if (interactive) {
+        // Start to build undo/redo scripts
+        // These script will be finished later when setHeading() is called
+        if (hasContextPos)
+            saveStateBeginScript(
+                    QString("Add new MapCenter at (%1)").arg(toS(contextPos)));
+        else
+            saveStateBeginScript("Add new MapCenter");
+    }
+
     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;
+        BranchContainer *bc;
         for (int i = 0; i < rootItem->branchCount(); ++i) {
             bi = rootItem->getBranchNum(i);
-            bo = (BranchObj *)bi->getLMO();
-            if (bo)
-                contextPos += bo->getAbsPos();
+            bc = bi->getBranchContainer();
+            if (bc)
+                contextPos += bc->pos();
         }
         if (rootItem->branchCount() > 1)
             contextPos *= 1 / (qreal)(rootItem->branchCount());
     }
 
-    BranchItem *bi = addMapCenter(contextPos);
+    BranchItem *newbi = addMapCenterAtPos(contextPos, interactive);
+
+    if (interactive)
+        mapEditor->editHeading(newbi);
+
     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;
+    return newbi;
 }
 
-BranchItem *VymModel::addMapCenter(QPointF absPos)
+BranchItem *VymModel::addMapCenterAtPos(QPointF absPos, bool interactive)
 // 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());
+    emit layoutAboutToBeChanged();
     beginInsertRows(parix, n, n);
 
     rootItem->appendChild(newbi);
 
     endInsertRows();
-    emit(layoutChanged());
+    emit layoutChanged();
 
-    // Create MapObj
-    newbi->setPositionMode(MapItem::Absolute);
-    BranchObj *bo = newbi->createMapObj(mapEditor->getScene());
-    if (bo)
-        bo->move(absPos);
+    // Create BranchContainer
+    BranchContainer *bc = newbi->createBranchContainer(getScene());
+    if (bc) {
+        bc->setPos(absPos);
 
+        if (!saveStateBlocked) {
+            // Don't apply design while loading map
+            applyDesign(MapDesign::CreatedByUser, newbi);
+
+            QString uc, rc, com;
+            com = QString("Add new MapCenter at (%1)").arg(toS(absPos));
+            uc = setBranchVar(newbi) + "map.removeBranch(b);";
+            rc = setBranchVar(rootItem) + QString(" b.loadBranchInsert(\"REDO_PATH\", %1);").arg(newbi->num());
+            logAction( rc, com, __func__);
+            saveState( uc, rc, com, nullptr, newbi);
+
+        }
+    }
+
+    reposition();
     return newbi;
 }
 
@@ -3219,9 +4333,9 @@ BranchItem *VymModel::addNewBranchInt(BranchItem *dst, int pos)
     // Create TreeItem
     BranchItem *parbi = dst;
     int n;
-    BranchItem *newbi = new BranchItem();
+    BranchItem *newbi = new BranchItem;
 
-    emit(layoutAboutToBeChanged());
+    emit layoutAboutToBeChanged();
 
     if (pos == -2) {
         n = parbi->getRowNumAppend(newbi);
@@ -3231,8 +4345,9 @@ BranchItem *VymModel::addNewBranchInt(BranchItem *dst, int pos)
     }
     else if (pos == -1 || pos == -3) {
         // insert below selection
-        parbi = (BranchItem *)dst->parent();
-        n = dst->childNumber() + (3 + pos) / 2; //-1 |-> 1;-3 |-> 0
+        parbi = dst->parentBranch();
+
+        n = dst->row() + (3 + pos) / 2; //-1 |-> 1;-3 |-> 0
         beginInsertRows(index(parbi), n, n);
         parbi->insertBranch(n, newbi);
         endInsertRows();
@@ -3243,276 +4358,470 @@ BranchItem *VymModel::addNewBranchInt(BranchItem *dst, int pos)
         parbi->insertBranch(pos, newbi);
         endInsertRows();
     }
-    emit(layoutChanged());
+    emit layoutChanged();
 
-    newbi->createMapObj(mapEditor->getScene());
+    // Create Container
+    newbi->createBranchContainer(getScene());
 
-    // Set color of heading to that of parent
-    newbi->setHeadingColor(parbi->getHeadingColor());
+    // Update parent item and stacking order of container to match order in model
+    newbi->updateContainerStackingOrder();
 
+    // Reposition now for correct position of e.g. LineEdit later and upLink
     reposition();
+
     return newbi;
 }
 
-BranchItem *VymModel::addNewBranch(BranchItem *bi, int pos)
-{
-    BranchItem *newbi = NULL;
-    if (!bi)
-        bi = getSelectedBranch();
+BranchItem *VymModel::addNewBranch(BranchItem *bi, int pos, bool interactive)
+{
+    BranchItem *selbi = getSelectedBranch(bi);
+    if (!selbi)
+        return nullptr;
+
+    QString comment;
+    if (interactive) {
+        // saveStateEndScript will be called in VymModel::setHeading()
+        if (pos == -2)
+            comment = QString("Add new branch to %1").arg(getObjectName(selbi));
+        else if (pos < -2)
+            comment = QString("Add new branch above %1").arg(getObjectName(selbi));
+        else
+            comment = QString("Add new branch below %1").arg(getObjectName(selbi));
+
+        logAction("", comment, __func__);
+        saveStateBeginScript(comment);
+    }
+
+    BranchItem *newbi = addNewBranchInt(selbi, pos);
+
+    // Required to initialize styles
+    if (!saveStateBlocked)
+        // Don't apply design while loading map
+        applyDesign(MapDesign::CreatedByUser, newbi);
+
+    if (newbi) {
+        QString uc, rc;
+        BranchItem *pbi;
+        if (pos == -2)
+            pbi = selbi;
+        else
+            pbi = selbi->parentBranch();
+
+        uc = setBranchVar(newbi) + "map.removeBranch(b);";
+        rc = setBranchVar(pbi) + QString(" b.loadBranchInsert(\"REDO_PATH\", %1);").arg(newbi->num());
+        saveState( uc, rc, "", nullptr, newbi);
+
+        latestAddedItemUuid = newbi->getUuid();
+        // 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  network problem:  QString ps=toS
+           (newbo->getAbsPos()); sendData ("selectLatestAdded ()"); sendData
+           (QString("move %1").arg(ps)); sendSelection();
+           */
+    }
+
+    if (interactive) {
+        select(newbi);
+        mapEditor->editHeading();
+    }
+
+    return newbi;
+}
+
+BranchItem *VymModel::addNewBranchBefore(BranchItem *bi, bool interactive)    // FIXME-3 Use position of selbi for newbi, if floating
+{
+    BranchItem *newbi = nullptr;
+    BranchItem *selbi = getSelectedBranch(bi);
+    if (selbi && selbi->depth() > 0)
+    // We accept no MapCenter here, so we _have_ a parent
+    {
+        QString comment;
+        if (interactive) {
+            // saveStateEndScript will be called in VymModel::setHeading()
+            comment = QString("Add new branch before %1").arg(getObjectName(selbi));
+            logAction("", comment, __func__);
+            saveStateBeginScript(comment);
+        }
+
+        // add below selection
+        newbi = addNewBranchInt(selbi, -1);
+
+        if (newbi) {
+            // Required to initialize styles
+            if (!saveStateBlocked)
+                // Don't apply design while loading map
+                applyDesign(MapDesign::CreatedByUser, newbi);
+
+            // newbi->move2RelPos (p);
+
+            // Move selection to new branch
+            // relink() would create another saveStateScript, block saveState for now
+            bool saveStateBlockedOrg = saveStateBlocked;
+            saveStateBlocked = true;
+            relinkBranch(selbi, newbi, 0);
+            saveStateBlocked = saveStateBlockedOrg;
+
+            QString uc = setBranchVar(newbi) + "map.removeKeepChildren(b);";
+            QString rc = setBranchVar(selbi) + "map.loadBranchReplace(\"REDO_PATH\",b);";
+            saveState(uc, rc, "", nullptr, newbi);
+
+            emitDataChanged(newbi);
+        }
+
+        if (interactive) {
+            select(newbi);
+            mapEditor->editHeading();
+        }
+    }
+
+    return newbi;
+}
+
+bool VymModel::relinkBranch(BranchItem *branch, BranchItem *dst, int num_dst)
+{
+    if (!branch)
+        return false;
+
+    QList <BranchItem*> branches = {branch};
+    return relinkBranches(branches, dst, num_dst);
+}
+
+bool VymModel::relinkBranches(QList <BranchItem*> branches, BranchItem *dst, int num_dst)   
+{
+    // qDebug() << "VM::relink " << branches.count() << " branches to " << headingText(dst) << "num_dst=" << num_dst;
+
+    // Selection is lost when removing rows from model
+    QList <TreeItem*> selectedItems = getSelectedItems();
+
+    if (branches.isEmpty())
+        branches = getSelectedBranches();
+
+    if (!dst || branches.isEmpty())
+        return false;
+
+    if (num_dst < 0 || num_dst >= dst->branchCount())
+        num_dst = dst->branchCount();
+
+    if (!saveStateBlocked)
+        // When ordering branches, we already saveState there and not for 
+        // each branch individually
+        saveStateBeginScript(
+            QString("Relink %1 objects to \"%2\"")
+                .arg(branches.count())
+                .arg(dst->headingPlain()));
+
+    foreach (BranchItem *bi, branches) {
+        logAction("", QString("Relink %1 branches to %2").arg(branches.count()).arg(headingText(dst)), __func__);
+        // Check if we link to ourself
+        if (dst == bi) {
+            logWarning("Attempting to relink to myself: " + bi->headingPlain(), __func__);
+            return false;
+        }
+
+        // Check if we relink down to own children
+        if (dst->isChildOf(bi)) {
+            logWarning("Attempting to relink to my own children", __func__);
+            return false;
+        }
+
+        // Save old selection for savestate
+        QString preNumString = QString::number(bi->num(), 10);
+        QString preParUidString = bi->parent()->getUuid().toString();
+
+        // Remember original position for saveState
+        bool rememberPos = false;
+        BranchItem *pbi = bi->parentBranch();
+        if (pbi == rootItem)
+        {
+            // Remember position of MapCenter
+            rememberPos = true;
+        } else {
+            BranchContainer *pbc = pbi->getBranchContainer();
+            if (pbc->hasFloatingBranchesLayout())
+                rememberPos = true;
+        }
+
+        // Prepare BranchContainers
+        BranchContainer *bc = bi->getBranchContainer();
+        BranchContainer *dstBC = dst->getBranchContainer(); // might be nullptr for MC!
+
+        // Keep position when detaching
+        bool keepPos;
+        QPointF preDetachPos;
+        if (dst == rootItem) {
+            keepPos = true;
+            preDetachPos = bc->getHeadingContainer()->scenePos();
+        } else
+            keepPos = false;
+
+        // What kind of relinking are we doing? Important for style updates
+        MapDesign::UpdateMode updateMode = MapDesign::RelinkedByUser; // FIXME-2 not used later   also not considering detaching
 
-    if (bi) {
-        QString redosel = getSelectString(bi);
-        newbi = addNewBranchInt(bi, pos);
-        QString undosel = getSelectString(newbi);
+        BranchItem *branchpi = bi->parentBranch();
 
-        if (newbi) {
-            saveState(undosel, "remove ()", redosel,
-                      QString("addBranch (%1)").arg(pos),
-                      QString("Add new branch to %1").arg(getObjectName(bi)));
+        // Remove at current position
+        int removeRowNum = bi->row();
+        int dstRowNum = num_dst + dst->branchOffset();
+
+        QModelIndex pix = index(branchpi);
+        /* FIXME-4 remove debug stuff
+        std::cout << "  VM::relink removing " << bi << " " << bi->headingPlain().toStdString()
+                  << " at n=" << removeRowNum
+                  << " from " << branchpi << "  " << branchpi->headingPlain().toStdString()
+                  << " to " << dst << " " << dst->headingPlain().toStdString() << endl;
+        std::cout << "      persIxList: " << (this->persistentIndexList()).count() << endl;
+        std::cout << "         num_dst: " << num_dst << endl;
+        std::cout << "    removeRowNum: " << removeRowNum << endl;
+        std::cout << "       dstRowNum: " << dstRowNum << endl;
+        */
 
-            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();
-               */
+        QModelIndex dix = index(dst);
+        if (branchpi == dst && dstRowNum > removeRowNum) {
+            // When moving down with same parent:
+            // Be careful to insert *before* destination index using beginMoveRows
+            // https://doc.qt.io/qt-6/qabstractitemmodel.html#moveRows
+            dstRowNum = dstRowNum + 1;
+            if (num_dst > bi->num() + 1)
+                // if not just moving down one level but further, adapt num_dst
+                num_dst--;
         }
-    }
-    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);
+        emit layoutAboutToBeChanged();
+        bool b = beginMoveRows(pix, removeRowNum, removeRowNum, dix, dstRowNum);
+        Q_ASSERT(b);
+        /*
+        std::cout << "beginMoveRows=" << toS(b).toStdString()
+                  << " removeRowNum=" << removeRowNum
+                  << "      num_dst=" << num_dst
+                  << "    dstRowNum=" << dstRowNum << endl;
+        */
+        branchpi->removeChild(removeRowNum);
+        dst->insertBranch(num_dst, bi);
+        endMoveRows();
+        emit layoutChanged();
 
-        if (newbi) {
-            saveState(
-                newbi, "remove ()", newbi, "addBranchBefore ()",
-                QString("Add branch before %1").arg(getObjectName(selbi)));
+        // Update upLink of BranchContainer to *parent* BC of destination
+        bc->linkTo(dstBC);
 
-            // newbi->move2RelPos (p);
+        // Update parent item and stacking order of container
+        bi->updateContainerStackingOrder();
 
-            // Move selection to new branch
-            relinkBranch(selbi, newbi, 0, true);
+        // reset parObj, fonts, frame, etc in related branch-container or other view-objects
+        applyDesign(MapDesign::RelinkedByUser, bi);
 
-            // Use color of child instead of parent
-            newbi->setHeadingColor(selbi->getHeadingColor());
-            emitDataChanged(newbi);
+
+        // Keep position when detaching
+        if (keepPos) {
+            bc->setPos(preDetachPos);
         }
-    }
-    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;
+        // Savestate, but not if just moving up/down
+        if (!saveStateBlocked) {
+            QString uc, rc;
 
-        if (updateSelection)
-            unselectAll();
+            if (rememberPos) {
+                // For undo move back to original position in old floating layout
+                uc = QString("setPos %1;").arg(toS(bc->getOriginalPos()));
+                rc = "";
 
-        // Do we need to update frame type?
-        bool keepFrame = true;
+                saveStateBranch( bi, uc, rc, QString("Move %1") .arg(headingText(bi)));
+            }
 
-        // Save old position for savestate
-        QString preSelStr = getSelectString(branch);
-        QString preNum = QString::number(branch->num(), 10);
-        QString preParStr = getSelectString(branch->parent());
+            QString postNumString = QString::number(bi->num(), 10);
 
-        emit(layoutAboutToBeChanged());
-        BranchItem *branchpi = (BranchItem *)branch->parent();
-        // Remove at current position
-        int n = branch->childNum();
+            QString bv = setBranchVar(bi);
+            if (pbi == rootItem)
+                uc = bv + " detach ()";
+            else {
+                uc = bv + QString(" dst = map.findBranchById(\"%1\");").arg(preParUidString);
+                uc += QString(" b.relinkToBranchAt (dst, \"%1\");").arg(preNumString);
+            }
+            rc = bv + QString(" dst = map.findBranchById(\"%1\");").arg(dst->getUuid().toString());
+            rc += QString(" b.relinkToBranchAt (dst, \"%1\");").arg(postNumString);
 
-        // If branch and dst have same parent, then pos needs to be adjusted 
-        // after removing branch
-        if (branchpi == dst && pos - 1 > n ) pos--;
+            saveState(uc, rc,
+                      QString("Relink %1 to %2")
+                          .arg(getObjectName(bi), getObjectName(dst)));
+
+            if (dstBC && dstBC->hasFloatingBranchesLayout()) {
+                // Save current position for redo
+                saveStateBranch(bi, "", 
+                          QString("setPos %1;").arg(toS(bc->pos())),
+                          QString("Move %1")
+                              .arg(getObjectName(bi)));
+            }
+        } // saveState not blocked
+    }   // Iterating over selbis    
 
-        beginRemoveRows(index(branchpi), n, n);
-        branchpi->removeChild(n);
-        endRemoveRows();
+    reposition();
 
-        if (pos < 0 || pos > dst->branchCount())
-            pos = dst->branchCount();
+    if (!saveStateBlocked)
+        saveStateEndScript();
 
-        // 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();
+    // Restore selection, which was lost when removing rows
+    select(selectedItems);
 
-        // Correct type if necessesary
-        if (branch->getType() == TreeItem::MapCenter && branch->depth() > 0) {
-            branch->setType(TreeItem::Branch);
-            keepFrame = false;
-        }
+    return true;
+}
 
-        // reset parObj, fonts, frame, etc in related LMO or other view-objects
-        branch->updateStyles(keepFrame);
+bool VymModel::relinkImage(ImageItem* image, TreeItem *dst_ti, int num_new) {
+    if (!image)
+        return false;
 
-        emitDataChanged(branch);
-        reposition(); // both for moveUp/Down and relinking
+    QList <ImageItem*> images = {image};
+    return relinkImages(images, dst_ti, num_new);
+}
 
-        // Savestate
-        QString postSelStr = getSelectString(branch);
-        QString postNum = QString::number(branch->num(), 10);
+bool VymModel::relinkImages(QList <ImageItem*> images, TreeItem *dst_ti, int num_new) // FIXME-2 Check relinking, if attributes are in dst_ti (*before) image rows
+{
+    // Selection is lost when removing rows from model
+    QList <TreeItem*> selectedItems = getSelectedItems();
 
-        QPointF savePos;
-        LinkableMapObj *lmosel = branch->getLMO();
-        if (lmosel)
-            savePos = lmosel->getAbsPos();
+    BranchItem *dst;
 
-        if (!saveStateBlocked) { // Don't build strings when moving up/down
-            QString undoCom =
-                "relinkTo (\"" + preParStr + "\"," + preNum + "," +
-                QString("%1,%2").arg(orgPos.x()).arg(orgPos.y()) + ")";
+    if (images.isEmpty())
+        images = getSelectedImages();
 
-            QString redoCom =
-                "relinkTo (\"" + getSelectString(dst) + "\"," + postNum + "," +
-                QString("%1,%2").arg(savePos.x()).arg(savePos.y()) + ")";
+    if (!dst_ti || images.isEmpty())
+        return false;
 
-            saveState(postSelStr, undoCom, preSelStr, redoCom,
-                      QString("Relink %1 to %2")
-                          .arg(getObjectName(branch))
-                          .arg(getObjectName(dst)));
-        }
+    if (dst_ti->hasTypeImage()) {
+        // Allow dropping on images,
+        // append at this num in parentBranch then:
+        dst = ((ImageItem*)dst_ti)->parentBranch();
+        if (!dst || dst == rootItem)
+            return false;
+        num_new = dst_ti->num();
+    } else if (dst_ti->hasTypeBranch()) {
+        dst = (BranchItem*)dst_ti;
+    } else
+        return false;
 
-        // New parent might be invisible
-        branch->updateVisibility();
+    if (num_new < 0 || num_new >= dst->imageCount())
+        num_new = dst->imageCount();
 
-        if (dst->isScrolled()) {
-            if (updateSelection)
-                select(dst);
-        }
-        else if (updateSelection)
-            select(branch);
-        return true;
-    }
-    return false;
-}
+    if (!saveStateBlocked)
+        // When ordering branches, we already saveState there and not for 
+        // each branch individually
+        saveStateBeginScript(
+            QString("Relink %1 objects to \"%2\"")
+                .arg(images.count())
+                .arg(dst->headingPlain()));
 
-bool VymModel::relinkImage(ImageItem *image, BranchItem *dst)
-{
-    if (image && dst) {
-        emit(layoutAboutToBeChanged());
+    foreach(ImageItem *ii, images) {
+        logAction("", QString("Relink %1 images to %2").arg(images.count()).arg(headingText(dst)), __func__);
+        emit layoutAboutToBeChanged();
 
-        BranchItem *pi = (BranchItem *)(image->parent());
-        QString oldParString = getSelectString(pi);
+        BranchItem *pi = (BranchItem *)(ii->parent());
         // Remove at current position
-        int n = image->childNum();
+        int n = ii->row();
         beginRemoveRows(index(pi), n, n);
         pi->removeChild(n);
         endRemoveRows();
 
-        // Add at dst
+        // Insert again
+        int insertRowNum;
+        if (dst->imageCount() == 0)
+            // Append as last image to dst
+            insertRowNum = 0;
+        else
+            insertRowNum = dst->getFirstImage()->row() + num_new;
         QModelIndex dstix = index(dst);
-        n = dst->getRowNumAppend(image);
-        beginInsertRows(dstix, n, n);
-        dst->appendChild(image);
+        n = dst->getRowNumAppend(ii);
+
+        beginInsertRows(dstix, insertRowNum + num_new, insertRowNum + num_new);
+        dst->insertImage(num_new, ii);
         endInsertRows();
 
-        // Set new parent also for lmo
-        if (image->getLMO() && dst->getLMO())
-            image->getLMO()->setParObj(dst->getLMO());
+        emit layoutChanged();
 
-        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;
+        ii->updateContainerStackingOrder();
+        // FIXME-3 relinkImages issues:
+        // - What about updating links of images (later)?
+        // - What about updating design (later)?
+        // - in ImageWrapper: num_new missing
+        // - does not save positions
+
+        QString iv = setImageVar(ii);
+        QString uc = setBranchVar(pi) + iv + "i.relinkToBranch(b);";
+        QString rc = setBranchVar(dst) + iv + "i.relinkToBranch(b);";
+        QString com = QString("Relink image to %1").arg(getObjectName(dst));
+
+        saveState(uc, rc, com);
     }
-    return false;
+
+    reposition();
+
+    if (!saveStateBlocked)
+        saveStateEndScript();
+
+    // Restore selection, which was lost when removing rows
+    select(selectedItems);
+
+    return true;
 }
 
-bool VymModel::relinkTo(const QString &dest, int num, QPointF pos)
+bool VymModel::relinkTo(const QString &dstString, int num)
 {
     TreeItem *selti = getSelectedItem();
     if (!selti)
         return false; // Nothing selected to relink
 
-    TreeItem *dst = findBySelectString(dest);
+    TreeItem *dst = findBySelectString(dstString);
+    if (!dst)
+        return false; // Could not find destination
+
+    if (!dst->hasTypeBranch())
+        return false; // Relinking only allowed to branchLike destinations
 
-    if (selti->isBranchLikeType()) {
+    if (selti->hasTypeBranch()) {
         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
-}
+        if (relinkBranch(selbi, (BranchItem *)dst, num))
+            return true;
 
-void VymModel::cleanupItems()
-{
-    while (!deleteLaterIDs.isEmpty()) {
-        TreeItem *ti = findID(deleteLaterIDs.takeFirst());
-        if (ti)
-            deleteItem(ti);
+    } else if (selti->hasTypeImage()) {
+        if (relinkImage(((ImageItem *)selti), (BranchItem *)dst))
+            return true;
     }
+    return false; // Relinking failed
 }
 
-void VymModel::deleteLater(uint id)
+void VymModel::deleteSelection(ulong selID)
 {
-    if (!deleteLaterIDs.contains(id))
-        deleteLaterIDs.append(id);
-}
+    QList<ulong> selectedIDs;
+    if (selID > 0)
+        selectedIDs << selID;
+    else
+        selectedIDs = getSelectedIDs();
 
-void VymModel::deleteSelection()
-{
-    QList<uint> selectedIDs = getSelectedIDs();
     unselectAll();
-    QString fn;
 
-    foreach (uint id, selectedIDs) {
+    mapEditor->stopContainerAnimations();  // FIXME-5 better tell ME about deleted items, so that ME can take care of race conditions, e.g. also deleting while moving objects
+
+    foreach (ulong id, selectedIDs) {
         TreeItem *ti = findID(id);
+        BranchItem *pbi;
         if (ti) {
-            if (ti->isBranchLikeType()) { // Delete branch
-                BranchItem *selbi = (BranchItem *)ti;
-                saveStateRemovingPart(
-                    selbi, QString("remove %1").arg(getObjectName(selbi)));
+            pbi = (BranchItem*)(ti->parent());
+            if (pbi && !pbi->hasTypeBranch())
+                pbi = nullptr;
+
+            if (ti->hasTypeBranch()) { // Delete branch
+                BranchItem *bi = (BranchItem *)ti;
+                BranchItem *pbi = bi->parentBranch();
+                QString bv = setBranchVar(bi);
+                QString pbv = setBranchVar(pbi, "pb");
+                QString uc = pbv + QString("pb.loadBranchInsert(\"UNDO_PATH\", %1)").arg(bi->num());
+                QString rc = bv + "map.removeBranch(b);";
+                QString com = QString("Remove branch \"%1\"").arg(bi->headingText());
+
+                logAction(rc, com, __func__);
+
+                saveState(uc, rc, com, bi, nullptr);
 
-                BranchItem *pi = (BranchItem *)(deleteItem(selbi));
+                BranchItem *pi = (BranchItem *)(deleteItem(bi));
                 if (pi) {
                     if (pi->isScrolled() && pi->branchCount() == 0)
                         pi->unScroll();
@@ -3521,101 +4830,156 @@ void VymModel::deleteSelection()
                 }
                 else
                     emitDataChanged(rootItem);
-                ti = NULL;
-            }
+                ti = nullptr;
+            } else if (ti->getType() == TreeItem::Image) {
+                QString iv = setImageVar((ImageItem*)ti);
+                QString bv = setBranchVar(pbi);
+                QString uc = bv + QString("map.loadBranchReplace(\"UNDO_PATH\", b);");
+                QString rc = iv + QString("map.removeImage(i);");
+                QString com = QString("Remove image \"%1\" from branch \"%2\"").arg(getObjectName(ti), getObjectName(pbi));
+
+                logAction(rc, com, __func__);
+
+                saveState(uc, rc, com, pbi);
+
+                deleteItem(ti);
+                emitDataChanged(pbi);
+                select(pbi);
+            } else if (ti->getType() == TreeItem::XLinkItemType) {
+                deleteXLink(((XLinkItem*)ti)->getXLink());
+            } else if (ti->getType() == AttributeItem::Attribute && pbi) {
+                AttributeItem *ai = (AttributeItem*)ti;
+                QString av = setAttributeVar((AttributeItem*)ti);
+                QString bv = setBranchVar(pbi);
+                QString uc = bv + QString("b.setAttribute(\"%1\",\"%2\");").arg(ai->key(), ai->value().toString());
+                QString rc = QString("map.removeImage(i);");
+                QString com = QString("Remove image \"%1\" from branch \"%2\"").arg(getObjectName(ti), getObjectName(pbi));
+
+                logAction(rc, com, __func__);
+
+                saveState(uc, rc, com, pbi);
+                deleteItem(ti);
+            } else
+                qWarning("VymmModel::deleteSelection()  unknown type?!");
+        } // ti found
+    } // Loop over selectedIDs
+
+    emptyXLinksTrash();
+
+    reposition();
+}
+
+void VymModel::deleteKeepChildren(BranchItem *bi)   // FIXME-3 does not work really with MapCenters. Floating positions also not saved for undo
+{
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    unselectAll();
+
+    foreach (BranchItem *selbi, selbis) {
+        if (selbi->depth() < 1) {
+            while (selbi->branchCount() > 0)
+                detach(selbi->getBranchNum(0));
+
+            deleteSelection(selbi->getID());
+        } else {
+            // Check if we have children at all to keep
+            if (selbi->branchCount() == 0)
+                deleteSelection(selbi->getID());
             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?!");
+
+                BranchItem *pi = (BranchItem *)(selbi->parent());
+
+                QString pbv = setBranchVar(pi, "pb");
+                QString bv = setBranchVar(selbi);
+                QString uc = pbv + "map.loadBranchReplace(\"UNDO_PATH\", pb);";
+                QString rc = bv + "map.removeKeepChildren(b);";
+                QString com = QString("Remove branch \"%1\" and keep children").arg(selbi->headingText());
+                logAction(rc, com, __func__);
+                saveState(uc, rc, com, pi);
+
+                bool oldSaveState = saveStateBlocked;
+                saveStateBlocked = true;
+
+                QString sel = getSelectString(selbi);
+                int num_dst = selbi->num();
+                BranchItem *bi = selbi->getFirstBranch();
+                while (bi) {
+                    relinkBranch(bi, pi, num_dst);
+                    bi = selbi->getFirstBranch();
+                    num_dst++;
                 }
+                saveStateBlocked = oldSaveState;
+
+                deleteItem(selbi);
+                reposition();
+
+                // Select the "new" branch  // FIXME-4 not really working with multiple selected branches...
+                select(sel);
             }
         }
     }
+
+    emptyXLinksTrash();
+
 }
 
-void VymModel::deleteKeepChildren(bool saveStateFlag)
-// deleteKeepChildren FIXME-3+ does not work yet for mapcenters
-// deleteKeepChildren FIXME-3+ children of scrolled branch stay invisible...
+void VymModel::deleteChildren(BranchItem *bi)
 {
-    BranchItem *selbi = getSelectedBranch();
-    BranchItem *pi;
-    if (selbi) {
-        // Don't use this on mapcenter
-        if (selbi->depth() < 1)
-            return;
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        QString bv = setBranchVar(selbi);
+        QString uc = bv + "map.loadBranchReplace(\"UNDO_PATH\", b);";
+        QString rc = bv + "b.removeChildren();";
+        QString com = QString("Remove children of \"%1\"").arg(selbi->headingText());
+        logAction(rc, com, __func__);
+        saveState(uc, rc, com, selbi);
+        emit layoutAboutToBeChanged();
 
-        pi = (BranchItem *)(selbi->parent());
-        // Check if we have children at all to keep
-        if (selbi->branchCount() == 0) {
-            deleteSelection();
-            return;
-        }
+        QModelIndex ix = index(selbi);
+        int n = selbi->childCount() - 1;
+        beginRemoveRows(ix, 0, n);
+        removeRows(0, n + 1, ix);
+        endRemoveRows();
+        if (selbi->isScrolled()) unscrollBranch(selbi);
 
-        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)));
+        updateJiraFlag(selbi);
+        emit layoutChanged();
 
-        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);
+        emitDataChanged(selbi);
         reposition();
-        emitDataChanged(pi);
-        select(sel);
-        BranchObj *bo = getSelectedBranchObj();
-        if (bo) {
-            bo->move2RelPos(p);
-            reposition();
-        }
-        saveStateBlocked = oldSaveState;
     }
+    emptyXLinksTrash();
 }
 
-void VymModel::deleteChildren()
-
+void VymModel::deleteChildrenBranches(BranchItem *bi)
 {
-    BranchItem *selbi = getSelectedBranch();
-    if (selbi) {
-        saveStateChangingPart(
-            selbi, selbi, "removeChildren ()",
-            QString("Remove children of branch %1").arg(getObjectName(selbi)));
-        emit(layoutAboutToBeChanged());
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        if (selbi->branchCount() > 0) {
+            int n_first = selbi->getFirstBranch()->row();
+            int n_last  = selbi->getLastBranch()->row();
+
+            QString bv = setBranchVar(selbi);
+            QString uc = bv + "map.loadBranchReplace(\"UNDO_PATH\", b);";
+            QString rc = bv + "b.removeChildrenBranches();";
+            QString com = QString("Remove children branches of \"%1\"").arg(selbi->headingText());
+            logAction(rc, com, __func__);
+            saveState(uc, rc, com, selbi);
+
+            emit layoutAboutToBeChanged();
+
+            QModelIndex ix = index(selbi);
+            beginRemoveRows(ix, n_first, n_last);
+            if (!removeRows(n_first, n_last - n_first + 1, ix))
+                qWarning() << "VymModel::deleteChildBranches removeRows() failed";
+            endRemoveRows();
+            if (selbi->isScrolled()) unscrollBranch(selbi);
+
+            emit layoutChanged();
+        }
+    }
 
-        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());
+    if (selbis.count() > 0)  {
+        emptyXLinksTrash();
         reposition();
     }
 }
@@ -3624,72 +4988,108 @@ 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();
+        bool wasAttribute = ti->hasTypeAttribute();
+        TreeItem *parentItem = ti->parent();
 
         QModelIndex parentIndex = index(pi);
 
-        emit(layoutAboutToBeChanged());
+        emit layoutAboutToBeChanged();
 
-        int n = ti->childNum();
+        int n = ti->row();
         beginRemoveRows(parentIndex, n, n);
-        removeRows(n, 1, parentIndex);
+        bool r = removeRows(n, 1, parentIndex);  // Deletes object!
         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();
 
-        emit(layoutChanged());
         emitUpdateQueries();
-        if (!cleaningUpLinks)
-            cleanupItems();
 
-        // qDebug()<<"VM::deleteItem  end   ti="<<ti;
+        if (wasAttribute)
+            updateJiraFlag(parentItem);
+
+        emitDataChanged(parentItem);
+        // reposition() is triggered in calling functions!
+
         if (pi->depth() >= 0)
             return pi;
     }
-    return NULL;
+    return nullptr;
 }
 
-void VymModel::deleteLink(Link *l)
+void VymModel::deleteXLink(XLink *xlink)
 {
-    if (xlinks.removeOne(l))
-        delete (l);
+    //qDebug() << "VM::deleteXLink start";
+
+
+    QString xv = setXLinkVar(xlink);
+    QString uc = QString("map.loadDataInsert(\"UNDO_PATH\");");
+    QString rc = xv + QString("map.removeXLink(x);");
+
+    QString com = QString("Remove XLink");
+    
+    logAction(rc, com, __func__);
+
+    saveState(uc, rc, com, xlink->beginXLinkItem());
+
+    deleteXLinkInt(xlink);
 }
 
-void VymModel::clearItem(TreeItem *ti)
+void VymModel::deleteXLinkLater(XLink *xlink)
 {
-    if (ti) {
-        // Clear task (or other data in item itself)
-        ti->clear();
+    //qDebug() << "VM::deleteXLinkLater: " << xlink;
 
-        QModelIndex parentIndex = index(ti);
-        if (!parentIndex.isValid())
-            return;
+    if (!xlinksTrash.contains(xlink)) {
+        xlinksTrash << xlink;
+        xlinks.removeOne(xlink);
+    }
+}
 
-        int n = ti->childCount();
-        if (n == 0)
-            return;
+void VymModel::emptyXLinksTrash()
+{
+    //qDebug() << "*** VM::emptyXLinksTrash " << xlinksTrash;
 
-        emit(layoutAboutToBeChanged());
+    foreach (XLink *xlink, xlinksTrash)
+        deleteXLinkInt(xlink);
 
-        beginRemoveRows(parentIndex, 0, n - 1);
-        removeRows(0, n, parentIndex);
-        endRemoveRows();
+    xlinksTrash.clear();
+}
 
-        reposition();
+void VymModel::deleteXLinkInt(XLink *xlink)
+{
+    //qDebug() << __func__ << xlink;
 
-        emit(layoutChanged());
+    if (!xlink) {
+        qWarning() << __func__ << "No xlink ?!";
+        return;
     }
-    return;
+
+    // Remove XLinkItems from TreeModel
+    XLinkItem *xli;
+    xli = xlink->beginXLinkItem();
+    if (xli) {
+        //qDebug() << "  Deleting begin xli" << xli;
+        xli->setXLink(nullptr);
+        deleteItem(xli);
+    }
+    xli = xlink->endXLinkItem();
+    if (xli) {
+        //qDebug() << "  Deleting end xli" << xli;
+        xli->setXLink(nullptr);
+        deleteItem(xli);
+    }
+
+    // Remove from list of items and delete xlink itself, including XLinkObj
+    if (xlinks.removeOne(xlink)) {
+        //qDebug() << "  Removing xlink from xlinks";
+        delete (xlink);
+    }
+    if (xlinksTrash.removeOne(xlink)) {
+        //qDebug() << "  Removing xlink from xlinksTrash";
+        delete (xlink);
+    }
+
+    reposition();
 }
 
 bool VymModel::scrollBranch(BranchItem *bi)
@@ -3702,16 +5102,14 @@ bool VymModel::scrollBranch(BranchItem *bi)
         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)));
+            QString u, r, c;
+            r = setBranchVar(bi) + " b.scroll();";
+            u = setBranchVar(bi) + " b.unscroll();";
+            c = QString("Scroll %1").arg(getObjectName(bi));
+            logAction(r, c, __func__);
+            saveState(u, r, c);
             emitDataChanged(bi);
-            emitSelectionChanged();
             reposition();
-            mapEditor->getScene()
-                ->update(); // Needed for _quick_ update,  even in 1.13.x
             return true;
         }
     }
@@ -3724,26 +5122,25 @@ bool VymModel::unscrollBranch(BranchItem *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)));
+            QString u, r, c;
+            u = setBranchVar(bi) + " b.scroll();";
+            r = setBranchVar(bi) + " b.unscroll();";
+            c = QString("Uncroll %1").arg(getObjectName(bi));
+            logAction(r, c, __func__);
+            saveState(u, r, c);
             emitDataChanged(bi);
-            emitSelectionChanged();
+
             reposition();
-            mapEditor->getScene()
-                ->update(); // Needed for _quick_ update,  even in 1.13.x
             return true;
         }
     }
     return false;
 }
 
-void VymModel::toggleScroll()
+void VymModel::toggleScroll(BranchItem *bi)
 {
-    BranchItem *selbi = getSelectedBranch();
-    if (selbi) {
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
         if (selbi->isScrolled())
             unscrollBranch(selbi);
         else
@@ -3752,15 +5149,19 @@ void VymModel::toggleScroll()
     }
 }
 
-void VymModel::unscrollChildren()
+void VymModel::unscrollSubtree(BranchItem *bi)
 {
-    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;
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        QString bv = setBranchVar(selbi);
+        QString uc = bv + QString("map.loadBranchReplace(\"UNDO_PATH\", b);");
+        QString rc = bv + QString("b.unscrollSubtree();");
+        QString comment = QString("Unscroll branch \"%1\" and all its scrolled children").arg(selbi->headingText());
+        logAction(rc, comment, __func__);
+        saveState(uc, rc, comment, selbi);
+
+        BranchItem *prev = nullptr;
+        BranchItem *cur = nullptr;
         nextBranch(cur, prev, true, selbi);
         while (cur) {
             if (cur->isScrolled()) {
@@ -3769,70 +5170,29 @@ void VymModel::unscrollChildren()
             }
             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();
     }
+    updateActions();
+    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::emitExpandAll() { emit expandAll(); }
 
-void VymModel::emitExpandOneLevel() { emit(expandOneLevel()); }
+void VymModel::emitExpandOneLevel() { emit expandOneLevel(); }
 
-void VymModel::emitCollapseOneLevel() { emit(collapseOneLevel()); }
+void VymModel::emitCollapseOneLevel() { emit collapseOneLevel(); }
 
-void VymModel::emitCollapseUnselected() { emit(collapseUnselected()); }
+void VymModel::emitCollapseUnselected() { emit collapseUnselected(); }
 
-void VymModel::toggleTarget()
+void VymModel::toggleTarget(BranchItem *bi)
 {
-    foreach (TreeItem *ti, getSelectedItems()) {
-        if (ti->isBranchLikeType()) {
-            ((BranchItem*)ti)->toggleTarget();
-            saveState(ti, "toggleTarget()", ti, "toggleTarget()",
-                      "Toggle target");
-        }
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
+        selbi->toggleTarget();
+        QString uc = QString("toggleTarget(%1);").arg(toS(!selbi->isTarget()));
+        QString rc = QString("toggleTarget(%1);").arg(toS(selbi->isTarget()));
+        QString com = "Toggle target flag of branch";
+        logAction(rc, com, __func__);
+        saveStateBranch(selbi, uc, rc, com);
     }
     reposition();
 }
@@ -3843,23 +5203,26 @@ ItemList VymModel::getLinkedMaps()
 
     // rmodel->setSearchString (s);
 
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     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+"), "");
+        if (cur->hasActiveSystemFlag("system-target") && cur->hasVymLink()) {
+            s = cur->heading().getTextASCII();
+            static QRegularExpression re;
+            re.setPattern("\n+");
+            s.replace(re, " ");
+            re.setPattern("\\s+");
+            s.replace(re, " ");
+            re.setPattern("\\s+");
+            s.replace(re, "");
 
             QStringList sl;
             sl << s;
-            sl << cur->getVymLink();
+            sl << cur->vymLink();
 
             targets[cur->getID()] = sl;
         }
@@ -3874,18 +5237,22 @@ ItemList VymModel::getTargets()
 
     // rmodel->setSearchString (s);
 
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev);
 
     QString s;
 
+    QRegularExpression re;
     while (cur) {
         if (cur->hasActiveSystemFlag("system-target")) {
-            s = cur->getHeading().getTextASCII();
-            s.replace(QRegularExpression("\n+"), " ");
-            s.replace(QRegularExpression("\\s+"), " ");
-            s.replace(QRegularExpression("^\\s+"), "");
+            s = cur->heading().getTextASCII();
+            re.setPattern("\n+");
+            s.replace(re, " ");
+            re.setPattern("\\s+");
+            s.replace(re, " ");
+            re.setPattern("^\\s+");
+            s.replace(re, "");
 
             QStringList sl;
             sl << s;
@@ -3918,82 +5285,113 @@ Flag* VymModel::findFlagByName(const QString &name)
     return nullptr;
 }
 
-void VymModel::setFlagByName(const QString &name, bool useGroups)
+void VymModel::setFlagByName(const QString &name, BranchItem *bi, bool useGroups)
 {
-    BranchItem *bi = getSelectedBranch();
+    QList <BranchItem*> selbis = getSelectedBranches(bi);
 
-    if (bi && !bi->hasActiveFlag(name)) {
-        toggleFlagByName(name, useGroups);
-    }
+    foreach (BranchItem* selbi, selbis)
+    if (!selbi->hasActiveFlag(name))
+        toggleFlagByName(name, selbi, useGroups);
 }
 
-void VymModel::unsetFlagByName(const QString &name)
+void VymModel::unsetFlagByName(const QString &name, BranchItem *bi)
 {
-    BranchItem *bi = getSelectedBranch();
+    QList <BranchItem*> selbis = getSelectedBranches(bi);
 
-    if (bi && bi->hasActiveFlag(name)) {
-        toggleFlagByName(name);
-    }
+    foreach (BranchItem* selbi, selbis)
+        if (selbi->hasActiveFlag(name))
+            toggleFlagByName(name, selbi);
 }
 
-void VymModel::toggleFlagByName(const QString &name, bool useGroups)
+void VymModel::toggleFlagByUid( const QUuid &uid, BranchItem *bi, bool useGroups)
 {
-    BranchItem *bi = getSelectedBranch();
+    QList <BranchItem*> selbis = getSelectedBranches(bi);
 
-    if (bi) {
-        Flag *f = findFlagByName(name);
+    foreach (BranchItem* selbi, selbis) {
+        // For undo save all currently set flags. (Independely of usage of flag groups)
+        QList <QUuid> oldFlags = selbi->activeFlagUids();
+        QStringList sl;
+        foreach (QUuid id, oldFlags)
+            sl << QString("\"%1\"").arg(id.toString());
 
-        if (!f) {
-            qWarning() << "VymModel::toggleFlagByName could not find flag named " << name;
-            return;
-        }
+        QString uc = QString("setOnlyFlags([%1]);").arg(sl.join(","));
 
-        toggleFlagByUid(f->getUuid(), useGroups);
+        Flag *flag = selbi->toggleFlagByUid(uid, useGroups);
+        if (flag) {
+            QString fname = flag->getName();
+            QString rc = QString("toggleFlagByUid(\"%1\");").arg(uid.toString());
+            QString com = QString("Toggle flag %1 of %2").arg(fname, getObjectName(selbi));
+            logAction(rc, com, __func__);
+            saveStateBranch(selbi, uc, rc, com);
+            emitDataChanged(selbi);
+        } else
+            qWarning() << "VymModel::toggleFlag failed for flag with uid "
+                       << uid;
     }
+    reposition();
 }
 
-void VymModel::toggleFlagByUid(
-    const QUuid &uid,
-    bool useGroups)
-    // FIXME-2  saveState not correct when toggling flags in groups
-    // (previous flags not saved!)
+void VymModel::toggleFlagByName(const QString &name, BranchItem *bi, bool useGroups)
 {
-    QStringList itemList = getSelectedUUIDs();
+    QList <BranchItem*> selbis = getSelectedBranches(bi);
 
-    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;
+    foreach (BranchItem* bi, selbis) {
+        Flag *flag = standardFlagsMaster->findFlagByName(name);
+        if (!flag) {
+            flag = userFlagsMaster->findFlagByName(name);
+            if (!flag) {
+                qWarning() << "VymModel::toggleFlag failed to find flag named "
+                           << name;
+                return;
             }
         }
-        reposition();
+
+        QUuid uid = flag->getUuid();
+
+        toggleFlagByUid(uid, bi, useGroups);
     }
 }
 
+void VymModel::setOnlyFlags(QList <QUuid> uids, BranchItem *bi)
+{
+    if (!bi) {
+        qWarning() << __func__ << "bi == nullptr";
+        return;
+    }
+
+    QList <QUuid> oldUids = bi->activeFlagUids();
+
+    QStringList sl_old;
+    QStringList sl_new;
+    foreach (QUuid id, oldUids)
+        sl_old << QString("\"%1\"").arg(id.toString());
+    foreach (QUuid id, uids)
+        sl_new << QString("\"%1\"").arg(id.toString());
+
+    QString uc = QString("setOnlyFlags([%1]);").arg(sl_old.join(","));
+    QString rc = QString("setOnlyFlags([%1]);").arg(sl_new.join(","));
+    QString com = QString("Set only flags %1").arg(getObjectName(bi));
+    logAction(rc, com, __func__);
+
+    foreach (QUuid uid, oldUids)
+        if (!uids.contains(uid))
+            // Unset flag
+            bi->toggleFlagByUid(uid);
 
-void VymModel::clearFlags()
+    foreach (QUuid uid, uids)
+        if (!oldUids.contains(uid))
+            // Set flag
+            bi->toggleFlagByUid(uid);
+
+    saveStateBranch(bi, uc, rc, com);
+    emitDataChanged(bi);
+    reposition();
+}
+
+void VymModel::clearFlags(BranchItem *bi)
 {
-    BranchItem *selbi = getSelectedBranch();
-    if (selbi) {
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+    foreach (BranchItem *selbi, selbis) {
         selbi->deactivateAllStandardFlags();
         reposition();
         emitDataChanged(selbi);
@@ -4001,43 +5399,44 @@ void VymModel::clearFlags()
     }
 }
 
-void VymModel::colorBranch(QColor c)
+void VymModel::colorBranch(QColor c, BranchItem *bi)
 {
-    QList<BranchItem *> selbis = getSelectedBranches();
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
     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()));
+        QString uc = QString("colorBranch (\"%1\");")
+                      .arg(selbi->headingColor().name());
+        QString rc = QString("colorBranch (\"%1\");").arg(c.name());
+        QString com = QString("Set color of %1 to %2").arg(getObjectName(selbi), c.name());
+
+        logAction(rc, com, __func__);
+        saveStateBranch(selbi, uc, rc, com);
         selbi->setHeadingColor(c); // color branch
+        selbi->getBranchContainer()->updateUpLink();
         emitDataChanged(selbi);
         taskEditor->showSelection();
     }
     mapEditor->getScene()->update();
 }
 
-void VymModel::colorSubtree(QColor c, BranchItem *b)
+void VymModel::colorSubtree(QColor c, BranchItem *bi)
 {
-    QList<BranchItem *> selbis;
-    if (b)
-        selbis.append(b);
-    else
-        selbis = getSelectedBranches();
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
 
     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;
+        QString bv = setBranchVar(bi);
+        QString uc = bv + "map.loadBranchReplace(\"UNDO_PATH\", b);";
+        QString rc = bv + QString("b.colorSubtree (\"%1\")").arg(c.name());
+        QString com = QString("Set color of %1 and children to %2").arg(getObjectName(bi), c.name());
+        logAction(rc, com, __func__);
+
+        saveState(uc, rc, com, bi);
+
+        BranchItem *prev = nullptr;
+        BranchItem *cur = nullptr;
         nextBranch(cur, prev, true, bi);
         while (cur) {
             cur->setHeadingColor(c); // color links, color children
+            cur->getBranchContainer()->updateUpLink();
             emitDataChanged(cur);
             nextBranch(cur, prev, true, bi);
         }
@@ -4050,7 +5449,7 @@ QColor VymModel::getCurrentHeadingColor()
 {
     BranchItem *selbi = getSelectedBranch();
     if (selbi)
-        return selbi->getHeadingColor();
+        return selbi->headingColor();
 
     QMessageBox::warning(
         0, "Warning",
@@ -4058,29 +5457,38 @@ QColor VymModel::getCurrentHeadingColor()
     return Qt::black;
 }
 
-void VymModel::note2URLs()
+void VymModel::note2URLs(BranchItem *bi) // FIXME-3 No saveState yet
 {
-    BranchItem *selbi = getSelectedBranch();
+    BranchItem *selbi = getSelectedBranch(bi);
     if (selbi) {
-        saveStateChangingPart(
+        /*
+        saveStateChangingPart(  // note2Urls
             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);
+        static QRegularExpression re;
+        re.setPattern("(http.*)(\\s|\"|')");
+        re.setPatternOptions(QRegularExpression::InvertedGreedinessOption);
 
         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();
+        QRegularExpressionMatch match;
+        while (pos >= 0) {
+            match = re.match(n, pos);
+            if (match.hasMatch()) {
+                bi = addNewBranch(selbi);
+                bi->setHeadingPlainText(match.captured(1));
+                bi->setUrl(match.captured(1));
+                emitDataChanged(bi);
+                pos = match.capturedEnd();
+            } else
+                pos = -1;
         }
+        reposition();
     }
 }
 
@@ -4088,11 +5496,11 @@ void VymModel::editHeading2URL()
 {
     TreeItem *selti = getSelectedItem();
     if (selti)
-        setURL(selti->getHeadingPlain());
+        setUrl(selti->headingPlain());
 }
 
-void VymModel::getJiraData(bool subtree) // FIXME-2 update error message, check
-                                         // if jiraClientAvail is set correctly
+void VymModel::getJiraData(bool subtree, BranchItem *bi)
+// getJiraData FIXME-3 check if jiraClientAvail is set correctly
 {
     if (!JiraAgent::available()) {
         WarningDialog dia;
@@ -4106,41 +5514,47 @@ void VymModel::getJiraData(bool subtree) // FIXME-2 update error message, check
             return;
     }
 
-    BranchItem *selbi = getSelectedBranch();
-    QRegExp re("(\\w+[-|\\s]\\d+)");
+    BranchItem *selbi = getSelectedBranch(bi);
 
     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);
+            QString query = cur->attributeValue("Jira.query").toString();
 
-                connect(agent, &JiraAgent::jiraTicketReady, this, &VymModel::updateJiraData);
+            bool startAgent = false;
+            JiraAgent *agent = new JiraAgent;
+            if (!agent->setBranch(cur))
+                qWarning () << "Could not set branch in JiraAgent to " << cur->headingPlain();
+            else {
+                if (!query.isEmpty() && agent->setQuery(query)) {
+                    // Branch has a query defined in attribute, try to use that
+                    agent->setJobType(JiraAgent::Query);
+                    connect(agent, &JiraAgent::jiraQueryReady, this, &VymModel::processJiraJqlQuery);
+                    startAgent = true;
+                } else {
+                    QString key = cur->attributeValue("Jira.key").toString();
+                    if (!key.isEmpty() && agent->setTicket(key)) {
+                        // Branch has issueKey, get info for ticket
+                        startAgent = true;
+                        connect(agent, &JiraAgent::jiraTicketReady, this, &VymModel::processJiraTicket);
+                    } else {
+                        if (agent->setTicket(cur->headingPlain())) {
+                            connect(agent, &JiraAgent::jiraTicketReady, this, &VymModel::processJiraTicket);
+                            startAgent = true;
+                        } else {
+                            mainWindow->statusMessage(tr("Could not setup JiraAgent to retrieve data from Jira"));
+                        }
+                    }
+                }
+            } // Successfully set branch in agent
 
-                // Start contacting JIRA in background
+            if (startAgent) {
                 agent->startJob();
                 mainWindow->statusMessage(tr("Contacting Jira...", "VymModel"));
-            }
-
+            } else
+                delete agent;
 
             if (subtree)
                 nextBranch(cur, prev, true, selbi);
@@ -4150,137 +5564,204 @@ void VymModel::getJiraData(bool subtree) // FIXME-2 update error message, check
     }
 }
 
-void VymModel::updateJiraData(QJsonObject jsobj)
+void VymModel::initAttributesFromJiraIssue(BranchItem *bi, const JiraIssue &ji)
 {
-    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();
+    if (!bi) {
+        qWarning() << __FUNCTION__ << " called without BranchItem";
+        return;
+    }
 
-    QJsonObject resolutionObj = fields["resolution"].toObject();
-    QString resolution  = resolutionObj["name"].toString();
+    setAttribute(bi, "Jira.assignee", ji.assignee());
+    setAttribute(bi, "Jira.components", ji.components());
+    //setAttribute(bi, "Jira.description", ji.description());
+    bi->setNote(VymText(ji.description()));
+    setAttribute(bi, "Jira.fixVersions", ji.fixVersions());
+    setAttribute(bi, "Jira.issuetype", ji.issueType());
+    setAttribute(bi, "Jira.issueUrl", ji.url());
+    setAttribute(bi, "Jira.key", ji.key());
+    setAttribute(bi, "Jira.parentKey", ji.parentKey());
+    setAttribute(bi, "Jira.status", ji.status());
+    setAttribute(bi, "Jira.reporter", ji.reporter());
+    setAttribute(bi, "Jira.resolution", ji.resolution());
+    setAttribute(bi, "Jira.issueLinks", ji.issueLinks());
+    setAttribute(bi, "Jira.subTasks", ji.subTasks());
+}
 
-    QJsonObject statusObj = fields["status"].toObject();
-    QString status  = statusObj["name"].toString();
+void VymModel::updateJiraFlag(TreeItem *ti)
+{
+    if(!ti) return;
 
-    QString summary = fields["summary"].toString();
+    AttributeItem *ai = getAttributeByKey("Jira.issueUrl", ti);
+    if (ai)
+        ti->setUrlType(TreeItem::JiraUrl);
+    else {
+        ai = getAttributeByKey("Jira.query");
+        if (ai)
+            ti->setUrlType(TreeItem::JiraUrl);  // FIXME-3 Dedicated flag for query missing
+        else {
+            if (ti->url().isEmpty())
+                ti->setUrlType(TreeItem::NoUrl);
+            else
+                ti->setUrlType(TreeItem::GeneralUrl);
+        }
+    }
 
-    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();
+    // Update UrlType
+    if (ti->urlType() == TreeItem::JiraUrl) {
+        ti->activateSystemFlagByName("system-jira");
+        ti->deactivateSystemFlagByName("system-url");
+    } else {
+        ti->deactivateSystemFlagByName("system-jira");
+        if (ti->urlType() == TreeItem::GeneralUrl)
+            ti->activateSystemFlagByName("system-url");
+        else
+            ti->deactivateSystemFlagByName("system-url");
     }
+    emitDataChanged(ti);
+}
 
+
+void VymModel::processJiraTicket(QJsonObject jsobj)
+{
     int branchID = jsobj["vymBranchId"].toInt();
 
-    QStringList solvedStates;
-    solvedStates << "Verification Done";
-    solvedStates << "Resolved";
-    solvedStates << "Closed";
+    repositionBlocked = true; // FIXME-4 block reposition during processing of Jira query?
 
-    QString keyName = key;
     BranchItem *bi = (BranchItem*)findID(branchID);
     if (bi) {
-        if (solvedStates.contains(status))    {
+        JiraIssue ji;
+        ji.initFromJsonObject(jsobj);
+        initAttributesFromJiraIssue(bi, ji);
+
+        QString keyName = ji.key();
+        if (ji.isFinished())    {
             keyName = "(" + keyName + ")";
-            colorSubtree (Qt::blue, bi);
+            colorSubtree (vymBlueColor, bi);
         }
 
-        setHeadingPlainText(keyName + ": " + summary, bi);
-        setURL(jsobj["vymTicketUrl"].toString());
+        setHeadingPlainText(keyName + ": " + ji.summary(), bi);
+        setUrl(ji.url(), false, bi);
+        bi->setUrlType(TreeItem::JiraUrl);
+        updateJiraFlag(bi);
+
+        emitDataChanged(bi);
+
+        // Pretty print JIRA ticket
+        // ji.print();
+    }
 
-        AttributeItem *ai;
+    repositionBlocked = false;
 
-        ai = new AttributeItem("JIRA.assignee", assignee);
-        setAttribute(bi, ai);
+    mainWindow->statusMessage(tr("Received Jira data.", "VymModel"));
 
-        ai = new AttributeItem("JIRA.reporter", reporter);
-        setAttribute(bi, ai);
+    // Update note
+    reselect();
 
-        ai = new AttributeItem("JIRA.resolution", resolution);
-        setAttribute(bi, ai);
+    reposition();
+}
 
-        ai = new AttributeItem("JIRA.status", status);
-        setAttribute(bi, ai);
+void VymModel::processJiraJqlQuery(QJsonObject jsobj)
+{
+    // Debugging only
+    //qDebug() << "VM::processJiraJqlQuery result...";
 
-        ai = new AttributeItem("JIRA.components", components);
-        setAttribute(bi, ai);
+    int branchId = jsobj.value("vymBranchId").toInt();
+    BranchItem *bi = (BranchItem*)findID(branchId);
+    if (!bi) {
+        mainWindow->statusMessage("VM::processJiraJqlQUery could not find branch with ID=" + jsobj.value("vymBranchId").toString());
+        return;
     }
+    QJsonArray issues = jsobj["issues"].toArray();
+
+    QString bv = setBranchVar(bi);
+    QString uc = bv + QString("map.loadBranchReplace(\"UNDO_PATH\", b);");
+    QString rc = bv + QString("b.getJiraData(%1);").arg(toS(jsobj["doSubtree"].toBool()));
+    QString comment = QString("Process Jira Jql query on \"%1\"").arg(bi->headingText());
+    logAction(rc, comment, __func__);
+    saveState(uc, rc, comment, bi->parentBranch());
 
-    /* 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;
-    */
+
+    saveStateBlocked = true;
+    repositionBlocked = true;
+
+    for (int i = 0; i < issues.size(); ++i) {
+        QJsonObject issue = issues[i].toObject();
+        JiraIssue ji(issue);
+
+        BranchItem *bi2 = addNewBranchInt(bi, -2);
+        if (bi2) {
+            QString keyName = ji.key();
+            if (ji.isFinished())    {
+                keyName = "(" + keyName + ")";
+                colorSubtree (vymBlueColor, bi2);
+            }
+
+            setHeadingPlainText(keyName + ": " + ji.summary(), bi2);
+            setUrl(ji.url(), false, bi2);
+            initAttributesFromJiraIssue(bi2, ji);
+        }
+
+        // Pretty print JIRA ticket
+        // ji.print();
+    }
+
+    setAttribute(bi, "Jira.query", jsobj["vymJiraLastQuery"].toString());
+
+    saveStateBlocked = false;
+    repositionBlocked = false;
 
     mainWindow->statusMessage(tr("Received Jira data.", "VymModel"));
-}
 
+    reposition();
+}
 
-void VymModel::setHeadingConfluencePageName()   // FIXME-2 always asks for Confluence credentials when adding any URL
+void VymModel::setConfluencePageDetails(bool recursive, BranchItem *bi)
 {
-    BranchItem *selbi = getSelectedBranch();
+    BranchItem *selbi = getSelectedBranch(bi);
     if (selbi) {
-        QString url = selbi->getURL();
-        if (!url.isEmpty() && 
+        QString url = selbi->url();
+        if (!url.isEmpty() &&
                 settings.contains("/atlassian/confluence/url") &&
                 url.contains(settings.value("/atlassian/confluence/url").toString())) {
 
+            logInfo("Preparing to get info from Confluence", __func__);
+
             ConfluenceAgent *ca_setHeading = new ConfluenceAgent(selbi);
             ca_setHeading->setPageURL(url);
-            ca_setHeading->setJobType(ConfluenceAgent::CopyPagenameToHeading);
+            if (recursive)
+                ca_setHeading->setJobType(ConfluenceAgent::GetPageDetailsRecursively);
+            else
+                ca_setHeading->setJobType(ConfluenceAgent::GetPageDetails);
             ca_setHeading->startJob();
         }
     }
 }
 
-void VymModel::setVymLink(const QString &s)
+void VymModel::setVymLink(const QString &s, BranchItem *bi)
 {
-    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);
+    BranchItem *selbi = getSelectedBranch(bi);
+    if (selbi) {
+        QString uc = QString("setVymLink(\"%1\");").arg(selbi->vymLink());
+        QString rc = QString("setVymLink(\"%1\");").arg(s);
+        QString com = QString("Set vymlink of %1 to %2").arg(getObjectName(selbi), s);
+        logAction(rc, com, __func__);
+        saveStateBranch(selbi, uc, rc, com);
+        selbi->setVymLink(s);
+        emitDataChanged(selbi);
         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();
-    }
+    setVymLink("");
 }
 
 QString VymModel::getVymLink()
 {
     BranchItem *bi = getSelectedBranch();
     if (bi)
-        return bi->getVymLink();
+        return bi->vymLink();
     else
         return "";
 }
@@ -4289,12 +5770,12 @@ QStringList VymModel::getVymLinks()
 {
     QStringList links;
     BranchItem *selbi = getSelectedBranch();
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev, true, selbi);
     while (cur) {
-        if (!cur->getVymLink().isEmpty())
-            links.append(cur->getVymLink());
+        if (cur->hasVymLink())
+            links.append(cur->vymLink());
         nextBranch(cur, prev, true, selbi);
     }
     return links;
@@ -4312,126 +5793,117 @@ void VymModel::followXLink(int i)
 
 void VymModel::editXLink()
 {
-    Link *l = getSelectedXLink();
-    if (l) {
+    XLink *xlink = getSelectedXLink();
+    if (xlink) {
         EditXLinkDialog dia;
-        dia.setLink(l);
+        dia.setLink(xlink);
         if (dia.exec() == QDialog::Accepted) {
             if (dia.useSettingsGlobal()) {
-                setMapDefXLinkPen(l->getPen());
-                setMapDefXLinkStyleBegin(l->getStyleBeginString());
-                setMapDefXLinkStyleEnd(l->getStyleEndString());
+                setDefXLinkPen(xlink->getPen());
+                setDefXLinkStyleBegin(xlink->getStyleBeginString());
+                setDefXLinkStyleEnd(xlink->getStyleEndString());
             }
         }
     }
 }
 
-void VymModel::setXLinkColor(const QString &new_col)
+void VymModel::setXLinkColor(const QString &new_col, XLink *xl)
 {
-    Link *l = getSelectedXLink();
-    if (l) {
-        QPen pen = l->getPen();
+    XLink *xlink = getSelectedXLink(xl);
+    if (xlink) {
+        QPen pen = xlink->getPen();
         QColor new_color = QColor(new_col);
         QColor old_color = pen.color();
         if (new_color == old_color)
             return;
+        QString xv = setXLinkVar(xlink, "xl");
+        QString uc = xv + QString("xl.setColor(\"%1\");").arg(old_color.name());
+        QString rc = xv + QString("xl.setColor(\"%1\");").arg(new_color.name());
+        QString com = QString("Set xlink color to \"%1\"").arg(new_color.name());
+        logAction(rc, com, __func__);
+        saveState(uc, rc, com);
+
         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()));
+        xlink->setPen(pen);
     }
 }
 
-void VymModel::setXLinkStyle(const QString &new_style)
+void VymModel::setXLinkStyle(const QString &new_style, XLink *xl)
 {
-    Link *l = getSelectedXLink();
-    if (l) {
-        QPen pen = l->getPen();
+    XLink *xlink = getSelectedXLink(xl);
+    if (xlink) {
+        QPen pen = xlink->getPen();
         QString old_style = penStyleToString(pen.style());
         if (new_style == old_style)
             return;
+        QString xv = setXLinkVar(xlink, "xl");
+        QString uc = xv + QString("xl.setStyle(\"%1\");").arg(old_style);
+        QString rc = xv + QString("xl.setStyle(\"%1\");").arg(new_style);
+        QString com = QString("Set xlink style to \"%1\"").arg(new_style);
+        logAction(rc, com, __func__);
+        saveState(uc, rc, com);
+
         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));
+        xlink->setPen(pen);
     }
 }
 
-void VymModel::setXLinkStyleBegin(const QString &new_style)
+void VymModel::setXLinkStyleBegin(const QString &new_style, XLink *xl)
 {
-    Link *l = getSelectedXLink();
-    if (l) {
-        QString old_style = l->getStyleBeginString();
+    XLink *xlink = getSelectedXLink(xl);
+    if (xlink) {
+        QString old_style = xlink->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");
+        QString xv = setXLinkVar(xlink, "xl");
+        QString uc = xv + QString("xl.setStyleBegin(\"%1\");").arg(old_style);
+        QString rc = xv + QString("xl.setStyleBegin(\"%1\");").arg(new_style);
+        QString com = QString("Set xlink begin style to \"%1\"").arg(new_style);
+        logAction(rc, com, __func__);
+        saveState(uc, rc, com);
+        xlink->setStyleBegin(new_style);
     }
 }
 
-void VymModel::setXLinkStyleEnd(const QString &new_style)
+void VymModel::setXLinkStyleEnd(const QString &new_style, XLink *xl)
 {
-    Link *l = getSelectedXLink();
-    if (l) {
-        QString old_style = l->getStyleEndString();
+    XLink *xlink = getSelectedXLink(xl);
+    if (xlink) {
+        QString old_style = xlink->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");
+
+        QString xv = setXLinkVar(xlink, "xl");
+        QString uc = xv + QString("xl.setStyleEnd(\"%1\");").arg(old_style);
+        QString rc = xv + QString("xl.setStyleEnd(\"%1\");").arg(new_style);
+        QString com = QString("Set xlink end style to \"%1\"").arg(new_style);
+        logAction(rc, com, __func__);
+        saveState(uc, rc, com);
+        xlink->setStyleEnd(new_style);
     }
 }
 
-void VymModel::setXLinkWidth(int new_width)
+void VymModel::setXLinkWidth(int new_width, XLink *xl)
 {
-    Link *l = getSelectedXLink();
-    if (l) {
-        QPen pen = l->getPen();
+    XLink *xlink = getSelectedXLink(xl);
+    if (xlink) {
+        QPen pen = xlink->getPen();
         int old_width = pen.width();
         if (new_width == old_width)
             return;
+        QString xv = setXLinkVar(xlink, "xl");
+        QString uc = xv + QString("xl.setWidth(%1);").arg(old_width);
+        QString rc = xv + QString("xl.setWidth(%1);").arg(new_width);
+        QString com = QString("Add xlink width to \"%1\"").arg(new_width);
+        logAction(rc, com, __func__);
+        saveState(uc, rc, com);
+
         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");
+        xlink->setPen(pen);
     }
 }
 
-//////////////////////////////////////////////
-// 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
@@ -4476,13 +5948,13 @@ QPointF VymModel::exportImage(QString fname, bool askName, QString format)
 
     setExportMode(true);
 
-    mapEditor->minimizeView();
+    mapEditor->minimizeView();  // Export minimal image
 
     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));
+            tr("Couldn't save QImage %1 in format %2").arg(fnameformat));
         ex.setResult(ExportBase::Failed);
     } else
         ex.setResult(ExportBase::Success);
@@ -4528,8 +6000,8 @@ void VymModel::exportPDF(QString fname, bool askName)
     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);
@@ -4544,6 +6016,7 @@ void VymModel::exportPDF(QString fname, bool askName)
 
     setExportMode(false);
 
+    ex.setResult(ExportBase::Success);
     ex.completeExport();
 }
 
@@ -4588,12 +6061,49 @@ QPointF VymModel::exportSVG(QString fname, bool askName)
     delete svgPainter;
 
     setExportMode(false);
+
+    ex.setResult(ExportBase::Success);
     ex.completeExport();
 
     return offset;
 }
 
-void VymModel::exportXML(QString fpath, QString dpath, bool useDialog)
+void VymModel::exportTaskJuggler(QString fname, bool askForName) // FIXME-3 not scriptable yet
+{
+    if (fname == "") {
+        if (!askForName) {
+            qWarning("VymModel::exportTaskJuggler called without filename (and "
+                     "askName==false)");
+            return;
+        }
+
+        fname = lastImageDir.absolutePath() + "/" + getMapName() + ".svg";
+    }
+
+    ExportTaskJuggler ex;
+    ex.setModel(this);
+    ex.setFilePath(fname);
+    ex.setWindowTitle(vymName + " - " + tr("Export to") + " Taskjuggler" +
+                          tr("(still experimental)"));
+    ex.addFilter("Taskjuggler (*.tjp)");
+
+    if (askForName) {
+        if (!ex.execDialog())
+            return;
+        fname = ex.getFilePath();
+        lastImageDir = QDir(fname);
+    }
+
+    setExportMode(true);
+
+    ex.doExport();
+
+    setExportMode(false);
+
+    ex.completeExport();
+}
+
+void VymModel::exportXML(QString fpath, bool useDialog)
 {
     ExportBase ex;
     ex.setName("XML");
@@ -4613,16 +6123,16 @@ void VymModel::exportXML(QString fpath, QString dpath, bool useDialog)
         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;
     }
+    QString dpath = fpath.left(fpath.lastIndexOf("/"));
+    if (!confirmDirectoryOverwrite(QDir(dpath)))
+        return;
+
     ex.setFilePath(fpath);
 
     QString mname = basename(fpath);
@@ -4645,8 +6155,11 @@ void VymModel::exportXML(QString fpath, QString dpath, bool useDialog)
     mapUnsaved = munsaved;
 
     // write to directory   //FIXME-3 check totalBBox here...
+    exportBoundingBoxes = true;
     QString saveFile =
-        saveToDir(dpath, mname + "-", FlagRowMaster::NoFlags, offset, NULL);
+        saveToDir(dpath, mname + "-", FlagRowMaster::AllFlags, offset);
+    exportBoundingBoxes = false;
+
     QFile file;
 
     file.setFileName(fpath);
@@ -4660,10 +6173,12 @@ void VymModel::exportXML(QString fpath, QString dpath, bool useDialog)
 
     // Write it finally, and write in UTF8, no matter what
     QTextStream ts(&file);
-    ts.setCodec("UTF-8");
     ts << saveFile;
     file.close();
 
+
+    ex.setResult(ExportBase::Success);
+
     setExportMode(false);
 
     QStringList args;
@@ -4786,7 +6301,9 @@ void VymModel::exportHTML(const QString &fpath, const QString &dpath,
     if (!fpath.isEmpty())
         ex.setFilePath(fpath);
 
+    exportBoundingBoxes = true;
     ex.doExport(useDialog);
+    exportBoundingBoxes = false;
 }
 
 void VymModel::exportConfluence(bool createPage, const QString &pageURL,
@@ -4794,7 +6311,7 @@ void VymModel::exportConfluence(bool createPage, const QString &pageURL,
 {
     ExportConfluence ex(this);
     ex.setCreateNewPage(createPage);
-    ex.setURL(pageURL);
+    ex.setUrl(pageURL);
     ex.setPageName(pageName);
     ex.setLastCommand(
         settings.localValue(filePath, "/export/last/command", "").toString());
@@ -4802,15 +6319,44 @@ void VymModel::exportConfluence(bool createPage, const QString &pageURL,
     ex.doExport(useDialog);
 }
 
-void VymModel::exportImpress(const QString &fn, const QString &cf)
+void VymModel::exportImpress(QString fname, QString configFile, bool useDialog)
 {
-    ExportOO ex;
-    ex.setFilePath(fn);
+    if (fname == "")
+        fname = mapName + ".odp";
+
+    if (useDialog) {
+        ExportImpressFileDialog fd;
+        fd.setWindowTitle(vymName + " - " + tr("Export to") + " LibreOffice Impress");
+        fd.setDirectory(lastExportDir);
+        fd.setAcceptMode(QFileDialog::AcceptSave);
+        fd.setFileMode(QFileDialog::AnyFile);
+        if (fd.foundConfig()) {
+            if (fd.exec() == QDialog::Accepted) {
+                configFile = fd.selectedConfig();
+                if (!fd.selectedFiles().isEmpty()) {
+                    QString fname = fd.selectedFiles().first();
+                    if (!fname.contains(".odp"))
+                        fname += ".odp";
+                }
+            }
+        }
+        else {
+            QMessageBox::warning(
+                0, tr("Warning"),
+                tr("Couldn't find configuration for export to LibreOffice Impress\n"));
+            return;
+        }
+    }
+
+
+    ExportImpress ex;
+    ex.setFilePath(fname);
+
     ex.setModel(this);
     ex.setLastCommand(
         settings.localValue(filePath, "/export/last/command", "").toString());
 
-    if (ex.setConfigFile(cf)) {
+    if (ex.setConfigFile(configFile)) {
         QString lastCommand =
             settings.localValue(filePath, "/export/last/command", "")
                 .toString();
@@ -4824,6 +6370,8 @@ void VymModel::exportImpress(const QString &fn, const QString &cf)
                 .toString();
         if (lastCommand != command)
             setChanged();
+
+        //lastExportDir = fname.left(fname.findRev ("/"));
     }
 }
 
@@ -4832,6 +6380,15 @@ bool VymModel::exportLastAvailable(QString &description, QString &command,
 {
     command =
         settings.localValue(filePath, "/export/last/command", "").toString();
+    static QRegularExpression re;
+    re.setPattern("exportMap\\((\".*)\\)");
+    QRegularExpressionMatch match = re.match(command);
+    if (match.hasMatch()) {
+        QString matched = match.captured(1); // matched == "23 def"
+        command = QString("vym.currentMap().exportMap([%1]);").arg(match.captured(1));
+        settings.setLocalValue(filePath, "/export/last/command", command);
+        qDebug() << "Rewriting last export command to version " << vymVersion << " format: " << command;
+    }
 
     description = settings.localValue(filePath, "/export/last/description", "")
                       .toString();
@@ -4843,13 +6400,27 @@ bool VymModel::exportLastAvailable(QString &description, QString &command,
         return false;
 }
 
+void VymModel::setExportLastCommand(const QString &cmd)
+{
+    settings.setLocalValue(filePath, "/export/last/command", cmd);
+}
+
+void VymModel::setExportLastDescription(const QString &desc)
+{
+    settings.setLocalValue(filePath, "/export/last/description", desc);
+}
+
+void VymModel::setExportLastDestination(const QString &displayedDest)
+{
+    settings.setLocalValue(filePath, "/export/last/displayedDestination", displayedDest);
+}
+
 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);
+        mainWindow->runScript(command);
     }
 }
 
@@ -4933,10 +6504,15 @@ void VymModel::setMapZoomFactor(const double &d)
     mapEditor->setZoomFactorTarget(d);
 }
 
-void VymModel::setMapRotationAngle(const double &d)
+void VymModel::setMapRotation(const double &a)
 {
-    rotationAngle = d;
-    mapEditor->setAngleTarget(d);
+    if (a < 1)
+        // Round to zero, otherwise selectionMode in MapEditor might be 
+        // "Geometric" when it should be "Classic"
+        mapRotationInt = 0;
+    else
+        mapRotationInt = a;
+    mapEditor->setRotationTarget(mapRotationInt);
 }
 
 void VymModel::setMapAnimDuration(const int &d) { animDuration = d; }
@@ -4946,11 +6522,11 @@ 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,
+    if (ti && (ti->hasTypeBranch() || ti->hasTypeImage())) {
+        Container *c = ((MapItem*)ti)->getContainer();
+        if (c && zoomFactor > 0 ) {
+            mapEditor->setViewCenterTarget(c->mapToScene(c->rect().center()), zoomFactor,
+                                           mapRotationInt, animDuration,
                                            animCurve);
             return true;
         }
@@ -4970,375 +6546,467 @@ void VymModel::unsetContextPos()
     hasContextPos = false;
 }
 
-void VymModel::reposition()
+void VymModel::reposition(bool force)
 {
-    if (repositionBlocked)
+    if (!force && repositionBlocked)
         return;
 
-    BranchObj *bo;
+    //qDebug() << "VM::reposition start force=" << force;
+
+    // Reposition containers
+    BranchItem *bi;
     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";
+        bi = rootItem->getBranchNum(i);
+        bi->repositionContainers();
     }
-    mapEditor->getTotalBBox();
 
-    // required to *reposition* the selection box. size is already correct:
-    emitSelectionChanged(); //FIXME-2 better only update selection geometry
+    repositionXLinks();
+
+    mapEditor->minimizeView();  // Optimize view when geometry changes in reposition()
+
+    //qDebug() << "VM::reposition end";
+    if (force)
+        qApp->processEvents();
 }
 
-bool VymModel::setMapLinkStyle(const QString &s)
+void VymModel::repositionXLinks()
 {
-    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;
+    // Reposition xlinks
+    foreach (XLink *xlink, xlinks)
+        xlink->updateXLink();
+}
+
+MapDesign* VymModel::mapDesign()
+{
+    return mapDesignInt;
+}
+
+void VymModel::applyDesign(     // FIXME-2 Check handling of autoDesign option
+        MapDesign::UpdateMode updateMode,
+        BranchItem *bi)
+{
+    /*
+    qDebug() << "VM::applyDesign  mode="
+        << MapDesign::updateModeString(updateMode)
+        << " of " << headingText(bi);
+    */
+
+    QList<BranchItem *> selbis = getSelectedBranches(bi);
+
+    bool saveStateBlockedOrg = saveStateBlocked;
+    saveStateBlocked = true;
+
+    bool updateRequired;
+    foreach (BranchItem *selbi, selbis) {
+        dataChangedBlocked = true;
+        int depth = selbi->depth();
+        bool selbiChanged = false;
+        BranchContainer *bc = selbi->getBranchContainer();
+
+        // Color of heading
+        QColor col = mapDesignInt->headingColor(
+                        updateMode,
+                        selbi,
+                        updateRequired);
+
+        if (updateRequired)
+            colorBranch(col, selbi);
+
+        // Inner frame
+        if (updateMode == MapDesign::CreatedByUser ||
+                (updateMode == MapDesign::RelinkedByUser && mapDesignInt->updateFrameWhenRelinking(true, depth))) {
+            bc->setFrameType(true, mapDesignInt->frameType(true, depth));
+            bc->setFrameBrushColor(true, mapDesignInt->frameBrushColor(true, depth));
+            bc->setFramePenColor(true, mapDesignInt->framePenColor(true, depth));
+            bc->setFramePenWidth(true, mapDesignInt->framePenWidth(true, depth));
+        }
+
+        // Outer frame
+        if (updateMode == MapDesign::CreatedByUser ||
+                (updateMode == MapDesign::RelinkedByUser && mapDesignInt->updateFrameWhenRelinking(false, depth))) {
+            bc->setFrameType(false, mapDesignInt->frameType(false, depth));
+            bc->setFrameBrushColor(false, mapDesignInt->frameBrushColor(false, depth));
+            bc->setFramePenColor(false, mapDesignInt->framePenColor(false, depth));
+            bc->setFramePenWidth(false, mapDesignInt->framePenWidth(false, depth));
+        }
+
+        // Column width and font
+        if (updateMode & MapDesign::CreatedByUser || updateMode & MapDesign::LoadingMap) {
+            HeadingContainer *hc = bc->getHeadingContainer();
+            hc->setColumnWidth(mapDesignInt->headingColumnWidth(depth));
+            hc->setFont(mapDesignInt->font());
+        }
+
+        // Layouts
+        if (bc->branchesContainerAutoLayout) {
+                bc->setBranchesContainerLayout(
+                        mapDesignInt->branchesContainerLayout(depth));
+                selbiChanged = true;
+                        mapDesignInt->branchesContainerLayout(depth);
+        }
+
+        if (bc->imagesContainerAutoLayout) {
+                bc->setImagesContainerLayout(
+                        mapDesignInt->imagesContainerLayout(depth));
+                selbiChanged = true;
+        }
+       bc->setBranchesContainerVerticalAlignment(
+               mapDesignInt->branchesContainerVerticalAlignment(depth));
+       bc->setBranchesContainerAndOrnamentsVertical(
+               mapDesignInt->branchesContainerAndOrnamentsVertical(depth));
+
+        // Links and bottomlines
+        bc->updateUpLink();
+
+        // Rotations
+        if (bc->rotationsAutoDesign()) {
+            qreal a = mapDesignInt->rotationHeading(depth);
+            if (a != bc->rotationHeading()) {
+                bc->setRotationHeading(a);
+                selbiChanged = true;
+            }
+            a = mapDesignInt->rotationSubtree(depth);
+            if (a != bc->rotationHeading()) {
+                bc->setRotationHeading(a);
+                selbiChanged = true;
+            }
+        }
+
+        if (bc->scaleAutoDesign()) {
+            qreal z = mapDesignInt->scaleHeading(depth);
+            if (z != bc->scaleHeading()) {
+                bc->setScaleHeading(z);
+                selbiChanged = true;
+            }
+            z = mapDesignInt->scaleSubtree(depth);
+            if (z != bc->scaleSubtree()) {
+                bc->setScaleSubtree(z);
+                selbiChanged = true;
+            }
+        }
+
+        dataChangedBlocked = false;
+        if (selbiChanged)
+            emitDataChanged(selbi);
     }
 
-    saveState(QString("setMapLinkStyle (\"%1\")").arg(s),
-              QString("setMapLinkStyle (\"%1\")").arg(snow),
-              QString("Set map link style (\"%1\")").arg(s));
+    saveStateBlocked = saveStateBlockedOrg;
+}
 
-    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;
+void VymModel::applyDesignRecursively(
+        MapDesign::UpdateMode updateMode,
+        BranchItem *bi)
+{
+    if (!bi) return;
 
-    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);
+    for (int i = 0; i < bi->branchCount(); ++i)
+        applyDesign(updateMode, bi->getBranchNum(i));
+}
+
+void VymModel::setDefaultFont(const QFont &font)    // FIXME-3 no saveState, no updates of existing headings ("applyDesign")
+{
+    mapDesignInt->setFont(font);
+}
+
+bool VymModel::setLinkStyle(const QString &newStyleString, int depth) // FIXME-2 saveState needs to be adapted, command new param depth
+                                                                      // See also mainWindow->updateActions context menu
+                                                                      // FIXME MapDesign setting with depth passed as argument is moved to MapDesign when parsing .xml
+                                                                      // Somehow MD needs to return undo command when setting an element
+{
+    // Default depth == -1 is used for legacy styles from version < 2.9.518
+    // or for using global setting from context menu
+
+    if (depth >= 0) {
+        QString currentStyleString = LinkObj::styleString(mapDesignInt->linkStyle(depth));
+
+        QString uc = QString("map.setLinkStyle (\"%1\");").arg(newStyleString);
+        QString rc = QString("map.setLinkStyle (\"%1\");").arg(currentStyleString);
+        QString com = QString("Set map link style (\"%1\")").arg(newStyleString);
+        logAction(rc, com, __func__);
+        saveState(uc, rc, com);
     }
+
+    auto style = LinkObj::styleFromString(newStyleString);
+
+    mapDesignInt->setLinkStyle(style, depth);
+
+    // If whole map is used e.g. for legacy maps, only apply the "thick" part
+    // on first level
+    if (depth < 0) {
+        if (style == LinkObj::PolyLine) 
+            mapDesignInt->setLinkStyle(LinkObj::Line, 1);
+        else if (style == LinkObj::PolyParabel) 
+            mapDesignInt->setLinkStyle(LinkObj::Parabel, 1);
+    }
+
+    applyDesignRecursively(MapDesign::LinkStyleChanged, rootItem);
     reposition();
+
     return true;
 }
 
-LinkableMapObj::Style VymModel::getMapLinkStyle() { return linkstyle; }
-
-uint VymModel::getModelID() { return modelID; }
+uint VymModel::modelId() { return modelIdInt; }
 
 void VymModel::setView(VymView *vv) { vymView = vv; }
 
-void VymModel::setMapDefLinkColor(QColor col)
+void VymModel::setDefaultLinkColor(const 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()));
+    if (!col.isValid()) return;
+
+    QString uc = QString("map.setDefaultLinkColor (\"%1\");").arg(mapDesignInt->defaultLinkColor().name());
+    QString rc = QString("map.setDefaultLinkColor (\"%1\");").arg(col.name());
+    QString com = QString("Set map link color to %1").arg(col.name());
+    logAction(rc, com, __func__);
+    saveState(uc, rc, com);
 
-    defLinkColor = col;
+    mapDesignInt->setDefaultLinkColor(col);
 
     // Set color for "link arrows" in TreeEditor
-    vymView->setLinkColor(col);
+    vymView->updateColors();
 
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
-    BranchObj *bo;
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev);
     while (cur) {
-        bo = (BranchObj *)(cur->getLMO());
-        bo->setLinkColor();
-
-        for (int i = 0; i < cur->imageCount(); ++i)
-            cur->getImageNum(i)->getLMO()->setLinkColor();
+        BranchContainer *bc = cur->getBranchContainer();
+        bc->updateUpLink();
+        // for (int i = 0; i < cur->imageCount(); ++i)
+        // FIXME-4 setLinkColorHint: images currently use branch link color
 
         nextBranch(cur, prev);
     }
     updateActions();
 }
 
-void VymModel::setMapLinkColorHintInt()
+void VymModel::setLinkColorHint(const LinkObj::ColorHint &newHint)
 {
-    // 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();
+    LinkObj::ColorHint oldHint = mapDesignInt->linkColorHint();
 
-        for (int i = 0; i < cur->imageCount(); ++i)
-            cur->getImageNum(i)->getLMO()->setLinkColor();
+    if (oldHint == newHint)
+        return;
 
-        nextBranch(cur, prev);
-    }
-}
+    mapDesignInt->setLinkColorHint(newHint);
 
-void VymModel::setMapLinkColorHint(LinkableMapObj::ColorHint lch)
-{
-    linkcolorhint = lch;
-    setMapLinkColorHintInt();
-}
+    QString oldHintName = LinkObj::linkColorHintName(oldHint);
+    QString newHintName = LinkObj::linkColorHintName(newHint);
 
-void VymModel::toggleMapLinkColorHint()
-{
-    if (linkcolorhint == LinkableMapObj::HeadingColor)
-        linkcolorhint = LinkableMapObj::DefaultColor;
-    else
-        linkcolorhint = LinkableMapObj::HeadingColor;
-    BranchItem *cur = NULL;
-    BranchItem *prev = NULL;
-    BranchObj *bo;
+    QString uc = QString("map.setLinkColorHint (\"%1\");").arg(oldHintName);
+    QString rc = QString("map.setLinkColorHint (\"%1\");").arg(newHintName);
+    QString com = QString("Set link color hint to %1").arg(newHintName);
+    logAction(rc, com, __func__);
+    saveState(uc, rc, com);
+    
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
     nextBranch(cur, prev);
     while (cur) {
-        bo = (BranchObj *)(cur->getLMO());
-        bo->setLinkColor();
-
-        for (int i = 0; i < cur->imageCount(); ++i)
-            cur->getImageNum(i)->getLMO()->setLinkColor();
-
+        BranchContainer *bc = cur->getBranchContainer();
+        LinkObj *upLink = bc->getLink();
+        if (upLink)
+            upLink->setLinkColorHint(newHint);
+
+        // FIXME-4 setLinkColorHint: images currently use branch link color
+        for (int i = 0; i < cur->imageCount(); ++i) {
+            upLink = cur->getImageNum(i)->getImageContainer()->getLink();
+            if (upLink)
+                upLink->setLinkColorHint(newHint);
+        }
         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());
-    }
+    applyDesignRecursively(MapDesign::LinkStyleChanged, rootItem);
+    reposition();
 }
 
-void VymModel::setMapBackgroundImage(
-    const QString &fn) // FIXME-3 missing savestate, move to ME
+void VymModel::toggleLinkColorHint()
 {
-    /*
-    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);
+    if (mapDesignInt->linkColorHint() == LinkObj::HeadingColor)
+        setLinkColorHint(LinkObj::DefaultColor);
+    else
+        setLinkColorHint(LinkObj::HeadingColor);
 }
 
-void VymModel::selectMapBackgroundColor()
+QColor VymModel::backgroundColor()
 {
-    QColor col = QColorDialog::getColor(
-        mapEditor->getScene()->backgroundBrush().color(), NULL);
-    if (!col.isValid())
-        return;
-    setMapBackgroundColor(col);
+    return mapDesignInt->backgroundColor();
 }
 
-void VymModel::setMapBackgroundColor(QColor col)
+void VymModel::setBackgroundColor(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 oldcol = mapDesignInt->backgroundColor();
 
-QColor VymModel::getMapBackgroundColor() // FIXME-4 move to ME
-{
-    return mapEditor->getScene()->backgroundBrush().color();
-}
+    saveStateBeginScript("Set background color");
+
+    if (hasBackgroundImage())
+        unsetBackgroundImage();
+
+    QString uc = QString("map.setBackgroundColor(\"%1\");").arg(mapDesignInt->backgroundColor().name());
+    QString rc = QString("map.setBackgroundColor(\"%1\");").arg(col.name());
+    QString com = QString("Set background color of map to %1").arg(col.name());
+    logAction(rc, com, __func__);
+    saveState(uc, rc, com);
 
-QFont VymModel::getMapDefaultFont() { return defaultFont; }
+    saveStateEndScript();
 
-void VymModel::setMapDefaultFont(const QFont &f) { defaultFont = f; }
+    mapDesignInt->setBackgroundColor(col);  // Used for backroundRole in TreeModel::data()
 
-LinkableMapObj::ColorHint VymModel::getMapLinkColorHint() // FIXME-4 move to ME
-{
-    return linkcolorhint;
+    vymView->updateColors();
 }
 
-QColor VymModel::getMapDefLinkColor() // FIXME-4 move to ME
+bool VymModel::loadBackgroundImage( const QString &imagePath)
 {
-    return defLinkColor;
-}
+    // FIXME-4 maybe also use: view.setCacheMode(QGraphicsView::CacheBackground);
 
-void VymModel::setMapDefXLinkPen(const QPen &p) // FIXME-4 move to ME
-{
-    defXLinkPen = p;
-}
+    if (!saveStateBlocked) {
+        QString uc, rc;
+        
+        QString comment = QString("Load background image: \"%1\"").arg(imagePath);
 
-QPen VymModel::getMapDefXLinkPen() // FIXME-4 move to ME
-{
-    return defXLinkPen;
-}
+        logAction(rc, comment, __func__);
 
-void VymModel::setMapDefXLinkStyleBegin(const QString &s)
-{
-    defXLinkStyleBegin = s;
-}
+        saveStateBeginScript(comment);
 
-QString VymModel::getMapDefXLinkStyleBegin() { return defXLinkStyleBegin; }
+        bool saveOldImage = false;
 
-void VymModel::setMapDefXLinkStyleEnd(const QString &s)
-{
-    defXLinkStyleEnd = s;
-}
+        QString oldImagePath = "images/background-image-old.png";
+        QString newImagePath = "images/background-image-new.png";
 
-QString VymModel::getMapDefXLinkStyleEnd() { return defXLinkStyleEnd; }
+        if (hasBackgroundImage()) {
+            saveOldImage = true;
+            uc = QString("map.loadBackgroundImage(\"HISTORY_PATH/%1\");").arg(oldImagePath);
+        } else
+            uc = QString("map.unsetBackgroundImage();");
 
-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();
+        QFile newImage (imagePath);
+        if (!newImage.exists()) {
+            qWarning() << __FUNCTION__ << " Image to load as background does not exist: " << imagePath;
+            return false;
+        }
+
+        rc = QString("map.loadBackgroundImage(\"HISTORY_PATH/%1\");").arg(newImagePath);
+
+        QString historyPath = saveState(uc, rc, comment, nullptr, nullptr, true);
+        if (saveOldImage) {
+            if (!mapDesignInt->saveBackgroundImage(historyPath + oldImagePath)) {
+                qWarning() << __FUNCTION__ << " Failed to save existing background image to: " << historyPath + oldImagePath;
+                return false;   // FIXME-4 For all aborts, drop last history step...
             }
         }
+
+        // Copy new image to historyPath and (if previously) used also old image
+        if (!newImage.copy(historyPath + newImagePath)) {
+            qWarning() << __FUNCTION__ << " failed to copy new background image to " << historyPath + newImagePath;
+            return false;
+        }
+        
+        setBackgroundImageName(basename(imagePath));
+
+        saveStateEndScript();
+    }
+
+    if (mapDesignInt->loadBackgroundImage(imagePath)) {
+        vymView->updateColors();
+        return true;
     }
+
+    qWarning() << __FUNCTION__ << " failed to load new background image from " << imagePath;
+
+    return false;
 }
 
-void VymModel::moveRel(const double &x, const double &y)
+void VymModel::setBackgroundImageName( const QString &newName)
 {
-    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();
-            }
-        }
-    }
+    QString oldName = mapDesignInt->backgroundImageName();
+
+    QString uc = QString("map.setBackgroundImageName(\"%1\");").arg(oldName);
+    QString rc = QString("map.setBackgroundImageName(\"%1\");").arg(newName);
+    QString com = QString("Set name of background image to \"%1\"").arg(newName);
+    logAction(rc, com, __func__);
+    saveState(uc, rc, com);
+    mapDesignInt->setBackgroundImageName(newName);
 }
 
-void VymModel::animate()
+void VymModel::unsetBackgroundImage()
 {
-    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--;
+    if (mapDesignInt->hasBackgroundImage()) {
+        if (!saveStateBlocked) {
+            QString uc, rc, com;
+
+            bool saveOldImage = false;
+
+            QString oldImagePath = "images/background-image-old.png";
+
+            uc = QString("map.loadBackgroundImage(\"HISTORY_PATH/%1\");").arg(oldImagePath);
+            rc = QString("map.unsetBackgroundImage();");
+            com = QString("Unset background image");
+            logAction(rc, com, __func__);
+
+            QString historyPath = saveState(uc, rc, com, nullptr, nullptr, true);
+            if (!mapDesignInt->saveBackgroundImage(historyPath + oldImagePath)) {
+                logWarning(" Failed to save existing background image to: " + historyPath + oldImagePath, __func__);
+                return;   // FIXME-4 For all aborts, drop last history step...
             }
         }
-        bo->reposition();
-        i++;
+
+        mapDesignInt->unsetBackgroundImage();
+        vymView->updateColors();
     }
-    emitSelectionChanged();
+}
 
-    if (!animObjList.isEmpty())
-        animationTimer->start(animationInterval);
+bool VymModel::hasBackgroundImage()
+{
+    return mapDesignInt->hasBackgroundImage();
 }
 
-void VymModel::startAnimation(BranchObj *bo, const QPointF &v)
+QString VymModel::backgroundImageName()
 {
-    if (!bo)
-        return;
+    return mapDesignInt->backgroundImageName();
+}
 
-    if (bo->getUseRelPos())
-        startAnimation(bo, bo->getRelPos(), bo->getRelPos() + v);
-    else
-        startAnimation(bo, bo->getAbsPos(), bo->getAbsPos() + v);
+void VymModel::setDefXLinkPen(const QPen &p)
+{
+    mapDesignInt->setDefXLinkPen(p);
 }
 
-void VymModel::startAnimation(BranchObj *bo, const QPointF &start,
-                              const QPointF &dest)
+void VymModel::setDefXLinkStyleBegin(const QString &s)
 {
-    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);
-    }
+    mapDesignInt->setDefXLinkStyleBegin(s);
 }
 
-void VymModel::stopAnimation(MapObj *mo)
+void VymModel::setDefXLinkStyleEnd(const QString &s)
 {
-    int i = animObjList.indexOf(mo);
-    if (i >= 0)
-        animObjList.removeAt(i);
+    mapDesignInt->setDefXLinkStyleEnd(s);
 }
 
-void VymModel::stopAllAnimation()
+void VymModel::setPos(const QPointF &pos_new, TreeItem *selti)
 {
-    BranchObj *bo;
-    int i = 0;
-    while (i < animObjList.size()) {
-        bo = (BranchObj *)animObjList.at(i);
-        bo->stopAnimation();
-        bo->requestReposition();
-        i++;
+    QList<TreeItem *> selItems;
+    if (selti)
+        selItems.append(selti);
+    else
+        selItems = getSelectedItems();
+
+    QString com = "Move items (non-interactive";
+    saveStateBeginScript("Move items (non-interactive)");
+    foreach (TreeItem *ti, selItems) {
+        if (ti->hasTypeBranch() || ti->hasTypeImage())
+        {
+            Container *c = ((MapItem*)ti)->getContainer();
+            QString pos_new_str = toS(pos_new);
+
+            QString uc, rc, itemVar;
+            if (ti->hasTypeBranch())
+                itemVar = setBranchVar((BranchItem*)ti) + "b.";
+            else 
+                itemVar = setImageVar((ImageItem*)ti) + "i.";
+            uc = QString("%1.setPos%2;").arg(itemVar, toS(c->getOriginalPos(), 5));
+            rc = QString("%1.setPos%2;").arg(itemVar, toS(c->pos(), 5));
+            logAction(rc, com, __func__);
+            saveState(uc, rc); 
+            c->setPos(pos_new);
+        }
     }
+    saveStateEndScript();
     reposition();
 }
 
@@ -5355,10 +7023,10 @@ void VymModel::newServer()
     sendCounter = 0;
     tcpServer = new QTcpServer(this);
     if (!tcpServer->listen(QHostAddress::Any, port)) {
-        QMessageBox::critical(NULL, "vym server",
+        QMessageBox::critical(nullptr, "vym server",
                               QString("Unable to start the server: %1.")
                                   .arg(tcpServer->errorString()));
-        // FIXME-3 needed? we are no widget any longer... close();
+        // FIXME needed? we are no widget any longer... close();
         return;
     }
     connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newClient()));
@@ -5427,7 +7095,7 @@ void VymModel::sendData(const QString &s)
     }
 }
 
-void VymModel::readData()
+void VymModel::readData()   // FIXME-5 not used currently
 {
     while (clientSocket->bytesAvailable() >= (int)sizeof(quint16)) {
         if (debug)
@@ -5448,7 +7116,7 @@ void VymModel::readData()
             qDebug() << "VymModel::readData  command=" << qPrintable(t);
         // bool noErr;
         // QString errMsg;
-        // parseAtom (t,noErr,errMsg);    //FIXME-4 needs rework using scripts
+        // parseAtom (t,noErr,errMsg);
     }
     return;
 }
@@ -5459,19 +7127,19 @@ void VymModel::displayNetworkError(QAbstractSocket::SocketError socketError)
     case QAbstractSocket::RemoteHostClosedError:
         break;
     case QAbstractSocket::HostNotFoundError:
-        QMessageBox::information(NULL, vymName + " Network client",
+        QMessageBox::information(nullptr, 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",
+        QMessageBox::information(nullptr, 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",
+        QMessageBox::information(nullptr, vymName + " Network client",
                                  QString("The following error occurred: %1.")
                                      .arg(clientSocket->errorString()));
     }
@@ -5482,16 +7150,15 @@ void VymModel::downloadImage(const QUrl &url, BranchItem *bi)
     if (!bi)
         bi = getSelectedBranch();
     if (!bi) {
-        qWarning("VM::download bi==NULL");
+        qWarning("VM::download bi==nullptr");
         return;
     }
 
-    // FIXME-3 download img to tmpfile and delete after running script in
-    // mainWindow
+    // FIXME-4 delete tmp file of image download after running script
     QString script;
-    script += QString("m = vym.currentMap();m.selectID(\"%1\");")
+    script += QString("m = vym.currentMap();b = m.findBranchBySelection(\"%1\");")
                   .arg(bi->getUuid().toString());
-    script += QString("m.loadImage(\"$TMPFILE\");");
+    script += QString("b.loadImage(\"$TMPFILE\");");
 
     DownloadAgent *agent = new DownloadAgent(url);
     agent->setFinishedAction(this, script);
@@ -5500,61 +7167,44 @@ void VymModel::downloadImage(const QUrl &url, BranchItem *bi)
     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()));
+    QPen selPen = mapDesignInt->selectionPen();
+    QString uc = QString("map.setSelectionPenColor (\"%1\");").arg(selPen.color().name());
+    QString rc = QString("map.setSelectionPenColor (\"%1\");").arg(col.name());
+    QString com = QString("Set pen color of selection box to %1").arg(col.name());
+    logAction(rc, com, __func__);
+    saveState(uc, rc, com);
 
     selPen.setColor(col);
-    mapEditor->setSelectionPen(selPen);
+    mapDesignInt->setSelectionPen(selPen);
+    vymView->updateColors();
 }
 
 QColor VymModel::getSelectionPenColor() {
-    return mapEditor->getSelectionPen().color();
+    return mapDesignInt->selectionPen().color();
 }
 
 void VymModel::setSelectionPenWidth(qreal w)
 {
-    QPen selPen = mapEditor->getSelectionPen();
+    QPen selPen = mapDesignInt->selectionPen();
     
-    saveState(QString("setSelectionPenWidth (\"%1\")")
-                  .arg(mapEditor->getSelectionPen().width()),
-              QString("setSelectionPenWidth (\"%1\")").arg(w),
-              QString("Set pen width of selection box to %1").arg(w));
+    QString uc = QString("map.setSelectionPenWidth (\"%1\");").arg(mapDesignInt->selectionPen().width());
+    QString rc = QString("map.setSelectionPenWidth (\"%1\");").arg(w);
+    QString com = QString("Set pen width of selection box to %1").arg(w);
+    logAction(rc, com, __func__);
+    saveState(uc, rc, com);
 
     selPen.setWidth(w);
-    mapEditor->setSelectionPen(selPen);
-    //vymView->setSelectionColor(col);
+    mapDesignInt->setSelectionPen(selPen);
+    vymView->updateColors();
 }
 
 qreal VymModel::getSelectionPenWidth() {
-    return mapEditor->getSelectionPen().width();
+    return mapDesignInt->selectionPen().width();
 }
 
 void VymModel::setSelectionBrushColor(QColor col)
@@ -5562,164 +7212,190 @@ 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()));
+    QBrush selBrush = mapDesignInt->selectionBrush();
+    QString uc = QString("map.setSelectionBrushColor (\"%1\");").arg(selBrush.color().name());
+    QString rc = QString("map.setSelectionBrushColor (\"%1\");").arg(col.name());
+    QString com = QString("Set Brush color of selection box to %1").arg(col.name());
+    logAction(rc, com, __func__);
+    saveState(uc, rc, com);
 
     selBrush.setColor(col);
-    vymView->setSelectionBrush(selBrush);
+    mapDesignInt->setSelectionBrush(selBrush);
+    vymView->updateColors();
 }
 
 QColor VymModel::getSelectionBrushColor() {
-    return mapEditor->getSelectionBrush().color();
+    return mapDesignInt->selectionBrush().color();
 }
 
-bool VymModel::initIterator(const QString &iname, bool deepLevelsFirst)
+void VymModel::newBranchIterator(
+        const QString &itname,
+        BranchItem *bi,
+        bool deepLevelsFirst)
 {
     Q_UNUSED(deepLevelsFirst);
 
     // Remove existing iterators first
-    selIterCur.remove(iname);
-    selIterPrev.remove(iname);
-    selIterStart.remove(iname);
-    selIterActive.remove(iname);
+    branchIterators.remove(itname);
+    branchIteratorsCurrentIndex.remove(itname);
+    branchIteratorsCurrentIndex[itname] = -1;
 
-    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;
-        }
+    BranchItem *cur = nullptr;
+    BranchItem *prev = nullptr;
+    nextBranch(cur, prev, true, bi);
+    while (cur) {
+        branchIterators[itname].append(cur->getUuid());
+        //qDebug() << "VM::newBranchIterator Adding " << headingText(cur) << " to " << itname;
+        nextBranch(cur, prev, true, bi);
     }
-    return false;
 }
 
-bool VymModel::nextIterator(const QString &iname)
+BranchItem* VymModel::nextBranchIterator(const QString &itname)
 {
-    if (selIterCur.keys().indexOf(iname) < 0) {
+    //qDebug() << "VM::nextBranchIterator itname=" << itname << " index=" << branchIteratorsCurrentIndex;
+    if (branchIterators.keys().indexOf(itname) < 0) {
         qWarning()
             << QString("VM::nextIterator couldn't find %1 in hash of iterators")
-                   .arg(iname);
-        return false;
+                   .arg(itname);
+        return nullptr;
     }
 
-    BranchItem *cur = (BranchItem *)(findUuid(selIterCur.value(iname)));
-    if (!cur) {
-        qWarning() << "VM::nextIterator couldn't find cur" << selIterCur;
-        return false;
-    }
+    branchIteratorsCurrentIndex[itname]++;
 
-    qDebug() << "  " << iname << "selecting " << cur->getHeadingPlain();
-    select(cur);
+    if (branchIteratorsCurrentIndex[itname] < 0 || branchIteratorsCurrentIndex[itname] > branchIterators[itname].size() - 1)
+        return nullptr;
 
-    if (!selIterActive.value(iname)) {
-        // Select for the first time
-        select(cur);
-        selIterActive[iname] = true;
-        return true;
+    BranchItem *bi = (BranchItem *)(findUuid(branchIterators[itname].at(branchIteratorsCurrentIndex[itname])));
+    if (!bi) {
+        qWarning() << "VM::nextIterator couldn't find branch with Uuid in list.";
+        return nullptr;
     }
 
-    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;
+    return bi;
 }
 
 void VymModel::setHideTmpMode(TreeItem::HideTmpMode mode)
 {
-    hidemode = mode;
+    if (hideMode == mode)
+        return;
+
+    hideMode = mode;
     for (int i = 0; i < rootItem->branchCount(); i++)
-        rootItem->getBranchNum(i)->setHideTmp(mode);
+        rootItem->getBranchNum(i)->setHideMode(mode);
     reposition();
     if (mode == TreeItem::HideExport)
         unselectAll();
     else
         reselect();
 
+    reposition();
+
     qApp->processEvents();
 }
 
+void VymModel::toggleHideTmpMode() {
+    if (hideMode == TreeItem::HideNone)
+        setHideTmpMode(TreeItem::HideExport);
+    else
+        setHideTmpMode(TreeItem::HideNone);
+}
+
 //////////////////////////////////////////////
 // Selection related
 //////////////////////////////////////////////
 
 void VymModel::updateSelection(QItemSelection newsel, QItemSelection dsel)
 {
+    // Set selection status in objects
+    // Temporary unscroll or rescroll as required
+
+    //qDebug() << "VM::updateSel  newsel=" << newsel << " dsel=" << dsel;
     QModelIndex ix;
     MapItem *mi;
     BranchItem *bi;
     bool do_reposition = false;
+    // Unselect objects (if not part of selection)
     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();
+        if (mi->hasTypeBranch() || mi->getType() == TreeItem::Image || mi->getType() == TreeItem::XLinkItemType) {
+            if (mi->hasTypeBranch()) {
+                BranchContainer *bc = ((BranchItem*)mi)->getBranchContainer();
+                bc->unselect();
+                bc->updateVisibility();
+                do_reposition =
+                    do_reposition || ((BranchItem *)mi)->resetTmpUnscroll();
+            }
+            if (mi->hasTypeImage()) {
+                ImageContainer *ic = ((ImageItem*)mi)->getImageContainer();
+                ic->unselect();
+                ic->updateVisibility();
+            }
+            if (mi->hasTypeXLink()) {
+                ((XLinkItem*)mi)->getXLinkObj()->unselect();
+                XLink *li = ((XLinkItem *)mi)->getXLink();
+
+                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()) {
+        if (mi->hasTypeBranch()) {
             bi = (BranchItem *)mi;
+            BranchContainer *bc = bi->getBranchContainer();
+            bc->select();
+            bc->updateVisibility();
             if (bi->hasScrolledParent()) {
                 bi->tmpUnscroll();
                 do_reposition = true;
             }
         }
-        if (mi->getType() == TreeItem::XLink) {
-            ((XLinkItem *)mi)->setSelection();
+        if (mi->hasTypeImage()) {
+            ImageContainer *ic = ((ImageItem*)mi)->getImageContainer();
+
+            // Make sure that images have correct upLinkPosSelf_sp
+            // (Could be off after loading and IF no sibling branches are following)
+            ic->updateUpLink();
+
+            ic->select();
+            ic->updateVisibility();
+        }
+
+        if (mi->getType() == TreeItem::XLinkItemType) {
+            XLinkItem *xli = (XLinkItem*)mi;
+            xli->setSelectionType();
+            xli->getXLinkObj()->select(
+                mapDesign()->selectionPen(),
+                mapDesign()->selectionBrush());
 
             // begin/end branches need to be tmp unscrolled
-            Link *li = ((XLinkItem *)mi)->getLink();
-            bi = li->getBeginBranch();
+            XLink *xl = ((XLinkItem *)mi)->getXLink();
+            bi = xl->getBeginBranch();
             if (bi->hasScrolledParent()) {
                 bi->tmpUnscroll();
                 do_reposition = true;
             }
-            bi = li->getEndBranch();
+            bi = xl->getEndBranch();
             if (bi->hasScrolledParent()) {
                 bi->tmpUnscroll();
                 do_reposition = true;
             }
         }
     }
+
+    // Show count of multiple selected items
+    int selCount = selModel->selection().indexes().count();
+    if (selCount > 1)
+        mainWindow->statusMessage(
+            tr("%1 items selected","Status message when selecting multiple items").arg(selCount));
+    else
+        mainWindow->statusMessage("");
+
     if (do_reposition)
         reposition();
 }
@@ -5732,41 +7408,44 @@ 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
+bool VymModel::select(const QString &s)
 {
     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;
+    QStringList list = s.split(";");
+
+    unselectAll();
+    foreach (QString t, list) {
+        TreeItem *ti = findBySelectString(t);
+        if (ti)
+            selectToggle(ti);
+        else
+            return false;
+    }
+    return true;
 }
 
-bool VymModel::select(LinkableMapObj *lmo)
+bool VymModel::selectUids(QStringList  uids)
 {
-    QItemSelection oldsel = selModel->selection();
-
-    if (lmo)
-        return select(lmo->getTreeItem());
-    else
+    if (uids.isEmpty())
         return false;
+
+    unselectAll();
+    foreach (auto uid, uids) {
+        TreeItem *ti = findUuid(QUuid(uid));
+        if (!ti)
+            return false;
+        selectToggle(ti);
+    }
+    return true;
 }
 
 bool VymModel::selectToggle(TreeItem *ti)
 {
     if (ti) {
         selModel->select(index(ti), QItemSelectionModel::Toggle);
-        // appendSelectionToHistory(); // FIXME-4 selection history not implemented yet
+        // appendSelectionToHistory(); // FIXME-3 selection history not implemented yet
         // for multiselections
         lastToggledUuid = ti->getUuid();
         return true;
@@ -5774,6 +7453,18 @@ bool VymModel::selectToggle(TreeItem *ti)
     return false;
 }
 
+bool VymModel::selectToggle(const uint &id)
+{
+    TreeItem *ti = findID(id);
+    return selectToggle(ti);
+}
+
+bool VymModel::selectToggle(const QUuid &uid)
+{
+    TreeItem *ti = findUuid(uid);
+    return selectToggle(ti);
+}
+
 bool VymModel::selectToggle(const QString &selectString)
 {
     TreeItem *ti = findBySelectString(selectString);
@@ -5788,11 +7479,17 @@ bool VymModel::select(TreeItem *ti)
         return false;
 }
 
+bool VymModel::select(const QUuid &uuid)
+{
+    TreeItem *ti = findUuid(uuid);
+    return select(ti);
+}
+
 bool VymModel::select(const QModelIndex &index)
 {
     if (index.isValid()) {
         TreeItem *ti = getItem(index);
-        if (ti->isBranchLikeType()) {
+        if (ti->hasTypeBranch()) {
             if (((BranchItem *)ti)->tmpUnscroll())
                 reposition();
         }
@@ -5803,14 +7500,23 @@ bool VymModel::select(const QModelIndex &index)
     return false;
 }
 
-void VymModel::unselectAll() { unselect(selModel->selection()); }
+void VymModel::select(QList <BranchItem*> selbis)
+{
+    unselectAll();
+    foreach (BranchItem* selbi, selbis)
+        selectToggle(selbi);
+}
 
-void VymModel::unselect(QItemSelection desel)
+void VymModel::select(QList <TreeItem*> tis)
 {
-    if (!desel.isEmpty()) {
-        lastSelectString = getSelectString();
-        selModel->clearSelection();
-    }
+    unselectAll();
+    foreach (TreeItem* ti, tis)
+        selectToggle(ti);
+}
+
+void VymModel::unselectAll() {
+    lastSelectString = getSelectString();
+    selModel->clearSelection();
 }
 
 bool VymModel::reselect()
@@ -5879,13 +7585,13 @@ void VymModel::resetSelectionHistory()
     appendSelectionToHistory();
 }
 
-void VymModel::appendSelectionToHistory() // FIXME-4 history unable to cope with multiple
+void VymModel::appendSelectionToHistory() // FIXME-3 history unable to cope with multiple
                                           // selections
 {
     uint id = 0;
     TreeItem *ti = getSelectedItem();
     if (ti && !keepSelectionHistory) {
-        if (ti->isBranchLikeType())
+        if (ti->hasTypeBranch())
             ((BranchItem *)ti)->setLastSelectedBranch();
         id = ti->getID();
         selectionHistory.append(id);
@@ -5894,10 +7600,10 @@ void VymModel::appendSelectionToHistory() // FIXME-4 history unable to cope with
     }
 }
 
-void VymModel::emitShowSelection(bool scaled)
+void VymModel::emitShowSelection(bool scaled, bool rotated)
 {
     if (!repositionBlocked)
-        emit(showSelection(scaled));
+        emit showSelection(scaled, rotated);
 }
 
 TreeItem* VymModel::lastToggledItem()
@@ -5908,18 +7614,19 @@ TreeItem* VymModel::lastToggledItem()
 void VymModel::emitNoteChanged(TreeItem *ti)
 {
     QModelIndex ix = index(ti);
-    emit(noteChanged(ix));
+    emit noteChanged(ix);
     mainWindow->updateNoteEditor(ti);
 }
 
 void VymModel::emitDataChanged(TreeItem *ti)
 {
-    QModelIndex ix = index(ti);
-    emit(dataChanged(ix, ix));
-    emitSelectionChanged();
-    if (!repositionBlocked) {
+    //qDebug() << "VM::emitDataChanged ti=" << ti;
+    if (!dataChangedBlocked && ti) {
+        QModelIndex ix = index(ti);
+        emit dataChanged(ix, ix);
+
         // Update taskmodel and recalc priorities there
-        if (ti->isBranchLikeType() && ((BranchItem *)ti)->getTask()) {
+        if (ti->hasTypeBranch() && ((BranchItem *)ti)->getTask()) {
             taskModel->emitDataChanged(((BranchItem *)ti)->getTask());
             taskModel->recalcPriorities();
         }
@@ -5929,21 +7636,21 @@ void VymModel::emitDataChanged(TreeItem *ti)
 void VymModel::emitUpdateQueries()
 {
     // Used to tell MainWindow to update query results
-    if (repositionBlocked)
-        return;
-    emit(updateQueries(this));
+    if (repositionBlocked) return;
+
+    emit updateQueries(this);
 }
 void VymModel::emitUpdateLayout()
 {
     if (settings.value("/mainwindow/autoLayout/use", "true") == "true")
-        emit(updateLayout());
+        emit updateLayout();
 }
 
-bool VymModel::selectFirstBranch()
+bool VymModel::selectFirstBranch(BranchItem *bi)
 {
-    TreeItem *ti = getSelectedBranch();
-    if (ti) {
-        TreeItem *par = ti->parent();
+    BranchItem* selbi = getSelectedBranch(bi);
+    if (selbi) {
+        TreeItem *par = selbi->parent();
         if (par) {
             TreeItem *ti2 = par->getFirstBranch();
             if (ti2)
@@ -5953,38 +7660,38 @@ bool VymModel::selectFirstBranch()
     return false;
 }
 
-bool VymModel::selectFirstChildBranch()
+bool VymModel::selectFirstChildBranch(BranchItem *bi)
 {
-    TreeItem *ti = getSelectedBranch();
-    if (ti) {
-        BranchItem *bi = ti->getFirstBranch();
-        if (bi)
-            return select(bi);
+    BranchItem *selbi = getSelectedBranch(bi);
+    if (selbi) {
+        BranchItem *bi2 = selbi->getFirstBranch();
+        if (bi2)
+            return select(bi2);
     }
     return false;
 }
 
-bool VymModel::selectLastBranch()
+bool VymModel::selectLastBranch(BranchItem *bi)
 {
-    TreeItem *ti = getSelectedBranch();
-    if (ti) {
-        TreeItem *par = ti->parent();
+    BranchItem* selbi = getSelectedBranch(bi);
+    if (selbi) {
+        TreeItem *par = selbi->parent();
         if (par) {
-            TreeItem *ti2 = par->getLastBranch();
-            if (ti2)
-                return select(ti2);
+            BranchItem *bi2 = par->getLastBranch();
+            if (bi2)
+                return select(bi2);
         }
     }
     return false;
 }
 
-bool VymModel::selectLastChildBranch()
+bool VymModel::selectLastChildBranch(BranchItem *bi)
 {
-    TreeItem *ti = getSelectedBranch();
-    if (ti) {
-        BranchItem *bi = ti->getLastBranch();
-        if (bi)
-            return select(bi);
+    BranchItem *selbi = getSelectedBranch(bi);
+    if (selbi) {
+        BranchItem *bi2 = selbi->getLastBranch();
+        if (bi2)
+            return select(bi2);
     }
     return false;
 }
@@ -6000,28 +7707,18 @@ bool VymModel::selectLastSelectedBranch()
     return false;
 }
 
-bool VymModel::selectLastImage()
+bool VymModel::selectLatestAdded()
 {
-    TreeItem *ti = getSelectedBranch();
-    if (ti) {
-        TreeItem *par = ti->parent();
-        if (par) {
-            TreeItem *ti2 = par->getLastImage();
-            if (ti2)
-                return select(ti2);
-        }
-    }
-    return false;
+    TreeItem *ti = findUuid(latestAddedItemUuid);
+        return select(ti);
 }
 
-bool VymModel::selectLatestAdded() { return select(latestAddedItem); }
-
-bool VymModel::selectParent()
+bool VymModel::selectParent(TreeItem *ti)
 {
-    TreeItem *ti = getSelectedItem();
+    TreeItem *selti = getSelectedItem(ti);
     TreeItem *par;
-    if (ti) {
-        par = ti->parent();
+    if (selti) {
+        par = selti->parent();
         if (par)
             return select(par);
     }
@@ -6037,57 +7734,62 @@ TreeItem::Type VymModel::selectionType()
         return TreeItem::Undefined;
 }
 
-LinkableMapObj *VymModel::getSelectedLMO()
+BranchItem *VymModel::getSelectedBranch(BranchItem *bi)
 {
-    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;
-}
+    if (bi) return bi;
 
-BranchObj *VymModel::getSelectedBranchObj() // convenience function
-{
-    TreeItem *ti = getSelectedBranch();
-    if (ti)
-        return (BranchObj *)(((MapItem *)ti)->getLMO());
-    else
-        return NULL;
+    // Return selected branch,
+    // if several are selected, return last selected
+    QList<BranchItem *> bis = getSelectedBranches();
+    if (bis.count() == 0) return nullptr;
+
+    return bis.last();
 }
 
-BranchItem *VymModel::getSelectedBranch()
+QList<BranchItem *> VymModel::getSelectedBranches(TreeItem *ti)
 {
-    TreeItem *ti = getSelectedItem();
-    if (ti) {
-        TreeItem::Type type = ti->getType();
-        if (type == TreeItem::Branch || type == TreeItem::MapCenter)
-            return (BranchItem *)ti;
+    // Return list of selected branches.
+    // If ti != nullptr and is branch, return only this one
+    QList<BranchItem *> selbis;
+
+    if (ti && ti->hasTypeBranch()) {
+        selbis << (BranchItem*)ti;
+        return selbis;
     }
-    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);
+        if (ti->hasTypeBranch())
+            selbis.append((BranchItem *)ti);
     }
-    return bis;
+    return selbis;
 }
 
 ImageItem *VymModel::getSelectedImage()
 {
-    TreeItem *ti = getSelectedItem();
-    if (ti && ti->getType() == TreeItem::Image)
-        return (ImageItem *)ti;
-    else
-        return NULL;
+    // Return selected image,
+    // if several are selected, return last selected
+    QList<ImageItem *> iis = getSelectedImages();
+    if (iis.count() == 0) return nullptr;
+
+    return iis.last();
+}
+
+QList<ImageItem *> VymModel::getSelectedImages(TreeItem *ti)
+{
+    // Return list of selected images.
+    // If ii != nullptr, return only this one
+    QList<ImageItem *> iis;
+
+    if (ti && ti->hasTypeImage()) {
+        iis << (ImageItem*)ti;
+        return iis;
+    }
+
+    foreach (TreeItem *ti, getSelectedItems()) {
+        if (ti->hasTypeImage())
+            iis.append((ImageItem *)ti);
+    }
+    return iis;
 }
 
 Task *VymModel::getSelectedTask()
@@ -6096,24 +7798,27 @@ Task *VymModel::getSelectedTask()
     if (selbi)
         return selbi->getTask();
     else
-        return NULL;
+        return nullptr;
 }
 
-Link *VymModel::getSelectedXLink()
+XLink *VymModel::getSelectedXLink(XLink *xl)
 {
+    if (xl)
+        return xl;
+
     XLinkItem *xli = getSelectedXLinkItem();
     if (xli)
-        return xli->getLink();
-    return NULL;
+        return xli->getXLink();
+    return nullptr;
 }
 
 XLinkItem *VymModel::getSelectedXLinkItem()
 {
     TreeItem *ti = getSelectedItem();
-    if (ti && ti->getType() == TreeItem::XLink)
+    if (ti && ti->getType() == TreeItem::XLinkItemType)
         return (XLinkItem *)ti;
     else
-        return NULL;
+        return nullptr;
 }
 
 AttributeItem *VymModel::getSelectedAttribute()
@@ -6122,29 +7827,66 @@ AttributeItem *VymModel::getSelectedAttribute()
     if (ti && ti->getType() == TreeItem::Attribute)
         return (AttributeItem *)ti;
     else
-        return NULL;
+        return nullptr;
 }
 
-TreeItem *VymModel::getSelectedItem()
+TreeItem *VymModel::getSelectedItem(TreeItem *ti)
 {
+    if (ti) return ti;
+
     if (!selModel)
-        return NULL;
+        return nullptr;
     QModelIndexList list = selModel->selectedIndexes();
     if (list.count() == 1)
         return getItem(list.first());
     else
-        return NULL;
+        return nullptr;
 }
 
-QList<TreeItem *> VymModel::getSelectedItems()
+QList<TreeItem *> VymModel::getSelectedItems(TreeItem *ti)
 {
-    QList<TreeItem *> l;
+    QList<TreeItem *> seltis;
+    if (ti) {
+        seltis << ti;
+        return seltis;
+    }
+
     if (!selModel)
-        return l;
+        return seltis;
+
     QModelIndexList list = selModel->selectedIndexes();
     foreach (QModelIndex ix, list)
-        l.append(getItem(ix));
-    return l;
+        seltis.append(getItem(ix));
+    return seltis;
+}
+
+QList<TreeItem *> VymModel::getSelectedItemsReduced()
+{
+    // Remove items, whose have parents already in list
+
+    QList<TreeItem *> list = getSelectedItems();
+
+    if (list.isEmpty()) return list;
+
+    // Bubble sort items by depth first
+    for (int n = list.size(); n > 1; n--)
+        for (int i = 0; i < n - 1; i++)
+            if (list.at(i)->depth() > list.at(i + 1)->depth() )
+                list.swapItemsAt(i, i + 1);
+
+    // Remove items, which have parents which have smaller depth
+    // (closer to center)
+    int i = list.size() - 1;
+    while (i > 0) {
+        for (int j = 0; j < i; j++)
+            if (list.at(i)->isChildOf(list.at(j))) {
+                list.removeAt(i);
+                break;
+            }
+        i--;
+    }
+
+    return list;
 }
 
 QModelIndex VymModel::getSelectedIndex()
@@ -6156,9 +7898,9 @@ QModelIndex VymModel::getSelectedIndex()
         return QModelIndex();
 }
 
-QList<uint> VymModel::getSelectedIDs()
+QList<ulong> VymModel::getSelectedIDs()
 {
-    QList<uint> uids;
+    QList<ulong> uids;
     foreach (TreeItem *ti, getSelectedItems())
         uids.append(ti->getID());
     return uids;
@@ -6179,15 +7921,12 @@ bool VymModel::isSelected(TreeItem *ti)
 
 QString VymModel::getSelectString()
 {
-    return getSelectString(getSelectedItem());
-}
+    QStringList list;
+    QList <TreeItem*> seltis = getSelectedItems();
+    foreach (TreeItem* selti, seltis)
+        list << getSelectString(selti);
 
-QString VymModel::getSelectString(
-    LinkableMapObj *lmo) // only for convenience. Used in MapEditor
-{
-    if (!lmo)
-        return QString();
-    return getSelectString(lmo->getTreeItem());
+    return list.join(";");
 }
 
 QString VymModel::getSelectString(TreeItem *ti)
@@ -6208,7 +7947,7 @@ QString VymModel::getSelectString(TreeItem *ti)
     case TreeItem::Attribute:
         s = "ai:";
         break;
-    case TreeItem::XLink:
+    case TreeItem::XLinkItemType:
         s = "xl:";
         break;
     default:
@@ -6232,63 +7971,70 @@ 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 *VymModel::addSlide()     // FIXME-3 missing saveState
 {
     SlideItem *si = slideModel->getSelectedItem();
     if (si)
-        si = slideModel->addSlide(NULL, si->childNumber() + 1);
+        si = slideModel->addSlide(nullptr, si->row() + 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);
+    if (si) {
+        if (seli) {
+            QString inScript;
+            if (!loadStringFromDisk(vymBaseDir.path() +
+                                        "/macros/slideeditor-snapshot.vys",
+                                    inScript)) {
+                qWarning() << "VymModel::addSlide couldn't load template for "
+                              "taking snapshot";
+                return nullptr;
+            }
+
+            inScript.replace(
+                "CURRENT_ZOOM",
+                QString().setNum(getMapEditor()->zoomFactorTarget()));
+            inScript.replace("CURRENT_ANGLE",
+                             QString().setNum(getMapEditor()->rotationTarget()));
+            inScript.replace("CURRENT_ID",
+                             "\"" + seli->getUuid().toString() + "\"");
+
+            si->setInScript(inScript);
+            slideModel->setData(slideModel->index(si), seli->headingPlain());
+        }
+
+        /*
+        QString s = "<vymmap>" + si->saveToDir() + "</vymmap>";
+        int pos = si->row();
+        saveStateold(File::PartOfMap, getSelectString(),    // FIXME addAddSlide
+                  QString("removeSlide (%1)").arg(pos), getSelectString(),
+                  QString("addMapInsert (\"PATH\",%1)").arg(pos), "Add slide", nullptr,
+                  s);
+          */
+        QString com = "Add slide";
+        logAction("", com, __func__);
+    }
     return si;
 }
 
-void VymModel::deleteSlide(SlideItem *si)
+void VymModel::deleteSlide(SlideItem *si)  // FIXME-3 missing saveState
 {
     if (si) {
         QString s = "<vymmap>" + si->saveToDir() + "</vymmap>";
-        int pos = si->childNumber();
-        saveState(PartOfMap, getSelectString(),
+        /*
+        int pos = si->row();
+        saveStateold(File::PartOfMap, getSelectString(),    // FIXME deleteAddSlide
                   QString("addMapInsert (\"PATH\",%1)").arg(pos),
                   getSelectString(), QString("removeSlide (%1)").arg(pos),
-                  "Remove slide", NULL, s);
+                  "Remove slide", nullptr, s);
+                  */
+        QString com = "Delete slide";
+        logAction("", com, __func__);
         slideModel->deleteSlide(si);
     }
 }
@@ -6303,24 +8049,27 @@ void VymModel::relinkSlide(SlideItem *si, int pos)
 
 bool VymModel::moveSlideDown(int n)
 {
-    SlideItem *si = NULL;
+    SlideItem *si = nullptr;
     if (n < 0) // default if called without parameters
     {
         si = slideModel->getSelectedItem();
         if (si)
-            n = si->childNumber();
+            n = si->row();
         else
             return false;
     }
     else
         si = slideModel->getSlide(n);
     if (si && n >= 0 && n < slideModel->count() - 1) {
+        QString uc = QString("map.moveSlideUp (%1);").arg(n + 1);
+        QString rc = QString("map.moveSlideDown (%1);").arg(n);
+        QString com = QString("Move slide %1 down").arg(n);
+        logAction(rc, com, __func__);
+
         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));
+        saveState(uc, rc, com);
         return true;
     }
     else
@@ -6329,24 +8078,26 @@ bool VymModel::moveSlideDown(int n)
 
 bool VymModel::moveSlideUp(int n)
 {
-    SlideItem *si = NULL;
+    SlideItem *si = nullptr;
     if (n < 0) // default if called without parameters
     {
         si = slideModel->getSelectedItem();
         if (si)
-            n = si->childNumber();
+            n = si->row();
         else
             return false;
     }
     else
         si = slideModel->getSlide(n);
     if (si && n > 0 && n < slideModel->count()) {
+        QString uc = QString("map.moveSlideDown (%1);").arg(n + 1);
+        QString rc = QString("map.moveSlideUp (%1);").arg(n);
+        QString com = QString("Move slide %1 up").arg(n);
+        logAction(rc, com, __func__);
         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));
+        saveState(uc, rc, com);
         return true;
     }
     else
@@ -6363,9 +8114,81 @@ void VymModel::updateSlideSelection(QItemSelection newsel, QItemSelection)
         QString inScript = si->getInScript();
 
         // show inScript in ScriptEditor
-        scriptEditor->setSlideScript(modelID, si->getID(), inScript);
+        scriptEditor->setSlideScript(modelIdInt, si->getID(), inScript);
 
         // Execute inScript
-        execute(inScript);
+        mainWindow->runScript(inScript);
     }
 }
+
+void VymModel::logDebug(const QString &comment, const QString &caller)
+{
+    QString place = QString("\"%1\"").arg(fileName);
+    if (!caller.isEmpty()) place += "  Called by: " + caller + "()";
+
+    QString log = QString("\n// %1 [Debug] %2\n// MapID: %3 Map: %4").arg(
+            QDateTime::currentDateTime().toString(Qt::ISODateWithMs),
+            comment,
+            QString::number(modelIdInt),
+            place);
+
+    if (debug)
+        std::cout << log.toStdString() << std::endl << std::flush;
+
+    if (!useActionLog) return;
+
+    appendStringToFile(actionLogPath, log);
+}
+
+void VymModel::logInfo(const QString &comment, const QString &caller)
+{
+    if (!useActionLog) return;
+
+    QString log = QString("\n// %1 [Info VymModel::%2 \"%3\"] %4").arg(
+            QDateTime::currentDateTime().toString(Qt::ISODateWithMs),
+            caller,
+            fileName,
+            comment
+//            QString::number(modelIdInt)
+    );
+
+    // std::cout << log.toStdString() << std::endl << std::flush;
+
+    appendStringToFile(actionLogPath, log);
+}
+
+void VymModel::logWarning(const QString &comment, const QString &caller)
+{
+    if (!useActionLog) return;
+
+    QString log = QString("\n// %1 [Warning VymModel::%2 \"%3\"] %4").arg(
+            QDateTime::currentDateTime().toString(Qt::ISODateWithMs),
+            caller,
+            fileName,
+            comment
+//            QString::number(modelIdInt)
+    );
+
+    std::cout << log.toStdString() << std::endl << std::flush;
+
+    appendStringToFile(actionLogPath, log);
+}
+
+void VymModel::logAction(const QString &command, const QString &comment, const QString &caller)
+{
+    QString place = QString("\"%1\"").arg(fileName);
+    if (!caller.isEmpty()) place += "  Called by: " + caller + "()";
+
+    QString log = QString("\n// %1 [Action] %2\n// MapID: %3 Map: %4").arg(
+            QDateTime::currentDateTime().toString(Qt::ISODateWithMs),
+            comment,
+            QString::number(modelIdInt),
+            place);
+
+    if (debug)
+        std::cout << log.toStdString() << std::endl << std::flush;
+
+    if (!useActionLog) return;
+
+    appendStringToFile(actionLogPath, log + "\n" + command + "\n");
+}