#include <QApplication>
#include <QSvgGenerator>

#if defined(VYM_DBUS)
#include <QtDBus/QDBusConnection>
#endif

#ifndef Q_OS_WINDOWS
#include <unistd.h>
#else
#define sleep Sleep
#endif

#include <QColorDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QPrinter>

#include "vymmodel.h"

#include "attributeitem.h"
#include "branchitem.h"
#include "confluence-agent.h"
#include "download-agent.h"
#include "editxlinkdialog.h"
#include "export-ao.h"
#include "export-ascii.h"
#include "export-confluence.h"
#include "export-csv.h"
#include "export-firefox.h"
#include "export-html.h"
#include "export-impress.h"
#include "export-latex.h"
#include "export-markdown.h"
#include "export-orgmode.h"
#include "file.h"
#include "findresultmodel.h"
#include "jira-agent.h"
#include "lockedfiledialog.h"
#include "mainwindow.h"
#include "misc.h"
#include "noteeditor.h"
#include "options.h"
#include "scripteditor.h"
#include "slideitem.h"
#include "slidemodel.h"
#include "taskeditor.h"
#include "taskmodel.h"
#include "treeitem.h"
#include "vymprocess.h"
#include "warningdialog.h"
#include "xlinkitem.h"
#include "xlinkobj.h"
#include "xml-freemind.h"
#include "xml-vym.h"
#include "xmlobj.h"

#ifdef Q_OS_WINDOWS
#include <windows.h>
#endif

extern bool debug;
extern bool testmode;
extern bool restoreMode;
extern QStringList ignoredLockedFiles;

extern Main *mainWindow;

extern QDir tmpVymDir;

extern NoteEditor *noteEditor;
extern TaskEditor *taskEditor;
extern ScriptEditor *scriptEditor;
extern FlagRowMaster *standardFlagsMaster;
extern FlagRowMaster *userFlagsMaster;

extern Options options;

extern QString clipboardDir;
extern QString clipboardFile;

extern ImageIO imageIO;

extern TaskModel *taskModel;

extern QString vymName;
extern QString vymVersion;
extern QDir vymBaseDir;

extern QDir lastImageDir;
extern QDir lastMapDir;
extern QDir lastExportDir;

extern Settings settings;
extern QTextStream vout;

uint VymModel::idLast = 0; // make instance

VymModel::VymModel()
{
    // qDebug()<< "Const VymModel" << this;
    init();
    rootItem->setModel(this);
    wrapper = new VymModelWrapper(this);
}

VymModel::~VymModel()
{
    // out << "Destr VymModel begin this="<<this<<"  "<<mapName<<flush;
    mapEditor = nullptr;
    repositionBlocked = true;
    autosaveTimer->stop();
    fileChangedTimer->stop();
    stopAllAnimation();

    // qApp->processEvents();	// Update view (scene()->update() is not enough)
    // qDebug() << "Destr VymModel end   this="<<this;

    vymLock.releaseLock();

    delete (wrapper);
}

void VymModel::clear()
{
    while (rootItem->childCount() > 0) {
        // qDebug()<<"VM::clear  ri="<<rootItem<<"
        // ri->count()="<<rootItem->childCount();
        deleteItem(rootItem->getChildNum(0));
    }
}

void VymModel::init()
{
    // No MapEditor yet
    mapEditor = NULL;

    // Use default author
    author =
        settings
            .value("/user/name", tr("unknown user",
                                    "default name for map author in settings"))
            .toString();

    // States and IDs
    idLast++;
    modelID = idLast;
    mapChanged = false;
    mapDefault = true;
    mapUnsaved = false;

    // Selection history
    selModel = NULL;
    selectionBlocked = false;
    resetSelectionHistory();

    resetHistory();

    // Create tmp dirs
    makeTmpDirectories();

    // Files
    readonly = false;
    zipped = true;
    filePath = "";
    fileName = tr("unnamed");
    mapName = fileName;
    repositionBlocked = false;
    saveStateBlocked = false;

    autosaveTimer = new QTimer(this);
    connect(autosaveTimer, SIGNAL(timeout()), this, SLOT(autosave()));

    fileChangedTimer = new QTimer(this);
    connect(fileChangedTimer, SIGNAL(timeout()), this, SLOT(fileChanged()));
    fileChangedTimer->start(3000);

    taskAlarmTimer = new QTimer(this);
    connect(taskAlarmTimer, SIGNAL(timeout()), this, SLOT(updateTasksAlarm()));
    taskAlarmTimer->start(3000);

    // animations   // FIXME-4 switch to new animation system
    animationUse =
        settings.value("/animation/use", false)
            .toBool(); // FIXME-4 add options to control _what_ is animated
    animationTicks = settings.value("/animation/ticks", 20).toInt();
    animationInterval = settings.value("/animation/interval", 5).toInt();
    animObjList.clear();
    animationTimer = new QTimer(this);
    connect(animationTimer, SIGNAL(timeout()), this, SLOT(animate()));

    // View - map
    defaultFont.setPointSizeF(16);
    defLinkColor = QColor(0, 0, 255);
    linkcolorhint = LinkableMapObj::DefaultColor;
    linkstyle = LinkableMapObj::PolyParabel;
    defXLinkPen.setWidth(1);
    defXLinkPen.setColor(QColor(50, 50, 255));
    defXLinkPen.setStyle(Qt::DashLine);
    defXLinkStyleBegin = "HeadFull";
    defXLinkStyleEnd = "HeadFull";

    hasContextPos = false;

    hidemode = TreeItem::HideNone;

    // Animation in MapEditor
    zoomFactor = 1;
    rotationAngle = 0;
    animDuration = 2000;
    animCurve = QEasingCurve::OutQuint;

    // Initialize presentation slides
    slideModel = new SlideModel(this);
    blockSlideSelection = false;

    // Avoid recursions later
    cleaningUpLinks = false;

    // Network
    netstate = Offline;

#if defined(VYM_DBUS)
    // Announce myself on DBUS
    new AdaptorModel(this); // Created and not deleted as documented in Qt
    if (!QDBusConnection::sessionBus().registerObject(
            QString("/vymmodel_%1").arg(modelID), this))
        qWarning("VymModel: Couldn't register DBUS object!");
#endif
}

void VymModel::makeTmpDirectories()
{
    // Create unique temporary directories
    tmpMapDirPath = tmpVymDir.path() + QString("/model-%1").arg(modelID);
    histPath = tmpMapDirPath + "/history";
    QDir d;
    d.mkdir(tmpMapDirPath);
}

QString VymModel::tmpDirPath() { return tmpMapDirPath; }

MapEditor *VymModel::getMapEditor() { return mapEditor; }

VymModelWrapper *VymModel::getWrapper() { return wrapper; }

bool VymModel::isRepositionBlocked() { return repositionBlocked; }

void VymModel::updateActions()
{
    // Tell mainwindow to update states of actions
    mainWindow->updateActions();
}

bool VymModel::setData(const QModelIndex &, const QVariant &value, int role)
{
    if (role != Qt::EditRole)
        return false;

    setHeadingPlainText(value.toString());

    return true;
}

void VymModel::resetUsedFlags()
{
    standardFlagsMaster->resetUsedCounter();
    userFlagsMaster->resetUsedCounter();
}

QString VymModel::saveToDir(const QString &tmpdir, const QString &prefix,
                            FlagRowMaster::WriteMode flagMode, const QPointF &offset,
                            TreeItem *saveSel)
{
    // tmpdir	    temporary directory to which data will be written
    // prefix	    mapname, which will be appended to images etc.
    //
    // writeflags   Only write flags for "real" save of map, not undo
    // offset	    offset of bbox of whole map in scene.
    //		    Needed for XML export

    XMLObj xml;

    // Save Header
    QString ls;
    switch (linkstyle) {
    case LinkableMapObj::Line:
        ls = "StyleLine";
        break;
    case LinkableMapObj::Parabel:
        ls = "StyleParabel";
        break;
    case LinkableMapObj::PolyLine:
        ls = "StylePolyLine";
        break;
    default:
        ls = "StylePolyParabel";
        break;
    }

    QString header =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?><!DOCTYPE vymmap>\n";
    QString colhint = "";
    if (linkcolorhint == LinkableMapObj::HeadingColor)
        colhint = xml.attribut("linkColorHint", "HeadingColor");

    QString mapAttr = xml.attribut("version", vymVersion);
    if (!saveSel) {
        QPen selPen = mapEditor->getSelectionPen();
        QBrush selBrush = mapEditor->getSelectionBrush();

        mapAttr +=
            xml.attribut("author", author) + xml.attribut("title", title) +
            xml.attribut("comment", comment) + xml.attribut("date", getDate()) +
            xml.attribut("branchCount", QString().number(branchCount())) +
            xml.attribut(
                "backgroundColor",
                mapEditor->getScene()->backgroundBrush().color().name()) +
            xml.attribut("defaultFont", defaultFont.toString()) +
            xml.attribut("selectionColor",  // FIXME-2 Only for compatibility until 2.9.513
                         selBrush.color().name(QColor::HexArgb)) +
            xml.attribut("selectionPenColor", selPen.color().name(QColor::HexArgb)) +
            xml.attribut("selectionPenWidth", 
                         QString().setNum(selPen.width())) + 
            xml.attribut("selectionBrushColor", selBrush.color().name(QColor::HexArgb)) +
            xml.attribut("linkStyle", ls) +
            xml.attribut("linkColor", defLinkColor.name()) +
            xml.attribut("defXLinkColor", defXLinkPen.color().name()) +
            xml.attribut("defXLinkWidth",
                         QString().setNum(defXLinkPen.width(), 10)) +
            xml.attribut("defXLinkPenStyle",
                         penStyleToString(defXLinkPen.style())) +
            xml.attribut("defXLinkStyleBegin", defXLinkStyleBegin) +
            xml.attribut("defXLinkStyleEnd", defXLinkStyleEnd) +
            xml.attribut("mapZoomFactor",
                         QString().setNum(mapEditor->getZoomFactorTarget())) +
            xml.attribut("mapRotationAngle",
                         QString().setNum(mapEditor->getAngleTarget())) +
            colhint;
    }
    header += xml.beginElement("vymmap", mapAttr);
    xml.incIndent();

    // Find the used flags while traversing the tree
    resetUsedFlags();

    // Temporary list of links
    QList<Link *> tmpLinks;

    QString tree;
    // Build xml recursivly
    if (!saveSel) {
        // Save all mapcenters as complete map, if saveSel not set
        tree += saveTreeToDir(tmpdir, prefix, offset, tmpLinks);

        // Save local settings
        tree += settings.getDataXML(destPath);

        // Save selection
        if (getSelectedItem() && !saveSel)
            tree += xml.valueElement("select", getSelectString());
    }
    else {
        switch (saveSel->getType()) {
        case TreeItem::Branch:
            // Save Subtree
            tree += ((BranchItem *)saveSel)
                        ->saveToDir(tmpdir, prefix, offset, tmpLinks);
            break;
        case TreeItem::MapCenter:
            // Save Subtree
            tree += ((BranchItem *)saveSel)
                        ->saveToDir(tmpdir, prefix, offset, tmpLinks);
            break;
        case TreeItem::Image:
            // Save Image
            tree += ((ImageItem *)saveSel)->saveToDir(tmpdir, prefix);
            break;
        default:
            // other types shouldn't be safed directly...
            break;
        }
    }

    QString flags;

    // Write images and definitions of used user flags
    if (flagMode != FlagRowMaster::NoFlags) {
        // First find out, which flags are used
        // Definitions
        flags += userFlagsMaster->saveDef(flagMode);

        userFlagsMaster->saveDataToDir(tmpdir + "flags/user/", flagMode);
        standardFlagsMaster->saveDataToDir(tmpdir + "flags/standard/",
                                           flagMode);
    }

    QString footer;
    // Save XLinks
    for (int i = 0; i < tmpLinks.count(); ++i)
        footer += tmpLinks.at(i)->saveToDir();

    // Save slides
    footer += slideModel->saveToDir();

    xml.decIndent();
    footer += xml.endElement("vymmap");

    return header + flags + tree + footer;
}

QString VymModel::saveTreeToDir(const QString &tmpdir, const QString &prefix,
                                const QPointF &offset, QList<Link *> &tmpLinks)
{
    QString s;
    for (int i = 0; i < rootItem->branchCount(); i++)
        s += rootItem->getBranchNum(i)->saveToDir(tmpdir, prefix, offset,
                                                  tmpLinks);
    return s;
}

void VymModel::setFilePath(QString fpath, QString destname)
{
    if (fpath.isEmpty() || fpath == "") {
        filePath = "";
        fileName = "";
        destPath = "";
    }
    else {
        filePath = fpath;    // becomes absolute path
        fileName = fpath;    // gets stripped of path
        destPath = destname; // needed for vymlinks and during load to reset
                             // fileChangedTime

        // If fpath is not an absolute path, complete it
        filePath = QDir(fpath).absolutePath();
        fileDir = filePath.left(1 + filePath.lastIndexOf("/"));

        // Set short name, too. Search from behind:
        fileName = basename(fileName);

        // Forget the .vym (or .xml) for name of map
        mapName =
            fileName.left(fileName.lastIndexOf(".", -1, Qt::CaseSensitive));
    }
}

void VymModel::setFilePath(QString fpath) { setFilePath(fpath, fpath); }

QString VymModel::getFileDir() { return fileDir; }

QString VymModel::getFilePath() { return filePath; }

QString VymModel::getFileName() { return fileName; }

QString VymModel::getMapName() { return mapName; }

QString VymModel::getDestPath() { return destPath; }

bool VymModel::parseVymText(const QString &s)
{
    bool ok = false;
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        parseBaseHandler *handler = new parseVYMHandler;

        bool saveStateBlockedOrg = saveStateBlocked;
        repositionBlocked = true;
        saveStateBlocked = true;
        QXmlInputSource source;
        source.setData(s);
        QXmlSimpleReader reader;
        reader.setContentHandler(handler);
        reader.setErrorHandler(handler);

        handler->setInputString(s);
        handler->setModel(this);
        handler->setLoadMode(ImportReplace, 0);

        ok = reader.parse(source);
        repositionBlocked = false;
        saveStateBlocked = saveStateBlockedOrg;
        if (ok) {
            if (s.startsWith("<vymnote"))
                emitNoteChanged(bi);
            emitDataChanged(bi);
            reposition(); // to generate bbox sizes
        }
        else {
            QMessageBox::critical(0, tr("Critical Parse Error"),
                                  tr(handler->errorProtocol().toUtf8()));
            // returnCode=1;
            // Still return "success": the map maybe at least
            // partially read by the parser
        }
    }
    return ok;
}

File::ErrorCode VymModel::loadMap(QString fname, const LoadMode &lmode,
                                  const FileType &ftype,
                                  const int &contentFilter, int pos)
{
    File::ErrorCode err = File::Success;

    // Get updated zoomFactor, before applying one read from file in the end
    if (mapEditor) {
        zoomFactor = mapEditor->getZoomFactorTarget();
        rotationAngle = mapEditor->getAngleTarget();
    }

    parseBaseHandler *handler;
    fileType = ftype;
    switch (fileType) {
    case VymMap:
        handler = new parseVYMHandler;
        ((parseVYMHandler *)handler)->setContentFilter(contentFilter);
        break;
    case FreemindMap:
        handler = new parseFreemindHandler;
        break;
    default:
        QMessageBox::critical(0, tr("Critical Parse Error"),
                              "Unknown FileType in VymModel::load()");
        return File::Aborted;
    }

    if (lmode == NewMap) {
        // Reset timestamp to check for later updates of file
        fileChangedTime = QFileInfo(destPath).lastModified();

        selModel->clearSelection();
    }

    bool zipped_org = zipped;

    // Create temporary directory for packing
    bool ok;
    QString tmpZipDir = makeTmpDir(ok, tmpDirPath(), "unzip");
    if (!ok) {
        QMessageBox::critical(
            0, tr("Critical Load Error"),
            tr("Couldn't create temporary directory before load\n"));
        return File::Aborted;
    }

    QString xmlfile;
    if (fname.right(4) == ".xml" || fname.right(3) == ".mm") {
        xmlfile = fname;
        zipped = false;

        if (lmode == NewMap || lmode == DefaultMap)
            zipped_org = false;
    }
    else {
        // Try to unzip file
        err = unzipDir(tmpZipDir, fname);
    }

    if (zipped) {
        // Look for mapname.xml
        xmlfile = fname.left(fname.lastIndexOf(".", -1, Qt::CaseSensitive));
        xmlfile = xmlfile.section('/', -1);
        QFile mfile(tmpZipDir + "/" + xmlfile + ".xml");
        if (!mfile.exists()) {
            // mapname.xml does not exist, well,
            // maybe someone renamed the mapname.vym file...
            // Try to find any .xml in the toplevel
            // directory of the .vym file
            QStringList filters;
            filters << "*.xml";
            QStringList flist = QDir(tmpZipDir).entryList(filters);
            if (flist.count() == 1) {
                // Only one entry, take this one
                xmlfile = tmpZipDir + "/" + flist.first();
            }
            else {
                for (QStringList::Iterator it = flist.begin();
                     it != flist.end(); ++it)
                    *it = tmpZipDir + "/" + *it;
                // FIXME-4 Multiple entries, load all (but only the first one
                // into this ME)
                // mainWindow->fileLoadFromTmp (flist);
                // returnCode = 1;	// Silently forget this attempt to load
                qWarning("MainWindow::load (fn)  multimap found...");
            }

            if (flist.isEmpty()) {
                QMessageBox::critical(
                    0, tr("Critical Load Error"),
                    tr("Couldn't find a map (*.xml) in .vym archive.\n"));
                err = File::Aborted;
            }
        } // file doesn't exist
        else
            xmlfile = mfile.fileName();
    }

    QFile file(xmlfile);

    // I am paranoid: file should exist anyway
    // according to check in mainwindow.
    if (!file.exists()) {
        QMessageBox::critical(
            0, tr("Critical Parse Error"),
            tr(QString("Couldn't open map %1").arg(file.fileName()).toUtf8()));
        err = File::Aborted;
    }
    else {
        bool saveStateBlockedOrg = saveStateBlocked;
        repositionBlocked = true;
        saveStateBlocked = true;
        mapEditor->setViewportUpdateMode(QGraphicsView::NoViewportUpdate);
        QXmlInputSource source(&file);
        QXmlSimpleReader reader;
        reader.setContentHandler(handler);
        reader.setErrorHandler(handler);
        handler->setModel(this);

        // We need to set the tmpDir in order  to load files with rel. path
        QString tmpdir;
        if (zipped)
            tmpdir = tmpZipDir;
        else
            tmpdir = fname.left(fname.lastIndexOf("/", -1));
        handler->setTmpDir(tmpdir);
        handler->setInputFile(file.fileName());
        if (lmode == ImportReplace)
            handler->setLoadMode(ImportReplace, pos);
        else
            handler->setLoadMode(lmode, pos);

        // Here we actually parse the XML file
        bool ok = reader.parse(source);

        // Aftermath
        repositionBlocked = false;
        saveStateBlocked = saveStateBlockedOrg;
        mapEditor->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate);
        file.close();
        if (ok) {
            reposition(); // to generate bbox sizes
            emitSelectionChanged();

            if (lmode == NewMap) // no lockfile for default map!
            {
                mapDefault = false;
                mapChanged = false;
                mapUnsaved = false;
                autosaveTimer->stop();

                resetHistory();
                resetSelectionHistory();

                // Set treeEditor and slideEditor visibilty per map
                vymView->readSettings();

                if (!tryVymLock() && debug)
                    qWarning() << "VM::loadMap  no lockfile created!";
            }

            // Recalc priorities and sort
            taskModel->recalcPriorities();
        }
        else {
            QMessageBox::critical(0, tr("Critical Parse Error"),
                                  tr(handler->errorProtocol().toUtf8()));
            // returnCode=1;
            // Still return "success": the map maybe at least
            // partially read by the parser
        }
    }

    // Delete tmpZipDir
    removeDir(QDir(tmpZipDir));

    // Restore original zip state
    zipped = zipped_org;

    updateActions();

    if (lmode != NewMap)
        emitUpdateQueries();

    if (mapEditor) {
        mapEditor->setZoomFactorTarget(zoomFactor);
        mapEditor->setAngleTarget(rotationAngle);
    }

    qApp->processEvents(); // Update view (scene()->update() is not enough)
    return err;
}

