X-Git-Url: https://git.sven.stormbind.net/?a=blobdiff_plain;f=src%2Fvymmodel.cpp;fp=src%2Fvymmodel.cpp;h=d3ce38483e9a1943381cdeb552e06cb486793094;hb=d483bd8e6523c23c6f1d8908a2e0611c2bc9ff4f;hp=0000000000000000000000000000000000000000;hpb=7dfa3fe589d1722d49681f42cdb0bf1e6efb5223;p=sven%2Fvym.git diff --git a/src/vymmodel.cpp b/src/vymmodel.cpp new file mode 100644 index 0000000..d3ce384 --- /dev/null +++ b/src/vymmodel.cpp @@ -0,0 +1,6371 @@ +#include +#include + +#if defined(VYM_DBUS) +#include +#endif + +#ifndef Q_OS_WINDOWS +#include +#else +#define sleep Sleep +#endif + +#include +#include +#include +#include + +#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 +#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="<stop(); + fileChangedTimer->stop(); + stopAllAnimation(); + + // qApp->processEvents(); // Update view (scene()->update() is not enough) + // qDebug() << "Destr VymModel end this="<childCount() > 0) { + // qDebug()<<"VM::clear ri="<count()="<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 = + "\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 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 &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("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="< 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="<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 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 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::const_iterator i = map.constBegin(); + QMultiMap::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 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 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 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(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 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="<getHeading()<<" + // 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="<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 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 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: "<runScript(script); +} + +void VymModel::setExportMode(bool b) +{ + // should be called before and after exports + // depending on the settings + if (b && settings.value("/export/useHideExport", "true") == "true") + setHideTmpMode(TreeItem::HideExport); + else + setHideTmpMode(TreeItem::HideNone); +} + +QPointF VymModel::exportImage(QString fname, bool askName, QString format) +{ + QPointF offset; // set later, when getting image from MapEditor + + if (fname == "") { + if (!askName) { + qWarning("VymModel::exportImage called without filename (and " + "askName==false)"); + return offset; + } + + fname = lastImageDir.absolutePath() + "/" + getMapName() + ".png"; + format = "PNG"; + } + + ExportBase ex; + ex.setName("Image"); + ex.setModel(this); + ex.setFilePath(fname); + ex.setWindowTitle(tr("Export map as image")); + ex.addFilter( + "PNG (*.png);;All (* *.*)"); // imageIO.getFilters().join(";;") + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (askName) { + if (!ex.execDialog()) + return offset; + fname = ex.getFilePath(); + lastImageDir = QDir(fname); + } + + setExportMode(true); + + mapEditor->minimizeView(); + + QImage img(mapEditor->getImage(offset)); + if (!img.save(fname, format.toLocal8Bit())) { + QMessageBox::critical( + 0, tr("Critical Error"), + tr("Couldn't save QImage %1 in format %2").arg(fname).arg(format)); + ex.setResult(ExportBase::Failed); + } else + ex.setResult(ExportBase::Success); + + setExportMode(false); + + ex.completeExport(); + + return offset; +} + +void VymModel::exportPDF(QString fname, bool askName) +{ + if (fname == "") { + if (!askName) { + qWarning("VymModel::exportPDF called without filename (and " + "askName==false)"); + return; + } + + fname = lastExportDir.absolutePath() + "/" + getMapName() + ".pdf"; + } + + ExportBase ex; + ex.setName("PDF"); + ex.setModel(this); + ex.setFilePath(fname); + ex.setWindowTitle(tr("Export map as PDF")); + ex.addFilter("PDF (*.pdf);;All (* *.*)"); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (askName) { + if (!ex.execDialog()) + return; + fname = ex.getFilePath(); + } + + setExportMode(true); + + // To PDF + QPrinter pdfPrinter(QPrinter::HighResolution); + pdfPrinter.setOutputFormat(QPrinter::PdfFormat); + pdfPrinter.setOutputFileName(fname); + pdfPrinter.setPageSize(QPageSize(QPageSize::A3)); + + QRectF bbox = mapEditor->getTotalBBox(); + if (bbox.width() > bbox.height()) + // recommend landscape + pdfPrinter.setPageOrientation(QPageLayout::Landscape); + else + // recommend portrait + pdfPrinter.setPageOrientation(QPageLayout::Portrait); + + QPainter *pdfPainter = new QPainter(&pdfPrinter); + getScene()->render(pdfPainter); + pdfPainter->end(); + delete pdfPainter; + + setExportMode(false); + + ex.completeExport(); +} + +QPointF VymModel::exportSVG(QString fname, bool askName) +{ + QPointF offset; // FIXME-3 not needed? + + if (fname == "") { + if (!askName) { + qWarning("VymModel::exportSVG called without filename (and " + "askName==false)"); + return offset; + } + + fname = lastImageDir.absolutePath() + "/" + getMapName() + ".svg"; + } + + ExportBase ex; + ex.setName("SVG"); + ex.setModel(this); + ex.setFilePath(fname); + ex.setWindowTitle(tr("Export map as SVG")); + ex.addFilter("SVG (*.svg);;All (* *.*)"); + + if (askName) { + if (!ex.execDialog()) + return offset; + fname = ex.getFilePath(); + lastImageDir = QDir(fname); + } + + setExportMode(true); + + QSvgGenerator generator; + generator.setFileName(fname); + QSize sceneSize = getScene()->sceneRect().size().toSize(); + generator.setSize(sceneSize); + generator.setViewBox(QRect(0, 0, sceneSize.width(), sceneSize.height())); + QPainter *svgPainter = new QPainter(&generator); + getScene()->render(svgPainter); + svgPainter->end(); + delete svgPainter; + + setExportMode(false); + ex.completeExport(); + + return offset; +} + +void VymModel::exportXML(QString fpath, QString dpath, bool useDialog) +{ + ExportBase ex; + ex.setName("XML"); + ex.setModel(this); + ex.setWindowTitle(tr("Export map as XML")); + ex.addFilter("XML (*.xml);;All (* *.*)"); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (useDialog) { + QFileDialog fd; + fd.setWindowTitle(vymName + " - " + tr("Export XML to directory")); + QStringList filters; + filters << "XML data (*.xml)"; + fd.setNameFilters(filters); + fd.setOption(QFileDialog::DontConfirmOverwrite, true); + fd.setAcceptMode(QFileDialog::AcceptSave); + fd.selectFile(mapName + ".xml"); + + QString fn; + if (fd.exec() != QDialog::Accepted || fd.selectedFiles().isEmpty()) + return; + + fpath = fd.selectedFiles().first(); + dpath = fpath.left(fpath.lastIndexOf("/")); + + if (!confirmDirectoryOverwrite(QDir(dpath))) + return; + } + ex.setFilePath(fpath); + + QString mname = basename(fpath); + + // Hide stuff during export, if settings want this + setExportMode(true); + + // Create subdirectories + makeSubDirs(dpath); + + // write image and calculate offset (Remember old mapSaved setting while + // exporting image) + bool mchanged = mapChanged; + bool munsaved = mapUnsaved; + + QPointF offset = + exportImage(dpath + "/images/" + mname + ".png", false, "PNG"); + + mapChanged = mchanged; + mapUnsaved = munsaved; + + // write to directory //FIXME-3 check totalBBox here... + QString saveFile = + saveToDir(dpath, mname + "-", FlagRowMaster::NoFlags, offset, NULL); + QFile file; + + file.setFileName(fpath); + if (!file.open(QIODevice::WriteOnly)) { + // This should neverever happen + QMessageBox::critical(0, tr("Critical Export Error"), + QString("VymModel::exportXML couldn't open %1") + .arg(file.fileName())); + return; + } + + // Write it finally, and write in UTF8, no matter what + QTextStream ts(&file); + ts.setCodec("UTF-8"); + ts << saveFile; + file.close(); + + setExportMode(false); + + QStringList args; + args << fpath; + args << dpath; + ex.completeExport(args); +} + +void VymModel::exportAO(QString fname, bool askName) +{ + ExportAO ex; + ex.setModel(this); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (fname == "") + ex.setFilePath(mapName + ".txt"); + else + ex.setFilePath(fname); + + if (askName) { + ex.setDirPath(lastExportDir.absolutePath()); + ex.execDialog(); + } + if (!ex.canceled()) { + setExportMode(true); + ex.doExport(); + setExportMode(false); + } +} + +void VymModel::exportASCII(const QString &fname, bool listTasks, bool askName) +{ + ExportASCII ex; + ex.setModel(this); + ex.setListTasks(listTasks); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (fname == "") + ex.setFilePath(mapName + ".txt"); + else + ex.setFilePath(fname); + + if (askName) { + ex.setDirPath(lastExportDir.absolutePath()); + ex.execDialog(); + } + + if (!ex.canceled()) { + setExportMode(true); + ex.doExport(); + setExportMode(false); + } +} + +void VymModel::exportCSV(const QString &fname, bool askName) +{ + ExportCSV ex; + ex.setModel(this); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (fname == "") + ex.setFilePath(mapName + ".csv"); + else + ex.setFilePath(fname); + + if (askName) { + ex.addFilter("CSV (*.csv);;All (* *.*)"); + ex.setDirPath(lastExportDir.absolutePath()); + ex.setWindowTitle(vymName + " -" + tr("Export as csv") + " " + + tr("(still experimental)")); + ex.execDialog(); + } + + if (!ex.canceled()) { + setExportMode(true); + ex.doExport(); + setExportMode(false); + } +} + +void VymModel::exportFirefoxBookmarks(const QString &fname, bool askName) +{ + ExportFirefox ex; + ex.setModel(this); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (fname == "") + ex.setFilePath(mapName + ".csv"); + else + ex.setFilePath(fname); + + if (askName) { + ex.addFilter("JSON (*.json);;All (* *.*)"); + ex.setDirPath(lastExportDir.absolutePath()); + ex.setWindowTitle(vymName + " -" + tr("Export as csv") + " " + + tr("(still experimental)")); + ex.execDialog(); + } + + if (!ex.canceled()) { + setExportMode(true); + ex.doExport(); + setExportMode(false); + } +} + +void VymModel::exportHTML(const QString &fpath, const QString &dpath, + bool useDialog) +{ + ExportHTML ex(this); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (!dpath.isEmpty()) + ex.setDirPath(dpath); + if (!fpath.isEmpty()) + ex.setFilePath(fpath); + + ex.doExport(useDialog); +} + +void VymModel::exportConfluence(bool createPage, const QString &pageURL, + const QString &pageName, bool useDialog) +{ + ExportConfluence ex(this); + ex.setCreateNewPage(createPage); + ex.setURL(pageURL); + ex.setPageName(pageName); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + ex.doExport(useDialog); +} + +void VymModel::exportImpress(const QString &fn, const QString &cf) +{ + ExportOO ex; + ex.setFilePath(fn); + ex.setModel(this); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (ex.setConfigFile(cf)) { + QString lastCommand = + settings.localValue(filePath, "/export/last/command", "") + .toString(); + + setExportMode(true); + ex.exportPresentation(); + setExportMode(false); + + QString command = + settings.localValue(filePath, "/export/last/command", "") + .toString(); + if (lastCommand != command) + setChanged(); + } +} + +bool VymModel::exportLastAvailable(QString &description, QString &command, + QString &dest) +{ + command = + settings.localValue(filePath, "/export/last/command", "").toString(); + + description = settings.localValue(filePath, "/export/last/description", "") + .toString(); + dest = settings.localValue(filePath, "/export/last/displayedDestination", "") + .toString(); + if (!command.isEmpty() && command.contains("exportMap")) + return true; + else + return false; +} + +void VymModel::exportLast() +{ + QString desc, command, + dest; // FIXME-3 better integrate configFile into command + if (exportLastAvailable(desc, command, dest)) { + //qDebug() << "VM::exportLast: " << command; + execute(command); + } +} + +void VymModel::exportLaTeX(const QString &fname, bool askName) +{ + ExportLaTeX ex; + ex.setModel(this); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (fname == "") + ex.setFilePath(mapName + ".tex"); + else + ex.setFilePath(fname); + + if (askName) + ex.execDialog(); + if (!ex.canceled()) { + setExportMode(true); + ex.doExport(); + setExportMode(false); + } +} + +void VymModel::exportOrgMode(const QString &fname, bool askName) +{ + ExportOrgMode ex; + ex.setModel(this); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (fname == "") + ex.setFilePath(mapName + ".org"); + else + ex.setFilePath(fname); + + if (askName) { + ex.setDirPath(lastExportDir.absolutePath()); + ex.execDialog(); + } + + if (!ex.canceled()) { + setExportMode(true); + ex.doExport(); + setExportMode(false); + } +} + +void VymModel::exportMarkdown(const QString &fname, bool askName) +{ + ExportMarkdown ex; + ex.setModel(this); + ex.setLastCommand( + settings.localValue(filePath, "/export/last/command", "").toString()); + + if (fname == "") + ex.setFilePath(mapName + ".md"); + else + ex.setFilePath(fname); + + if (askName) { + ex.setDirPath(lastExportDir.absolutePath()); + ex.execDialog(); + } + + if (!ex.canceled()) { + setExportMode(true); + ex.doExport(); + setExportMode(false); + } +} +////////////////////////////////////////////// +// View related +////////////////////////////////////////////// + +void VymModel::registerMapEditor(QWidget *e) { mapEditor = (MapEditor *)e; } + +void VymModel::setMapZoomFactor(const double &d) +{ + zoomFactor = d; + mapEditor->setZoomFactorTarget(d); +} + +void VymModel::setMapRotationAngle(const double &d) +{ + rotationAngle = d; + mapEditor->setAngleTarget(d); +} + +void VymModel::setMapAnimDuration(const int &d) { animDuration = d; } + +void VymModel::setMapAnimCurve(const QEasingCurve &c) { animCurve = c; } + +bool VymModel::centerOnID(const QString &id) +{ + TreeItem *ti = findUuid(QUuid(id)); + if (ti) { + LinkableMapObj *lmo = ((MapItem *)ti)->getLMO(); + if (zoomFactor > 0 && lmo) { + mapEditor->setViewCenterTarget(lmo->getBBox().center(), zoomFactor, + rotationAngle, animDuration, + animCurve); + return true; + } + } + return false; +} + +void VymModel::setContextPos(QPointF p) +{ + contextPos = p; + hasContextPos = true; +} + +void VymModel::unsetContextPos() +{ + contextPos = QPointF(); + hasContextPos = false; +} + +void VymModel::reposition() +{ + if (repositionBlocked) + return; + + BranchObj *bo; + for (int i = 0; i < rootItem->branchCount(); i++) { + bo = rootItem->getBranchObjNum(i); + if (bo) + bo->reposition(); // for positioning heading + else + qDebug() << "VM::reposition bo=0"; + } + mapEditor->getTotalBBox(); + + // required to *reposition* the selection box. size is already correct: + emitSelectionChanged(); //FIXME-2 better only update selection geometry +} + +bool VymModel::setMapLinkStyle(const QString &s) +{ + QString snow; + switch (linkstyle) { + case LinkableMapObj::Line: + snow = "StyleLine"; + break; + case LinkableMapObj::Parabel: + snow = "StyleParabel"; + break; + case LinkableMapObj::PolyLine: + snow = "StylePolyLine"; + break; + case LinkableMapObj::PolyParabel: + snow = "StylePolyParabel"; + break; + default: + return false; + break; + } + + saveState(QString("setMapLinkStyle (\"%1\")").arg(s), + QString("setMapLinkStyle (\"%1\")").arg(snow), + QString("Set map link style (\"%1\")").arg(s)); + + if (s == "StyleLine") + linkstyle = LinkableMapObj::Line; + else if (s == "StyleParabel") + linkstyle = LinkableMapObj::Parabel; + else if (s == "StylePolyLine") + linkstyle = LinkableMapObj::PolyLine; + else if (s == "StylePolyParabel") + linkstyle = LinkableMapObj::PolyParabel; + else + linkstyle = LinkableMapObj::UndefinedStyle; + + BranchItem *cur = NULL; + BranchItem *prev = NULL; + BranchObj *bo; + nextBranch(cur, prev); + while (cur) { + bo = (BranchObj *)(cur->getLMO()); + bo->setLinkStyle(bo->getDefLinkStyle( + cur->parent())); // FIXME-4 better emit dataCHanged and leave the + // changes to View + nextBranch(cur, prev); + } + reposition(); + return true; +} + +LinkableMapObj::Style VymModel::getMapLinkStyle() { return linkstyle; } + +uint VymModel::getModelID() { return modelID; } + +void VymModel::setView(VymView *vv) { vymView = vv; } + +void VymModel::setMapDefLinkColor(QColor col) +{ + if (!col.isValid()) + return; + saveState( + QString("setMapDefLinkColor (\"%1\")").arg(getMapDefLinkColor().name()), + QString("setMapDefLinkColor (\"%1\")").arg(col.name()), + QString("Set map link color to %1").arg(col.name())); + + defLinkColor = col; + + // Set color for "link arrows" in TreeEditor + vymView->setLinkColor(col); + + BranchItem *cur = NULL; + BranchItem *prev = NULL; + BranchObj *bo; + nextBranch(cur, prev); + while (cur) { + bo = (BranchObj *)(cur->getLMO()); + bo->setLinkColor(); + + for (int i = 0; i < cur->imageCount(); ++i) + cur->getImageNum(i)->getLMO()->setLinkColor(); + + nextBranch(cur, prev); + } + updateActions(); +} + +void VymModel::setMapLinkColorHintInt() +{ + // called from setMapLinkColorHint(lch) or at end of parse + BranchItem *cur = NULL; + BranchItem *prev = NULL; + BranchObj *bo; + nextBranch(cur, prev); + while (cur) { + bo = (BranchObj *)(cur->getLMO()); + bo->setLinkColor(); + + for (int i = 0; i < cur->imageCount(); ++i) + cur->getImageNum(i)->getLMO()->setLinkColor(); + + nextBranch(cur, prev); + } +} + +void VymModel::setMapLinkColorHint(LinkableMapObj::ColorHint lch) +{ + linkcolorhint = lch; + setMapLinkColorHintInt(); +} + +void VymModel::toggleMapLinkColorHint() +{ + if (linkcolorhint == LinkableMapObj::HeadingColor) + linkcolorhint = LinkableMapObj::DefaultColor; + else + linkcolorhint = LinkableMapObj::HeadingColor; + BranchItem *cur = NULL; + BranchItem *prev = NULL; + BranchObj *bo; + nextBranch(cur, prev); + while (cur) { + bo = (BranchObj *)(cur->getLMO()); + bo->setLinkColor(); + + for (int i = 0; i < cur->imageCount(); ++i) + cur->getImageNum(i)->getLMO()->setLinkColor(); + + nextBranch(cur, prev); + } +} + +void VymModel:: + selectMapBackgroundImage() // FIXME-3 for using background image: + // view.setCacheMode(QGraphicsView::CacheBackground); + // Also this belongs into ME +{ + QStringList filters; + filters << tr("Images") + + " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif *.pnm)"; + QFileDialog fd; + fd.setFileMode(QFileDialog::ExistingFile); + fd.setWindowTitle(vymName + " - " + tr("Load background image")); + fd.setDirectory(lastImageDir); + fd.setAcceptMode(QFileDialog::AcceptOpen); + + if (fd.exec() == QDialog::Accepted && !fd.selectedFiles().isEmpty()) { + // TODO selectMapBackgroundImg in QT4 use: lastImageDir=fd.directory(); + lastImageDir = QDir(fd.directory().path()); + setMapBackgroundImage(fd.selectedFiles().first()); + } +} + +void VymModel::setMapBackgroundImage( + const QString &fn) // FIXME-3 missing savestate, move to ME +{ + /* + QColor oldcol=mapEditor->getScene()->backgroundBrush().color(); + saveState( + selection, + QString ("setMapBackgroundImage (%1)").arg(oldcol.name()), + selection, + QString ("setMapBackgroundImage (%1)").arg(col.name()), + QString("Set background color of map to %1").arg(col.name())); + */ + QBrush brush; + brush.setTextureImage(QImage(fn)); + mapEditor->getScene()->setBackgroundBrush(brush); +} + +void VymModel::selectMapBackgroundColor() +{ + QColor col = QColorDialog::getColor( + mapEditor->getScene()->backgroundBrush().color(), NULL); + if (!col.isValid()) + return; + setMapBackgroundColor(col); +} + +void VymModel::setMapBackgroundColor(QColor col) +{ + QColor oldcol = mapEditor->getScene()->backgroundBrush().color(); + saveState(QString("setMapBackgroundColor (\"%1\")").arg(oldcol.name()), + QString("setMapBackgroundColor (\"%1\")").arg(col.name()), + QString("Set background color of map to %1").arg(col.name())); + backgroundColor = col; // Used for backroundRole in TreeModel::data() + vymView->setBackgroundColor(backgroundColor); +} + +QColor VymModel::getMapBackgroundColor() // FIXME-4 move to ME +{ + return mapEditor->getScene()->backgroundBrush().color(); +} + +QFont VymModel::getMapDefaultFont() { return defaultFont; } + +void VymModel::setMapDefaultFont(const QFont &f) { defaultFont = f; } + +LinkableMapObj::ColorHint VymModel::getMapLinkColorHint() // FIXME-4 move to ME +{ + return linkcolorhint; +} + +QColor VymModel::getMapDefLinkColor() // FIXME-4 move to ME +{ + return defLinkColor; +} + +void VymModel::setMapDefXLinkPen(const QPen &p) // FIXME-4 move to ME +{ + defXLinkPen = p; +} + +QPen VymModel::getMapDefXLinkPen() // FIXME-4 move to ME +{ + return defXLinkPen; +} + +void VymModel::setMapDefXLinkStyleBegin(const QString &s) +{ + defXLinkStyleBegin = s; +} + +QString VymModel::getMapDefXLinkStyleBegin() { return defXLinkStyleBegin; } + +void VymModel::setMapDefXLinkStyleEnd(const QString &s) +{ + defXLinkStyleEnd = s; +} + +QString VymModel::getMapDefXLinkStyleEnd() { return defXLinkStyleEnd; } + +void VymModel::move(const double &x, const double &y) +{ + MapItem *seli = (MapItem *)getSelectedItem(); + if (seli && + (seli->isBranchLikeType() || seli->getType() == TreeItem::Image)) { + LinkableMapObj *lmo = seli->getLMO(); + if (lmo) { + QPointF ap(lmo->getAbsPos()); + QPointF to(x, y); + if (ap != to) { + QString ps = qpointFToString(ap); + QString s = getSelectString(seli); + saveState( + s, "move " + ps, s, "move " + qpointFToString(to), + QString("Move %1 to %2").arg(getObjectName(seli)).arg(ps)); + lmo->move(x, y); + reposition(); + emitSelectionChanged(); + } + } + } +} + +void VymModel::moveRel(const double &x, const double &y) +{ + MapItem *seli = (MapItem *)getSelectedItem(); + if (seli && + (seli->isBranchLikeType() || seli->getType() == TreeItem::Image)) { + LinkableMapObj *lmo = seli->getLMO(); + if (lmo) { + QPointF rp(lmo->getRelPos()); + QPointF to(x, y); + if (rp != to) { + QString ps = qpointFToString(lmo->getRelPos()); + QString s = getSelectString(seli); + saveState(s, "moveRel " + ps, s, + "moveRel " + qpointFToString(to), + QString("Move %1 to relative position %2") + .arg(getObjectName(seli)) + .arg(ps)); + ((OrnamentedObj *)lmo)->move2RelPos(x, y); + reposition(); + lmo->updateLinkGeometry(); + emitSelectionChanged(); + } + } + } +} + +void VymModel::animate() +{ + animationTimer->stop(); + BranchObj *bo; + int i = 0; + while (i < animObjList.size()) { + bo = (BranchObj *)animObjList.at(i); + if (!bo->animate()) { + if (i >= 0) { + animObjList.removeAt(i); + i--; + } + } + bo->reposition(); + i++; + } + emitSelectionChanged(); + + if (!animObjList.isEmpty()) + animationTimer->start(animationInterval); +} + +void VymModel::startAnimation(BranchObj *bo, const QPointF &v) +{ + if (!bo) + return; + + if (bo->getUseRelPos()) + startAnimation(bo, bo->getRelPos(), bo->getRelPos() + v); + else + startAnimation(bo, bo->getAbsPos(), bo->getAbsPos() + v); +} + +void VymModel::startAnimation(BranchObj *bo, const QPointF &start, + const QPointF &dest) +{ + if (start == dest) + return; + if (bo && bo->getTreeItem()->depth() >= 0) { + AnimPoint ap; + ap.setStart(start); + ap.setDest(dest); + ap.setTicks(animationTicks); + ap.setAnimated(true); + bo->setAnimation(ap); + if (!animObjList.contains(bo)) + animObjList.append(bo); + animationTimer->setSingleShot(true); + animationTimer->start(animationInterval); + } +} + +void VymModel::stopAnimation(MapObj *mo) +{ + int i = animObjList.indexOf(mo); + if (i >= 0) + animObjList.removeAt(i); +} + +void VymModel::stopAllAnimation() +{ + BranchObj *bo; + int i = 0; + while (i < animObjList.size()) { + bo = (BranchObj *)animObjList.at(i); + bo->stopAnimation(); + bo->requestReposition(); + i++; + } + reposition(); +} + +void VymModel::sendSelection() +{ + if (netstate != Server) + return; + sendData(QString("select (\"%1\")").arg(getSelectString())); +} + +void VymModel::newServer() +{ + port = 54321; + sendCounter = 0; + tcpServer = new QTcpServer(this); + if (!tcpServer->listen(QHostAddress::Any, port)) { + QMessageBox::critical(NULL, "vym server", + QString("Unable to start the server: %1.") + .arg(tcpServer->errorString())); + // FIXME-3 needed? we are no widget any longer... close(); + return; + } + connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newClient())); + netstate = Server; + qDebug() << "Server is running on port " << tcpServer->serverPort(); +} + +void VymModel::connectToServer() +{ + port = 54321; + server = "salam.suse.de"; + server = "localhost"; + clientSocket = new QTcpSocket(this); + clientSocket->abort(); + clientSocket->connectToHost(server, port); + connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readData())); + connect(clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, + SLOT(displayNetworkError(QAbstractSocket::SocketError))); + netstate = Client; + qDebug() << "connected to " << qPrintable(server) << " port " << port; +} + +void VymModel::newClient() +{ + QTcpSocket *newClient = tcpServer->nextPendingConnection(); + connect(newClient, SIGNAL(disconnected()), newClient, SLOT(deleteLater())); + + qDebug() << "ME::newClient at " + << qPrintable(newClient->peerAddress().toString()); + + clientList.append(newClient); +} + +void VymModel::sendData(const QString &s) +{ + if (clientList.size() == 0) + return; + + // Create bytearray to send + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_4_0); + + // Reserve some space for blocksize + out << (quint16)0; + + // Write sendCounter + out << sendCounter++; + + // Write data + out << s; + + // Go back and write blocksize so far + out.device()->seek(0); + quint16 bs = (quint16)(block.size() - 2 * sizeof(quint16)); + out << bs; + + if (debug) + qDebug() << "ME::sendData bs=" << bs << " counter=" << sendCounter + << " s=" << qPrintable(s); + + for (int i = 0; i < clientList.size(); ++i) { + // qDebug() << "Sending \""<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 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(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(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 VymModel::getSelectedBranches() +{ + QList 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 VymModel::getSelectedItems() +{ + QList 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 VymModel::getSelectedIDs() +{ + QList 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 = "" + si->saveToDir() + ""; + 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 = "" + si->saveToDir() + ""; + 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(ix.internalPointer()); + QString inScript = si->getInScript(); + + // show inScript in ScriptEditor + scriptEditor->setSlideScript(modelID, si->getID(), inScript); + + // Execute inScript + execute(inScript); + } +}