File::ErrorCode VymModel::save(const SaveMode &savemode)
{
    QString tmpZipDir;
    QString mapFileName;
    QString saveFilePath;

    File::ErrorCode err = File::Success;

    if (zipped)
        // save as .xml
        mapFileName = mapName + ".xml";
    else
        // use name given by user, could be anything
        mapFileName = fileName;

    // Look, if we should zip the data:
    if (!zipped)
    {
        QMessageBox mb(vymName,
                       tr("The map %1\ndid not use the compressed "
                          "vym file format.\nWriting it uncompressed will also "
                          "write images \n"
                          "and flags and thus may overwrite files into the "
                          "given directory\n\nDo you want to write the map")
                           .arg(filePath),
                       QMessageBox::Warning,
                       QMessageBox::Yes | QMessageBox::Default, QMessageBox::No,
                       QMessageBox::Cancel | QMessageBox::Escape);
        mb.setButtonText(QMessageBox::Yes, tr("compressed (vym default)"));
        mb.setButtonText(
            QMessageBox::No,
            tr("uncompressed, potentially overwrite existing data"));
        mb.setButtonText(QMessageBox::Cancel, tr("Cancel"));
        switch (mb.exec()) {
            case QMessageBox::Yes:
                // save compressed (default file format)
                zipped = true;
                break;
            case QMessageBox::No:
                // save uncompressed
                zipped = false;
                break;
            case QMessageBox::Cancel:
                // do nothing
                return File::Aborted;
                break;
        }
    }

    // First backup existing file, we
    // don't want to add to old zip archives
    QFile f(destPath);
    if (f.exists()) {
        if (settings.value("/system/writeBackupFile").toBool()) {
            QString backupFileName(destPath + "~");
            QFile backupFile(backupFileName);
            if (backupFile.exists() && !backupFile.remove()) {
                QMessageBox::warning(
                    0, tr("Save Error"),
                    tr("%1\ncould not be removed before saving")
                        .arg(backupFileName));
            }
            else {
                if (!f.rename(backupFileName)) {
                    QMessageBox::warning(
                        0, tr("Save Error"),
                        tr("%1\ncould not be renamed before saving")
                            .arg(destPath));
                }
            }
        }
    }

    if (zipped) {
        // Create temporary directory for packing
        bool ok;
        tmpZipDir = makeTmpDir(ok, tmpDirPath(), "zip");
        if (!ok) {
            QMessageBox::critical(
                0, tr("Critical Save Error"),
                tr("Couldn't create temporary directory before save\n"));
            return File::Aborted;
        }

        saveFilePath = filePath;
        setFilePath(tmpZipDir + "/" + mapName + ".xml", saveFilePath);
    } // zipped

    // Create mapName and fileDir
    makeSubDirs(fileDir);

    QString saveFile;
    if (savemode == CompleteMap || selModel->selection().isEmpty()) {
        // Save complete map
        if (zipped)
            // Use defined name for map within zipfile to avoid problems
            // with zip library and umlauts (see #98)
            saveFile =
                saveToDir(fileDir, "", FlagRowMaster::UsedFlags, QPointF(), NULL);
        else
            saveFile = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags,
                                 QPointF(), NULL);
        mapChanged = false;
        mapUnsaved = false;
        autosaveTimer->stop();
    }
    else {
        // Save part of map
        if (selectionType() == TreeItem::Image)
            saveImage();
        else
            saveFile = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags,
                                 QPointF(), getSelectedBranch());
        // FIXME-3 take care of multiselections when saving parts
    }

    bool saved;
    if (zipped)
        // Use defined map name "map.xml", if zipped. Introduce in 2.6.6
        saved = saveStringToDisk(fileDir + "map.xml", saveFile);
    else
        // Use regular mapName, when saved as XML
        saved = saveStringToDisk(fileDir + mapFileName, saveFile);
    if (!saved) {
        err = File::Aborted;
        qWarning("ME::saveStringToDisk failed!");
    }

    if (zipped) {
        // zip
        if (err == File::Success)
            err = zipDir(tmpZipDir, destPath);

        // Delete tmpDir
        removeDir(QDir(tmpZipDir));

        // Restore original filepath outside of tmp zip dir
        setFilePath(saveFilePath);
    }

    updateActions();

    fileChangedTime = QFileInfo(destPath).lastModified();
    return err;
}

ImageItem* VymModel::loadImage(BranchItem *dst, const QString &fn)  // FIXME-2 better move filedialog to MainWindow
{
    if (!dst)
        dst = getSelectedBranch();
    if (dst) {
        QString filter = QString(tr("Images") +
                                 " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif "
                                 "*.pnm *.svg *.svgz);;" +
                                 tr("All", "Filedialog") + " (*.*)");
        QStringList fns;
        if (fn.isEmpty())
            fns = QFileDialog::getOpenFileNames(
                NULL, vymName + " - " + tr("Load image"), lastImageDir.path(),
                filter);
        else
            fns.append(fn);

        if (!fns.isEmpty()) {
            lastImageDir.setPath(
                fns.first().left(fns.first().lastIndexOf("/")));
            QString s;
            for (int j = 0; j < fns.count(); j++) {
                s = fns.at(j);
                ImageItem *ii = createImage(dst);
                if (ii && ii->load(s)) {
                    saveState((TreeItem *)ii, "remove()", dst,
                              QString("loadImage (\"%1\")").arg(s),
                              QString("Add image %1 to %2")
                                  .arg(s)
                                  .arg(getObjectName(dst)));
                    // Find nice position for new image, take childPos // FIXME-1 position below last image
                    FloatImageObj *fio = (FloatImageObj *)(ii->getMO());
                    if (fio) {
                        LinkableMapObj *parLMO = dst->getLMO();

                        if (parLMO) {
                            fio->move(parLMO->getChildRefPos());
                            fio->setRelPos();
                        }
                    }

                    // On default include image // FIXME-4 check, if we change
                    // default settings...
                    select(dst);
                    setIncludeImagesHor(false);
                    setIncludeImagesVer(true);

                    reposition();
                    return ii;
                }
                else {
                    qWarning() << "vymmodel: Failed to load " + s;
                    deleteItem(ii);
                }
            }
        }
    }
    return nullptr;
}

void VymModel::saveImage(ImageItem *ii, QString fn)
{
    if (!ii)
        ii = getSelectedImage();
    if (ii) {
        QString filter = QString(
            tr("Images") +
            " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif *.pnm *.svg);;" +
            tr("All", "Filedialog") + " (*.*)");
        if (fn.isEmpty())
            fn = QFileDialog::getSaveFileName(
                NULL, vymName + " - " + tr("Save image"), lastImageDir.path(),
                filter, NULL, QFileDialog::DontConfirmOverwrite);

        if (!fn.isEmpty()) {
            lastImageDir.setPath(fn.left(fn.lastIndexOf("/")));
            if (QFile(fn).exists()) {
                QMessageBox mb(vymName,
                               tr("The file %1 exists already.\n"
                                  "Do you want to overwrite it?")
                                   .arg(fn),
                               QMessageBox::Warning,
                               QMessageBox::Yes | QMessageBox::Default,
                               QMessageBox::Cancel | QMessageBox::Escape,
                               QMessageBox::NoButton);

                mb.setButtonText(QMessageBox::Yes, tr("Overwrite"));
                mb.setButtonText(QMessageBox::No, tr("Cancel"));
                switch (mb.exec()) {
                case QMessageBox::Yes:
                    // save
                    break;
                case QMessageBox::Cancel:
                    // do nothing
                    return;
                    break;
                }
            }
            if (!ii->saveImage(fn))
                QMessageBox::critical(0, tr("Critical Error"),
                                      tr("Couldn't save %1").arg(fn));
        }
    }
}

void VymModel::importDirInt(BranchItem *dst, QDir d)
{
    bool oldSaveState = saveStateBlocked;
    saveStateBlocked = true;
    BranchItem *bi = dst;
    if (bi) {
        int beginDepth = bi->depth();

        d.setFilter(QDir::AllEntries | QDir::Hidden);
        QFileInfoList list = d.entryInfoList();
        QFileInfo fi;

        // Traverse directories
        for (int i = 0; i < list.size(); ++i) {
            fi = list.at(i);
            if (fi.isDir() && fi.fileName() != "." && fi.fileName() != "..") {
                bi = addNewBranchInt(dst, -2);
                bi->setHeadingPlainText(fi.fileName());
                bi->setHeadingColor(QColor("blue"));
                if (debug)
                    qDebug() << "Added subdir: " << fi.fileName();
                if (!d.cd(fi.fileName()))
                    QMessageBox::critical(
                        0, tr("Critical Import Error"),
                        tr("Cannot find the directory %1").arg(fi.fileName()));
                else {
                    // Recursively add subdirs
                    importDirInt(bi, d);
                    d.cdUp();
                }
                emitDataChanged(bi);
            }
        }

        for (int i = 0; i < list.size(); ++i) {
            fi = list.at(i);
            if (fi.isFile()) {
                bi = addNewBranchInt(dst, -2);
                bi->setHeadingPlainText(fi.fileName());
                bi->setHeadingColor(QColor("black"));
                if (fi.fileName().right(4) == ".vym")
                    bi->setVymLink(fi.filePath());
                emitDataChanged(bi);
            }
        }

        // Scroll at least some stuff
        if (dst->branchCount() > 1 && dst->depth() - beginDepth > 2)
            dst->toggleScroll();
    }
    saveStateBlocked = oldSaveState;
}

void VymModel::importDir(const QString &s)
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        saveStateChangingPart(
            selbi, selbi, QString("importDir (\"%1\")").arg(s),
            QString("Import directory structure from %1").arg(s));

        QDir d(s);
        importDirInt(selbi, d);
    }
}

void VymModel::importDir()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        QStringList filters;
        filters << "VYM map (*.vym)";
        QFileDialog fd;
        fd.setWindowTitle(vymName + " - " +
                          tr("Choose directory structure to import"));
        fd.setFileMode(QFileDialog::DirectoryOnly);
        fd.setNameFilters(filters);
        fd.setWindowTitle(vymName + " - " +
                          tr("Choose directory structure to import"));
        fd.setAcceptMode(QFileDialog::AcceptOpen);

        QString fn;
        if (fd.exec() == QDialog::Accepted && !fd.selectedFiles().isEmpty()) {
            importDir(fd.selectedFiles().first());
            reposition();
        }
    }
}

bool VymModel::removeVymLock()
{
    if (vymLock.removeLockForced()) {
        mainWindow->statusMessage(tr("Removed lockfile for %1").arg(mapName));
        setReadOnly(false);
        return true;
    } else
        return false;
}

bool VymModel::tryVymLock()
{
    // Defaults for author and host in vymLock
    QString defAuthor =
        settings
            .value("/user/name",
                   tr("unknown user", "Default for lockfiles of maps"))
            .toString();
    QString defHost = QHostInfo::localHostName();
    vymLock.setMapPath(filePath);
    vymLock.setAuthor(settings.value("/user/name", defAuthor).toString());
    if (getenv("HOST") != 0)
        vymLock.setHost(getenv("HOST"));
    else
        vymLock.setHost(defHost);

    // Now try to lock
    if (!vymLock.tryLock()) {
        if (debug)
            qDebug() << "VymModel::tryLock failed!";
        setReadOnly(true);
        if (vymLock.getState() == VymLock::LockedByOther) {
            if (restoreMode) {
                // While restoring maps, existing lockfiles will be ignored for
                // loading, but listed in a warning dialog
                ignoredLockedFiles << filePath;
                return removeVymLock();
            }
            else {
                LockedFileDialog dia;
                QString a = vymLock.getAuthor();
                QString h = vymLock.getHost();
                QString s =
                    QString(
                        tr("Map seems to be already opened in another vym "
                           "instance!\n\n "
                           "Map is locked by \"%1\" on \"%2\"\n\n"
                           "Please only delete the lockfile, if you are sure "
                           "nobody else is currently working on this map."))
                        .arg(a)
                        .arg(h);
                dia.setText(s);
                dia.setWindowTitle(
                    tr("Warning: Map already opended", "VymModel"));
                if (dia.execDialog() == LockedFileDialog::DeleteLockfile) {
                    if (!removeVymLock()) {
                        // Could not remove existing lockfile, give up
                        QMessageBox::warning(
                            0, tr("Warning"),
                            tr("Couldn't remove lockfile for %1").arg(mapName));
                        return false;
                    }
                    if (!tryVymLock()) {
                        // Was able to remove existing lockfile, but not able to 
                        // create new one.
                        qWarning() << "VymModel::tryVymLock could not create new lockfile after removing old";
                        return false;
                    }
                }
            }
        }
        else if (vymLock.getState() == VymLock::NotWritable) {
            WarningDialog dia;
            QString s = QString(tr("Cannot create lockfile of map! "
                                   "It will be opened in readonly mode.\n\n"));
            dia.setText(s);
            dia.setWindowTitle(tr("Warning", "VymModel"));
            dia.showCancelButton(false);
            // dia.setShowAgainName("/mainwindow/mapIsLocked");
            dia.exec();
        }
        return false;
    }
    return true;
}

bool VymModel::renameMap(const QString &newPath)
{
    QString oldPath = filePath;
    if (vymLock.getState() == VymLock::LockedByMyself || vymLock.getState() == VymLock::Undefined) {
        // vymModel owns the lockfile, try to create new lock
        VymLock newLock;
        newLock = vymLock;
        newLock.setMapPath(newPath);    // Resets state for newLock to "Undefined"
        if (!newLock.tryLock()) {
            qWarning() << QString("VymModel::renameMap  could not create lockfile for %1").arg(newPath);
            return false;
        }

        // Change lockfiles now
        if (!vymLock.releaseLock())
            qWarning() << "VymModel::renameMap failed to release lock for " << oldPath;
        vymLock = newLock;
        setFilePath(newPath);
        return true;
    }
    qWarning() << "VymModel::renameMap failed to get lockfile. state=" << vymLock.getState();
    return false;
}

void VymModel::setReadOnly(bool b)
{
    readonly = b;
    mainWindow->updateTabName(this);
}

bool VymModel::isReadOnly() { return readonly; }

void VymModel::autosave()
{
    // Check if autosave is disabled due to testmode
    if (testmode)
    {
        qWarning()
            << QString("VymModel::autosave disabled in testmode!  Current map: %1")
                   .arg(filePath);
        return;
    }

    // Check if autosave is disabled globally
    if (!mainWindow->useAutosave()) {
        qWarning()
            << QString("VymModel::autosave disabled globally!  Current map: %1")
                   .arg(filePath);
        return;
    }

    QDateTime now = QDateTime().currentDateTime();

    // Disable autosave, while we have gone back in history
    int redosAvail = undoSet.numValue(QString("/history/redosAvail"));
    if (redosAvail > 0)
        return;

    // Also disable autosave for new map without filename
    if (filePath.isEmpty()) {
        if (debug)
            qWarning()
                << "VymModel::autosave rejected due to missing filePath\n";
        return;
    }

    if (mapUnsaved && mapChanged && mainWindow->useAutosave() && !testmode) {
        if (QFileInfo(filePath).lastModified() <= fileChangedTime)
            mainWindow->fileSave(this);
        else if (debug)
            qDebug() << "  ME::autosave  rejected, file on disk is newer than "
                        "last save.\n";
    }
}

void VymModel::fileChanged()
{
    // Check if file on disk has changed meanwhile
    if (!filePath.isEmpty()) {
        if (readonly) {
            // unset readonly if lockfile is gone
            if (vymLock.tryLock())
                setReadOnly(false);
        }
        else {
            // We could check, if somebody else removed/replaced lockfile
            // (A unique vym ID would be needed)

            QDateTime tmod = QFileInfo(filePath).lastModified();
            if (tmod > fileChangedTime) {
                // FIXME-4 VM switch to current mapeditor and finish
                // lineedits...
                QMessageBox mb(
                    vymName,
                    tr("The file of the map  on disk has changed:\n\n"
                       "   %1\n\nDo you want to reload that map with the new "
                       "file?")
                        .arg(filePath),
                    QMessageBox::Question, QMessageBox::Yes,
                    QMessageBox::Cancel | QMessageBox::Default,
                    QMessageBox::NoButton);

                mb.setButtonText(QMessageBox::Yes, tr("Reload"));
                mb.setButtonText(QMessageBox::No, tr("Ignore"));
                switch (mb.exec()) {
                case QMessageBox::Yes:
                    // Reload map
                    mainWindow->initProgressCounter(1);
                    loadMap(filePath);
                    mainWindow->removeProgressCounter();
                    break;
                case QMessageBox::Cancel:
                    fileChangedTime =
                        tmod; // allow autosave to overwrite newer file!
                }
            }
        }
    }
}

void VymModel::blockReposition()
{
    repositionBlocked = true;
}

void VymModel::unblockReposition()
{
    repositionBlocked = false;
    reposition();
}

bool VymModel::isDefault() { return mapDefault; }

void VymModel::makeDefault()
{
    mapChanged = false;
    mapDefault = true;
}

bool VymModel::hasChanged() { return mapChanged; }

void VymModel::setChanged()
{
    if (!mapChanged)
        autosaveTimer->start(
            settings.value("/system/autosave/ms/", 30000).toInt());
    mapChanged = true;
    mapDefault = false;
    mapUnsaved = true;
    updateActions();
}

QString VymModel::getObjectName(LinkableMapObj *lmo)
{
    if (!lmo || !lmo->getTreeItem())
        return QString();
    return getObjectName(lmo->getTreeItem());
}

QString VymModel::getObjectName(TreeItem *ti)
{
    QString s;
    if (!ti)
        return QString("Error: NULL has no name!");
    s = ti->getHeadingPlain();
    if (s == "")
        s = "unnamed";

    return QString("%1 (%2)").arg(ti->getTypeName()).arg(s);
}

void VymModel::redo()
{
    // Can we undo at all?
    if (redosAvail < 1)
        return;

    bool saveStateBlockedOrg = saveStateBlocked;
    saveStateBlocked = true;

    redosAvail--;

    if (undosAvail < stepsTotal)
        undosAvail++;
    curStep++;
    if (curStep > stepsTotal)
        curStep = 1;
    QString undoCommand =
        undoSet.value(QString("/history/step-%1/undoCommand").arg(curStep));
    QString undoSelection =
        undoSet.value(QString("/history/step-%1/undoSelection").arg(curStep));
    QString redoCommand =
        undoSet.value(QString("/history/step-%1/redoCommand").arg(curStep));
    QString redoSelection =
        undoSet.value(QString("/history/step-%1/redoSelection").arg(curStep));
    QString comment =
        undoSet.value(QString("/history/step-%1/comment").arg(curStep));
    QString version = undoSet.value("/history/version");

    /* TODO Maybe check for version, if we save the history
    if (!checkVersion(version))
    QMessageBox::warning(0,tr("Warning"),
        tr("Version %1 of saved undo/redo data\ndoes not match current vym
    version %2.").arg(version).arg(vymVersion));
    */

    // Find out current undo directory
    QString bakMapDir(QString(tmpMapDirPath + "/undo-%1").arg(curStep));

    if (debug) {
        qDebug() << "VymModel::redo() begin\n";
        qDebug() << "    undosAvail=" << undosAvail;
        qDebug() << "    redosAvail=" << redosAvail;
        qDebug() << "       curStep=" << curStep;
        qDebug() << "    ---------------------------";
        qDebug() << "    comment=" << comment;
        qDebug() << "    undoSel=" << undoSelection;
        qDebug() << "    redoSel=" << redoSelection;
        qDebug() << "    undoCom:";
        cout << qPrintable(undoCommand);
        qDebug() << "    redoCom=";
        cout << qPrintable(redoCommand);
        qDebug() << "    ---------------------------";
    }

    // select  object before redo
    if (!redoSelection.isEmpty())
        select(redoSelection);

    QString errMsg;
    QString redoScript =
        QString("model = vym.currentMap(); model.%1").arg(redoCommand);
    errMsg = QVariant(execute(redoScript)).toString();
    saveStateBlocked = saveStateBlockedOrg;

    undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
    undoSet.setValue("/history/redosAvail", QString::number(redosAvail));
    undoSet.setValue("/history/curStep", QString::number(curStep));
    undoSet.writeSettings(histPath);

    mainWindow->updateHistory(undoSet);
    updateActions();

    /* TODO remove testing
    qDebug() << "ME::redo() end\n";
    qDebug() << "    undosAvail="<<undosAvail;
    qDebug() << "    redosAvail="<<redosAvail;
    qDebug() << "       curStep="<<curStep;
    qDebug() << "    ---------------------------";
    */
}

bool VymModel::isRedoAvailable()
{
    if (undoSet.numValue("/history/redosAvail", 0) > 0)
        return true;
    else
        return false;
}

QString VymModel::lastRedoSelection()
{
    if (isUndoAvailable())
        return undoSet.value(
            QString("/history/step-%1/redoSelection").arg(curStep));
    else
        return QString();
}

QString VymModel::lastRedoCommand()
{
    if (isUndoAvailable())
        return undoSet.value(
            QString("/history/step-%1/redoCommand").arg(curStep));
    else
        return QString();
}

QVariant VymModel::repeatLastCommand()
{
    QString command = "m = vym.currentMap();";
    if (isUndoAvailable())
        command += "m." +
                   undoSet.value(
                       QString("/history/step-%1/redoCommand").arg(curStep)) +
                   ";";
    else
        return false;
    return execute(command);
}

void VymModel::undo()
{
    // Can we undo at all?
    if (undosAvail < 1)
        return;

    mainWindow->statusMessage(tr("Autosave disabled during undo."));

    bool saveStateBlockedOrg = saveStateBlocked;
    saveStateBlocked = true;

    QString undoCommand =
        undoSet.value(QString("/history/step-%1/undoCommand").arg(curStep));
    QString undoSelection =
        undoSet.value(QString("/history/step-%1/undoSelection").arg(curStep));
    QString redoCommand =
        undoSet.value(QString("/history/step-%1/redoCommand").arg(curStep));
    QString redoSelection =
        undoSet.value(QString("/history/step-%1/redoSelection").arg(curStep));
    QString comment =
        undoSet.value(QString("/history/step-%1/comment").arg(curStep));
    QString version = undoSet.value("/history/version");

    /* TODO Maybe check for version, if we save the history
    if (!checkVersion(version))
    QMessageBox::warning(0,tr("Warning"),
        tr("Version %1 of saved undo/redo data\ndoes not match current vym
    version %2.").arg(version).arg(vymVersion));
    */

    // Find out current undo directory
    QString bakMapDir(QString(tmpMapDirPath + "/undo-%1").arg(curStep));

    // select  object before undo
    if (!undoSelection.isEmpty() && !select(undoSelection)) {
        qWarning("VymModel::undo()  Could not select object for undo");
        return;
    }

    if (debug) {
        qDebug() << "VymModel::undo() begin\n";
        qDebug() << "    undosAvail=" << undosAvail;
        qDebug() << "    redosAvail=" << redosAvail;
        qDebug() << "       curStep=" << curStep;
        cout << "    ---------------------------" << endl;
        qDebug() << "    comment=" << comment;
        qDebug() << "    undoSel=" << undoSelection;
        qDebug() << "    redoSel=" << redoSelection;
        cout << "    undoCom:" << endl;
        cout << qPrintable(undoCommand) << endl;
        cout << "    redoCom:" << endl;
        cout << qPrintable(redoCommand) << endl;
        cout << "    ---------------------------" << endl;
    }

    // select  object before undo   // FIXME-2 double select, see above
    if (!undoSelection.isEmpty())
        select(undoSelection);

    // bool noErr;
    QString errMsg;
    QString undoScript =
        QString("model = vym.currentMap(); model.%1").arg(undoCommand);
    errMsg = QVariant(execute(undoScript)).toString();

    undosAvail--;
    curStep--;
    if (curStep < 1)
        curStep = stepsTotal;

    redosAvail++;

    saveStateBlocked = saveStateBlockedOrg;
    /* testing only
        qDebug() << "VymModel::undo() end\n";
        qDebug() << "    undosAvail="<<undosAvail;
        qDebug() << "    redosAvail="<<redosAvail;
        qDebug() << "       curStep="<<curStep;
        qDebug() << "    ---------------------------";
    */

    undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
    undoSet.setValue("/history/redosAvail", QString::number(redosAvail));
    undoSet.setValue("/history/curStep", QString::number(curStep));
    undoSet.writeSettings(histPath);

    mainWindow->updateHistory(undoSet);
    updateActions();
}

bool VymModel::isUndoAvailable()
{
    if (undoSet.numValue("/history/undosAvail", 0) > 0)
        return true;
    return false;
}

void VymModel::gotoHistoryStep(int i)
{
    // Restore variables
    int undosAvail = undoSet.numValue(QString("/history/undosAvail"));
    int redosAvail = undoSet.numValue(QString("/history/redosAvail"));

    if (i < 0)
        i = undosAvail + redosAvail;

    // Clicking above current step makes us undo things
    if (i < undosAvail) {
        for (int j = 0; j < undosAvail - i; j++)
            undo();
        return;
    }
    // Clicking below current step makes us redo things
    if (i > undosAvail)
        for (int j = undosAvail; j < i; j++) {
            if (debug)
                qDebug() << "VymModel::gotoHistoryStep redo " << j << "/"
                         << undosAvail << " i=" << i;
            redo();
        }

    // And ignore clicking the current row ;-)
}

QString VymModel::getHistoryPath()
{
    QString histName(QString("history-%1").arg(curStep));
    return (tmpMapDirPath + "/" + histName);
}

void VymModel::resetHistory()
{
    curStep = 0;
    redosAvail = 0;
    undosAvail = 0;

    stepsTotal = settings.value("/history/stepsTotal", 100).toInt();
    undoSet.setValue("/history/stepsTotal", QString::number(stepsTotal));
    mainWindow->updateHistory(undoSet);
}

void VymModel::saveState(const SaveMode &savemode, const QString &undoSelection,
                         const QString &undoCom, const QString &redoSelection,
                         const QString &redoCom, const QString &comment,
                         TreeItem *saveSel, QString dataXML)
{
    sendData(redoCom); // FIXME-4 testing

    // Main saveState

    if (saveStateBlocked)
        return;

    if (debug)
        qDebug() << "VM::saveState() for map " << mapName;

    QString undoCommand = undoCom;
    QString redoCommand = redoCom;


    // Increase undo steps, but check for repeated actions
    // like editing a vymNote - then do not increase but replace last command
    /*
    QRegExp re ("parseVymText.*\\(.*vymnote");
    if (curStep > 0 && redoSelection == lastRedoSelection() &&
        lastRedoCommand().contains(re)) {
        undoCommand = undoSet.value(
            QString("/history/step-%1/undoCommand").arg(curStep), undoCommand);
    }
    else {
    */
        if (undosAvail < stepsTotal)
            undosAvail++;

        curStep++;
        if (curStep > stepsTotal)
            curStep = 1;
    //}

    QString histDir = getHistoryPath();
    QString bakMapPath = histDir + "/map.xml";

    // Create histDir if not available
    QDir d(histDir);
    if (!d.exists())
        makeSubDirs(histDir);

    // Save depending on how much needs to be saved
    QList<Link *> tmpLinks;
    if (saveSel)
        dataXML = saveToDir(histDir, mapName + "-", FlagRowMaster::NoFlags, QPointF(),
                            saveSel);

    if (savemode == PartOfMap) {
        undoCommand.replace("PATH", bakMapPath);
        redoCommand.replace("PATH", bakMapPath);
    }

    if (!dataXML.isEmpty())
        // Write XML Data to disk
        saveStringToDisk(bakMapPath, dataXML);

    // We would have to save all actions in a tree, to keep track of
    // possible redos after a action. Possible, but we are too lazy: forget
    // about redos.
    redosAvail = 0;

    // Write the current state to disk
    undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
    undoSet.setValue("/history/redosAvail", QString::number(redosAvail));
    undoSet.setValue("/history/curStep", QString::number(curStep));
    undoSet.setValue(QString("/history/step-%1/undoCommand").arg(curStep),
                     undoCommand);
    undoSet.setValue(QString("/history/step-%1/undoSelection").arg(curStep),
                     undoSelection);
    undoSet.setValue(QString("/history/step-%1/redoCommand").arg(curStep),
                     redoCommand);
    undoSet.setValue(QString("/history/step-%1/redoSelection").arg(curStep),
                     redoSelection);
    undoSet.setValue(QString("/history/step-%1/comment").arg(curStep), comment);
    undoSet.setValue(QString("/history/version"), vymVersion);
    undoSet.writeSettings(histPath);

    if (debug) {
        // qDebug() << "          into="<< histPath;
        qDebug() << "    stepsTotal=" << stepsTotal
                 << ", undosAvail=" << undosAvail
                 << ", redosAvail=" << redosAvail << ", curStep=" << curStep;
        cout << "    ---------------------------" << endl;
        qDebug() << "    comment=" << comment;
        qDebug() << "    undoSel=" << undoSelection;
        qDebug() << "    redoSel=" << redoSelection;
        if (saveSel)
            qDebug() << "    saveSel=" << qPrintable(getSelectString(saveSel));
        cout << "    undoCom:" << endl;
        cout << qPrintable(undoCommand) << endl;
        cout << "    redoCom:" << endl;
        cout << qPrintable(redoCommand) << endl;
        cout << "    ---------------------------" << endl;
    }

    mainWindow->updateHistory(undoSet);

    setChanged();
}

void VymModel::saveStateChangingPart(TreeItem *undoSel, TreeItem *redoSel,
                                     const QString &rc, const QString &comment)
{
    // save the selected part of the map, Undo will replace part of map
    QString undoSelection = "";
    if (undoSel)
        undoSelection = getSelectString(undoSel);
    else
        qWarning("VymModel::saveStateChangingPart  no undoSel given!");
    QString redoSelection = "";
    if (redoSel)
        redoSelection = getSelectString(undoSel);
    else
        qWarning("VymModel::saveStateChangingPart  no redoSel given!");

    saveState(PartOfMap, undoSelection, "addMapReplace (\"PATH\")",
              redoSelection, rc, comment, undoSel);
}

void VymModel::saveStateRemovingPart(TreeItem *redoSel, const QString &comment)
{
    if (!redoSel) {
        qWarning("VymModel::saveStateRemovingPart  no redoSel given!");
        return;
    }
    QString undoSelection;
    QString redoSelection = getSelectString(redoSel);
    if (redoSel->isBranchLikeType()) {
        // save the selected branch of the map, Undo will insert part of map
        if (redoSel->depth() > 0)
            undoSelection = getSelectString(redoSel->parent());
        saveState(PartOfMap, undoSelection,
                  QString("addMapInsert (\"PATH\",%1,%2)")
                      .arg(redoSel->num())
                      .arg(SlideContent),
                  redoSelection, "remove ()", comment, redoSel);
    }
}

void VymModel::saveState(TreeItem *undoSel, const QString &uc,
                         TreeItem *redoSel, const QString &rc,
                         const QString &comment)
{
    // "Normal" savestate: save commands, selections and comment
    // so just save commands for undo and redo
    // and use current selection, if empty parameter passed

    QString redoSelection = "";
    if (redoSel)
        redoSelection = getSelectString(redoSel);
    QString undoSelection = "";
    if (undoSel)
        undoSelection = getSelectString(undoSel);

    saveState(UndoCommand, undoSelection, uc, redoSelection, rc, comment, NULL);
}

void VymModel::saveState(const QString &undoSel, const QString &uc,
                         const QString &redoSel, const QString &rc,
                         const QString &comment)
{
    // "Normal" savestate: save commands, selections and comment
    // so just save commands for undo and redo
    // and use current selection
    saveState(UndoCommand, undoSel, uc, redoSel, rc, comment, NULL);
}

void VymModel::saveState(const QString &uc, const QString &rc,
                         const QString &comment)
{
    // "Normal" savestate applied to model (no selection needed):
    // save commands  and comment
    saveState(UndoCommand, NULL, uc, NULL, rc, comment, NULL);
}

void VymModel::saveStateMinimal(TreeItem *undoSel, const QString &uc,
                                TreeItem *redoSel, const QString &rc,
                                const QString &comment)
{ //  Save a change in string and merge
    //  minor sequential  changes  */
    QString redoSelection = "";
    if (redoSel)
        redoSelection = getSelectString(redoSel);
    QString undoSelection = "";
    if (undoSel)
        undoSelection = getSelectString(undoSel);

    saveState(UndoCommand, undoSelection, uc, redoSelection, rc, comment, NULL);
}

void VymModel::saveStateBeforeLoad(LoadMode lmode, const QString &fname)
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        if (lmode == ImportAdd)
            saveStateChangingPart(selbi, selbi,
                                  QString("addMapInsert (\"%1\")").arg(fname),
                                  QString("Add map %1 to %2")
                                      .arg(fname)
                                      .arg(getObjectName(selbi)));
        if (lmode == ImportReplace) {
            BranchItem *pi = (BranchItem *)(selbi->parent());
            saveStateChangingPart(pi, pi,
                                  QString("addMapReplace(%1)").arg(fname),
                                  QString("Add map %1 to %2")
                                      .arg(fname)
                                      .arg(getObjectName(selbi)));
        }
    }
}

QGraphicsScene *VymModel::getScene() { return mapEditor->getScene(); }

TreeItem *VymModel::findBySelectString(QString s)
{
    if (s.isEmpty())
        return NULL;

    // Old maps don't have multiple mapcenters and don't save full path
    if (s.left(2) != "mc")
        s = "mc:0," + s;

    QStringList parts = s.split(",");
    QString typ;
    int n;
    TreeItem *ti = rootItem;

    while (!parts.isEmpty()) {
        typ = parts.first().left(2);
        n = parts.first().right(parts.first().length() - 3).toInt();
        parts.removeFirst();
        if (typ == "mc" || typ == "bo")
            ti = ti->getBranchNum(n);
        else if (typ == "fi")
            ti = ti->getImageNum(n);
        else if (typ == "ai")
            ti = ti->getAttributeNum(n);
        else if (typ == "xl")
            ti = ti->getXLinkItemNum(n);
        if (!ti)
            return NULL;
    }
    return ti;
}

TreeItem *VymModel::findID(const uint &id)
{
    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    nextBranch(cur, prev);
    while (cur) {
        if (id == cur->getID())
            return cur;
        int j = 0;
        while (j < cur->xlinkCount()) {
            XLinkItem *xli = cur->getXLinkItemNum(j);
            if (id == xli->getID())
                return xli;
            j++;
        }
        j = 0;
        while (j < cur->imageCount()) {
            ImageItem *ii = cur->getImageNum(j);
            if (id == ii->getID())
                return ii;
            j++;
        }
        nextBranch(cur, prev);
    }
    return NULL;
}

TreeItem *VymModel::findUuid(const QUuid &id)
{
    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    nextBranch(cur, prev);
    while (cur) {
        if (id == cur->getUuid())
            return cur;
        int j = 0;
        while (j < cur->xlinkCount()) {
            XLinkItem *xli = cur->getXLinkItemNum(j);
            if (id == xli->getUuid())
                return xli;
            j++;
        }
        j = 0;
        while (j < cur->imageCount()) {
            ImageItem *ii = cur->getImageNum(j);
            if (id == ii->getUuid())
                return ii;
            j++;
        }
        nextBranch(cur, prev);
    }
    return NULL;
}

//////////////////////////////////////////////
// Interface
//////////////////////////////////////////////
void VymModel::setVersion(const QString &s) { version = s; }

QString VymModel::getVersion() { return version; }

void VymModel::setTitle(const QString &s)
{
    saveState(QString("setMapTitle (\"%1\")").arg(title),
              QString("setMapTitle (\"%1\")").arg(s),
              QString("Set title of map to \"%1\"").arg(s));
    title = s;
}

QString VymModel::getTitle() { return title; }

void VymModel::setAuthor(const QString &s)
{
    saveState(QString("setMapAuthor (\"%1\")").arg(author),
              QString("setMapAuthor (\"%1\")").arg(s),
              QString("Set author of map to \"%1\"").arg(s));
    author = s;
}

QString VymModel::getAuthor() { return author; }

void VymModel::setComment(const QString &s)
{
    saveState(QString("setMapComment (\"%1\")").arg(comment),
              QString("setMapComment (\"%1\")").arg(s),
              QString("Set comment of map"));
    comment = s;
}

QString VymModel::getComment() { return comment; }

QString VymModel::getDate()
{
    return QDate::currentDate().toString("yyyy-MM-dd");
}

int VymModel::branchCount()
{
    int c = 0;
    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    nextBranch(cur, prev);
    while (cur) {
        c++;
        nextBranch(cur, prev);
    }
    return c;
}

int VymModel::centerCount() { return rootItem->branchCount(); }

void VymModel::setSortFilter(const QString &s)
{
    sortFilter = s;
    emit(sortFilterChanged(sortFilter));
}

QString VymModel::getSortFilter() { return sortFilter; }

void VymModel::setHeading(const VymText &vt, BranchItem *bi)
{
    Heading h_old;
    Heading h_new;
    h_new = vt;
    QString s = vt.getTextASCII();
    if (!bi)
        bi = getSelectedBranch();
    if (bi) {
        h_old = bi->getHeading();
        if (h_old == h_new)
            return;
        saveState(bi, "parseVymText (\"" + quoteQuotes(h_old.saveToDir()) + "\")", bi,
                  "parseVymText (\"" + quoteQuotes(h_new.saveToDir()) + "\")",
                  QString("Set heading of %1 to \"%2\"")
                      .arg(getObjectName(bi))
                      .arg(s));
        bi->setHeading(vt);
        emitDataChanged(bi);
        emitUpdateQueries();
        mainWindow->updateHeadingEditor(bi);    // Update HeadingEditor with new heading
        reposition();
    }
}

void VymModel::setHeadingPlainText(const QString &s, BranchItem *bi)
{
    if (!bi)
        bi = getSelectedBranch();
    if (bi) {
        VymText vt = bi->getHeading();
        vt.setPlainText(s);
        if (bi->getHeading() == vt)
            return;
        setHeading(vt, bi);

        // Set URL
        if ((s.startsWith("http://") || s.startsWith("https://")) &&
            bi->getURL().isEmpty())
            setURL(s);
    }
}

Heading VymModel::getHeading()
{
    TreeItem *selti = getSelectedItem();
    if (selti)
        return selti->getHeading();
    qWarning() << "VymModel::getHeading Nothing selected.";
    return Heading();
}

void VymModel::updateNoteText(const VymText &vt)
{
    bool editorStateChanged = false;

    TreeItem *selti = getSelectedItem();
    if (selti) {
        VymNote note_old = selti->getNote();
        VymNote note_new(vt);
        if (note_new.getText() != note_old.getText()) {
            if ((note_new.isEmpty() && !note_old.isEmpty()) ||
                (!note_new.isEmpty() && note_old.isEmpty()))
                editorStateChanged = true;

            VymNote vn;
            vn.copy(vt);

            saveState(selti, "parseVymText (\"" + quoteQuotes(note_old.saveToDir()) + "\")",
                      selti, "parseVymText (\"" + quoteQuotes(note_new.saveToDir()) + "\")",
                      QString("Set note of %1 to \"%2\"")
                          .arg(getObjectName(selti))
                          .arg(note_new.getTextASCII().left(20)));

            selti->setNote(vn);
        }

        // Update also flags after changes in NoteEditor
        emitDataChanged(selti);

        // Only update flag, if state has changed
        if (editorStateChanged)
            reposition();
    }
}

void VymModel::setNote(const VymNote &vn)
{
    TreeItem *selti = getSelectedItem();
    if (selti) {
        VymNote n_old;
        VymNote n_new;
        n_old = selti->getNote();
        n_new = vn;
        saveState(selti, "parseVymText (\"" + quoteQuotes(n_old.saveToDir()) + "\")", selti,
                  "parseVymText (\"" + quoteQuotes(n_new.saveToDir()) + "\")",
                  QString("Set note of %1 to \"%2\"")
                      .arg(getObjectName(selti))
                      .arg(n_new.getTextASCII().left(40)));
        selti->setNote(n_new);
        emitNoteChanged(selti);
        emitDataChanged(selti);
    }
}

VymNote VymModel::getNote()
{
    TreeItem *selti = getSelectedItem();
    if (selti) {
        VymNote n = selti->getNote();
        return n;
    }
    qWarning() << "VymModel::getNote Nothing selected.";
    return VymNote();
}

bool VymModel::hasRichTextNote()
{
    TreeItem *selti = getSelectedItem();
    if (selti) {
        return selti->getNote().isRichText();
    }
    return false;
}

void VymModel::loadNote(const QString &fn)
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        QString n;
        if (!loadStringFromDisk(fn, n))
            qWarning() << "VymModel::loadNote Couldn't load " << fn;
        else {
            VymNote vn;
            vn.setAutoText(n);
            setNote(vn);
            emitDataChanged(selbi);
            emitUpdateQueries();
            reposition();
        }
    }
    else
        qWarning("VymModel::loadNote no branch selected");
}

void VymModel::saveNote(const QString &fn)
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        VymNote n = selbi->getNote();
        if (n.isEmpty())
            qWarning() << "VymModel::saveNote  note is empty, won't save to "
                       << fn;
        else {
            if (!saveStringToDisk(fn, n.saveToDir()))
                qWarning() << "VymModel::saveNote Couldn't save " << fn;
        }
    }
    else
        qWarning("VymModel::saveNote no branch selected");
}

void VymModel::findDuplicateURLs() // FIXME-3 needs GUI
{
    // Generate map containing _all_ URLs and branches
    QString u;
    QMultiMap<QString, BranchItem *> map;
    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    nextBranch(cur, prev);
    while (cur) {
        u = cur->getURL();
        if (!u.isEmpty())
            map.insert(u, cur);
        nextBranch(cur, prev);
    }

    // Extract duplicate URLs
    QMultiMap<QString, BranchItem *>::const_iterator i = map.constBegin();
    QMultiMap<QString, BranchItem *>::const_iterator firstdup =
        map.constEnd(); // invalid
    while (i != map.constEnd()) {
        if (i != map.constBegin() && i.key() == firstdup.key()) {
            if (i - 1 == firstdup) {
                qDebug() << firstdup.key();
                qDebug() << " - " << firstdup.value() << " - "
                         << firstdup.value()->getHeading().getText();
            }
            qDebug() << " - " << i.value() << " - "
                     << i.value()->getHeading().getText();
        }
        else
            firstdup = i;

        ++i;
    }
}

bool VymModel::findAll(FindResultModel *rmodel, QString s,
                       Qt::CaseSensitivity cs, bool searchNotes)
{
    rmodel->clear();
    rmodel->setSearchString(s);
    rmodel->setSearchFlags(QTextDocument::FindFlags()); // FIXME-4 translate cs to
                               // QTextDocument::FindFlag
    bool hit = false;

    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    nextBranch(cur, prev);

    FindResultItem *lastParent = NULL;
    while (cur) {
        lastParent = NULL;
        if (cur->getHeading().getTextASCII().contains(s, cs)) {
            lastParent = rmodel->addItem(cur);
            hit = true;
        }

        if (searchNotes) {
            QString n = cur->getNoteASCII();
            int i = 0;
            int j = 0;
            while (i >= 0) {
                i = n.indexOf(s, i, cs);
                if (i >= 0) {
                    // If not there yet, add "parent" item
                    if (!lastParent) {
                        lastParent = rmodel->addItem(cur);
                        hit = true;
                        if (!lastParent)
                            qWarning()
                                << "VymModel::findAll still no lastParent?!";
                        /*
                        else
                            lastParent->setSelectable (false);
                        */
                    }

                    // save index of occurence
                    QString e = n.mid(i - 15, 30);
                    n.replace('\n', ' ');
                    rmodel->addSubItem(
                        lastParent,
                        QString(tr("Note", "FindAll in VymModel") +
                                ": \"...%1...\"")
                            .arg(n.mid(i - 8, 80)),
                        cur, j);
                    j++;
                    i++;
                }
            }
        }
        nextBranch(cur, prev);
    }
    return hit;
}

void VymModel::setURL(QString url, bool updateFromCloud, BranchItem *bi)
{
    if (!bi) bi = getSelectedBranch();
    if (bi->getURL() == url)
        return;

    if (bi) {
        QString oldurl = bi->getURL();
        bi->setURL(url);
        saveState(
            bi, QString("setURL (\"%1\")").arg(oldurl), bi,
            QString("setURL (\"%1\")").arg(url),
            QString("set URL of %1 to %2").arg(getObjectName(bi)).arg(url));
        emitDataChanged(bi);
        reposition();

        if (updateFromCloud)    // FIXME-2 use oembed.com also for Youtube and other cloud providers
            // Check for Confluence
            setHeadingConfluencePageName();
    }
}

QString VymModel::getURL()
{
    TreeItem *selti = getSelectedItem();
    if (selti)
        return selti->getURL();
    else
        return QString();
}

QStringList VymModel::getURLs(bool ignoreScrolled)
{
    QStringList urls;
    BranchItem *selbi = getSelectedBranch();
    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    nextBranch(cur, prev, true, selbi);
    while (cur) {
        if (!cur->getURL().isEmpty() &&
            !(ignoreScrolled && cur->hasScrolledParent()))
            urls.append(cur->getURL());
        nextBranch(cur, prev, true, selbi);
    }
    return urls;
}

void VymModel::setFrameType(const FrameObj::FrameType &t)
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        BranchObj *bo = (BranchObj *)(bi->getLMO());
        if (bo) {
            QString s = bo->getFrameTypeName();
            bo->setFrameType(t);
            saveState(
                bi, QString("setFrameType (\"%1\")").arg(s), bi,
                QString("setFrameType (\"%1\")").arg(bo->getFrameTypeName()),
                QString("set type of frame to %1").arg(s));
            reposition();
            bo->updateLinkGeometry();
        }
    }
}

void VymModel::setFrameType(const QString &s)
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        BranchObj *bo = (BranchObj *)(bi->getLMO());
        if (bo) {
            saveState(
                bi,
                QString("setFrameType (\"%1\")").arg(bo->getFrameTypeName()),
                bi, QString("setFrameType (\"%1\")").arg(s),
                QString("set type of frame to %1").arg(s));
            bo->setFrameType(s);
            reposition();
            bo->updateLinkGeometry();
        }
    }
}

void VymModel::toggleFrameIncludeChildren()
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        bool b = bi->getFrameIncludeChildren();
        setFrameIncludeChildren(!b);
    }
}

void VymModel::setFrameIncludeChildren(bool b)
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        QString u = b ? "false" : "true";
        QString r = !b ? "false" : "true";

        saveState(bi, QString("setFrameIncludeChildren(%1)").arg(u), bi,
                  QString("setFrameIncludeChildren(%1)").arg(r),
                  QString("Include children in %1").arg(getObjectName(bi)));
        bi->setFrameIncludeChildren(b);
        emitDataChanged(bi);
        reposition();
    }
}

void VymModel::setFramePenColor(
    const QColor &c) // FIXME-4 not saved if there is no LMO

{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        BranchObj *bo = (BranchObj *)(bi->getLMO());
        if (bo) {
            saveState(bi,
                      QString("setFramePenColor (\"%1\")")
                          .arg(bo->getFramePenColor().name()),
                      bi, QString("setFramePenColor (\"%1\")").arg(c.name()),
                      QString("set pen color of frame to %1").arg(c.name()));
            bo->setFramePenColor(c);
        }
    }
}

void VymModel::setFrameBrushColor(
    const QColor &c) // FIXME-4 not saved if there is no LMO
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        BranchObj *bo = (BranchObj *)(bi->getLMO());
        if (bo) {
            saveState(bi,
                      QString("setFrameBrushColor (\"%1\")")
                          .arg(bo->getFrameBrushColor().name()),
                      bi, QString("setFrameBrushColor (\"%1\")").arg(c.name()),
                      QString("set brush color of frame to %1").arg(c.name()));
            bo->setFrameBrushColor(c);
            bi->setBackgroundColor(c); // FIXME-4 redundant with above
        }
        emitDataChanged(bi);  // Notify HeadingEditor to eventually change BG color
    }
}

void VymModel::setFramePadding(
    const int &i)
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        BranchObj *bo = (BranchObj *)(bi->getLMO());
        if (bo) {
            saveState(
                bi,
                QString("setFramePadding (\"%1\")").arg(bo->getFramePadding()),
                bi, QString("setFramePadding (\"%1\")").arg(i),
                QString("set brush color of frame to %1").arg(i));
            bo->setFramePadding(i);
            reposition();
            bo->updateLinkGeometry();
        }
    }
}

void VymModel::setFrameBorderWidth(
    const int &i)
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        BranchObj *bo = (BranchObj *)(bi->getLMO());
        if (bo) {
            saveState(bi,
                      QString("setFrameBorderWidth (\"%1\")")
                          .arg(bo->getFrameBorderWidth()),
                      bi, QString("setFrameBorderWidth (\"%1\")").arg(i),
                      QString("set border width of frame to %1").arg(i));
            bo->setFrameBorderWidth(i);
            reposition();
            bo->updateLinkGeometry();
        }
    }
}

void VymModel::setIncludeImagesVer(bool b)
{
    BranchItem *bi = getSelectedBranch();
    if (bi && b != bi->getIncludeImagesVer()) {
        QString u = b ? "false" : "true";
        QString r = !b ? "false" : "true";

        saveState(
            bi, QString("setIncludeImagesVertically (%1)").arg(u), bi,
            QString("setIncludeImagesVertically (%1)").arg(r),
            QString("Include images vertically in %1").arg(getObjectName(bi)));
        bi->setIncludeImagesVer(b);
        emitDataChanged(bi);
        reposition();
    }
}

void VymModel::setIncludeImagesHor(bool b)
{
    BranchItem *bi = getSelectedBranch();
    if (bi && b != bi->getIncludeImagesHor()) {
        QString u = b ? "false" : "true";
        QString r = !b ? "false" : "true";

        saveState(bi, QString("setIncludeImagesHorizontally (%1)").arg(u), bi,
                  QString("setIncludeImagesHorizontally (%1)").arg(r),
                  QString("Include images horizontally in %1")
                      .arg(getObjectName(bi)));
        bi->setIncludeImagesHor(b);
        emitDataChanged(bi);
        reposition();
    }
}

void VymModel::setChildrenLayout(
    BranchItem::LayoutHint layoutHint) // FIXME-3 no savestate yet
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        /*
        QString u= b ? "false" : "true";
        QString r=!b ? "false" : "true";

        saveState(
            bi,
            QString("setIncludeImagesHorizontally (%1)").arg(u),
            bi,
            QString("setIncludeImagesHorizontally (%1)").arg(r),
            QString("Include images horizontally in %1").arg(getObjectName(bi))
        );
        */
        bi->setChildrenLayout(layoutHint);
        emitDataChanged(bi);
        reposition();
    }
}

void VymModel::setHideLinkUnselected(bool b)
{
    TreeItem *ti = getSelectedItem();
    if (ti && (ti->getType() == TreeItem::Image || ti->isBranchLikeType())) {
        QString u = b ? "false" : "true";
        QString r = !b ? "false" : "true";

        saveState(
            ti, QString("setHideLinkUnselected (%1)").arg(u), ti,
            QString("setHideLinkUnselected (%1)").arg(r),
            QString("Hide link of %1 if unselected").arg(getObjectName(ti)));
        ((MapItem *)ti)->setHideLinkUnselected(b);
    }
}

void VymModel::setHideExport(bool b, TreeItem *ti)
{
    if (!ti)
        ti = getSelectedItem();
    if (ti && (ti->getType() == TreeItem::Image || ti->isBranchLikeType()) &&
        ti->hideInExport() != b) {
        ti->setHideInExport(b);
        QString u = b ? "false" : "true";
        QString r = !b ? "false" : "true";

        saveState(ti, QString("setHideExport (%1)").arg(u), ti,
                  QString("setHideExport (%1)").arg(r),
                  QString("Set HideExport flag of %1 to %2")
                      .arg(getObjectName(ti))
                      .arg(r));
        emitDataChanged(ti);
        emitSelectionChanged();
        reposition();
    }
}

void VymModel::toggleHideExport()
{
    QList<TreeItem *> selItems = getSelectedItems();
    if (selItems.count() > 0) {
        foreach (TreeItem *ti, selItems) {
            bool b = !ti->hideInExport();
            setHideExport(b, ti);
        }
    }
}

void VymModel::toggleTask()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        saveStateChangingPart(
            selbi, selbi, QString("toggleTask()"),
            QString("Toggle task of %1").arg(getObjectName(selbi)));
        Task *task = selbi->getTask();
        if (!task) {
            task = taskModel->createTask(selbi);
            taskEditor->select(task);
        }
        else
            taskModel->deleteTask(task);

        emitDataChanged(selbi);
        emitSelectionChanged();
        reposition();
    }
}

bool VymModel::cycleTaskStatus(bool reverse)
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        Task *task = selbi->getTask();
        if (task) {
            saveStateChangingPart(
                selbi, selbi, QString("cycleTask()"),
                QString("Toggle task of %1").arg(getObjectName(selbi)));
            task->cycleStatus(reverse);
            task->setDateModification();

            // make sure task is still visible
            taskEditor->select(task);
            emitDataChanged(selbi);
            reposition();
            return true;
        }
    }
    return false;
}

bool VymModel::setTaskSleep(const QString &s)
{
    bool ok = false;
    BranchItem *selbi = getSelectedBranch();
    if (selbi && !s.isEmpty()) {
        Task *task = selbi->getTask();
        if (task) {
            QDateTime oldSleep = task->getSleep();

            // Parse the string, which could be days, hours or one of several
            // time formats

            if (s == "0") {
                ok = task->setSecsSleep(0);
            }
            else {
                QRegExp re("^\\s*(\\d+)\\s*$");
                re.setMinimal(false);
                int pos = re.indexIn(s);
                if (pos >= 0) {
                    // Found only digit, considered as days
                    ok = task->setDaysSleep(re.cap(1).toInt());
                }
                else {
                    QRegExp re("^\\s*(\\d+)\\s*h\\s*$");
                    pos = re.indexIn(s);
                    if (pos >= 0) {
                        // Found digit followed by "h", considered as hours
                        ok = task->setHoursSleep(re.cap(1).toInt());
                    }
                    else {
                        QRegExp re("^\\s*(\\d+)\\s*w\\s*$");
                        pos = re.indexIn(s);
                        if (pos >= 0) {
                            // Found digit followed by "w", considered as weeks
                            ok = task->setDaysSleep(7 * re.cap(1).toInt());
                        }
                        else {
                            QRegExp re("^\\s*(\\d+)\\s*s\\s*$");
                            pos = re.indexIn(s);
                            if (pos >= 0) {
                                // Found digit followed by "s", considered as
                                // seconds
                                ok = task->setSecsSleep(re.cap(1).toInt());
                            }
                            else {
                                ok = task->setDateSleep(
                                    s); // ISO date YYYY-MM-DDTHH:mm:ss

                                if (!ok) {
                                    QRegExp re("(\\d+)\\.(\\d+)\\.(\\d+)");
                                    re.setMinimal(false);
                                    int pos = re.indexIn(s);
                                    QStringList list = re.capturedTexts();
                                    QDateTime d;
                                    if (pos >= 0) {
                                        d = QDateTime(
                                            QDate(list.at(3).toInt(),
                                                  list.at(2).toInt(),
                                                  list.at(1).toInt()).startOfDay());
                                        // d = QDate(list.at(3).toInt(),
                                        // list.at(2).toInt(),
                                        // list.at(1).toInt()).startOfDay();
                                        ok = task->setDateSleep(
                                            d); // German format,
                                                // e.g. 24.12.2012
                                    }
                                    else {
                                        re.setPattern("(\\d+)\\.(\\d+)\\.");
                                        pos = re.indexIn(s);
                                        list = re.capturedTexts();
                                        if (pos >= 0) {
                                            int month = list.at(2).toInt();
                                            int day = list.at(1).toInt();
                                            int year =
                                                QDate::currentDate().year();
                                            d = QDateTime(
                                                QDate(year, month, day).startOfDay());
                                            // d = QDate(year, month,
                                            // day).startOfDay();
                                            if (QDateTime::currentDateTime()
                                                    .daysTo(d) < 0) {
                                                year++;
                                                d = QDateTime(
                                                    QDate(year, month, day).startOfDay());
                                                // d = QDate(year, month,
                                                // day).startOfDay();
                                            }
                                            ok = task->setDateSleep(
                                                d); // Short German format,
                                                    // e.g. 24.12.
                                        }
                                        else {
                                            re.setPattern("(\\d+)\\:(\\d+)");
                                            pos = re.indexIn(s);
                                            list = re.capturedTexts();
                                            if (pos >= 0) {
                                                int hour = list.at(1).toInt();
                                                int min = list.at(2).toInt();
                                                d = QDateTime(
                                                    QDate::currentDate(),
                                                    QTime(hour, min));
                                                ok = task->setDateSleep(
                                                    d); // Time HH:MM
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (ok) {
                QString oldSleepString;
                if (oldSleep.isValid())
                    oldSleepString = oldSleep.toString(Qt::ISODate);
                else
                    oldSleepString =
                        "1970-01-26T00:00:00"; // Some date long ago

                QString newSleepString = task->getSleep().toString(Qt::ISODate);
                task->setDateModification();
                selbi->updateTaskFlag(); // If tasks changes awake mode, then
                                         // flag needs to change
                saveState(
                    selbi, QString("setTaskSleep (\"%1\")").arg(oldSleepString),
                    selbi, QString("setTaskSleep (\"%1\")").arg(newSleepString),
                    QString("setTaskSleep (\"%1\")").arg(newSleepString));
                emitDataChanged(selbi);
                reposition();
            }

        } // Found task
    }     // Found branch
    return ok;
}

void VymModel::setTaskPriorityDelta(const int &pd, BranchItem *bi)
{
    QList<BranchItem *> selbis;
    if (bi)
        selbis << bi;
    else
        selbis = getSelectedBranches();

    foreach (BranchItem *selbi, selbis) {
        Task *task = selbi->getTask();
        if (task) {
            saveState(selbi,
                      QString("setTaskPriorityDelta (%1)")
                          .arg(task->getPriorityDelta()),
                      selbi,
                      QString("setTaskPriorityDelta (%1)")
                          .arg(pd),
                      "Set delta for priority of task");
            task->setPriorityDelta(pd);
            emitDataChanged(selbi);
        }
    }
}

int VymModel::getTaskPriorityDelta()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        Task *task = selbi->getTask();
        if (task)
            return task->getPriorityDelta();
    }
    return 0;
}

int VymModel::taskCount() { return taskModel->count(this); }

void VymModel::updateTasksAlarm(bool force)
{
    if (taskModel->updateAwake(force) || force) {
        reposition();
    }
}

BranchItem *VymModel::addTimestamp()
{
    BranchItem *selbi = addNewBranch();
    if (selbi) {
        QDate today = QDate::currentDate();
        QChar c = '0';
        selbi->setHeadingPlainText(QString("%1-%2-%3")
                                       .arg(today.year(), 4, 10, c)
                                       .arg(today.month(), 2, 10, c)
                                       .arg(today.day(), 2, 10, c));
        emitDataChanged(selbi);
        reposition();
        select(selbi);
    }
    return selbi;
}

void VymModel::copy()
{
    if (readonly)
        return;

    QList<TreeItem *> itemList = getSelectedItems();

    QStringList clipboardFiles;

    if (itemList.count() > 0) {
        uint i = 1;
        QString fn;
        foreach (TreeItem *ti, itemList) {
            fn = QString("%1/%2-%3.xml")
                     .arg(clipboardDir)
                     .arg(clipboardFile)
                     .arg(i);
            QString content = saveToDir(clipboardDir, clipboardFile,
                                        FlagRowMaster::NoFlags, QPointF(), ti);

            if (!saveStringToDisk(fn, content))
                qWarning() << "ME::saveStringToDisk failed: " << fn;
            else {
                i++;
                clipboardFiles.append(fn);
            }
        }
        QClipboard *clipboard = QApplication::clipboard();
        QMimeData *mimeData = new QMimeData;
        mimeData->setData("application/x-vym", clipboardFiles.join(",").toLatin1());
        clipboard->setMimeData(mimeData);
    }
}

void VymModel::paste()
{
    if (readonly)
        return;

    BranchItem *selbi = getSelectedBranch();

    if (selbi) {
        const QClipboard *clipboard = QApplication::clipboard();
        const QMimeData *mimeData = clipboard->mimeData();

        if (mimeData->formats().contains("application/x-vym")) {
            QStringList clipboardFiles = QString(mimeData->data("application/x-vym")).split(",");

            saveStateChangingPart(selbi, selbi, QString("paste ()"),
                                  QString("Paste"));

            bool zippedOrg = zipped;
            foreach(QString fn, clipboardFiles) {
                if (File::Success != loadMap(fn, ImportAdd, VymMap, SlideContent))
                    qWarning() << "VM::paste Loading clipboard failed: " << fn;
            }
            zipped = zippedOrg;
            reposition();
        } else if (mimeData->hasImage()) {
            QImage image = qvariant_cast<QImage>(mimeData->imageData());
            QString fn = clipboardDir + "/" + "image.png";
            if (!image.save(fn))
                qWarning() << "VM::paste  Could not save copy of image in system clipboard";
            else {
                ImageItem *ii = loadImage(selbi, fn);
                if (ii)
                    setScaleFactor(300.0 / image.width(), ii);    // FIXME-2 Better use user-defined fixed width
            }
        } else if (mimeData->hasHtml()) {
            //setText(mimeData->html());
            //setTextFormat(Qt::RichText);
            qDebug() << "VM::paste found html...";
        } else if (mimeData->hasText()) {
            //setText(mimeData->text());
            //setTextFormat(Qt::PlainText);
            qDebug() << "VM::paste found text...";
        } else {
            qWarning() << "VM::paste Cannot paste data, mimeData->formats=" << mimeData->formats();
        }
    }
}

void VymModel::cut()
{
    if (readonly)
        return;

    copy();
    deleteSelection();
}

bool VymModel::moveUp(BranchItem *bi)
{
    if (readonly)
        return false;

    bool oldState = saveStateBlocked;
    saveStateBlocked = true;
    bool result = false;
    if (bi && bi->canMoveUp())
        result =
            relinkBranch(bi, (BranchItem *)bi->parent(), bi->num() - 1, false);
    saveStateBlocked = oldState;
    return result;
}

void VymModel::moveUp()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        QString oldsel = getSelectString(selbi);
        if (moveUp(selbi)) {
            saveState(getSelectString(selbi), "moveDown ()", oldsel,
                      "moveUp ()",
                      QString("Move up %1").arg(getObjectName(selbi)));
            select(selbi);
        }
    }
}

bool VymModel::moveDown(BranchItem *bi)
{
    if (readonly)
        return false;

    bool oldState = saveStateBlocked;
    saveStateBlocked = true;
    bool result = false;
    if (bi && bi->canMoveDown())
        result =
            relinkBranch(bi, (BranchItem *)bi->parent(), bi->num() + 1, false);
    saveStateBlocked = oldState;
    return result;
}

void VymModel::moveDown()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        QString oldsel = getSelectString(selbi);
        if (moveDown(selbi)) {
            saveState(getSelectString(selbi), "moveUp ()", oldsel,
                      "moveDown ()",
                      QString("Move down %1").arg(getObjectName(selbi)));
            select(selbi);
        }
    }
}

void VymModel::moveUpDiagonally()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        BranchItem *parent = selbi->parentBranch();
        if (parent == rootItem) return;

        int n = selbi->num();
        if (n == 0) return;

        BranchItem *dst = parent->getBranchNum(n-1);
        if (!dst) return;

        relinkBranch(selbi, dst, dst->branchCount() + 1, true);
     }
}

void VymModel::moveDownDiagonally()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        BranchItem *parent = selbi->parentBranch();
        if (parent == rootItem) return;
        BranchItem *parentParent = parent->parentBranch();
        int n = parent->num();

        relinkBranch(selbi, parentParent, n + 1, true);
     }
}

void VymModel::detach()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi && selbi->depth() > 0) {
        // if no relPos have been set before, try to use current rel positions
        if (selbi->getLMO())
            for (int i = 0; i < selbi->branchCount(); ++i)
                selbi->getBranchNum(i)->getBranchObj()->setRelPos();

        QString oldsel = getSelectString();
        int n = selbi->num();
        QPointF p;
        BranchObj *bo = selbi->getBranchObj();
        if (bo)
            p = bo->getAbsPos();
        QString parsel = getSelectString(selbi->parent());
        if (relinkBranch(selbi, rootItem, -1, true))
            saveState(getSelectString(selbi),
                      QString("relinkTo (\"%1\",%2,%3,%4)")
                          .arg(parsel)
                          .arg(n)
                          .arg(p.x())
                          .arg(p.y()),
                      oldsel, "detach ()",
                      QString("Detach %1").arg(getObjectName(selbi)));
    }
}

void VymModel::sortChildren(bool inverse)
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        if (selbi->branchCount() > 1) {
            if (!inverse)
                saveStateChangingPart(
                    selbi, selbi, "sortChildren ()",
                    QString("Sort children of %1").arg(getObjectName(selbi)));
            else
                saveStateChangingPart(selbi, selbi, "sortChildren (false)",
                                      QString("Inverse sort children of %1")
                                          .arg(getObjectName(selbi)));

            selbi->sortChildren(inverse);
            select(selbi);
            reposition();
        }
    }
}

BranchItem *VymModel::createMapCenter()
{
    BranchItem *newbi = addMapCenter(QPointF(0, 0));
    return newbi;
}

BranchItem *VymModel::createBranch(BranchItem *dst)
{
    if (dst)
        return addNewBranchInt(dst, -2);
    else
        return NULL;
}

ImageItem *VymModel::createImage(BranchItem *dst)
{
    if (dst) {
        QModelIndex parix;
        int n;

        ImageItem *newii = new ImageItem();
        // newii->setHeading (QApplication::translate("Heading of new image in
        // map", "new image"));

        emit(layoutAboutToBeChanged());

        parix = index(dst);
        if (!parix.isValid())
            qDebug() << "VM::createII invalid index\n";
        n = dst->getRowNumAppend(newii);
        beginInsertRows(parix, n, n);
        dst->appendChild(newii);
        endInsertRows();

        emit(layoutChanged());

        // save scroll state. If scrolled, automatically select
        // new branch in order to tmp unscroll parent...
        newii->createMapObj();
        latestAddedItem = newii;
        reposition();
        return newii;
    }
    return NULL;
}

bool VymModel::createLink(Link *link)
{
    BranchItem *begin = link->getBeginBranch();
    BranchItem *end = link->getEndBranch();

    if (!begin || !end) {
        qWarning() << "VM::createXLinkNew part of XLink is NULL";
        return false;
    }

    if (begin == end) {
        if (debug)
            qDebug() << "VymModel::createLink begin==end, aborting";
        return false;
    }

    // check, if link already exists
    foreach (Link *l, xlinks) {
        if ((l->getBeginBranch() == begin && l->getEndBranch() == end) ||
            (l->getBeginBranch() == end && l->getEndBranch() == begin)) {
            qWarning() << "VymModel::createLink link exists already, aborting";
            return false;
        }
    }

    QModelIndex parix;
    int n;

    XLinkItem *newli = new XLinkItem();
    newli->setLink(link);
    link->setBeginLinkItem(newli);

    emit(layoutAboutToBeChanged());

    parix = index(begin);
    n = begin->getRowNumAppend(newli);
    beginInsertRows(parix, n, n);
    begin->appendChild(newli);
    endInsertRows();

    newli = new XLinkItem();
    newli->setLink(link);
    link->setEndLinkItem(newli);

    parix = index(end);
    n = end->getRowNumAppend(newli);
    beginInsertRows(parix, n, n);
    end->appendChild(newli);
    endInsertRows();

    emit(layoutChanged());

    xlinks.append(link);
    link->activate();

    latestAddedItem = newli;

    if (!link->getMO()) {
        link->createMapObj();
        reposition();
    }
    else
        link->updateLink();

    link->setStyleBegin(defXLinkStyleBegin);
    link->setStyleEnd(defXLinkStyleEnd);
    return true;
}

QColor VymModel::getXLinkColor()
{
    Link *l = getSelectedXLink();
    if (l)
        return l->getPen().color();
    else
        return QColor();
}

int VymModel::getXLinkWidth()
{
    Link *l = getSelectedXLink();
    if (l)
        return l->getPen().width();
    else
        return -1;
}

Qt::PenStyle VymModel::getXLinkStyle()
{
    Link *l = getSelectedXLink();
    if (l)
        return l->getPen().style();
    else
        return Qt::NoPen;
}

QString VymModel::getXLinkStyleBegin()
{
    Link *l = getSelectedXLink();
    if (l)
        return l->getStyleBeginString();
    else
        return QString();
}

QString VymModel::getXLinkStyleEnd()
{
    Link *l = getSelectedXLink();
    if (l)
        return l->getStyleEndString();
    else
        return QString();
}

AttributeItem *VymModel::setAttribute() // FIXME-3 Experimental, savestate missing

{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        AttributeItem *ai = new AttributeItem();
        ai->setAttributeType(AttributeItem::String);
        ai->setKey("Foo Attrib");
        ai->setValue(QString("Att val"));

        return setAttribute(selbi, ai);
    }
    return nullptr;
}

AttributeItem *VymModel::setAttribute(BranchItem *dst, AttributeItem *ai_new)
{
    if (dst) {

        // Check if there is already an attribute with same key
        AttributeItem *ai;
        for (int i = 0; i < dst->attributeCount(); i++) {
            ai = dst->getAttributeNum(i);
            if (ai->getKey() == ai_new->getKey()) 
            {
                // Key exists, overwrite value
                ai->copy(ai_new);

                // Delete original attribute, this is basically a move...
                delete ai_new;
                emitDataChanged(dst);
                return ai;
            }
        }

        // Create new attribute
        emit(layoutAboutToBeChanged());

        QModelIndex parix = index(dst);
        int n = dst->getRowNumAppend(ai_new);
        beginInsertRows(parix, n, n);
        dst->appendChild(ai_new);
        endInsertRows();

        emit(layoutChanged());

        emitDataChanged(dst);
        return ai_new;  // FIXME-3 Check if ai is used or deleted - deep copy here?
    }
    return NULL;
}

BranchItem *VymModel::addMapCenter(bool saveStateFlag)
{
    if (!hasContextPos) {
        // E.g. when called via keypresss:
        // Place new MCO in middle of existing ones,
        // Useful for "brainstorming" mode...
        contextPos = QPointF();
        BranchItem *bi;
        BranchObj *bo;
        for (int i = 0; i < rootItem->branchCount(); ++i) {
            bi = rootItem->getBranchNum(i);
            bo = (BranchObj *)bi->getLMO();
            if (bo)
                contextPos += bo->getAbsPos();
        }
        if (rootItem->branchCount() > 1)
            contextPos *= 1 / (qreal)(rootItem->branchCount());
    }

    BranchItem *bi = addMapCenter(contextPos);
    updateActions();
    emitShowSelection();
    if (saveStateFlag)
        saveState(bi, "remove()", NULL,
                  QString("addMapCenter (%1,%2)")
                      .arg(contextPos.x())
                      .arg(contextPos.y()),
                  QString("Adding MapCenter to (%1,%2)")
                      .arg(contextPos.x())
                      .arg(contextPos.y()));
    emitUpdateLayout();
    return bi;
}

BranchItem *VymModel::addMapCenter(QPointF absPos)
// createMapCenter could then probably be merged with createBranch
{

    // Create TreeItem
    QModelIndex parix = index(rootItem);

    BranchItem *newbi = new BranchItem(rootItem);
    newbi->setHeadingPlainText(tr("New map", "New map"));
    int n = rootItem->getRowNumAppend(newbi);

    emit(layoutAboutToBeChanged());
    beginInsertRows(parix, n, n);

    rootItem->appendChild(newbi);

    endInsertRows();
    emit(layoutChanged());

    // Create MapObj
    newbi->setPositionMode(MapItem::Absolute);
    BranchObj *bo = newbi->createMapObj(mapEditor->getScene());
    if (bo)
        bo->move(absPos);

    return newbi;
}

BranchItem *VymModel::addNewBranchInt(BranchItem *dst, int pos)
{
    // Depending on pos:
    // -3	insert in children of parent  above selection
    // -2	add branch to selection
    // -1	insert in children of parent below selection
    // 0..n	insert in children of parent at pos

    // Create TreeItem
    BranchItem *parbi = dst;
    int n;
    BranchItem *newbi = new BranchItem();

    emit(layoutAboutToBeChanged());

    if (pos == -2) {
        n = parbi->getRowNumAppend(newbi);
        beginInsertRows(index(parbi), n, n);
        parbi->appendChild(newbi);
        endInsertRows();
    }
    else if (pos == -1 || pos == -3) {
        // insert below selection
        parbi = (BranchItem *)dst->parent();
        n = dst->childNumber() + (3 + pos) / 2; //-1 |-> 1;-3 |-> 0
        beginInsertRows(index(parbi), n, n);
        parbi->insertBranch(n, newbi);
        endInsertRows();
    }
    else { // pos >= 0
        n = parbi->getRowNumAppend(newbi) - (parbi->branchCount() - pos);
        beginInsertRows(index(parbi), n, n);
        parbi->insertBranch(pos, newbi);
        endInsertRows();
    }
    emit(layoutChanged());

    newbi->createMapObj(mapEditor->getScene());

    // Set color of heading to that of parent
    newbi->setHeadingColor(parbi->getHeadingColor());

    reposition();
    return newbi;
}

BranchItem *VymModel::addNewBranch(BranchItem *bi, int pos)
{
    BranchItem *newbi = NULL;
    if (!bi)
        bi = getSelectedBranch();

    if (bi) {
        QString redosel = getSelectString(bi);
        newbi = addNewBranchInt(bi, pos);
        QString undosel = getSelectString(newbi);

        if (newbi) {
            saveState(undosel, "remove ()", redosel,
                      QString("addBranch (%1)").arg(pos),
                      QString("Add new branch to %1").arg(getObjectName(bi)));

            latestAddedItem = newbi;
            // In Network mode, the client needs to know where the new branch
            // is, so we have to pass on this information via saveState.
            // TODO: Get rid of this positioning workaround
            /* FIXME-4  network problem:  QString ps=qpointfToString
               (newbo->getAbsPos()); sendData ("selectLatestAdded ()"); sendData
               (QString("move %1").arg(ps)); sendSelection();
               */
        }
    }
    return newbi;
}

BranchItem *VymModel::addNewBranchBefore()
{
    BranchItem *newbi = NULL;
    BranchItem *selbi = getSelectedBranch();
    if (selbi && selbi->getType() == TreeItem::Branch)
    // We accept no MapCenter here, so we _have_ a parent
    {
        // add below selection
        newbi = addNewBranchInt(selbi, -1);

        if (newbi) {
            saveState(
                newbi, "remove ()", newbi, "addBranchBefore ()",
                QString("Add branch before %1").arg(getObjectName(selbi)));

            // newbi->move2RelPos (p);

            // Move selection to new branch
            relinkBranch(selbi, newbi, 0, true);

            // Use color of child instead of parent
            newbi->setHeadingColor(selbi->getHeadingColor());
            emitDataChanged(newbi);
        }
    }
    return newbi;
}

bool VymModel::relinkBranch(BranchItem *branch, BranchItem *dst, int pos,
                            bool updateSelection, QPointF orgPos)
{
    if (branch && dst) {
        // Check if we relink to ourselves
        if (dst->isChildOf(branch))
            return false;

        if (updateSelection)
            unselectAll();

        // Do we need to update frame type?
        bool keepFrame = true;

        // Save old position for savestate
        QString preSelStr = getSelectString(branch);
        QString preNum = QString::number(branch->num(), 10);
        QString preParStr = getSelectString(branch->parent());

        emit(layoutAboutToBeChanged());
        BranchItem *branchpi = (BranchItem *)branch->parent();
        // Remove at current position
        int n = branch->childNum();

        // If branch and dst have same parent, then pos needs to be adjusted 
        // after removing branch
        if (branchpi == dst && pos - 1 > n ) pos--;

        beginRemoveRows(index(branchpi), n, n);
        branchpi->removeChild(n);
        endRemoveRows();

        if (pos < 0 || pos > dst->branchCount())
            pos = dst->branchCount();

        // Append as last branch to dst
        if (dst->branchCount() == 0)
            n = 0;
        else
            n = dst->getFirstBranch()->childNumber();
        
        beginInsertRows(index(dst), n + pos, n + pos);
        dst->insertBranch(pos, branch);
        endInsertRows();

        // Correct type if necessesary
        if (branch->getType() == TreeItem::MapCenter && branch->depth() > 0) {
            branch->setType(TreeItem::Branch);
            keepFrame = false;
        }

        // reset parObj, fonts, frame, etc in related LMO or other view-objects
        branch->updateStyles(keepFrame);

        emitDataChanged(branch);
        reposition(); // both for moveUp/Down and relinking

        // Savestate
        QString postSelStr = getSelectString(branch);
        QString postNum = QString::number(branch->num(), 10);

        QPointF savePos;
        LinkableMapObj *lmosel = branch->getLMO();
        if (lmosel)
            savePos = lmosel->getAbsPos();

        if (!saveStateBlocked) { // Don't build strings when moving up/down
            QString undoCom =
                "relinkTo (\"" + preParStr + "\"," + preNum + "," +
                QString("%1,%2").arg(orgPos.x()).arg(orgPos.y()) + ")";

            QString redoCom =
                "relinkTo (\"" + getSelectString(dst) + "\"," + postNum + "," +
                QString("%1,%2").arg(savePos.x()).arg(savePos.y()) + ")";

            saveState(postSelStr, undoCom, preSelStr, redoCom,
                      QString("Relink %1 to %2")
                          .arg(getObjectName(branch))
                          .arg(getObjectName(dst)));
        }

        // New parent might be invisible
        branch->updateVisibility();

        if (dst->isScrolled()) {
            if (updateSelection)
                select(dst);
        }
        else if (updateSelection)
            select(branch);
        return true;
    }
    return false;
}

bool VymModel::relinkImage(ImageItem *image, BranchItem *dst)
{
    if (image && dst) {
        emit(layoutAboutToBeChanged());

        BranchItem *pi = (BranchItem *)(image->parent());
        QString oldParString = getSelectString(pi);
        // Remove at current position
        int n = image->childNum();
        beginRemoveRows(index(pi), n, n);
        pi->removeChild(n);
        endRemoveRows();

        // Add at dst
        QModelIndex dstix = index(dst);
        n = dst->getRowNumAppend(image);
        beginInsertRows(dstix, n, n);
        dst->appendChild(image);
        endInsertRows();

        // Set new parent also for lmo
        if (image->getLMO() && dst->getLMO())
            image->getLMO()->setParObj(dst->getLMO());

        emit(layoutChanged());
        saveState(image, QString("relinkTo (\"%1\")").arg(oldParString), image,
                  QString("relinkTo (\"%1\")").arg(getSelectString(dst)),
                  QString("Relink floatimage to %1").arg(getObjectName(dst)));
        return true;
    }
    return false;
}

bool VymModel::relinkTo(const QString &dest, int num, QPointF pos)
{
    TreeItem *selti = getSelectedItem();
    if (!selti)
        return false; // Nothing selected to relink

    TreeItem *dst = findBySelectString(dest);

    if (selti->isBranchLikeType()) {
        BranchItem *selbi = (BranchItem *)selti;
        if (!dst)
            return false; // Could not find destination

        if (dst->getType() == TreeItem::Branch) {
            // Now try to relink to branch
            if (relinkBranch(selbi, (BranchItem *)dst, num, true)) {
                emitSelectionChanged();
                return true;
            }
            else
                return false; // Relinking failed
        }
        else if (dst->getType() == TreeItem::MapCenter) {
            if (relinkBranch(selbi, (BranchItem *)dst, -1, true)) {
                // Get coordinates of mainbranch
                if (selbi->getLMO()) {
                    ((BranchObj *)selbi->getLMO())->move(pos);
                    ((BranchObj *)selbi->getLMO())->setRelPos();
                }
                reposition();
                emitSelectionChanged();
                return true;
            }
        }
        return false; // Relinking failed
    }
    else if (selti->getType() == TreeItem::Image) {
        if (dst->isBranchLikeType())
            if (relinkImage(((ImageItem *)selti), (BranchItem *)dst))
                return true;
    }
    return false; // Relinking failed
}

void VymModel::cleanupItems()
{
    while (!deleteLaterIDs.isEmpty()) {
        TreeItem *ti = findID(deleteLaterIDs.takeFirst());
        if (ti)
            deleteItem(ti);
    }
}

void VymModel::deleteLater(uint id)
{
    if (!deleteLaterIDs.contains(id))
        deleteLaterIDs.append(id);
}

void VymModel::deleteSelection()
{
    QList<uint> selectedIDs = getSelectedIDs();
    unselectAll();
    QString fn;

    foreach (uint id, selectedIDs) {
        TreeItem *ti = findID(id);
        if (ti) {
            if (ti->isBranchLikeType()) { // Delete branch
                BranchItem *selbi = (BranchItem *)ti;
                saveStateRemovingPart(
                    selbi, QString("remove %1").arg(getObjectName(selbi)));

                BranchItem *pi = (BranchItem *)(deleteItem(selbi));
                if (pi) {
                    if (pi->isScrolled() && pi->branchCount() == 0)
                        pi->unScroll();
                    emitDataChanged(pi);
                    select(pi);
                }
                else
                    emitDataChanged(rootItem);
                ti = NULL;
            }
            else {
                // Delete other item
                TreeItem *pi = ti->parent();
                if (pi) {
                    if (ti->getType() == TreeItem::Image ||
                        ti->getType() == TreeItem::Attribute ||
                        ti->getType() == TreeItem::XLink) {
                        saveStateChangingPart(
                            pi, ti, "remove ()",
                            QString("Remove %1").arg(getObjectName(ti)));

                        deleteItem(ti);
                        emitDataChanged(pi);
                        select(pi);
                        reposition();
                    }
                    else
                        qWarning(
                            "VymmModel::deleteSelection()  unknown type?!");
                }
            }
        }
    }
}

void VymModel::deleteKeepChildren(bool saveStateFlag)
// deleteKeepChildren FIXME-3+ does not work yet for mapcenters
// deleteKeepChildren FIXME-3+ children of scrolled branch stay invisible...
{
    BranchItem *selbi = getSelectedBranch();
    BranchItem *pi;
    if (selbi) {
        // Don't use this on mapcenter
        if (selbi->depth() < 1)
            return;

        pi = (BranchItem *)(selbi->parent());
        // Check if we have children at all to keep
        if (selbi->branchCount() == 0) {
            deleteSelection();
            return;
        }

        QPointF p;
        if (selbi->getLMO())
            p = selbi->getLMO()->getRelPos();
        if (saveStateFlag)
            saveStateChangingPart(pi, pi, "removeKeepChildren ()",
                                  QString("Remove %1 and keep its children")
                                      .arg(getObjectName(selbi)));

        QString sel = getSelectString(selbi);
        unselectAll();
        bool oldSaveState = saveStateBlocked;
        saveStateBlocked = true;
        int pos = selbi->num();
        BranchItem *bi = selbi->getFirstBranch();
        while (bi) {
            relinkBranch(bi, pi, pos, true);
            bi = selbi->getFirstBranch();
            pos++;
        }
        deleteItem(selbi);
        reposition();
        emitDataChanged(pi);
        select(sel);
        BranchObj *bo = getSelectedBranchObj();
        if (bo) {
            bo->move2RelPos(p);
            reposition();
        }
        saveStateBlocked = oldSaveState;
    }
}

void VymModel::deleteChildren()

{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        saveStateChangingPart(
            selbi, selbi, "removeChildren ()",
            QString("Remove children of branch %1").arg(getObjectName(selbi)));
        emit(layoutAboutToBeChanged());

        QModelIndex ix = index(selbi);
        int n = selbi->branchCount() - 1;
        beginRemoveRows(ix, 0, n);
        removeRows(0, n + 1, ix);
        endRemoveRows();
        if (selbi->isScrolled())
            unscrollBranch(selbi);
        emit(layoutChanged());
        reposition();
    }
}

TreeItem *VymModel::deleteItem(TreeItem *ti)
{
    if (ti) {
        TreeItem *pi = ti->parent();
        // qDebug()<<"VM::deleteItem  start ti="<<ti<<"  "<<ti->getHeading()<<"
        // pi="<<pi<<"="<<pi->getHeading();

        TreeItem::Type t = ti->getType();

        QModelIndex parentIndex = index(pi);

        emit(layoutAboutToBeChanged());

        int n = ti->childNum();
        beginRemoveRows(parentIndex, n, n);
        removeRows(n, 1, parentIndex);
        endRemoveRows();

        // Size of parent branch might change when deleting images
        if (t == TreeItem::Image) {
            BranchObj *bo = (BranchObj *)(((BranchItem *)pi)->getMO());
            if (bo)
                bo->calcBBoxSize();
        }

        reposition();

        emit(layoutChanged());
        emitUpdateQueries();
        if (!cleaningUpLinks)
            cleanupItems();

        // qDebug()<<"VM::deleteItem  end   ti="<<ti;
        if (pi->depth() >= 0)
            return pi;
    }
    return NULL;
}

void VymModel::deleteLink(Link *l)
{
    if (xlinks.removeOne(l))
        delete (l);
}

void VymModel::clearItem(TreeItem *ti)
{
    if (ti) {
        // Clear task (or other data in item itself)
        ti->clear();

        QModelIndex parentIndex = index(ti);
        if (!parentIndex.isValid())
            return;

        int n = ti->childCount();
        if (n == 0)
            return;

        emit(layoutAboutToBeChanged());

        beginRemoveRows(parentIndex, 0, n - 1);
        removeRows(0, n, parentIndex);
        endRemoveRows();

        reposition();

        emit(layoutChanged());
    }
    return;
}

bool VymModel::scrollBranch(BranchItem *bi)
{
    if (bi) {
        if (bi->isScrolled())
            return false;
        if (bi->branchCount() == 0)
            return false;
        if (bi->depth() == 0)
            return false;
        if (bi->toggleScroll()) {
            QString u, r;
            r = "scroll";
            u = "unscroll";
            saveState(bi, QString("%1 ()").arg(u), bi, QString("%1 ()").arg(r),
                      QString("%1 %2").arg(r).arg(getObjectName(bi)));
            emitDataChanged(bi);
            emitSelectionChanged();
            reposition();
            mapEditor->getScene()
                ->update(); // Needed for _quick_ update,  even in 1.13.x
            return true;
        }
    }
    return false;
}

bool VymModel::unscrollBranch(BranchItem *bi)
{
    if (bi) {
        if (!bi->isScrolled())
            return false;
        if (bi->toggleScroll()) {
            QString u, r;
            u = "scroll";
            r = "unscroll";
            saveState(bi, QString("%1 ()").arg(u), bi, QString("%1 ()").arg(r),
                      QString("%1 %2").arg(r).arg(getObjectName(bi)));
            emitDataChanged(bi);
            emitSelectionChanged();
            reposition();
            mapEditor->getScene()
                ->update(); // Needed for _quick_ update,  even in 1.13.x
            return true;
        }
    }
    return false;
}

void VymModel::toggleScroll()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        if (selbi->isScrolled())
            unscrollBranch(selbi);
        else
            scrollBranch(selbi);
        // Note: saveState & reposition are called in above functions
    }
}

void VymModel::unscrollChildren()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        saveStateChangingPart(
            selbi, selbi, QString("unscrollChildren ()"),
            QString("unscroll all children of %1").arg(getObjectName(selbi)));
        BranchItem *prev = NULL;
        BranchItem *cur = NULL;
        nextBranch(cur, prev, true, selbi);
        while (cur) {
            if (cur->isScrolled()) {
                cur->toggleScroll();
                emitDataChanged(cur);
            }
            nextBranch(cur, prev, true, selbi);
        }
        updateActions();
        reposition();
        // Would this help??? emitSelectionChanged();
    }
}

void VymModel::setScaleFactor(qreal f, ImageItem *selii)
{
    if (!selii)
        selii = getSelectedImage();

    if (selii) {
        qreal f_old = selii->getScaleFactor();
        selii->setScaleFactor(f);
        saveState(selii, QString("setScaleFactor(%1)").arg(f_old), selii,
                  QString("setScaleFactor(%1)").arg(f),
                  QString("Scale %1").arg(getObjectName(selii)));
        reposition();
    }
}

void VymModel::growSelectionSize() // FIXME-3 Also for heading in BranchItem?
{
    ImageItem *selii = getSelectedImage();
    if (selii) {
        qreal f = 0.05;
        qreal sx = selii->getScaleFactor();
        setScaleFactor(sx + f);
    }
}

void VymModel::shrinkSelectionSize()
{
    ImageItem *selii = getSelectedImage();
    if (selii) {
        qreal f = 0.05;
        qreal sx = selii->getScaleFactor();
        setScaleFactor(sx - f);
    }
}

void VymModel::resetSelectionSize()
{
    ImageItem *selii = getSelectedImage();
    if (selii)
        setScaleFactor(1);
}

void VymModel::emitExpandAll() { emit(expandAll()); }

void VymModel::emitExpandOneLevel() { emit(expandOneLevel()); }

void VymModel::emitCollapseOneLevel() { emit(collapseOneLevel()); }

void VymModel::emitCollapseUnselected() { emit(collapseUnselected()); }

void VymModel::toggleTarget()
{
    foreach (TreeItem *ti, getSelectedItems()) {
        if (ti->isBranchLikeType()) {
            ((BranchItem*)ti)->toggleTarget();
            saveState(ti, "toggleTarget()", ti, "toggleTarget()",
                      "Toggle target");
        }
    }
    reposition();
}

ItemList VymModel::getLinkedMaps()
{
    ItemList targets;

    // rmodel->setSearchString (s);

    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    nextBranch(cur, prev);

    QString s;

    while (cur) {
        if (cur->hasActiveSystemFlag("system-target") &&
            !cur->getVymLink().isEmpty()) {
            s = cur->getHeading().getTextASCII();
            s.replace(QRegularExpression("\n+"), " ");
            s.replace(QRegularExpression("\\s+"), " ");
            s.replace(QRegularExpression("^\\s+"), "");

            QStringList sl;
            sl << s;
            sl << cur->getVymLink();

            targets[cur->getID()] = sl;
        }
        nextBranch(cur, prev);
    }
    return targets;
}

ItemList VymModel::getTargets()
{
    ItemList targets;

    // rmodel->setSearchString (s);

    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    nextBranch(cur, prev);

    QString s;

    while (cur) {
        if (cur->hasActiveSystemFlag("system-target")) {
            s = cur->getHeading().getTextASCII();
            s.replace(QRegularExpression("\n+"), " ");
            s.replace(QRegularExpression("\\s+"), " ");
            s.replace(QRegularExpression("^\\s+"), "");

            QStringList sl;
            sl << s;

            targets[cur->getID()] = sl;
        }
        nextBranch(cur, prev);
    }
    return targets;
}

Flag* VymModel::findFlagByName(const QString &name)
{
    BranchItem *bi = getSelectedBranch();

    if (bi) {
        Flag *f = standardFlagsMaster->findFlagByName(name);
        if (!f) {
            f = userFlagsMaster->findFlagByName(name);
            if (!f) {
                qWarning() << "VymModel::findFlagByName failed for flag named "
                           << name;
                return nullptr;
            }
        }
        return f;
    }

    // Nothing selected, so no flag found
    return nullptr;
}

void VymModel::setFlagByName(const QString &name, bool useGroups)
{
    BranchItem *bi = getSelectedBranch();

    if (bi && !bi->hasActiveFlag(name)) {
        toggleFlagByName(name, useGroups);
    }
}

void VymModel::unsetFlagByName(const QString &name)
{
    BranchItem *bi = getSelectedBranch();

    if (bi && bi->hasActiveFlag(name)) {
        toggleFlagByName(name);
    }
}

void VymModel::toggleFlagByName(const QString &name, bool useGroups)
{
    BranchItem *bi = getSelectedBranch();

    if (bi) {
        Flag *f = findFlagByName(name);

        if (!f) {
            qWarning() << "VymModel::toggleFlagByName could not find flag named " << name;
            return;
        }

        toggleFlagByUid(f->getUuid(), useGroups);
    }
}

void VymModel::toggleFlagByUid(
    const QUuid &uid,
    bool useGroups)
    // FIXME-2  saveState not correct when toggling flags in groups
    // (previous flags not saved!)
{
    QStringList itemList = getSelectedUUIDs();

    if (itemList.count() > 0) {
        QString fn;
        TreeItem *ti;
        BranchItem *bi;
        Flag *f;
        foreach (QString id, itemList) {
            ti = findUuid(QUuid(id));
            if (ti && ti->isBranchLikeType()) {
                    bi = (BranchItem*)ti;
                f = bi->toggleFlagByUid(uid, useGroups);

                if (f) {
                    QString u = "toggleFlagByUid";
                    QString name = f->getName();
                    saveState(bi, QString("%1 (\"%2\")").arg(u).arg(uid.toString()), bi,
                              QString("%1 (\"%2\")").arg(u).arg(uid.toString()),
                              QString("Toggling flag \"%1\" of %2")
                                  .arg(name)
                                  .arg(getObjectName(bi)));
                    emitDataChanged(bi);
                } else
                    qWarning() << "VymModel::toggleFlag failed for flag with uid "
                               << uid;
            }
        }
        reposition();
    }
}


void VymModel::clearFlags()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        selbi->deactivateAllStandardFlags();
        reposition();
        emitDataChanged(selbi);
        setChanged();
    }
}

void VymModel::colorBranch(QColor c)
{
    QList<BranchItem *> selbis = getSelectedBranches();
    foreach (BranchItem *selbi, selbis) {
        saveState(selbi,
                  QString("colorBranch (\"%1\")")
                      .arg(selbi->getHeadingColor().name()),
                  selbi, QString("colorBranch (\"%1\")").arg(c.name()),
                  QString("Set color of %1 to %2")
                      .arg(getObjectName(selbi))
                      .arg(c.name()));
        selbi->setHeadingColor(c); // color branch
        emitDataChanged(selbi);
        taskEditor->showSelection();
    }
    mapEditor->getScene()->update();
}

void VymModel::colorSubtree(QColor c, BranchItem *b)
{
    QList<BranchItem *> selbis;
    if (b)
        selbis.append(b);
    else
        selbis = getSelectedBranches();

    foreach (BranchItem *bi, selbis) {
        saveStateChangingPart(bi, bi,
                              QString("colorSubtree (\"%1\")").arg(c.name()),
                              QString("Set color of %1 and children to %2")
                                  .arg(getObjectName(bi))
                                  .arg(c.name()));
        BranchItem *prev = NULL;
        BranchItem *cur = NULL;
        nextBranch(cur, prev, true, bi);
        while (cur) {
            cur->setHeadingColor(c); // color links, color children
            emitDataChanged(cur);
            nextBranch(cur, prev, true, bi);
        }
    }
    taskEditor->showSelection();
    mapEditor->getScene()->update();
}

QColor VymModel::getCurrentHeadingColor()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi)
        return selbi->getHeadingColor();

    QMessageBox::warning(
        0, "Warning",
        "Can't get color of heading,\nthere's no branch selected");
    return Qt::black;
}

void VymModel::note2URLs()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        saveStateChangingPart(
            selbi, selbi, QString("note2URLs()"),
            QString("Extract URLs from note of %1").arg(getObjectName(selbi)));

        QString n = selbi->getNoteASCII();
        if (n.isEmpty())
            return;
        QRegExp re("(http.*)(\\s|\"|')");
        re.setMinimal(true);

        BranchItem *bi;
        int pos = 0;
        while ((pos = re.indexIn(n, pos)) != -1) {
            bi = createBranch(selbi);
            bi->setHeadingPlainText(re.cap(1));
            bi->setURL(re.cap(1));
            emitDataChanged(bi);
            pos += re.matchedLength();
        }
    }
}

void VymModel::editHeading2URL()
{
    TreeItem *selti = getSelectedItem();
    if (selti)
        setURL(selti->getHeadingPlain());
}

void VymModel::getJiraData(bool subtree) // FIXME-2 update error message, check
                                         // if jiraClientAvail is set correctly
{
    if (!JiraAgent::available()) {
        WarningDialog dia;
        QString w = QObject::tr("JIRA agent not setup.");
        dia.setText(w);
        dia.setWindowTitle( tr("Warning") + ": " + w);
        dia.setShowAgainName("/JiraAgent/notdefined");
        dia.exec();

        if (!mainWindow->settingsJIRA())
            return;
    }

    BranchItem *selbi = getSelectedBranch();
    QRegExp re("(\\w+[-|\\s]\\d+)");

    if (selbi) {
        QString url;
        BranchItem *prev = nullptr;
        BranchItem *cur = nullptr;
        nextBranch(cur, prev, true, selbi);
        while (cur) {
            QString heading = cur->getHeadingPlain();

            if (re.indexIn(heading) >= 0) {
                // Create agent
                JiraAgent *agent = new JiraAgent;
                agent->setJobType(JiraAgent::GetTicketInfo);
                if (!agent->setBranch(cur)) {
                    qWarning () << "Could not set branch in JiraAgent to " << cur->getHeadingPlain();
                    delete agent;
                    return;
                }
                if (!agent->setTicket(heading)) {
                    mainWindow->statusMessage(tr("Could not find Jira ticket pattern in %1", "VymModel").arg(cur->getHeadingPlain()));
                    delete agent;
                    return;
                }

                //setURL(agent->url(), false, cur);

                connect(agent, &JiraAgent::jiraTicketReady, this, &VymModel::updateJiraData);

                // Start contacting JIRA in background
                agent->startJob();
                mainWindow->statusMessage(tr("Contacting Jira...", "VymModel"));
            }


            if (subtree)
                nextBranch(cur, prev, true, selbi);
            else
                cur = nullptr;
        }
    }
}

void VymModel::updateJiraData(QJsonObject jsobj)
{
    QJsonDocument jsdoc = QJsonDocument (jsobj);
    QString key = jsobj["key"].toString();
    QJsonObject fields = jsobj["fields"].toObject();

    QJsonObject assigneeObj = fields["assignee"].toObject();
    QString assignee = assigneeObj["emailAddress"].toString();

    QJsonObject reporterObj = fields["reporter"].toObject();
    QString reporter  = reporterObj["emailAddress"].toString();

    QJsonObject resolutionObj = fields["resolution"].toObject();
    QString resolution  = resolutionObj["name"].toString();

    QJsonObject statusObj = fields["status"].toObject();
    QString status  = statusObj["name"].toString();

    QString summary = fields["summary"].toString();

    QJsonArray componentsArray = fields["components"].toArray();
    QJsonObject compObj;
    QString components;
    for (int i = 0; i < componentsArray.size(); ++i) {
        compObj = componentsArray[i].toObject();
        components += compObj["name"].toString();
    }

    int branchID = jsobj["vymBranchId"].toInt();

    QStringList solvedStates;
    solvedStates << "Verification Done";
    solvedStates << "Resolved";
    solvedStates << "Closed";

    QString keyName = key;
    BranchItem *bi = (BranchItem*)findID(branchID);
    if (bi) {
        if (solvedStates.contains(status))    {
            keyName = "(" + keyName + ")";
            colorSubtree (Qt::blue, bi);
        }

        setHeadingPlainText(keyName + ": " + summary, bi);
        setURL(jsobj["vymTicketUrl"].toString());

        AttributeItem *ai;

        ai = new AttributeItem("JIRA.assignee", assignee);
        setAttribute(bi, ai);

        ai = new AttributeItem("JIRA.reporter", reporter);
        setAttribute(bi, ai);

        ai = new AttributeItem("JIRA.resolution", resolution);
        setAttribute(bi, ai);

        ai = new AttributeItem("JIRA.status", status);
        setAttribute(bi, ai);

        ai = new AttributeItem("JIRA.components", components);
        setAttribute(bi, ai);
    }

    /* Pretty print JIRA ticket
    vout << jsdoc.toJson(QJsonDocument::Indented) << endl;
    vout << "       Key: " + key << endl;
    vout << "      Desc: " + summary << endl;
    vout << "  Assignee: " + assignee << endl;
    vout << "Components: " + components << endl;
    vout << "  Reporter: " + reporter << endl;
    vout << "Resolution: " + resolution << endl;
    vout << "    Status: " + status << endl;
    */

    mainWindow->statusMessage(tr("Received Jira data.", "VymModel"));
}


void VymModel::setHeadingConfluencePageName()   // FIXME-2 always asks for Confluence credentials when adding any URL
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        QString url = selbi->getURL();
        if (!url.isEmpty() && 
                settings.contains("/atlassian/confluence/url") &&
                url.contains(settings.value("/atlassian/confluence/url").toString())) {

            ConfluenceAgent *ca_setHeading = new ConfluenceAgent(selbi);
            ca_setHeading->setPageURL(url);
            ca_setHeading->setJobType(ConfluenceAgent::CopyPagenameToHeading);
            ca_setHeading->startJob();
        }
    }
}

void VymModel::setVymLink(const QString &s)
{
    if (s.isEmpty()) return;

    BranchItem *bi = getSelectedBranch();
    if (bi) {
        saveState(
            bi, "setVymLink (\"" + bi->getVymLink() + "\")", bi,
            "setVymLink (\"" + s + "\")",
            QString("Set vymlink of %1 to %2").arg(getObjectName(bi)).arg(s));
        bi->setVymLink(s);
        emitDataChanged(bi);
        reposition();
    }
}

void VymModel::deleteVymLink()
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        saveState(bi, "setVymLink (\"" + bi->getVymLink() + "\")", bi,
                  "setVymLink (\"\")",
                  QString("Unset vymlink of %1").arg(getObjectName(bi)));
        bi->setVymLink("");
        emitDataChanged(bi);
        reposition();
        updateActions();
    }
}

QString VymModel::getVymLink()
{
    BranchItem *bi = getSelectedBranch();
    if (bi)
        return bi->getVymLink();
    else
        return "";
}

QStringList VymModel::getVymLinks()
{
    QStringList links;
    BranchItem *selbi = getSelectedBranch();
    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    nextBranch(cur, prev, true, selbi);
    while (cur) {
        if (!cur->getVymLink().isEmpty())
            links.append(cur->getVymLink());
        nextBranch(cur, prev, true, selbi);
    }
    return links;
}

void VymModel::followXLink(int i)
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi) {
        selbi = selbi->getXLinkItemNum(i)->getPartnerBranch();
        if (selbi)
            select(selbi);
    }
}

void VymModel::editXLink()
{
    Link *l = getSelectedXLink();
    if (l) {
        EditXLinkDialog dia;
        dia.setLink(l);
        if (dia.exec() == QDialog::Accepted) {
            if (dia.useSettingsGlobal()) {
                setMapDefXLinkPen(l->getPen());
                setMapDefXLinkStyleBegin(l->getStyleBeginString());
                setMapDefXLinkStyleEnd(l->getStyleEndString());
            }
        }
    }
}

void VymModel::setXLinkColor(const QString &new_col)
{
    Link *l = getSelectedXLink();
    if (l) {
        QPen pen = l->getPen();
        QColor new_color = QColor(new_col);
        QColor old_color = pen.color();
        if (new_color == old_color)
            return;
        pen.setColor(new_color);
        l->setPen(pen);
        saveState(l->getBeginLinkItem(),
                  QString("setXLinkColor(\"%1\")").arg(old_color.name()),
                  l->getBeginLinkItem(),
                  QString("setXLinkColor(\"%1\")").arg(new_color.name()),
                  QString("set color of xlink to %1").arg(new_color.name()));
    }
}

void VymModel::setXLinkStyle(const QString &new_style)
{
    Link *l = getSelectedXLink();
    if (l) {
        QPen pen = l->getPen();
        QString old_style = penStyleToString(pen.style());
        if (new_style == old_style)
            return;
        bool ok;
        pen.setStyle(penStyle(new_style, ok));
        l->setPen(pen);
        saveState(l->getBeginLinkItem(),
                  QString("setXLinkStyle(\"%1\")").arg(old_style),
                  l->getBeginLinkItem(),
                  QString("setXLinkStyle(\"%1\")").arg(new_style),
                  QString("set style of xlink to %1").arg(new_style));
    }
}

void VymModel::setXLinkStyleBegin(const QString &new_style)
{
    Link *l = getSelectedXLink();
    if (l) {
        QString old_style = l->getStyleBeginString();
        if (new_style == old_style)
            return;
        l->setStyleBegin(new_style);
        saveState(l->getBeginLinkItem(),
                  QString("setXLinkStyleBegin(\"%1\")").arg(old_style),
                  l->getBeginLinkItem(),
                  QString("setXLinkStyleBegin(\"%1\")").arg(new_style),
                  "set style of xlink begin");
    }
}

void VymModel::setXLinkStyleEnd(const QString &new_style)
{
    Link *l = getSelectedXLink();
    if (l) {
        QString old_style = l->getStyleEndString();
        if (new_style == old_style)
            return;
        l->setStyleEnd(new_style);
        saveState(l->getBeginLinkItem(),
                  QString("setXLinkStyleEnd(\"%1\")").arg(old_style),
                  l->getBeginLinkItem(),
                  QString("setXLinkStyleEnd(\"%1\")").arg(new_style),
                  "set style of xlink end");
    }
}

void VymModel::setXLinkWidth(int new_width)
{
    Link *l = getSelectedXLink();
    if (l) {
        QPen pen = l->getPen();
        int old_width = pen.width();
        if (new_width == old_width)
            return;
        pen.setWidth(new_width);
        l->setPen(pen);
        saveState(
            l->getBeginLinkItem(), QString("setXLinkWidth(%1)").arg(old_width),
            l->getBeginLinkItem(), QString("setXLinkWidth(%1)").arg(new_width),
            "set width of xlink");
    }
}

//////////////////////////////////////////////
// Scripting
//////////////////////////////////////////////

QVariant VymModel::execute(
    const QString &script) // FIXME-3 still required???
                           // Called from these places:
                           //
                           // scripts/vym-ruby.rb  (and adaptormodel) used for
                           // testing Main::callMacro Main::checkReleaseNotes
                           // VymModel::undo
                           // VymModel::redo
                           // VymModel::exportLast
                           // VymModel::updateSlideSelection
{
    // qDebug()<<"VM::execute called: "<<script;
    return mainWindow->runScript(script);
}

void VymModel::setExportMode(bool b)
{
    // should be called before and after exports
    // depending on the settings
    if (b && settings.value("/export/useHideExport", "true") == "true")
        setHideTmpMode(TreeItem::HideExport);
    else
        setHideTmpMode(TreeItem::HideNone);
}

QPointF VymModel::exportImage(QString fname, bool askName, QString format)
{
    QPointF offset; // set later, when getting image from MapEditor

    if (fname == "") {
        if (!askName) {
            qWarning("VymModel::exportImage called without filename (and "
                     "askName==false)");
            return offset;
        }

        fname = lastImageDir.absolutePath() + "/" + getMapName() + ".png";
        format = "PNG";
    }

    ExportBase ex;
    ex.setName("Image");
    ex.setModel(this);
    ex.setFilePath(fname);
    ex.setWindowTitle(tr("Export map as image"));
    ex.addFilter(
        "PNG (*.png);;All (* *.*)"); //  imageIO.getFilters().join(";;")
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (askName) {
        if (!ex.execDialog())
            return offset;
        fname = ex.getFilePath();
        lastImageDir = QDir(fname);
    }

    setExportMode(true);

    mapEditor->minimizeView();

    QImage img(mapEditor->getImage(offset));
    if (!img.save(fname, format.toLocal8Bit())) {
        QMessageBox::critical(
            0, tr("Critical Error"),
            tr("Couldn't save QImage %1 in format %2").arg(fname).arg(format));
        ex.setResult(ExportBase::Failed);
    } else
        ex.setResult(ExportBase::Success);

    setExportMode(false);

    ex.completeExport();

    return offset;
}

void VymModel::exportPDF(QString fname, bool askName)
{
    if (fname == "") {
        if (!askName) {
            qWarning("VymModel::exportPDF called without filename (and "
                     "askName==false)");
            return;
        }

        fname = lastExportDir.absolutePath() + "/" + getMapName() + ".pdf";
    }

    ExportBase ex;
    ex.setName("PDF");
    ex.setModel(this);
    ex.setFilePath(fname);
    ex.setWindowTitle(tr("Export map as PDF"));
    ex.addFilter("PDF (*.pdf);;All (* *.*)");
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (askName) {
        if (!ex.execDialog())
            return;
        fname = ex.getFilePath();
    }

    setExportMode(true);

    // To PDF
    QPrinter pdfPrinter(QPrinter::HighResolution);
    pdfPrinter.setOutputFormat(QPrinter::PdfFormat);
    pdfPrinter.setOutputFileName(fname);
    pdfPrinter.setPageSize(QPageSize(QPageSize::A3));

    QRectF bbox = mapEditor->getTotalBBox();
    if (bbox.width() > bbox.height())
        // recommend landscape
        pdfPrinter.setPageOrientation(QPageLayout::Landscape);
    else
        // recommend portrait
        pdfPrinter.setPageOrientation(QPageLayout::Portrait);

    QPainter *pdfPainter = new QPainter(&pdfPrinter);
    getScene()->render(pdfPainter);
    pdfPainter->end();
    delete pdfPainter;

    setExportMode(false);

    ex.completeExport();
}

QPointF VymModel::exportSVG(QString fname, bool askName)
{
    QPointF offset; // FIXME-3 not needed?

    if (fname == "") {
        if (!askName) {
            qWarning("VymModel::exportSVG called without filename (and "
                     "askName==false)");
            return offset;
        }

        fname = lastImageDir.absolutePath() + "/" + getMapName() + ".svg";
    }

    ExportBase ex;
    ex.setName("SVG");
    ex.setModel(this);
    ex.setFilePath(fname);
    ex.setWindowTitle(tr("Export map as SVG"));
    ex.addFilter("SVG (*.svg);;All (* *.*)");

    if (askName) {
        if (!ex.execDialog())
            return offset;
        fname = ex.getFilePath();
        lastImageDir = QDir(fname);
    }

    setExportMode(true);

    QSvgGenerator generator;
    generator.setFileName(fname);
    QSize sceneSize = getScene()->sceneRect().size().toSize();
    generator.setSize(sceneSize);
    generator.setViewBox(QRect(0, 0, sceneSize.width(), sceneSize.height()));
    QPainter *svgPainter = new QPainter(&generator);
    getScene()->render(svgPainter);
    svgPainter->end();
    delete svgPainter;

    setExportMode(false);
    ex.completeExport();

    return offset;
}

void VymModel::exportXML(QString fpath, QString dpath, bool useDialog)
{
    ExportBase ex;
    ex.setName("XML");
    ex.setModel(this);
    ex.setWindowTitle(tr("Export map as XML"));
    ex.addFilter("XML (*.xml);;All (* *.*)");
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (useDialog) {
        QFileDialog fd;
        fd.setWindowTitle(vymName + " - " + tr("Export XML to directory"));
        QStringList filters;
        filters << "XML data (*.xml)";
        fd.setNameFilters(filters);
        fd.setOption(QFileDialog::DontConfirmOverwrite, true);
        fd.setAcceptMode(QFileDialog::AcceptSave);
        fd.selectFile(mapName + ".xml");

        QString fn;
        if (fd.exec() != QDialog::Accepted || fd.selectedFiles().isEmpty())
            return;

        fpath = fd.selectedFiles().first();
        dpath = fpath.left(fpath.lastIndexOf("/"));

        if (!confirmDirectoryOverwrite(QDir(dpath)))
            return;
    }
    ex.setFilePath(fpath);

    QString mname = basename(fpath);

    // Hide stuff during export, if settings want this
    setExportMode(true);

    // Create subdirectories
    makeSubDirs(dpath);

    // write image and calculate offset (Remember old mapSaved setting while
    // exporting image)
    bool mchanged = mapChanged;
    bool munsaved = mapUnsaved;

    QPointF offset =
        exportImage(dpath + "/images/" + mname + ".png", false, "PNG");

    mapChanged = mchanged;
    mapUnsaved = munsaved;

    // write to directory   //FIXME-3 check totalBBox here...
    QString saveFile =
        saveToDir(dpath, mname + "-", FlagRowMaster::NoFlags, offset, NULL);
    QFile file;

    file.setFileName(fpath);
    if (!file.open(QIODevice::WriteOnly)) {
        // This should neverever happen
        QMessageBox::critical(0, tr("Critical Export Error"),
                              QString("VymModel::exportXML couldn't open %1")
                                  .arg(file.fileName()));
        return;
    }

    // Write it finally, and write in UTF8, no matter what
    QTextStream ts(&file);
    ts.setCodec("UTF-8");
    ts << saveFile;
    file.close();

    setExportMode(false);

    QStringList args;
    args << fpath;
    args << dpath;
    ex.completeExport(args);
}

void VymModel::exportAO(QString fname, bool askName)
{
    ExportAO ex;
    ex.setModel(this);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (fname == "")
        ex.setFilePath(mapName + ".txt");
    else
        ex.setFilePath(fname);

    if (askName) {
        ex.setDirPath(lastExportDir.absolutePath());
        ex.execDialog();
    }
    if (!ex.canceled()) {
        setExportMode(true);
        ex.doExport();
        setExportMode(false);
    }
}

void VymModel::exportASCII(const QString &fname, bool listTasks, bool askName)
{
    ExportASCII ex;
    ex.setModel(this);
    ex.setListTasks(listTasks);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (fname == "")
        ex.setFilePath(mapName + ".txt");
    else
        ex.setFilePath(fname);

    if (askName) {
        ex.setDirPath(lastExportDir.absolutePath());
        ex.execDialog();
    }

    if (!ex.canceled()) {
        setExportMode(true);
        ex.doExport();
        setExportMode(false);
    }
}

void VymModel::exportCSV(const QString &fname, bool askName)
{
    ExportCSV ex;
    ex.setModel(this);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (fname == "")
        ex.setFilePath(mapName + ".csv");
    else
        ex.setFilePath(fname);

    if (askName) {
        ex.addFilter("CSV (*.csv);;All (* *.*)");
        ex.setDirPath(lastExportDir.absolutePath());
        ex.setWindowTitle(vymName + " -" + tr("Export as csv") + " " +
                          tr("(still experimental)"));
        ex.execDialog();
    }

    if (!ex.canceled()) {
        setExportMode(true);
        ex.doExport();
        setExportMode(false);
    }
}

void VymModel::exportFirefoxBookmarks(const QString &fname, bool askName)
{
    ExportFirefox ex;
    ex.setModel(this);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (fname == "")
        ex.setFilePath(mapName + ".csv");
    else
        ex.setFilePath(fname);

    if (askName) {
        ex.addFilter("JSON (*.json);;All (* *.*)");
        ex.setDirPath(lastExportDir.absolutePath());
        ex.setWindowTitle(vymName + " -" + tr("Export as csv") + " " +
                          tr("(still experimental)"));
        ex.execDialog();
    }

    if (!ex.canceled()) {
        setExportMode(true);
        ex.doExport();
        setExportMode(false);
    }
}

void VymModel::exportHTML(const QString &fpath, const QString &dpath,
                          bool useDialog)
{
    ExportHTML ex(this);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (!dpath.isEmpty())
        ex.setDirPath(dpath);
    if (!fpath.isEmpty())
        ex.setFilePath(fpath);

    ex.doExport(useDialog);
}

void VymModel::exportConfluence(bool createPage, const QString &pageURL,
                                const QString &pageName, bool useDialog)
{
    ExportConfluence ex(this);
    ex.setCreateNewPage(createPage);
    ex.setURL(pageURL);
    ex.setPageName(pageName);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    ex.doExport(useDialog);
}

void VymModel::exportImpress(const QString &fn, const QString &cf)
{
    ExportOO ex;
    ex.setFilePath(fn);
    ex.setModel(this);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (ex.setConfigFile(cf)) {
        QString lastCommand =
            settings.localValue(filePath, "/export/last/command", "")
                .toString();

        setExportMode(true);
        ex.exportPresentation();
        setExportMode(false);

        QString command =
            settings.localValue(filePath, "/export/last/command", "")
                .toString();
        if (lastCommand != command)
            setChanged();
    }
}

bool VymModel::exportLastAvailable(QString &description, QString &command,
                                   QString &dest)
{
    command =
        settings.localValue(filePath, "/export/last/command", "").toString();

    description = settings.localValue(filePath, "/export/last/description", "")
                      .toString();
    dest = settings.localValue(filePath, "/export/last/displayedDestination", "")
               .toString();
    if (!command.isEmpty() && command.contains("exportMap"))
        return true;
    else
        return false;
}

void VymModel::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);
    }
}

void VymModel::exportLaTeX(const QString &fname, bool askName)
{
    ExportLaTeX ex;
    ex.setModel(this);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (fname == "")
        ex.setFilePath(mapName + ".tex");
    else
        ex.setFilePath(fname);

    if (askName)
        ex.execDialog();
    if (!ex.canceled()) {
        setExportMode(true);
        ex.doExport();
        setExportMode(false);
    }
}

void VymModel::exportOrgMode(const QString &fname, bool askName)
{
    ExportOrgMode ex;
    ex.setModel(this);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (fname == "")
        ex.setFilePath(mapName + ".org");
    else
        ex.setFilePath(fname);

    if (askName) {
        ex.setDirPath(lastExportDir.absolutePath());
        ex.execDialog();
    }

    if (!ex.canceled()) {
        setExportMode(true);
        ex.doExport();
        setExportMode(false);
    }
}

void VymModel::exportMarkdown(const QString &fname, bool askName)
{
    ExportMarkdown ex;
    ex.setModel(this);
    ex.setLastCommand(
        settings.localValue(filePath, "/export/last/command", "").toString());

    if (fname == "")
        ex.setFilePath(mapName + ".md");
    else
        ex.setFilePath(fname);

    if (askName) {
        ex.setDirPath(lastExportDir.absolutePath());
        ex.execDialog();
    }

    if (!ex.canceled()) {
        setExportMode(true);
        ex.doExport();
        setExportMode(false);
    }
}
//////////////////////////////////////////////
// View related
//////////////////////////////////////////////

void VymModel::registerMapEditor(QWidget *e) { mapEditor = (MapEditor *)e; }

void VymModel::setMapZoomFactor(const double &d)
{
    zoomFactor = d;
    mapEditor->setZoomFactorTarget(d);
}

void VymModel::setMapRotationAngle(const double &d)
{
    rotationAngle = d;
    mapEditor->setAngleTarget(d);
}

void VymModel::setMapAnimDuration(const int &d) { animDuration = d; }

void VymModel::setMapAnimCurve(const QEasingCurve &c) { animCurve = c; }

bool VymModel::centerOnID(const QString &id)
{
    TreeItem *ti = findUuid(QUuid(id));
    if (ti) {
        LinkableMapObj *lmo = ((MapItem *)ti)->getLMO();
        if (zoomFactor > 0 && lmo) {
            mapEditor->setViewCenterTarget(lmo->getBBox().center(), zoomFactor,
                                           rotationAngle, animDuration,
                                           animCurve);
            return true;
        }
    }
    return false;
}

void VymModel::setContextPos(QPointF p)
{
    contextPos = p;
    hasContextPos = true;
}

void VymModel::unsetContextPos()
{
    contextPos = QPointF();
    hasContextPos = false;
}

void VymModel::reposition()
{
    if (repositionBlocked)
        return;

    BranchObj *bo;
    for (int i = 0; i < rootItem->branchCount(); i++) {
        bo = rootItem->getBranchObjNum(i);
        if (bo)
            bo->reposition(); //  for positioning heading
        else
            qDebug() << "VM::reposition bo=0";
    }
    mapEditor->getTotalBBox();

    // required to *reposition* the selection box. size is already correct:
    emitSelectionChanged(); //FIXME-2 better only update selection geometry
}

bool VymModel::setMapLinkStyle(const QString &s)
{
    QString snow;
    switch (linkstyle) {
    case LinkableMapObj::Line:
        snow = "StyleLine";
        break;
    case LinkableMapObj::Parabel:
        snow = "StyleParabel";
        break;
    case LinkableMapObj::PolyLine:
        snow = "StylePolyLine";
        break;
    case LinkableMapObj::PolyParabel:
        snow = "StylePolyParabel";
        break;
    default:
        return false;
        break;
    }

    saveState(QString("setMapLinkStyle (\"%1\")").arg(s),
              QString("setMapLinkStyle (\"%1\")").arg(snow),
              QString("Set map link style (\"%1\")").arg(s));

    if (s == "StyleLine")
        linkstyle = LinkableMapObj::Line;
    else if (s == "StyleParabel")
        linkstyle = LinkableMapObj::Parabel;
    else if (s == "StylePolyLine")
        linkstyle = LinkableMapObj::PolyLine;
    else if (s == "StylePolyParabel")
        linkstyle = LinkableMapObj::PolyParabel;
    else
        linkstyle = LinkableMapObj::UndefinedStyle;

    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    BranchObj *bo;
    nextBranch(cur, prev);
    while (cur) {
        bo = (BranchObj *)(cur->getLMO());
        bo->setLinkStyle(bo->getDefLinkStyle(
            cur->parent())); // FIXME-4 better emit dataCHanged and leave the
                             // changes to View
        nextBranch(cur, prev);
    }
    reposition();
    return true;
}

LinkableMapObj::Style VymModel::getMapLinkStyle() { return linkstyle; }

uint VymModel::getModelID() { return modelID; }

void VymModel::setView(VymView *vv) { vymView = vv; }

void VymModel::setMapDefLinkColor(QColor col)
{
    if (!col.isValid())
        return;
    saveState(
        QString("setMapDefLinkColor (\"%1\")").arg(getMapDefLinkColor().name()),
        QString("setMapDefLinkColor (\"%1\")").arg(col.name()),
        QString("Set map link color to %1").arg(col.name()));

    defLinkColor = col;

    // Set color for "link arrows" in TreeEditor
    vymView->setLinkColor(col);

    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    BranchObj *bo;
    nextBranch(cur, prev);
    while (cur) {
        bo = (BranchObj *)(cur->getLMO());
        bo->setLinkColor();

        for (int i = 0; i < cur->imageCount(); ++i)
            cur->getImageNum(i)->getLMO()->setLinkColor();

        nextBranch(cur, prev);
    }
    updateActions();
}

void VymModel::setMapLinkColorHintInt()
{
    // called from setMapLinkColorHint(lch) or at end of parse
    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    BranchObj *bo;
    nextBranch(cur, prev);
    while (cur) {
        bo = (BranchObj *)(cur->getLMO());
        bo->setLinkColor();

        for (int i = 0; i < cur->imageCount(); ++i)
            cur->getImageNum(i)->getLMO()->setLinkColor();

        nextBranch(cur, prev);
    }
}

void VymModel::setMapLinkColorHint(LinkableMapObj::ColorHint lch)
{
    linkcolorhint = lch;
    setMapLinkColorHintInt();
}

void VymModel::toggleMapLinkColorHint()
{
    if (linkcolorhint == LinkableMapObj::HeadingColor)
        linkcolorhint = LinkableMapObj::DefaultColor;
    else
        linkcolorhint = LinkableMapObj::HeadingColor;
    BranchItem *cur = NULL;
    BranchItem *prev = NULL;
    BranchObj *bo;
    nextBranch(cur, prev);
    while (cur) {
        bo = (BranchObj *)(cur->getLMO());
        bo->setLinkColor();

        for (int i = 0; i < cur->imageCount(); ++i)
            cur->getImageNum(i)->getLMO()->setLinkColor();

        nextBranch(cur, prev);
    }
}

void VymModel::
    selectMapBackgroundImage() // FIXME-3 for using background image:
                               // view.setCacheMode(QGraphicsView::CacheBackground);
                               // Also this belongs into ME
{
    QStringList filters;
    filters << tr("Images") +
                   " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif *.pnm)";
    QFileDialog fd;
    fd.setFileMode(QFileDialog::ExistingFile);
    fd.setWindowTitle(vymName + " - " + tr("Load background image"));
    fd.setDirectory(lastImageDir);
    fd.setAcceptMode(QFileDialog::AcceptOpen);

    if (fd.exec() == QDialog::Accepted && !fd.selectedFiles().isEmpty()) {
        // TODO selectMapBackgroundImg in QT4 use:  lastImageDir=fd.directory();
        lastImageDir = QDir(fd.directory().path());
        setMapBackgroundImage(fd.selectedFiles().first());
    }
}

void VymModel::setMapBackgroundImage(
    const QString &fn) // FIXME-3 missing savestate, move to ME
{
    /*
    QColor oldcol=mapEditor->getScene()->backgroundBrush().color();
    saveState(
    selection,
    QString ("setMapBackgroundImage (%1)").arg(oldcol.name()),
    selection,
    QString ("setMapBackgroundImage (%1)").arg(col.name()),
    QString("Set background color of map to %1").arg(col.name()));
    */
    QBrush brush;
    brush.setTextureImage(QImage(fn));
    mapEditor->getScene()->setBackgroundBrush(brush);
}

void VymModel::selectMapBackgroundColor()
{
    QColor col = QColorDialog::getColor(
        mapEditor->getScene()->backgroundBrush().color(), NULL);
    if (!col.isValid())
        return;
    setMapBackgroundColor(col);
}

void VymModel::setMapBackgroundColor(QColor col)
{
    QColor oldcol = mapEditor->getScene()->backgroundBrush().color();
    saveState(QString("setMapBackgroundColor (\"%1\")").arg(oldcol.name()),
              QString("setMapBackgroundColor (\"%1\")").arg(col.name()),
              QString("Set background color of map to %1").arg(col.name()));
    backgroundColor = col;  // Used for backroundRole in TreeModel::data()
    vymView->setBackgroundColor(backgroundColor);
}

QColor VymModel::getMapBackgroundColor() // FIXME-4 move to ME
{
    return mapEditor->getScene()->backgroundBrush().color();
}

QFont VymModel::getMapDefaultFont() { return defaultFont; }

void VymModel::setMapDefaultFont(const QFont &f) { defaultFont = f; }

LinkableMapObj::ColorHint VymModel::getMapLinkColorHint() // FIXME-4 move to ME
{
    return linkcolorhint;
}

QColor VymModel::getMapDefLinkColor() // FIXME-4 move to ME
{
    return defLinkColor;
}

void VymModel::setMapDefXLinkPen(const QPen &p) // FIXME-4 move to ME
{
    defXLinkPen = p;
}

QPen VymModel::getMapDefXLinkPen() // FIXME-4 move to ME
{
    return defXLinkPen;
}

void VymModel::setMapDefXLinkStyleBegin(const QString &s)
{
    defXLinkStyleBegin = s;
}

QString VymModel::getMapDefXLinkStyleBegin() { return defXLinkStyleBegin; }

void VymModel::setMapDefXLinkStyleEnd(const QString &s)
{
    defXLinkStyleEnd = s;
}

QString VymModel::getMapDefXLinkStyleEnd() { return defXLinkStyleEnd; }

void VymModel::move(const double &x, const double &y)
{
    MapItem *seli = (MapItem *)getSelectedItem();
    if (seli &&
        (seli->isBranchLikeType() || seli->getType() == TreeItem::Image)) {
        LinkableMapObj *lmo = seli->getLMO();
        if (lmo) {
            QPointF ap(lmo->getAbsPos());
            QPointF to(x, y);
            if (ap != to) {
                QString ps = qpointFToString(ap);
                QString s = getSelectString(seli);
                saveState(
                    s, "move " + ps, s, "move " + qpointFToString(to),
                    QString("Move %1 to %2").arg(getObjectName(seli)).arg(ps));
                lmo->move(x, y);
                reposition();
                emitSelectionChanged();
            }
        }
    }
}

void VymModel::moveRel(const double &x, const double &y)
{
    MapItem *seli = (MapItem *)getSelectedItem();
    if (seli &&
        (seli->isBranchLikeType() || seli->getType() == TreeItem::Image)) {
        LinkableMapObj *lmo = seli->getLMO();
        if (lmo) {
            QPointF rp(lmo->getRelPos());
            QPointF to(x, y);
            if (rp != to) {
                QString ps = qpointFToString(lmo->getRelPos());
                QString s = getSelectString(seli);
                saveState(s, "moveRel " + ps, s,
                          "moveRel " + qpointFToString(to),
                          QString("Move %1 to relative position %2")
                              .arg(getObjectName(seli))
                              .arg(ps));
                ((OrnamentedObj *)lmo)->move2RelPos(x, y);
                reposition();
                lmo->updateLinkGeometry();
                emitSelectionChanged();
            }
        }
    }
}

void VymModel::animate()
{
    animationTimer->stop();
    BranchObj *bo;
    int i = 0;
    while (i < animObjList.size()) {
        bo = (BranchObj *)animObjList.at(i);
        if (!bo->animate()) {
            if (i >= 0) {
                animObjList.removeAt(i);
                i--;
            }
        }
        bo->reposition();
        i++;
    }
    emitSelectionChanged();

    if (!animObjList.isEmpty())
        animationTimer->start(animationInterval);
}

void VymModel::startAnimation(BranchObj *bo, const QPointF &v)
{
    if (!bo)
        return;

    if (bo->getUseRelPos())
        startAnimation(bo, bo->getRelPos(), bo->getRelPos() + v);
    else
        startAnimation(bo, bo->getAbsPos(), bo->getAbsPos() + v);
}

void VymModel::startAnimation(BranchObj *bo, const QPointF &start,
                              const QPointF &dest)
{
    if (start == dest)
        return;
    if (bo && bo->getTreeItem()->depth() >= 0) {
        AnimPoint ap;
        ap.setStart(start);
        ap.setDest(dest);
        ap.setTicks(animationTicks);
        ap.setAnimated(true);
        bo->setAnimation(ap);
        if (!animObjList.contains(bo))
            animObjList.append(bo);
        animationTimer->setSingleShot(true);
        animationTimer->start(animationInterval);
    }
}

void VymModel::stopAnimation(MapObj *mo)
{
    int i = animObjList.indexOf(mo);
    if (i >= 0)
        animObjList.removeAt(i);
}

void VymModel::stopAllAnimation()
{
    BranchObj *bo;
    int i = 0;
    while (i < animObjList.size()) {
        bo = (BranchObj *)animObjList.at(i);
        bo->stopAnimation();
        bo->requestReposition();
        i++;
    }
    reposition();
}

void VymModel::sendSelection()
{
    if (netstate != Server)
        return;
    sendData(QString("select (\"%1\")").arg(getSelectString()));
}

void VymModel::newServer()
{
    port = 54321;
    sendCounter = 0;
    tcpServer = new QTcpServer(this);
    if (!tcpServer->listen(QHostAddress::Any, port)) {
        QMessageBox::critical(NULL, "vym server",
                              QString("Unable to start the server: %1.")
                                  .arg(tcpServer->errorString()));
        // FIXME-3 needed? we are no widget any longer... close();
        return;
    }
    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newClient()));
    netstate = Server;
    qDebug() << "Server is running on port " << tcpServer->serverPort();
}

void VymModel::connectToServer()
{
    port = 54321;
    server = "salam.suse.de";
    server = "localhost";
    clientSocket = new QTcpSocket(this);
    clientSocket->abort();
    clientSocket->connectToHost(server, port);
    connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readData()));
    connect(clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this,
            SLOT(displayNetworkError(QAbstractSocket::SocketError)));
    netstate = Client;
    qDebug() << "connected to " << qPrintable(server) << " port " << port;
}

void VymModel::newClient()
{
    QTcpSocket *newClient = tcpServer->nextPendingConnection();
    connect(newClient, SIGNAL(disconnected()), newClient, SLOT(deleteLater()));

    qDebug() << "ME::newClient  at "
             << qPrintable(newClient->peerAddress().toString());

    clientList.append(newClient);
}

void VymModel::sendData(const QString &s)
{
    if (clientList.size() == 0)
        return;

    // Create bytearray to send
    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_0);

    // Reserve some space for blocksize
    out << (quint16)0;

    // Write sendCounter
    out << sendCounter++;

    // Write data
    out << s;

    // Go back and write blocksize so far
    out.device()->seek(0);
    quint16 bs = (quint16)(block.size() - 2 * sizeof(quint16));
    out << bs;

    if (debug)
        qDebug() << "ME::sendData  bs=" << bs << "  counter=" << sendCounter
                 << "  s=" << qPrintable(s);

    for (int i = 0; i < clientList.size(); ++i) {
        // qDebug() << "Sending \""<<qPrintable (s)<<"\" to "<<qPrintable
        // (clientList.at(i)->peerAddress().toString());
        clientList.at(i)->write(block);
    }
}

void VymModel::readData()
{
    while (clientSocket->bytesAvailable() >= (int)sizeof(quint16)) {
        if (debug)
            qDebug() << "readData  bytesAvail="
                     << clientSocket->bytesAvailable();
        quint16 recCounter;
        quint16 blockSize;

        QDataStream in(clientSocket);
        in.setVersion(QDataStream::Qt_4_0);

        in >> blockSize;
        in >> recCounter;

        QString t;
        in >> t;
        if (debug)
            qDebug() << "VymModel::readData  command=" << qPrintable(t);
        // bool noErr;
        // QString errMsg;
        // parseAtom (t,noErr,errMsg);    //FIXME-4 needs rework using scripts
    }
    return;
}

void VymModel::displayNetworkError(QAbstractSocket::SocketError socketError)
{
    switch (socketError) {
    case QAbstractSocket::RemoteHostClosedError:
        break;
    case QAbstractSocket::HostNotFoundError:
        QMessageBox::information(NULL, vymName + " Network client",
                                 "The host was not found. Please check the "
                                 "host name and port settings.");
        break;
    case QAbstractSocket::ConnectionRefusedError:
        QMessageBox::information(NULL, vymName + " Network client",
                                 "The connection was refused by the peer. "
                                 "Make sure the fortune server is running, "
                                 "and check that the host name and port "
                                 "settings are correct.");
        break;
    default:
        QMessageBox::information(NULL, vymName + " Network client",
                                 QString("The following error occurred: %1.")
                                     .arg(clientSocket->errorString()));
    }
}

void VymModel::downloadImage(const QUrl &url, BranchItem *bi)
{
    if (!bi)
        bi = getSelectedBranch();
    if (!bi) {
        qWarning("VM::download bi==NULL");
        return;
    }

    // FIXME-3 download img to tmpfile and delete after running script in
    // mainWindow
    QString script;
    script += QString("m = vym.currentMap();m.selectID(\"%1\");")
                  .arg(bi->getUuid().toString());
    script += QString("m.loadImage(\"$TMPFILE\");");

    DownloadAgent *agent = new DownloadAgent(url);
    agent->setFinishedAction(this, script);
    connect(agent, SIGNAL(downloadFinished()), mainWindow,
            SLOT(downloadFinished()));
    QTimer::singleShot(0, agent, SLOT(execute()));
}

void VymModel::selectMapSelectionColor()    // FIXME-2 move out of VymModel, consider Pen/Brush
{
    QColor col = QColorDialog::getColor(defLinkColor, NULL);
    setSelectionPenColor(col);
    setSelectionBrushColor(col);
}

void VymModel::emitSelectionChanged(const QItemSelection &newsel)
{
    emit(selectionChanged(newsel,
                          newsel)); // needed e.g. to update geometry in editor
    sendSelection();
}

void VymModel::emitSelectionChanged()
{
    QItemSelection newsel = selModel->selection();
    emitSelectionChanged(newsel);
}

void VymModel::setSelectionPenColor(QColor col)
{
    if (!col.isValid())
        return;

    QPen selPen = mapEditor->getSelectionPen();
    saveState(QString("setSelectionPenColor (\"%1\")")
                  .arg(selPen.color().name()),
              QString("setSelectionPenColor (\"%1\")").arg(col.name()),
              QString("Set pen color of selection box to %1").arg(col.name()));

    selPen.setColor(col);
    mapEditor->setSelectionPen(selPen);
}

QColor VymModel::getSelectionPenColor() {
    return mapEditor->getSelectionPen().color();
}

void VymModel::setSelectionPenWidth(qreal w)
{
    QPen selPen = mapEditor->getSelectionPen();
    
    saveState(QString("setSelectionPenWidth (\"%1\")")
                  .arg(mapEditor->getSelectionPen().width()),
              QString("setSelectionPenWidth (\"%1\")").arg(w),
              QString("Set pen width of selection box to %1").arg(w));

    selPen.setWidth(w);
    mapEditor->setSelectionPen(selPen);
    //vymView->setSelectionColor(col);
}

qreal VymModel::getSelectionPenWidth() {
    return mapEditor->getSelectionPen().width();
}

void VymModel::setSelectionBrushColor(QColor col)
{
    if (!col.isValid())
        return;

    QBrush selBrush = mapEditor->getSelectionBrush();
    saveState(QString("setSelectionBrushColor (\"%1\")")
                  .arg(selBrush.color().name()),
              QString("setSelectionBrushColor (\"%1\")").arg(col.name()),
              QString("Set Brush color of selection box to %1").arg(col.name()));

    selBrush.setColor(col);
    vymView->setSelectionBrush(selBrush);
}

QColor VymModel::getSelectionBrushColor() {
    return mapEditor->getSelectionBrush().color();
}

bool VymModel::initIterator(const QString &iname, bool deepLevelsFirst)
{
    Q_UNUSED(deepLevelsFirst);

    // Remove existing iterators first
    selIterCur.remove(iname);
    selIterPrev.remove(iname);
    selIterStart.remove(iname);
    selIterActive.remove(iname);

    QList<BranchItem *> selbis;
    selbis = getSelectedBranches();
    if (selbis.count() == 1) {
        BranchItem *prev = NULL;
        BranchItem *cur = NULL;
        nextBranch(cur, prev, false, selbis.first());
        if (cur) {
            selIterCur.insert(iname, cur->getUuid());
            selIterPrev.insert(iname, prev->getUuid());
            selIterStart.insert(iname, selbis.first()->getUuid());
            selIterActive.insert(iname, false);
            // qDebug() << "Created iterator " << iname;
            return true;
        }
    }
    return false;
}

bool VymModel::nextIterator(const QString &iname)
{
    if (selIterCur.keys().indexOf(iname) < 0) {
        qWarning()
            << QString("VM::nextIterator couldn't find %1 in hash of iterators")
                   .arg(iname);
        return false;
    }

    BranchItem *cur = (BranchItem *)(findUuid(selIterCur.value(iname)));
    if (!cur) {
        qWarning() << "VM::nextIterator couldn't find cur" << selIterCur;
        return false;
    }

    qDebug() << "  " << iname << "selecting " << cur->getHeadingPlain();
    select(cur);

    if (!selIterActive.value(iname)) {
        // Select for the first time
        select(cur);
        selIterActive[iname] = true;
        return true;
    }

    BranchItem *prev = (BranchItem *)(findUuid(selIterPrev.value(iname)));
    BranchItem *start = (BranchItem *)(findUuid(selIterStart.value(iname)));
    if (!prev)
        qWarning() << "VM::nextIterator couldn't find prev"
                   << selIterPrev.value(iname);
    if (!start)
        qWarning() << "VM::nextIterator couldn't find start "
                   << selIterStart.value(iname);

    if (cur && prev && start) {
        nextBranch(cur, prev, false, start);
        if (cur) {
            selIterCur[iname] = cur->getUuid();
            selIterPrev[iname] = prev->getUuid();
            select(cur);
            return true;
        }
        else
            return false;
    }
    return false;
}

void VymModel::setHideTmpMode(TreeItem::HideTmpMode mode)
{
    hidemode = mode;
    for (int i = 0; i < rootItem->branchCount(); i++)
        rootItem->getBranchNum(i)->setHideTmp(mode);
    reposition();
    if (mode == TreeItem::HideExport)
        unselectAll();
    else
        reselect();

    qApp->processEvents();
}

//////////////////////////////////////////////
// Selection related
//////////////////////////////////////////////

void VymModel::updateSelection(QItemSelection newsel, QItemSelection dsel)
{
    QModelIndex ix;
    MapItem *mi;
    BranchItem *bi;
    bool do_reposition = false;
    foreach (ix, dsel.indexes()) {
        mi = static_cast<MapItem *>(ix.internalPointer());
        if (mi->isBranchLikeType())
            do_reposition =
                do_reposition || ((BranchItem *)mi)->resetTmpUnscroll();
        if (mi->getType() == TreeItem::XLink) {
            Link *li = ((XLinkItem *)mi)->getLink();
            XLinkObj *xlo = li->getXLinkObj();
            if (xlo)
                xlo->setSelection(XLinkObj::Unselected);

            do_reposition =
                do_reposition || li->getBeginBranch()->resetTmpUnscroll();
            do_reposition =
                do_reposition || li->getEndBranch()->resetTmpUnscroll();
        }
    }

    foreach (ix, newsel.indexes()) {
        mi = static_cast<MapItem *>(ix.internalPointer());
        if (mi->isBranchLikeType()) {
            bi = (BranchItem *)mi;
            if (bi->hasScrolledParent()) {
                bi->tmpUnscroll();
                do_reposition = true;
            }
        }
        if (mi->getType() == TreeItem::XLink) {
            ((XLinkItem *)mi)->setSelection();

            // begin/end branches need to be tmp unscrolled
            Link *li = ((XLinkItem *)mi)->getLink();
            bi = li->getBeginBranch();
            if (bi->hasScrolledParent()) {
                bi->tmpUnscroll();
                do_reposition = true;
            }
            bi = li->getEndBranch();
            if (bi->hasScrolledParent()) {
                bi->tmpUnscroll();
                do_reposition = true;
            }
        }
    }
    if (do_reposition)
        reposition();
}

void VymModel::setSelectionModel(QItemSelectionModel *sm) { selModel = sm; }

QItemSelectionModel *VymModel::getSelectionModel() { return selModel; }

void VymModel::setSelectionBlocked(bool b) { selectionBlocked = b; }

bool VymModel::isSelectionBlocked() { return selectionBlocked; }

bool VymModel::select(const QString &s) // FIXME-2 Does not support multiple selections yet
{
    if (s.isEmpty())
        return false;
    TreeItem *ti = findBySelectString(s);
    if (ti)
        return select(index(ti));
    return false;
}

bool VymModel::selectID(const QString &s)
{
    if (s.isEmpty())
        return false;
    TreeItem *ti = findUuid(QUuid(s));
    if (ti)
        return select(index(ti));
    return false;
}

bool VymModel::select(LinkableMapObj *lmo)
{
    QItemSelection oldsel = selModel->selection();

    if (lmo)
        return select(lmo->getTreeItem());
    else
        return false;
}

bool VymModel::selectToggle(TreeItem *ti)
{
    if (ti) {
        selModel->select(index(ti), QItemSelectionModel::Toggle);
        // appendSelectionToHistory();	// FIXME-4 selection history not implemented yet
        // for multiselections
        lastToggledUuid = ti->getUuid();
        return true;
    }
    return false;
}

bool VymModel::selectToggle(const QString &selectString)
{
    TreeItem *ti = findBySelectString(selectString);
    return selectToggle(ti);
}

bool VymModel::select(TreeItem *ti)
{
    if (ti)
        return select(index(ti));
    else
        return false;
}

bool VymModel::select(const QModelIndex &index)
{
    if (index.isValid()) {
        TreeItem *ti = getItem(index);
        if (ti->isBranchLikeType()) {
            if (((BranchItem *)ti)->tmpUnscroll())
                reposition();
        }
        selModel->select(index, QItemSelectionModel::ClearAndSelect);
        appendSelectionToHistory();
        return true;
    }
    return false;
}

void VymModel::unselectAll() { unselect(selModel->selection()); }

void VymModel::unselect(QItemSelection desel)
{
    if (!desel.isEmpty()) {
        lastSelectString = getSelectString();
        selModel->clearSelection();
    }
}

bool VymModel::reselect()
{
    bool b = select(lastSelectString);
    return b;
}

bool VymModel::canSelectPrevious()
{
    if (currentSelection > 0)
        return true;
    else
        return false;
}

bool VymModel::selectPrevious()
{
    keepSelectionHistory = true;
    bool result = false;
    while (currentSelection > 0) {
        currentSelection--;
        TreeItem *ti = findID(selectionHistory.at(currentSelection));
        if (ti) {
            result = select(ti);
            break;
        }
        else
            selectionHistory.removeAt(currentSelection);
    }
    keepSelectionHistory = false;
    return result;
}

bool VymModel::canSelectNext()
{
    if (currentSelection < selectionHistory.count() - 1)
        return true;
    else
        return false;
}

bool VymModel::selectNext()
{
    keepSelectionHistory = true;
    bool result = false;
    while (currentSelection < selectionHistory.count() - 1) {
        currentSelection++;
        TreeItem *ti = findID(selectionHistory.at(currentSelection));
        if (ti) {
            result = select(ti);
            break;
        }
        else
            selectionHistory.removeAt(currentSelection);
    }
    keepSelectionHistory = false;
    return result;
}

void VymModel::resetSelectionHistory()
{
    selectionHistory.clear();
    currentSelection = -1;
    keepSelectionHistory = false;
    appendSelectionToHistory();
}

void VymModel::appendSelectionToHistory() // FIXME-4 history unable to cope with multiple
                                          // selections
{
    uint id = 0;
    TreeItem *ti = getSelectedItem();
    if (ti && !keepSelectionHistory) {
        if (ti->isBranchLikeType())
            ((BranchItem *)ti)->setLastSelectedBranch();
        id = ti->getID();
        selectionHistory.append(id);
        currentSelection = selectionHistory.count() - 1;
        updateActions();
    }
}

void VymModel::emitShowSelection(bool scaled)
{
    if (!repositionBlocked)
        emit(showSelection(scaled));
}

TreeItem* VymModel::lastToggledItem()
{
    return findUuid(lastToggledUuid);
}

void VymModel::emitNoteChanged(TreeItem *ti)
{
    QModelIndex ix = index(ti);
    emit(noteChanged(ix));
    mainWindow->updateNoteEditor(ti);
}

void VymModel::emitDataChanged(TreeItem *ti)
{
    QModelIndex ix = index(ti);
    emit(dataChanged(ix, ix));
    emitSelectionChanged();
    if (!repositionBlocked) {
        // Update taskmodel and recalc priorities there
        if (ti->isBranchLikeType() && ((BranchItem *)ti)->getTask()) {
            taskModel->emitDataChanged(((BranchItem *)ti)->getTask());
            taskModel->recalcPriorities();
        }
    }
}

void VymModel::emitUpdateQueries()
{
    // Used to tell MainWindow to update query results
    if (repositionBlocked)
        return;
    emit(updateQueries(this));
}
void VymModel::emitUpdateLayout()
{
    if (settings.value("/mainwindow/autoLayout/use", "true") == "true")
        emit(updateLayout());
}

bool VymModel::selectFirstBranch()
{
    TreeItem *ti = getSelectedBranch();
    if (ti) {
        TreeItem *par = ti->parent();
        if (par) {
            TreeItem *ti2 = par->getFirstBranch();
            if (ti2)
                return select(ti2);
        }
    }
    return false;
}

bool VymModel::selectFirstChildBranch()
{
    TreeItem *ti = getSelectedBranch();
    if (ti) {
        BranchItem *bi = ti->getFirstBranch();
        if (bi)
            return select(bi);
    }
    return false;
}

bool VymModel::selectLastBranch()
{
    TreeItem *ti = getSelectedBranch();
    if (ti) {
        TreeItem *par = ti->parent();
        if (par) {
            TreeItem *ti2 = par->getLastBranch();
            if (ti2)
                return select(ti2);
        }
    }
    return false;
}

bool VymModel::selectLastChildBranch()
{
    TreeItem *ti = getSelectedBranch();
    if (ti) {
        BranchItem *bi = ti->getLastBranch();
        if (bi)
            return select(bi);
    }
    return false;
}

bool VymModel::selectLastSelectedBranch()
{
    BranchItem *bi = getSelectedBranch();
    if (bi) {
        bi = bi->getLastSelectedBranch();
        if (bi)
            return select(bi);
    }
    return false;
}

bool VymModel::selectLastImage()
{
    TreeItem *ti = getSelectedBranch();
    if (ti) {
        TreeItem *par = ti->parent();
        if (par) {
            TreeItem *ti2 = par->getLastImage();
            if (ti2)
                return select(ti2);
        }
    }
    return false;
}

bool VymModel::selectLatestAdded() { return select(latestAddedItem); }

bool VymModel::selectParent()
{
    TreeItem *ti = getSelectedItem();
    TreeItem *par;
    if (ti) {
        par = ti->parent();
        if (par)
            return select(par);
    }
    return false;
}

TreeItem::Type VymModel::selectionType()
{
    TreeItem *ti = getSelectedItem();
    if (ti)
        return ti->getType();
    else
        return TreeItem::Undefined;
}

LinkableMapObj *VymModel::getSelectedLMO()
{
    QModelIndexList list = selModel->selectedIndexes();
    if (list.count() == 1) {
        TreeItem *ti = getItem(list.first());
        TreeItem::Type type = ti->getType();
        if (type == TreeItem::Branch || type == TreeItem::MapCenter ||
            type == TreeItem::Image)
            return ((MapItem *)ti)->getLMO();
    }
    return NULL;
}

BranchObj *VymModel::getSelectedBranchObj() // convenience function
{
    TreeItem *ti = getSelectedBranch();
    if (ti)
        return (BranchObj *)(((MapItem *)ti)->getLMO());
    else
        return NULL;
}

BranchItem *VymModel::getSelectedBranch()
{
    TreeItem *ti = getSelectedItem();
    if (ti) {
        TreeItem::Type type = ti->getType();
        if (type == TreeItem::Branch || type == TreeItem::MapCenter)
            return (BranchItem *)ti;
    }
    return NULL;
}

QList<BranchItem *> VymModel::getSelectedBranches()
{
    QList<BranchItem *> bis;
    foreach (TreeItem *ti, getSelectedItems()) {
        TreeItem::Type type = ti->getType();
        if (type == TreeItem::Branch || type == TreeItem::MapCenter)
            bis.append((BranchItem *)ti);
    }
    return bis;
}

ImageItem *VymModel::getSelectedImage()
{
    TreeItem *ti = getSelectedItem();
    if (ti && ti->getType() == TreeItem::Image)
        return (ImageItem *)ti;
    else
        return NULL;
}

Task *VymModel::getSelectedTask()
{
    BranchItem *selbi = getSelectedBranch();
    if (selbi)
        return selbi->getTask();
    else
        return NULL;
}

Link *VymModel::getSelectedXLink()
{
    XLinkItem *xli = getSelectedXLinkItem();
    if (xli)
        return xli->getLink();
    return NULL;
}

XLinkItem *VymModel::getSelectedXLinkItem()
{
    TreeItem *ti = getSelectedItem();
    if (ti && ti->getType() == TreeItem::XLink)
        return (XLinkItem *)ti;
    else
        return NULL;
}

AttributeItem *VymModel::getSelectedAttribute()
{
    TreeItem *ti = getSelectedItem();
    if (ti && ti->getType() == TreeItem::Attribute)
        return (AttributeItem *)ti;
    else
        return NULL;
}

TreeItem *VymModel::getSelectedItem()
{
    if (!selModel)
        return NULL;
    QModelIndexList list = selModel->selectedIndexes();
    if (list.count() == 1)
        return getItem(list.first());
    else
        return NULL;
}

QList<TreeItem *> VymModel::getSelectedItems()
{
    QList<TreeItem *> l;
    if (!selModel)
        return l;
    QModelIndexList list = selModel->selectedIndexes();
    foreach (QModelIndex ix, list)
        l.append(getItem(ix));
    return l;
}

QModelIndex VymModel::getSelectedIndex()
{
    QModelIndexList list = selModel->selectedIndexes();
    if (list.count() == 1)
        return list.first();
    else
        return QModelIndex();
}

QList<uint> VymModel::getSelectedIDs()
{
    QList<uint> uids;
    foreach (TreeItem *ti, getSelectedItems())
        uids.append(ti->getID());
    return uids;
}

QStringList VymModel::getSelectedUUIDs()
{
    QStringList uids;
    foreach (TreeItem *ti, getSelectedItems())
        uids.append(ti->getUuid().toString());
    return uids;
}

bool VymModel::isSelected(TreeItem *ti)
{
    return getSelectedItems().contains(ti);
}

QString VymModel::getSelectString()
{
    return getSelectString(getSelectedItem());
}

QString VymModel::getSelectString(
    LinkableMapObj *lmo) // only for convenience. Used in MapEditor
{
    if (!lmo)
        return QString();
    return getSelectString(lmo->getTreeItem());
}

QString VymModel::getSelectString(TreeItem *ti)
{
    QString s;
    if (!ti || ti->depth() < 0)
        return s;
    switch (ti->getType()) {
    case TreeItem::MapCenter:
        s = "mc:";
        break;
    case TreeItem::Branch:
        s = "bo:";
        break;
    case TreeItem::Image:
        s = "fi:";
        break;
    case TreeItem::Attribute:
        s = "ai:";
        break;
    case TreeItem::XLink:
        s = "xl:";
        break;
    default:
        s = "unknown type in VymModel::getSelectString()";
        break;
    }
    s = s + QString("%1").arg(ti->num());
    if (ti->depth() > 0)
        // call myself recursively
        s = getSelectString(ti->parent()) + "," + s;
    return s;
}

QString VymModel::getSelectString(BranchItem *bi)
{
    return getSelectString((TreeItem *)bi);
}

QString VymModel::getSelectString(const uint &i)
{
    return getSelectString(findID(i));
}

void VymModel::setLatestAddedItem(TreeItem *ti) { latestAddedItem = ti; }

TreeItem *VymModel::getLatestAddedItem() { return latestAddedItem; }

SlideModel *VymModel::getSlideModel() { return slideModel; }

int VymModel::slideCount() { return slideModel->count(); }

SlideItem *VymModel::addSlide()
{
    SlideItem *si = slideModel->getSelectedItem();
    if (si)
        si = slideModel->addSlide(NULL, si->childNumber() + 1);
    else
        si = slideModel->addSlide();

    TreeItem *seli = getSelectedItem();

    if (si && seli) {
        QString inScript;
        if (!loadStringFromDisk(vymBaseDir.path() +
                                    "/macros/slideeditor-snapshot.vys",
                                inScript)) {
            qWarning() << "VymModel::addSlide couldn't load template for "
                          "taking snapshot";
            return NULL;
        }

        inScript.replace(
            "CURRENT_ZOOM",
            QString().setNum(getMapEditor()->getZoomFactorTarget()));
        inScript.replace("CURRENT_ANGLE",
                         QString().setNum(getMapEditor()->getAngleTarget()));
        inScript.replace("CURRENT_ID",
                         "\"" + seli->getUuid().toString() + "\"");

        si->setInScript(inScript);
        slideModel->setData(slideModel->index(si), seli->getHeadingPlain());
    }
    QString s = "<vymmap>" + si->saveToDir() + "</vymmap>";
    int pos = si->childNumber();
    saveState(PartOfMap, getSelectString(),
              QString("removeSlide (%1)").arg(pos), getSelectString(),
              QString("addMapInsert (\"PATH\",%1)").arg(pos), "Add slide", NULL,
              s);
    return si;
}

void VymModel::deleteSlide(SlideItem *si)
{
    if (si) {
        QString s = "<vymmap>" + si->saveToDir() + "</vymmap>";
        int pos = si->childNumber();
        saveState(PartOfMap, getSelectString(),
                  QString("addMapInsert (\"PATH\",%1)").arg(pos),
                  getSelectString(), QString("removeSlide (%1)").arg(pos),
                  "Remove slide", NULL, s);
        slideModel->deleteSlide(si);
    }
}

void VymModel::deleteSlide(int n) { deleteSlide(slideModel->getSlide(n)); }

void VymModel::relinkSlide(SlideItem *si, int pos)
{
    if (si && pos >= 0)
        slideModel->relinkSlide(si, si->parent(), pos);
}

bool VymModel::moveSlideDown(int n)
{
    SlideItem *si = NULL;
    if (n < 0) // default if called without parameters
    {
        si = slideModel->getSelectedItem();
        if (si)
            n = si->childNumber();
        else
            return false;
    }
    else
        si = slideModel->getSlide(n);
    if (si && n >= 0 && n < slideModel->count() - 1) {
        blockSlideSelection = true;
        slideModel->relinkSlide(si, si->parent(), n + 1);
        blockSlideSelection = false;
        saveState(getSelectString(), QString("moveSlideUp (%1)").arg(n + 1),
                  getSelectString(), QString("moveSlideDown (%1)").arg(n),
                  QString("Move slide %1 down").arg(n));
        return true;
    }
    else
        return false;
}

bool VymModel::moveSlideUp(int n)
{
    SlideItem *si = NULL;
    if (n < 0) // default if called without parameters
    {
        si = slideModel->getSelectedItem();
        if (si)
            n = si->childNumber();
        else
            return false;
    }
    else
        si = slideModel->getSlide(n);
    if (si && n > 0 && n < slideModel->count()) {
        blockSlideSelection = true;
        slideModel->relinkSlide(si, si->parent(), n - 1);
        blockSlideSelection = false;
        saveState(getSelectString(), QString("moveSlideDown (%1)").arg(n - 1),
                  getSelectString(), QString("moveSlideUp (%1)").arg(n),
                  QString("Move slide %1 up").arg(n));
        return true;
    }
    else
        return false;
}

void VymModel::updateSlideSelection(QItemSelection newsel, QItemSelection)
{
    if (blockSlideSelection)
        return;
    QModelIndex ix;
    foreach (ix, newsel.indexes()) {
        SlideItem *si = static_cast<SlideItem *>(ix.internalPointer());
        QString inScript = si->getInScript();

        // show inScript in ScriptEditor
        scriptEditor->setSlideScript(modelID, si->getID(), inScript);

        // Execute inScript
        execute(inScript);
    }
}
