X-Git-Url: https://git.sven.stormbind.net/?a=blobdiff_plain;f=src%2Fmapeditor.cpp;fp=src%2Fmapeditor.cpp;h=4da600b775e2784ce1c0361785924e2d159d40b2;hb=d483bd8e6523c23c6f1d8908a2e0611c2bc9ff4f;hp=0000000000000000000000000000000000000000;hpb=7dfa3fe589d1722d49681f42cdb0bf1e6efb5223;p=sven%2Fvym.git diff --git a/src/mapeditor.cpp b/src/mapeditor.cpp new file mode 100644 index 0000000..4da600b --- /dev/null +++ b/src/mapeditor.cpp @@ -0,0 +1,2366 @@ +#include "mapeditor.h" + +#include +#include +#include +#include +#include +#include + +#include "branchitem.h" +#include "geometry.h" +#include "mainwindow.h" +#include "misc.h" +#include "shortcuts.h" +#include "warningdialog.h" +#include "winter.h" +#include "xlinkitem.h" + +extern Main *mainWindow; +extern QString clipboardDir; +extern QString clipboardFile; +extern bool debug; +extern QPrinter *printer; + +extern QMenu *branchContextMenu; +extern QMenu *canvasContextMenu; +extern QMenu *floatimageContextMenu; +extern QMenu *taskContextMenu; + +extern Switchboard switchboard; +extern Settings settings; + +extern QTextStream vout; + +extern QString editorFocusStyle; + +extern FlagRowMaster *systemFlagsMaster; + +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// +MapEditor::MapEditor(VymModel *vm) +{ + // qDebug() << "Constructor ME " << this; + + QString shortcutScope = tr("Map Editor", "Shortcut scope"); + mapScene = new QGraphicsScene(NULL); + mapScene->setBackgroundBrush(QBrush(Qt::white, Qt::SolidPattern)); + + zoomFactor = zoomFactorTarget = 1; + angle = angleTarget = 0; + + model = vm; + model->registerMapEditor(this); + + setScene(mapScene); + + setStyleSheet("QGraphicsView:focus {" + editorFocusStyle + "}"); + + // Create bitmap cursors, platform dependant + HandOpenCursor = QCursor(QPixmap(":/mode-move-view.png"), 1, 1); + PickColorCursor = QCursor(QPixmap(":/cursorcolorpicker.png"), 5, 27); + XLinkCursor = QCursor(QPixmap(":/cursorxlink.png"), 1, 7); + + editingBO = NULL; + + printFrame = true; + printFooter = true; + + setAcceptDrops(true); + + // Shortcuts and actions + QAction *a; + + a = new QAction("Select upper branch", this); + a->setShortcut(Qt::Key_Up); + a->setShortcutContext(Qt::WidgetShortcut); + connect(a, SIGNAL(triggered()), this, SLOT(cursorUp())); + addAction(a); + + a = new QAction("Add upper branch to selection", this); + a->setShortcut(Qt::Key_Up + Qt::SHIFT); + a->setShortcutContext(Qt::WidgetShortcut); + addAction(a); + connect(a, SIGNAL(triggered()), this, SLOT(cursorUpToggleSelection())); + + a = new QAction("Select lower branch", this); + a->setShortcut(Qt::Key_Down); + a->setShortcutContext(Qt::WidgetShortcut); + addAction(a); + connect(a, SIGNAL(triggered()), this, SLOT(cursorDown())); + + a = new QAction("Add lower branch to selection", this); + a->setShortcut(Qt::Key_Down + Qt::SHIFT); + a->setShortcutContext(Qt::WidgetShortcut); + addAction(a); + connect(a, SIGNAL(triggered()), this, SLOT(cursorDownToggleSelection())); + + a = new QAction("Select left branch", this); + a->setShortcut(Qt::Key_Left); + // a->setShortcutContext (Qt::WidgetWithChildrenShortcut); + addAction(a); + connect(a, SIGNAL(triggered()), this, SLOT(cursorLeft())); + + a = new QAction("Select child branch", this); + a->setShortcut(Qt::Key_Right); + // a->setShortcutContext (Qt::WidgetWithChildrenShortcut); + addAction(a); + connect(a, SIGNAL(triggered()), this, SLOT(cursorRight())); + + a = new QAction("Select first branch", this); + a->setShortcut(Qt::Key_Home); + a->setShortcutContext(Qt::WidgetWithChildrenShortcut); + addAction(a); + connect(a, SIGNAL(triggered()), this, SLOT(cursorFirst())); + + a = new QAction("Select last branch", this); + a->setShortcut(Qt::Key_End); + a->setShortcutContext(Qt::WidgetWithChildrenShortcut); + addAction(a); + connect(a, SIGNAL(triggered()), this, SLOT(cursorLast())); + + // Action to embed LineEdit for heading in Scene + lineEdit = NULL; + + a = new QAction(tr("Edit heading", "MapEditor"), this); + a->setShortcut(Qt::Key_Return); // Edit heading + a->setShortcutContext(Qt::WidgetShortcut); + addAction(a); + connect(a, SIGNAL(triggered()), this, SLOT(editHeading())); + a = new QAction(tr("Edit heading", "MapEditor"), this); + a->setShortcut(Qt::Key_Enter); // Edit heading + a->setShortcutContext(Qt::WidgetShortcut); + addAction(a); + connect(a, SIGNAL(triggered()), this, SLOT(editHeading())); + + // Selections + selectionPen = QPen(QColor(255,255,0), 1); + selectionBrush = QBrush(QColor(255,255,0)); + + // Panning + panningTimer = new QTimer(this); + vPan = QPointF(); + connect(panningTimer, SIGNAL(timeout()), this, SLOT(panView())); + + // Clone actions defined in MainWindow + foreach (QAction *qa, mainWindow->mapEditorActions) { + a = new QAction(this); + a->setShortcut(qa->shortcut()); + a->setShortcutContext(qa->shortcutContext()); + connect(a, SIGNAL(triggered()), qa, SLOT(trigger())); + addAction(a); + } + + setState(Neutral); + + winter = NULL; +} + +MapEditor::~MapEditor() +{ + // qDebug ()<<"Destr MapEditor this="< 0) + px = width() + vPan.x(); + if (vPan.y() < 0) + py = vPan.y(); + else if (vPan.y() > 0) + py = height() + vPan.y(); + + QPointF q = mapToScene(QPoint(px, py)); + QRectF r = QRectF(q, QPointF(q.x() + 1, q.y() + 1)); + + // Expand view if necessary + setScrollBarPosTarget(r); // FIXME-2 mapToScene first? + + // Stop possible other animations + if (scrollBarPosAnimation.state() == QAbstractAnimation::Running) + scrollBarPosAnimation.stop(); + + // Do linear animation + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + + vPan.x()); + verticalScrollBar()->setValue(verticalScrollBar()->value() + vPan.y()); + + // Update currently moving object + moveObject(); + } +} + +void MapEditor::ensureAreaVisibleAnimated(const QRectF &area, bool maximizeArea) // FIXME-2 zooming in not working yet (fit to selection) +{ + // Changes viewCenter to make sure that + // r is within the margins of the viewport + // + // Only zooms, if r NOT fit into viewport + // view is centered then on bounding box. + // + // Similar to QGraphicsItem::ensureVisible, + // but with animation and (if necessary) + // zooming + + int xmargin = settings.value("/mapeditor/scrollToMarginX/", 50).toInt(); + int ymargin = settings.value("/mapeditor/scrollToMarginY/", 50).toInt(); + + // Do we need to zoom out to show area? + QRect areaViewCoord = mapFromScene(area).boundingRect(); + + // Visible area within margins + QRect visibleViewCoord = rect(); + visibleViewCoord -= QMargins(xmargin, ymargin, xmargin, ymargin); + + + // Calculate required width and height considering rotation of view + qreal a = angle / 180 * M_PI; + qreal area_w_viewCoord = abs(sin(a) * area.height()) + abs(cos(a) * area.width()); + qreal area_h_viewCoord = abs(sin(a) * area.width()) + abs(cos(a) * area.height()); + qreal z_x = 1.0 * visibleViewCoord.width() / area_w_viewCoord; + qreal z_y = 1.0 * visibleViewCoord.height() / area_h_viewCoord; + + qreal zf = min (z_x, z_y); + + bool zoomOutRequired = + (visibleViewCoord.width() < areaViewCoord.width() || + visibleViewCoord.height() < areaViewCoord.height()); + bool zoomInRequired = + (visibleViewCoord.width() > areaViewCoord.width() && + visibleViewCoord.height() > areaViewCoord.height()); + + //qDebug() << " zoom out: " << zoomOutRequired; + //qDebug() << " zoom in: " << zoomInRequired << " zoomFactor=" << zoomFactor << " zf=" << zf; + if (zoomOutRequired || maximizeArea) { + setViewCenterTarget(area.center(), zf, angle); + return; + } + + + // After zooming bbox would fit into margins of viewport + long view_dx = 0; + long view_dy = 0; + if (areaViewCoord.left() < xmargin) + // move left + view_dx = areaViewCoord.left() - xmargin; + else if (areaViewCoord.right() > viewport()->width()) + // move right + view_dx = areaViewCoord.x() + areaViewCoord.width() - viewport()->width() + xmargin; + + if (areaViewCoord.top() < ymargin) + // move up + view_dy = areaViewCoord.top() - ymargin; + else if (areaViewCoord.bottom() > viewport()->height() - ymargin) + // move down + view_dy = areaViewCoord.y() + areaViewCoord.height() - viewport()->height() + ymargin; + + if (abs(view_dx) > 5 || abs(view_dy) > 5) + setViewCenterTarget( + mapToScene(viewport()->geometry().center() + QPoint (view_dx, view_dy)), + zoomFactor, + angle, + 2000, + QEasingCurve::OutQuint); +} + +void MapEditor::ensureSelectionVisibleAnimated(bool maximizeArea) +{ + // Changes viewCenter to make sure that bounding box of all currently + // selected items is within the margins of the viewport + // + // Only zooms, if bounding box of items does NOT fit into viewport + // view is centered then on bounding box. (Useful also for big images) + // + // Similar to QGraphicsItem::ensureVisible, but with animation and (if necessary) + // zooming + + QList selis = model->getSelectedItems(); + + // Nothing to do, if nothing is selected + if (selis.isEmpty()) return; + + // Calculate total bounding box + QRectF bbox; + bool firstIteration = true; + + foreach (TreeItem *ti, selis) { + LinkableMapObj *lmo = nullptr; + if (ti->getType() == TreeItem::Image || ti->isBranchLikeType()) + lmo = ((MapItem *)ti)->getLMO(); + if (lmo) { + if (firstIteration) { + bbox = lmo->getBBox(); + firstIteration = false; + } else + bbox = bbox.united(lmo->getBBox()); + } + } + + ensureAreaVisibleAnimated(bbox, maximizeArea); +} + +void MapEditor::scrollTo(const QModelIndex &index) +{ + if (index.isValid()) { + LinkableMapObj *lmo = NULL; + TreeItem *ti = static_cast(index.internalPointer()); + if (ti->getType() == TreeItem::Image || ti->isBranchLikeType()) + lmo = ((MapItem *)ti)->getLMO(); + if (lmo) { + QRectF r = lmo->getBBox(); + setScrollBarPosTarget(r); + animateScrollBars(); + } + } +} + +void MapEditor::setScrollBarPosTarget(QRectF rect) +{ + // Expand viewport, if rect is not contained + if (!sceneRect().contains(rect)) + setSceneRect(sceneRect().united(rect)); + + int xmargin = settings.value("/mapeditor/scrollToMarginX/", 80).toInt(); + int ymargin = settings.value("/mapeditor/scrollToMarginX/", 80).toInt(); + + // Prepare scrolling + qreal width = viewport()->width(); + qreal height = viewport()->height(); + QRectF viewRect = transform().scale(zoomFactorTarget, zoomFactorTarget).mapRect(rect); + + qreal left = horizontalScrollBar()->value(); + qreal right = left + width; + qreal top = verticalScrollBar()->value(); + qreal bottom = top + height; + + scrollBarPosTarget = getScrollBarPos(); + + if (viewRect.left() <= left + xmargin) { + // need to scroll from the left + scrollBarPosTarget.setX(int(viewRect.left() - xmargin - 0.5)); + } + if (viewRect.right() >= right - xmargin) { + // need to scroll from the right + scrollBarPosTarget.setX(int(viewRect.right() - width + xmargin + 0.5)); + } + if (viewRect.top() <= top + ymargin) { + // need to scroll from the top + scrollBarPosTarget.setY(int(viewRect.top() - ymargin - 0.5)); + } + if (viewRect.bottom() >= bottom - ymargin) { + // need to scroll from the bottom + scrollBarPosTarget.setY( + int(viewRect.bottom() - height + ymargin + 0.5)); + } +} + +QPointF MapEditor::getScrollBarPosTarget() { return scrollBarPosTarget; } + +void MapEditor::setScrollBarPos(const QPointF &p) +{ + scrollBarPos = p; + horizontalScrollBar()->setValue(int(p.x())); + verticalScrollBar()->setValue(int(p.y())); +} + +QPointF MapEditor::getScrollBarPos() +{ + return QPointF(horizontalScrollBar()->value(), + verticalScrollBar()->value()); + // return scrollBarPos; +} + +void MapEditor::animateScrollBars() +{ + if (scrollBarPosAnimation.state() == QAbstractAnimation::Running) + scrollBarPosAnimation.stop(); + + if (settings.value("/animation/use/", true).toBool()) { + scrollBarPosAnimation.setTargetObject(this); + scrollBarPosAnimation.setPropertyName("scrollBarPos"); + scrollBarPosAnimation.setDuration( + settings.value("/animation/duration/scrollbar", 2000).toInt()); + scrollBarPosAnimation.setEasingCurve(QEasingCurve::OutQuint); + scrollBarPosAnimation.setStartValue(QPointF( + horizontalScrollBar()->value(), verticalScrollBar()->value())); + scrollBarPosAnimation.setEndValue(scrollBarPosTarget); + scrollBarPosAnimation.start(); + } + else + setScrollBarPos(scrollBarPosTarget); +} + +void MapEditor::setZoomFactorTarget(const qreal &zft) +{ + zoomFactorTarget = zft; + if (zoomAnimation.state() == QAbstractAnimation::Running) + zoomAnimation.stop(); + if (settings.value("/animation/use/", true).toBool()) { + zoomAnimation.setTargetObject(this); + zoomAnimation.setPropertyName("zoomFactor"); + zoomAnimation.setDuration( + settings.value("/animation/duration/zoom", 2000).toInt()); + zoomAnimation.setEasingCurve(QEasingCurve::OutQuint); + zoomAnimation.setStartValue(zoomFactor); + zoomAnimation.setEndValue(zft); + zoomAnimation.start(); + } + else + setZoomFactor(zft); +} + +qreal MapEditor::getZoomFactorTarget() { return zoomFactorTarget; } + +void MapEditor::setZoomFactor(const qreal &zf) +{ + zoomFactor = zf; + updateMatrix(); +} + +qreal MapEditor::getZoomFactor() { return zoomFactor; } + +void MapEditor::setAngleTarget(const qreal &at) +{ + angleTarget = at; + if (rotationAnimation.state() == QAbstractAnimation::Running) + rotationAnimation.stop(); + if (settings.value("/animation/use/", true).toBool()) { + rotationAnimation.setTargetObject(this); + rotationAnimation.setPropertyName("angle"); + rotationAnimation.setDuration( + settings.value("/animation/duration/rotation", 2000).toInt()); + rotationAnimation.setEasingCurve(QEasingCurve::OutQuint); + rotationAnimation.setStartValue(angle); + rotationAnimation.setEndValue(at); + rotationAnimation.start(); + } + else + setAngle(angleTarget); +} + +qreal MapEditor::getAngleTarget() { return angleTarget; } + +void MapEditor::setAngle(const qreal &a) +{ + angle = a; + updateMatrix(); + if (winter) + winter->updateView(); +} + +qreal MapEditor::getAngle() { return angle; } + +void MapEditor::setViewCenterTarget(const QPointF &p, const qreal &zft, + const qreal &at, const int duration, + const QEasingCurve &easingCurve) +{ + viewCenterTarget = p; + zoomFactorTarget = zft; + angleTarget = at; + + viewCenter = mapToScene(viewport()->geometry()).boundingRect().center(); + + if (viewCenterAnimation.state() == QAbstractAnimation::Running) + viewCenterAnimation.stop(); + if (rotationAnimation.state() == QAbstractAnimation::Running) + rotationAnimation.stop(); + if (zoomAnimation.state() == QAbstractAnimation::Running) + zoomAnimation.stop(); + + if (settings.value("/animation/use/", true).toBool()) { + viewCenterAnimation.setTargetObject(this); + viewCenterAnimation.setPropertyName("viewCenter"); + viewCenterAnimation.setDuration( + settings.value("/animation/duration/scrollbar", duration).toInt()); + viewCenterAnimation.setEasingCurve(easingCurve); + viewCenterAnimation.setStartValue(viewCenter); + viewCenterAnimation.setEndValue(viewCenterTarget); + viewCenterAnimation.start(); + + rotationAnimation.setTargetObject(this); + rotationAnimation.setPropertyName("angle"); + rotationAnimation.setDuration( + settings.value("/animation/duration/rotation", duration).toInt()); + rotationAnimation.setEasingCurve(easingCurve); + rotationAnimation.setStartValue(angle); + rotationAnimation.setEndValue(angleTarget); + rotationAnimation.start(); + + zoomAnimation.setTargetObject(this); + zoomAnimation.setPropertyName("zoomFactor"); + zoomAnimation.setDuration( + settings.value("/animation/duration/zoom", duration).toInt()); + zoomAnimation.setEasingCurve(easingCurve); + zoomAnimation.setStartValue(zoomFactor); + zoomAnimation.setEndValue(zoomFactorTarget); + zoomAnimation.start(); + } + else { + setAngle(angleTarget); + setZoomFactor(zft); + setViewCenter(viewCenterTarget); + } +} + +void MapEditor::setViewCenterTarget() +{ + MapItem *selti = (MapItem *)(model->getSelectedItem()); + if (selti) { + LinkableMapObj *lmo = selti->getLMO(); + if (lmo) + setViewCenterTarget(lmo->getBBox().center(), 1, 0); + } +} + +QPointF MapEditor::getViewCenterTarget() { return viewCenterTarget; } + +void MapEditor::setViewCenter(const QPointF &vc) { centerOn(vc); } + +QPointF MapEditor::getViewCenter() { return viewCenter; } + +void MapEditor::updateMatrix() +{ + QTransform t_zoom; + t_zoom.scale(zoomFactor, zoomFactor); + QTransform t_rot; + t_rot.rotate(angle); + setTransform(t_zoom * t_rot); +} + +void MapEditor::minimizeView() { + // If we only would set scene rectangle to existing items, then + // view fould "jump", when Qt automatically tries to center. + // Better consider the currently visible viewport (with slight offset) + QRectF r = mapToScene(viewport()->geometry()).boundingRect(); + r.translate(-2,-3); + setSceneRect(scene()->itemsBoundingRect().united(r)); +} + +void MapEditor::print() +{ + QRectF totalBBox = getTotalBBox(); + + if (!printer) + printer = mainWindow->setupPrinter(); + + // Try to set orientation automagically + // Note: Interpretation of generated postscript is amibiguous, if + // there are problems with landscape mode, see + // http://sdb.suse.de/de/sdb/html/jsmeix_print-cups-landscape-81.html + + if (totalBBox.width() > totalBBox.height()) + // recommend landscape + printer->setPageOrientation(QPageLayout::Landscape); + else + // recommend portrait + printer->setPageOrientation(QPageLayout::Portrait); + + QPrintDialog dialog(printer, this); + dialog.setWindowTitle(tr("Print vym map", "MapEditor")); + if (dialog.exec() == QDialog::Accepted) { + QPainter pp(printer); + + pp.setRenderHint(QPainter::Antialiasing, true); + + // Don't print the visualisation of selection + model->unselectAll(); + + QRectF mapRect = totalBBox; + QGraphicsRectItem *frame = NULL; + + if (printFrame) { + // Print frame around map + mapRect.setRect(totalBBox.x() - 10, totalBBox.y() - 10, + totalBBox.width() + 20, totalBBox.height() + 20); + frame = mapScene->addRect(mapRect, QPen(Qt::black), + QBrush(Qt::NoBrush)); + frame->setZValue(0); + frame->show(); + } + + double paperAspect = + (double)printer->width() / (double)printer->height(); + double mapAspect = (double)mapRect.width() / (double)mapRect.height(); + int viewBottom; + if (mapAspect >= paperAspect) { + // Fit horizontally to paper width + // pp.setViewport(0,0, + // printer->width(),(int)(printer->width()/mapAspect) ); + viewBottom = (int)(printer->width() / mapAspect); + } + else { + // Fit vertically to paper height + // pp.setViewport(0,0,(int)(printer->height()*mapAspect),printer->height()); + viewBottom = printer->height(); + } + + if (printFooter) { + // Print footer below map + QFont font; + font.setPointSize(10); + pp.setFont(font); + QRectF footerBox(0, viewBottom, printer->width(), 15); + pp.drawText(footerBox, Qt::AlignLeft, + "VYM - " + model->getFileName()); + pp.drawText(footerBox, Qt::AlignRight, + QDate::currentDate().toString(Qt::TextDate)); + } + mapScene->render(&pp, + QRectF(0, 0, printer->width(), printer->height() - 15), + QRectF(mapRect.x(), mapRect.y(), mapRect.width(), + mapRect.height())); + + // Viewport has paper dimension + if (frame) + delete (frame); + + // Restore selection + model->reselect(); + } +} + +QRectF MapEditor::getTotalBBox() // FIXME-2 really needed? Overlaps with scene and VM... +{ + minimizeView(); + return sceneRect(); +} + +QImage MapEditor::getImage(QPointF &offset) +{ + QRectF mapRect = getTotalBBox(); // minimized sceneRect + + int d = 10; // border + offset = QPointF(mapRect.x() - d / 2, mapRect.y() - d / 2); + QImage pix(mapRect.width() + d, mapRect.height() + d, QImage::Format_RGB32); + + QPainter pp(&pix); + pp.setRenderHints(renderHints()); + mapScene->render(&pp, + // Destination: + QRectF(0, 0, mapRect.width() + d, mapRect.height() + d), + // Source in scene: + QRectF(mapRect.x() - d / 2, mapRect.y() - d / 2, + mapRect.width() + d, mapRect.height() + d)); + return pix; +} + +void MapEditor::setAntiAlias(bool b) +{ + setRenderHint(QPainter::Antialiasing, b); +} + +void MapEditor::setSmoothPixmap(bool b) +{ + setRenderHint(QPainter::SmoothPixmapTransform, b); +} + +void MapEditor::autoLayout() +{ + // Create list with all bounding polygons + QList mapobjects; + QList polys; + ConvexPolygon p; + QList vectors; + QList orgpos; + QStringList headings; // FIXME-3 testing only + Vector v; + BranchItem *bi; + BranchItem *bi2; + BranchObj *bo; + + // Outer loop: Iterate until we no more changes in orientation + bool orientationChanged = true; + while (orientationChanged) { + BranchItem *ri = model->getRootItem(); + for (int i = 0; i < ri->branchCount(); ++i) { + bi = ri->getBranchNum(i); + bo = (BranchObj *)bi->getLMO(); + if (bo) { + mapobjects.append(bo); + p = bo->getBoundingPolygon(); + p.calcCentroid(); + polys.append(p); + vectors.append(QPointF(0, 0)); + orgpos.append(p.at(0)); + headings.append(bi->getHeadingPlain()); + } + for (int j = 0; j < bi->branchCount(); ++j) { + bi2 = bi->getBranchNum(j); + bo = (BranchObj *)bi2->getLMO(); + if (bo) { + mapobjects.append(bo); + p = bo->getBoundingPolygon(); + p.calcCentroid(); + polys.append(p); + vectors.append(QPointF(0, 0)); + orgpos.append(p.at(0)); + headings.append(bi2->getHeadingPlain()); + } + } + } + + // Iterate moving bounding polygons until we have no more collisions + int collisions = 1; + while (collisions > 0) { + collisions = 0; + for (int i = 0; i < polys.size() - 1; ++i) { + for (int j = i + 1; j < polys.size(); ++j) { + if (polygonCollision(polys.at(i), polys.at(j), + QPointF(0, 0)) + .intersect) { + collisions++; + if (debug) + qDebug() << "Collision: " << headings[i] << " - " + << headings[j]; + v = polys.at(j).centroid() - polys.at(i).centroid(); + v.normalize(); + // Add random direction, if only two polygons with + // identical y or x + if (v.x() == 0 || v.y() == 0) { + Vector w(cos(double((int)rand() % 1000)), + sin(double((int)rand() % 1000))); + w.normalize(); + v = v + w; + } + + // Scale translation vector by area of polygons + vectors[j] = v * 10000 / polys.at(j).weight(); + vectors[i] = v * 10000 / polys.at(i).weight(); + vectors[i].invert(); + // FIXME-3 outer loop, "i" get's changed several + // times... + // Better not move away from centroid of 2 colliding + // polys, but from centroid of _all_ + } + } + } + for (int i = 0; i < vectors.size(); i++) { + // qDebug() << " v="< orients; + for (int i = 0; i < polys.size(); i++) { + Vector v = polys[i].at(0) - orgpos[i]; + orients.append(mapobjects[i]->getOrientation()); + if (!v.isNull()) { + if (debug) + qDebug() << " Moving " << polys.at(i).weight() << " " + << mapobjects[i]->getAbsPos() << " -> " + << mapobjects[i]->getAbsPos() + v << " " + << headings[i]; + // mapobjects[i]->moveBy(v.x(),v.y() ); + // mapobjects[i]->setRelPos(); + model->startAnimation((BranchObj *)mapobjects[i], v); + if (debug) + qDebug() << i << " Weight: " << polys.at(i).weight() << " " + << v << " " << headings.at(i); + } + } + /* + model->reposition(); + orientationChanged=false; + for (int i=0;igetOrientation()) + { + orientationChanged=true; + break; + } + */ + + break; + + // orientationChanged=false; + } // loop if orientation has changed + + model->emitSelectionChanged(); +} + +TreeItem *MapEditor::findMapItem(QPointF p, TreeItem *exclude) +{ + // Search XLinks + Link *link; + for (int i = 0; i < model->xlinkCount(); i++) { + link = model->getXLinkNum(i); + if (link) { + XLinkObj *xlo = link->getXLinkObj(); + if (xlo && xlo->isInClickBox(p)) { + // Found XLink, now return the nearest XLinkItem of p + qreal d0 = Geometry::distance(p, xlo->getBeginPos()); + qreal d1 = Geometry::distance(p, xlo->getEndPos()); + if (d0 > d1) + return link->getBeginLinkItem(); + else + return link->getEndLinkItem(); + } + } + } + + // Search branches (and their childs, e.g. images + // Start with mapcenter, no images allowed at rootItem + int i = 0; + BranchItem *bi = model->getRootItem()->getFirstBranch(); + TreeItem *found = NULL; + while (bi) { + found = bi->findMapItem(p, exclude); + if (found) + return found; + i++; + bi = model->getRootItem()->getBranchNum(i); + } + return NULL; +} + +void MapEditor::testFunction1() {} + +void MapEditor::testFunction2() { autoLayout(); } + +void MapEditor::toggleWinter() +{ + if (winter) { + delete winter; + winter = NULL; + } + else { + winter = new Winter(this); + QList obstacles; + BranchObj *bo; + BranchItem *cur = NULL; + BranchItem *prev = NULL; + model->nextBranch(cur, prev); + while (cur) { + if (!cur->hasHiddenExportParent()) { + // Branches + bo = (BranchObj *)(cur->getLMO()); + if (bo && bo->isVisibleObj()) + obstacles.append(bo->getBBox()); + } + model->nextBranch(cur, prev); + } + winter->setObstacles(obstacles); + } +} + +BranchItem *MapEditor::getBranchDirectAbove(BranchItem *bi) +{ + if (bi) { + int i = bi->num(); + if (i > 0) + return bi->parent()->getBranchNum(i - 1); + } + return NULL; +} + +BranchItem *MapEditor::getBranchAbove(BranchItem *selbi) +{ + if (selbi) { + int dz = selbi->depth(); // original depth + bool invert = false; + if (selbi->getLMO()->getOrientation() == LinkableMapObj::LeftOfCenter) + invert = true; + + BranchItem *bi; + + // Look for branch with same parent but directly above + if (dz == 1 && invert) + bi = getBranchDirectBelow(selbi); + else + bi = getBranchDirectAbove(selbi); + + if (bi) + // direct predecessor + return bi; + + // Go towards center and look for predecessor + while (selbi->depth() > 0) { + selbi = (BranchItem *)(selbi->parent()); + if (selbi->depth() == 1 && invert) + bi = getBranchDirectBelow(selbi); + else + bi = getBranchDirectAbove(selbi); + if (bi) { + // turn + selbi = bi; + while (selbi->depth() < dz) { + // try to get back to original depth dz + bi = selbi->getLastBranch(); + if (!bi) { + return selbi; + } + selbi = bi; + } + return selbi; + } + } + } + return NULL; +} + +BranchItem *MapEditor::getBranchDirectBelow(BranchItem *bi) +{ + if (bi) { + int i = bi->num(); + if (i + 1 < bi->parent()->branchCount()) + return bi->parent()->getBranchNum(i + 1); + } + return NULL; +} + +BranchItem *MapEditor::getBranchBelow(BranchItem *selbi) +{ + if (selbi) { + BranchItem *bi; + int dz = selbi->depth(); // original depth + bool invert = false; + if (selbi->getLMO()->getOrientation() == LinkableMapObj::LeftOfCenter) + invert = true; + + // Look for branch with same parent but directly below + if (dz == 1 && invert) + bi = getBranchDirectAbove(selbi); + else + bi = getBranchDirectBelow(selbi); + if (bi) + // direct successor + return bi; + + // Go towards center and look for neighbour + while (selbi->depth() > 0) { + selbi = (BranchItem *)(selbi->parent()); + if (selbi->depth() == 1 && invert) + bi = getBranchDirectAbove(selbi); + else + bi = getBranchDirectBelow(selbi); + if (bi) { + // turn + selbi = bi; + while (selbi->depth() < dz) { + // try to get back to original depth dz + bi = selbi->getFirstBranch(); + if (!bi) { + return selbi; + } + selbi = bi; + } + return selbi; + } + } + } + return NULL; +} + +BranchItem *MapEditor::getLeftBranch(TreeItem *ti) +{ + if (!ti) + return NULL; + + if (ti->isBranchLikeType()) { + BranchItem *bi = (BranchItem *)ti; + if (bi->depth() == 0) { + // Special case: use alternative selection index + BranchItem *newbi = bi->getLastSelectedBranchAlt(); + if (!newbi) { + BranchObj *bo; + // Try to find a mainbranch left of center + for (int i = 0; i < bi->branchCount(); i++) { + newbi = bi->getBranchNum(i); + bo = newbi->getBranchObj(); + if (bo && + bo->getOrientation() == LinkableMapObj::LeftOfCenter) + break; + } + } + return newbi; + } + if (bi->getBranchObj()->getOrientation() == + LinkableMapObj::RightOfCenter) + // right of center + return (BranchItem *)(bi->parent()); + else + // left of center + if (bi->getType() == TreeItem::Branch) + return bi->getLastSelectedBranch(); + } + + if (ti->parent() && ti->parent()->isBranchLikeType()) + return (BranchItem *)(ti->parent()); + return NULL; +} + +BranchItem *MapEditor::getRightBranch(TreeItem *ti) +{ + if (!ti) + return NULL; + + if (ti->isBranchLikeType()) { + BranchItem *bi = (BranchItem *)ti; + if (bi->depth() == 0) { + // Special case: use alternative selection index + BranchItem *newbi = bi->getLastSelectedBranch(); + if (!newbi) { + BranchObj *bo; + // Try to find a mainbranch right of center + for (int i = 0; i < bi->branchCount(); i++) { + newbi = bi->getBranchNum(i); + bo = newbi->getBranchObj(); + if (bo && + bo->getOrientation() == LinkableMapObj::RightOfCenter) + qDebug() + << "BI found right: " << newbi->getHeadingPlain(); + } + } + return newbi; + } + if (bi->getBranchObj()->getOrientation() == + LinkableMapObj::LeftOfCenter) + // left of center + return (BranchItem *)(bi->parent()); + else + // right of center + if (bi->getType() == TreeItem::Branch) + return (BranchItem *)bi->getLastSelectedBranch(); + } + + if (ti->parent() && ti->parent()->isBranchLikeType()) + return (BranchItem *)(ti->parent()); + + return NULL; +} + +void MapEditor::cursorUp() +{ + if (state == MapEditor::EditingHeading) + return; + + BranchItem *selbi = model->getSelectedBranch(); + BranchItem *bi; + if (selbi) { + // Exactly one branch is currently selected + bi = getBranchAbove(selbi); + if (bi) { + model->select(bi); + } + } else { + // Nothing selected or already multiple selections + TreeItem *ti = model->lastToggledItem(); + if (ti && ti->isBranchLikeType()) { + bi = getBranchAbove( (BranchItem*)ti); + if (bi) + model->select(bi); + } + } +} + +void MapEditor::cursorUpToggleSelection() +{ + if (state == MapEditor::EditingHeading) + return; + + BranchItem *selbi = model->getSelectedBranch(); + BranchItem *bi; + + if (selbi) { + // Exactly one branch is currently selected + bi = getBranchAbove(selbi); + if (bi) model->selectToggle(bi); + } else { + // Nothing selected or already multiple selections + TreeItem *ti = model->lastToggledItem(); + if (ti && ti->isBranchLikeType()) { + if (lastToggleDirection == toggleUp) + bi = getBranchAbove( (BranchItem*)ti); + else + bi = (BranchItem*)ti; + + if (bi) + model->selectToggle(bi); + } + } + lastToggleDirection = toggleUp; +} + +void MapEditor::cursorDown() +{ + if (state == MapEditor::EditingHeading) + return; + + BranchItem *selbi = model->getSelectedBranch(); + BranchItem *bi; + if (selbi) { + // Exactly one branch is currently selected + bi = getBranchBelow(selbi); + if (bi) { + model->select(bi); + } + } else { + // Nothing selected or already multiple selections + TreeItem *ti = model->lastToggledItem(); + if (ti && ti->isBranchLikeType()) { + bi = getBranchBelow( (BranchItem*)ti); + + if (bi) + model->select(bi); + } + } +} + +void MapEditor::cursorDownToggleSelection() +{ + if (state == MapEditor::EditingHeading) + return; + + BranchItem *selbi = model->getSelectedBranch(); + BranchItem *bi; + if (selbi) { + // Exactly one branch is currently selected + bi = getBranchBelow(selbi); + if (bi) { + model->selectToggle(bi); + } + } else { + // Nothing selected or already multiple selections + TreeItem *ti = model->lastToggledItem(); + if (ti && ti->isBranchLikeType()) { + if (lastToggleDirection == toggleDown) + bi = getBranchBelow( (BranchItem*)ti); + else + bi = (BranchItem*)ti; + + if (bi) + model->selectToggle(bi); + } + } + lastToggleDirection = toggleDown; +} + +void MapEditor::cursorLeft() +{ + TreeItem *ti = model->getSelectedItem(); + if (!ti) { + ti = model->lastToggledItem(); + if (!ti) return; + } + + BranchItem *bi = getLeftBranch(ti); + if (bi) + model->select(bi); + else { + ImageItem *ii = ti->getFirstImage(); + if (ii) + model->select(ii); + } +} + +void MapEditor::cursorRight() +{ + TreeItem *ti = model->getSelectedItem(); + if (!ti) { + ti = model->lastToggledItem(); + if (!ti) return; + } + + BranchItem *bi = getRightBranch(ti); + if (bi) + model->select(bi); + else { + ImageItem *ii = ti->getFirstImage(); + if (ii) + model->select(ii); + } +} + +void MapEditor::cursorFirst() { model->selectFirstBranch(); } + +void MapEditor::cursorLast() { model->selectLastBranch(); } + +void MapEditor::editHeading() +{ + if (state == EditingHeading) { + editHeadingFinished(); + return; + } + + BranchObj *bo = model->getSelectedBranchObj(); + BranchItem *bi = model->getSelectedBranch(); + if (bo && bo) { + VymText heading = bi->getHeading(); + if (heading.isRichText() || bi->getHeadingPlain().contains("\n")) { + mainWindow->windowShowHeadingEditor(); + ensureSelectionVisibleAnimated(); + return; + } + model->setSelectionBlocked(true); + + lineEdit = new QLineEdit; + QGraphicsProxyWidget *pw = mapScene->addWidget(lineEdit); + pw->setZValue(Z_LINEEDIT); + lineEdit->setCursor(Qt::IBeamCursor); + lineEdit->setCursorPosition(1); + +#if defined(Q_OS_WINDOWS) + QFont font = lineEdit->font(); + font.setPointSize(font.pointSize() + 4); + lineEdit->setFont(font); +#endif + + QPointF tl; + QPointF br; + qreal w = 230; + qreal h = 30; + if (bo->getOrientation() != LinkableMapObj::LeftOfCenter) { + tl = bo->getOrnamentsBBox().topLeft(); + br = tl + QPointF(w, h); + } + else { + br = bo->getOrnamentsBBox().bottomRight(); + tl = br - QPointF(w, h); + } + QRectF r(tl, br); + lineEdit->setGeometry(r.toRect()); + pw->setGeometry(r.toRect()); + + minimizeView(); + + // Set focus to MapEditor first + // To avoid problems with Cursor up/down + setFocus(); + + ensureAreaVisibleAnimated(r); + + lineEdit->setText(heading.getTextASCII()); + lineEdit->setFocus(); + lineEdit->selectAll(); // Hack to enable cursor in lineEdit + lineEdit->deselect(); // probably a Qt bug... + + setState(EditingHeading); + } +} + +void MapEditor::editHeadingFinished() +{ + if (state != EditingHeading || !lineEdit ) { + qWarning() << "ME::editHeadingFinished not editing heading!"; + } else { + lineEdit->clearFocus(); + QString s = lineEdit->text(); + s.replace(QRegExp("\\n"), " "); // Don't paste newline chars + if (s.length() == 0) + s = " "; // Don't allow empty lines, which would screw up drawing + model->setHeadingPlainText(s); + delete (lineEdit); + lineEdit = nullptr; + + // FIXME-2 ensureAreaVisible like in starting editing? + + // Maybe reselect previous branch + mainWindow->editHeadingFinished(model); + + // Autolayout to avoid overlapping branches with longer headings + if (settings.value("/mainwindow/autoLayout/use", "true") == "true") + autoLayout(); + } + + model->setSelectionBlocked(false); + setState(Neutral); +} + +void MapEditor::contextMenuEvent(QContextMenuEvent *e) +{ + // Lineedits are already closed by preceding + // mouseEvent, we don't need to close here. + + QPointF p = mapToScene(e->pos()); + TreeItem *ti = findMapItem(p, NULL); + + if (ti) { // MapObj was found + model->select(ti); + + LinkableMapObj *lmo = NULL; + BranchItem *selbi = model->getSelectedBranch(); + if (ti) + lmo = ((MapItem *)ti)->getLMO(); + + // Context Menu + if (lmo && selbi) { + QString sysFlagName; + QUuid uid = ((BranchObj *)lmo)->findSystemFlagUidByPos(p); + if (!uid.isNull()) { + Flag *flag = systemFlagsMaster->findFlagByUid(uid); + if (flag) + sysFlagName = flag->getName(); + } + + if (sysFlagName.startsWith("system-task")) + taskContextMenu->popup(e->globalPos()); + else + // Context Menu on branch or mapcenter + branchContextMenu->popup(e->globalPos()); + } + else { + if (model->getSelectedImage()) { + // Context Menu on floatimage + floatimageContextMenu->popup(e->globalPos()); + } + else { + if (model->getSelectedXLink()) + // Context Menu on XLink + model->editXLink(); + } + } + } + else { // No MapObj found, we are on the Canvas itself + // Context Menu on scene + + // Open context menu synchronously to position new mapcenter + model->setContextPos(p); + canvasContextMenu->exec(e->globalPos()); + model->unsetContextPos(); + } + e->accept(); +} + +void MapEditor::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown) + // Ignore PageUP/Down to avoid scrolling with keys + return; + + if (e->modifiers() & Qt::ShiftModifier) { + switch (mainWindow->getModMode()) { + case Main::ModModePoint: + setCursor(Qt::ArrowCursor); + break; + case Main::ModModeColor: + setCursor(PickColorCursor); + break; + case Main::ModModeXLink: + setCursor(XLinkCursor); + break; + case Main::ModModeMoveObject: + setCursor(Qt::PointingHandCursor); + break; + case Main::ModModeMoveView: + setCursor(QPixmap(":/mode-move-view.png")); + break; + default: + setCursor(Qt::ArrowCursor); + break; + } + } + QGraphicsView::keyPressEvent(e); +} + +void MapEditor::keyReleaseEvent(QKeyEvent *e) +{ + if (!(e->modifiers() & Qt::ControlModifier)) + setCursor(Qt::ArrowCursor); +} + +void MapEditor::startMovingView(QMouseEvent *e) +{ + setState(MovingView); + movingObj = NULL; // move Content not Obj + movingObj_offset = e->globalPos(); + movingCont_start = + QPointF(horizontalScrollBar()->value(), verticalScrollBar()->value()); + movingVec = QPointF(0, 0); + setCursor(HandOpenCursor); +} + +void MapEditor::mousePressEvent(QMouseEvent *e) +{ + // Ignore right clicks + if (e->button() == Qt::RightButton) { + e->ignore(); + QGraphicsView::mousePressEvent(e); + return; + } + + // Check if we need to reset zoomFactor for middle button + Ctrl + if (e->button() == Qt::MidButton && e->modifiers() & Qt::ControlModifier) { + setZoomFactorTarget(1); + setAngleTarget(0); + return; + } + + QPointF p = mapToScene(e->pos()); + TreeItem *ti_found = findMapItem(p, NULL); + LinkableMapObj *lmo_found = NULL; + if (ti_found) + lmo_found = ((MapItem *)ti_found)->getLMO(); + + + // Allow selecting text in QLineEdit if necessary + if (model->isSelectionBlocked()) { + e->ignore(); + QGraphicsView::mousePressEvent(e); + return; + } + + // Stop editing in LineEdit + if (state == EditingHeading) editHeadingFinished(); + + QString sysFlagName; + QUuid uid; + if (lmo_found) { + uid = ((BranchObj *)lmo_found)->findSystemFlagUidByPos(p); + if (!uid.isNull()) { + Flag *flag = systemFlagsMaster->findFlagByUid(uid); + if (flag) + sysFlagName = flag->getName(); + } + } + + /* + qDebug() << "ME::mouse pressed\n"; + qDebug() << " lmo_found=" << lmo_found; + qDebug() << " ti_found=" << ti_found; + //if (ti_found) qDebug() << " ti_found="<getHeading(); + qDebug() << " flag=" << sysFlagName; + */ + + // Check modifier key (before selecting object!) + if (ti_found && (e->modifiers() & Qt::ShiftModifier)) { + if (mainWindow->getModMode() == Main::ModModeColor) { + setState(PickingColor); + mainWindow->setCurrentColor(ti_found->getHeadingColor()); + if (e->modifiers() & Qt::ControlModifier) + model->colorBranch(ti_found->getHeadingColor()); + else + model->colorSubtree(ti_found->getHeadingColor()); + return; + } + + if (mainWindow->getModMode() == Main::ModModeMoveView) { + startMovingView(e); + return; + } + } + + // Check vymlink modifier (before selecting object!) + if (ti_found && sysFlagName == "system-vymLink") { + model->select(ti_found); + if (e->modifiers() & Qt::ControlModifier) { + if (e->modifiers() & Qt::ShiftModifier) + model->deleteVymLink(); + else + mainWindow->editOpenVymLink(true); + } else + mainWindow->editOpenVymLink(false); + return; + } + + // Select the clicked object, if not moving without linking + if (ti_found && (e->modifiers() & Qt::ShiftModifier)) { + if (mainWindow->getModMode() == Main::ModModePoint) { + model->selectToggle(ti_found); + lastToggleDirection = toggleUndefined; + } + } + else + model->select(ti_found); + + e->accept(); + + // Take care of remaining system flags _or_ modifier modes + if (lmo_found) { + if (!sysFlagName.isEmpty()) { + // systemFlag clicked + if (sysFlagName.contains("system-url")) { + if (e->modifiers() & Qt::ControlModifier) + mainWindow->editOpenURLTab(); + else + mainWindow->editOpenURL(); + } + else if (sysFlagName == "system-note") + mainWindow->windowToggleNoteEditor(); + else if (sysFlagName == "hideInExport") + model->toggleHideExport(); + else if (sysFlagName.startsWith("system-task-")) + model->cycleTaskStatus(); + return; + } + else { + // Take care of xLink: Open context menu with targets + // if clicked near to begin of xlink + if (ti_found->xlinkCount() > 0 && + ti_found->getType() != TreeItem::MapCenter && + lmo_found->getBBox().width() > 30) { + if ((lmo_found->getOrientation() != + LinkableMapObj::RightOfCenter && + p.x() < lmo_found->getBBox().left() + 10) || + (lmo_found->getOrientation() != + LinkableMapObj::LeftOfCenter && + p.x() > lmo_found->getBBox().right() - 10)) { + // FIXME-4 similar code in mainwindow::updateActions + QMenu menu; + QList alist; + QList blist; + for (int i = 0; i < ti_found->xlinkCount(); i++) { + XLinkItem *xli = ti_found->getXLinkItemNum(i); + BranchItem *bit = xli->getPartnerBranch(); + if (bit) + alist.append( + new QAction(ti_found->getXLinkItemNum(i) + ->getPartnerBranch() + ->getHeadingPlain(), + &menu)); + } + menu.addActions(alist); + QAction *ra = menu.exec(e->globalPos()); + if (ra) + model->select(blist.at(alist.indexOf(ra))); + while (!alist.isEmpty()) { + QAction *a = alist.takeFirst(); + delete a; + } + return; + } + } + } + } + + // XLink modifier, create new XLink + BranchItem *selbi = model->getSelectedBranch(); + if (selbi && mainWindow->getModMode() == Main::ModModeXLink && + (e->modifiers() & Qt::ShiftModifier)) { + setState(DrawingLink); + tmpLink = new Link(model); + tmpLink->setBeginBranch(selbi); + tmpLink->createMapObj(); + tmpLink->setStyleBegin("None"); + tmpLink->setStyleEnd("None"); + tmpLink->setEndPoint(mapToScene(e->pos())); + tmpLink->updateLink(); + return; + } + + // Start moving around + if (lmo_found) { + // Left Button Move Branches + if (e->button() == Qt::LeftButton) { + // No system flag clicked, take care of moving modes or simply + // moving + movingObj_offset.setX(p.x() - lmo_found->x()); + movingObj_offset.setY(p.y() - lmo_found->y()); + movingObj_orgPos.setX(lmo_found->x()); + movingObj_orgPos.setY(lmo_found->y()); + if (ti_found->depth() > 0) { + lmo_found->setRelPos(); + movingObj_orgRelPos = lmo_found->getRelPos(); + } + + if (mainWindow->getModMode() == Main::ModModeMoveObject && + e->modifiers() & Qt::ShiftModifier) { + setState(MovingObjectWithoutLinking); + } + else + setState(MovingObject); + + movingObj = model->getSelectedLMO(); + } + else + // Middle Button Toggle Scroll + // (On Mac OS X this won't work, but we still have + // a button in the toolbar) + if (e->button() == Qt::MidButton) + model->toggleScroll(); + } + else { // No lmo found, check XLinks + if (ti_found) { + if (ti_found->getType() == TreeItem::XLink) { + XLinkObj *xlo = (XLinkObj *)((MapItem *)ti_found)->getMO(); + if (xlo) { + setState(DrawingXLink); + int i = xlo->ctrlPointInClickBox(p); + if (i >= 0) + xlo->setSelection(i); + movingObj_offset.setX(p.x() - xlo->x()); + movingObj_offset.setY(p.y() - xlo->y()); + movingObj_orgPos.setX(xlo->x()); + movingObj_orgPos.setY(xlo->y()); + } + } + } + else { // No MapObj found, we are on the scene itself + // Left Button move Pos of sceneView + if (e->button() == Qt::LeftButton || + e->button() == Qt::MiddleButton) { + startMovingView(e); + return; + } + } + } +} + +void MapEditor::mouseMoveEvent(QMouseEvent *e) +{ + // Show mouse position for debugging in statusBar + if (debug && e->modifiers() & Qt::ControlModifier) + mainWindow->statusMessage( + QString("ME::mousePressEvent Scene: %1 widget: %2") + .arg(qpointFToString(mapToScene(e->pos()))) + .arg(qpointFToString(e->pos()))); + + // Allow selecting text in QLineEdit if necessary + if (model->isSelectionBlocked()) { + e->ignore(); + QGraphicsView::mouseMoveEvent(e); + return; + } + + // Move sceneView + if (state == MovingView && + (e->buttons() == Qt::LeftButton || e->buttons() == Qt::MiddleButton)) { + QPointF p = e->globalPos(); + movingVec.setX(-p.x() + movingObj_offset.x()); + movingVec.setY(-p.y() + movingObj_offset.y()); + horizontalScrollBar()->setSliderPosition( + (int)(movingCont_start.x() + movingVec.x())); + verticalScrollBar()->setSliderPosition( + (int)(movingCont_start.y() + movingVec.y())); + // Avoid flickering + scrollBarPosAnimation.stop(); + viewCenterAnimation.stop(); + rotationAnimation.stop(); + // zoomAnimation.stop(); + + return; + } + + TreeItem *seli = model->getSelectedItem(); + + MapObj *mosel = NULL; + if (seli) + mosel = ((MapItem *)seli)->getMO(); + + // If not already happened during mousepress, we might need to switch state + if (mainWindow->getModMode() == Main::ModModeMoveObject && + e->modifiers() & Qt::ShiftModifier && e->buttons() == Qt::LeftButton) { + state = MovingObjectWithoutLinking; + } + + // Move the selected MapObj + if (mosel && + (state == MovingObject || state == MovingObjectWithoutLinking || + state == DrawingXLink)) { + int margin = 50; + + // Check if we have to scroll + vPan.setX(0); + vPan.setY(0); + if (e->y() >= 0 && e->y() <= margin) + vPan.setY(e->y() - margin); + else if (e->y() <= height() && e->y() > height() - margin) + vPan.setY(e->y() - height() + margin); + if (e->x() >= 0 && e->x() <= margin) + vPan.setX(e->x() - margin); + else if (e->x() <= width() && e->x() > width() - margin) + vPan.setX(e->x() - width() + margin); + + pointerPos = e->pos(); + pointerMod = e->modifiers(); + moveObject(); + } // selection && moving_obj + + // Draw a link from one branch to another + if (state == DrawingLink) { + tmpLink->setEndPoint(mapToScene(e->pos())); + tmpLink->updateLink(); + } +} + +void MapEditor::moveObject() +{ + if (!panningTimer->isActive()) + panningTimer->start(50); + + QPointF p = mapToScene(pointerPos); + TreeItem *seli = model->getSelectedItem(); + LinkableMapObj *lmosel = NULL; + if (seli) + lmosel = ((MapItem *)seli)->getLMO(); + + objectMoved = true; + + // reset cursor if we are moving and don't copy + + // Check if we could link + TreeItem *ti_found = findMapItem(p, seli); + BranchItem *bi_dst = NULL; + LinkableMapObj *lmo_dst = NULL; + if (ti_found && ti_found != seli && ti_found->isBranchLikeType()) { + bi_dst = (BranchItem *)ti_found; + lmo_dst = bi_dst->getLMO(); + } + else + bi_dst = NULL; + + if (lmosel) { + if (seli->getType() == TreeItem::Image) { + FloatImageObj *fio = (FloatImageObj *)lmosel; + fio->moveCenter(p.x() - movingObj_offset.x(), + p.y() - movingObj_offset.y()); + fio->setRelPos(); + fio->updateLinkGeometry(); // no need for reposition, if we update + // link here + model->emitSelectionChanged(); // position has changed + + // Relink float to new mapcenter or branch, if shift is pressed + // Only relink, if selection really has a new parent + if (pointerMod == Qt::ShiftModifier && bi_dst && + bi_dst != seli->parent()) { + // Also save the move which was done so far + QString pold = qpointFToString(movingObj_orgRelPos); + QString pnow = qpointFToString(fio->getRelPos()); + model->saveState(seli, "moveRel " + pold, seli, + "moveRel " + pnow, + QString("Move %1 to relative position %2") + .arg(model->getObjectName(lmosel)) + .arg(pnow)); + model->reposition(); + + model->relinkImage((ImageItem *)seli, bi_dst); + model->select(seli); + } + } + else if (seli->isBranchLikeType()) { // selection != a FloatObj + if (seli->depth() == 0) { + // Move mapcenter + lmosel->move(p - movingObj_offset); + if (pointerMod == Qt::ControlModifier) { + // Move only mapcenter, leave its children where they are + QPointF v; + v = lmosel->getAbsPos(); + for (int i = 0; i < seli->branchCount(); ++i) { + seli->getBranchObjNum(i)->setRelPos(); + seli->getBranchObjNum(i)->setOrientation(); + } + } + } + else { + if (seli->depth() == 1) { + // Move mainbranch + if (!lmosel->hasParObjTmp()) + lmosel->move(p - movingObj_offset); + lmosel->setRelPos(); + } + else { + // d>1, move ordinary branch + if (lmosel->getOrientation() == + LinkableMapObj::LeftOfCenter) + // Add width of bbox here, otherwise alignRelTo will + // cause jumping around + lmosel->move(p.x() - movingObj_offset.x(), + p.y() - movingObj_offset.y() + + lmosel->getTopPad()); + else + lmosel->move(p.x() - movingObj_offset.x(), + p.y() - movingObj_offset.y() - + lmosel->getTopPad()); + BranchItem *selbi = ((BranchItem *)seli); + if (selbi->parentBranch()->getChildrenLayout() == + BranchItem::FreePositioning) + lmosel->setRelPos(); + } + + } // depth>0 + + // Maybe we can relink temporary? + if (bi_dst && state != MovingObjectWithoutLinking) { + if (pointerMod == Qt::ControlModifier) { + // Special case: CTRL to link below dst + lmosel->setParObjTmp(lmo_dst, p, +1); + } + else if (pointerMod == Qt::ShiftModifier) + lmosel->setParObjTmp(lmo_dst, p, -1); + else + lmosel->setParObjTmp(lmo_dst, p, 0); + } + else + lmosel->unsetParObjTmp(); + + // reposition subbranch + lmosel->reposition(); + + QItemSelection sel = model->getSelectionModel()->selection(); + updateSelection(sel, sel); // position has changed + + // In winter mode shake snow from heading + if (winter) + model->emitDataChanged(seli); + } // Moving branchLikeType + } // End of lmosel != NULL + else if (seli && seli->getType() == TreeItem::XLink) { + // Move XLink control point + MapObj *mosel = ((MapItem *)seli)->getMO(); + if (mosel) { + mosel->move(p - movingObj_offset); // FIXME-3 Missing savestate + model->setChanged(); + model->emitSelectionChanged(); + } + } + else + qWarning("ME::moveObject Huh? I'm confused."); + + scene()->update(); + + return; +} + +void MapEditor::mouseReleaseEvent(QMouseEvent *e) +{ + // Allow selecting text in QLineEdit if necessary + if (model->isSelectionBlocked()) { + e->ignore(); + QGraphicsView::mouseReleaseEvent(e); + return; + } + + QPointF p = mapToScene(e->pos()); + TreeItem *seli = model->getSelectedItem(); + + TreeItem *dsti = NULL; + if (seli) + dsti = findMapItem(p, seli); + LinkableMapObj *dst = NULL; + BranchItem *selbi = model->getSelectedBranch(); + if (dsti && dsti->isBranchLikeType()) + dst = ((MapItem *)dsti)->getLMO(); + else + dsti = NULL; + + // Have we been picking color? + if (state == PickingColor) { + setCursor(Qt::ArrowCursor); + // Check if we are over another branch + if (dst) { + if (e->modifiers() & Qt::ShiftModifier) + model->colorBranch(mainWindow->getCurrentColor()); + else + model->colorSubtree(mainWindow->getCurrentColor()); + } + setState(Neutral); + return; + } + + // Have we been drawing a link? + if (state == DrawingLink) { + setState(Neutral); + // Check if we are over another branch + if (dsti) { + tmpLink->setEndBranch(((BranchItem *)dsti)); + tmpLink->activate(); + tmpLink->updateLink(); + if (model->createLink(tmpLink)) { + model->saveState( + tmpLink->getBeginLinkItem(), "remove ()", seli, + QString("addXLink (\"%1\",\"%2\",%3,\"%4\",\"%5\")") + .arg(model->getSelectString(tmpLink->getBeginBranch())) + .arg(model->getSelectString(tmpLink->getEndBranch())) + .arg(tmpLink->getPen().width()) + .arg(tmpLink->getPen().color().name()) + .arg(penStyleToString(tmpLink->getPen().style())), + QString("Adding Link from %1 to %2") + .arg(model->getObjectName(seli)) + .arg(model->getObjectName(dsti))); + return; + } + } + delete (tmpLink); + tmpLink = NULL; + return; + } + + // Have we been moving something? + if (seli && state == MovingObject) { + panningTimer->stop(); + if (seli->getType() == TreeItem::Image) { + FloatImageObj *fio = (FloatImageObj *)(((MapItem *)seli)->getLMO()); + if (fio) { + // Moved Image, we need to reposition + QString pold = qpointFToString(movingObj_orgRelPos); + QString pnow = qpointFToString(fio->getRelPos()); + model->saveState(seli, "moveRel " + pold, seli, + "moveRel " + pnow, + QString("Move %1 to relative position %2") + .arg(model->getObjectName(seli)) + .arg(pnow)); + + model->emitDataChanged( + seli->parent()); // Parent of image has changed + model->reposition(); + } + } + + if (selbi && selbi->depth() == 0) { + if (movingObj_orgPos != selbi->getBranchObj()->getAbsPos()) { + QString pold = qpointFToString(movingObj_orgPos); + QString pnow = + qpointFToString(selbi->getBranchObj()->getAbsPos()); + + model->saveState(selbi, "move " + pold, selbi, "move " + pnow, + QString("Move mapcenter %1 to position %2") + .arg(model->getObjectName(selbi)) + .arg(pnow)); + } + } + + if (seli->isBranchLikeType()) //(seli->getType() == TreeItem::Branch ) + { // A branch was moved + LinkableMapObj *lmosel = NULL; + lmosel = ((MapItem *)seli)->getLMO(); + + // save the position in case we link to mapcenter + QPointF savePos = QPointF(lmosel->getAbsPos()); + + // Reset the temporary drawn link to the original one + lmosel->unsetParObjTmp(); + + // For Redo we may need to save original selection + QString preSelStr = model->getSelectString(seli); + + if (dsti && objectMoved && state != MovingObjectWithoutLinking) { + // We have a destination, relink to that + BranchObj *selbo = model->getSelectedBranchObj(); + + QString preParStr = model->getSelectString(seli->parent()); + QString preNum = QString::number(seli->num(), 10); + QString preDstParStr; + + if (e->modifiers() & Qt::ShiftModifier && + dsti->parent()) { // Link above dst + preDstParStr = model->getSelectString(dsti->parent()); + model->relinkBranch((BranchItem *)seli, + (BranchItem *)dsti->parent(), + ((BranchItem *)dsti)->num(), true); + } + else if (e->modifiers() & Qt::ControlModifier && + dsti->parent()) { + // Link below dst + preDstParStr = model->getSelectString(dsti->parent()); + model->relinkBranch((BranchItem *)seli, + (BranchItem *)dsti->parent(), + ((BranchItem *)dsti)->num() + 1, true); + } + else { // Append to dst + preDstParStr = model->getSelectString(dsti); + model->relinkBranch((BranchItem *)seli, (BranchItem *)dsti, + -1, true, movingObj_orgPos); + if (dsti->depth() == 0) + selbo->move(savePos); + } + } + else { + // No destination, undo temporary move + + if (seli->depth() == 1) { + // The select string might be different _after_ moving + // around. Therefor reposition and then use string of old + // selection, too + model->reposition(); + + QPointF rp(lmosel->getRelPos()); + if (rp != movingObj_orgRelPos) { + QString ps = qpointFToString(rp); + model->saveState( + model->getSelectString(lmosel), + "moveRel " + qpointFToString(movingObj_orgRelPos), + preSelStr, "moveRel " + ps, + QString("Move %1 to relative position %2") + .arg(model->getObjectName(lmosel)) + .arg(ps)); + } + } + + if (selbi->parentBranch()->getChildrenLayout() == + BranchItem::FreePositioning) { + lmosel->setRelPos(); + model->reposition(); + } + else { + + // Draw the original link, before selection was moved around + if (settings.value("/animation/use", true).toBool() && + seli->depth() > 1 + // && distance + //(lmosel->getRelPos(),movingObj_orgRelPos)<3 + ) { + lmosel->setRelPos(); // calc relPos first for starting + // point + + model->startAnimation((BranchObj *)lmosel, + lmosel->getRelPos(), + movingObj_orgRelPos); + } + else + model->reposition(); + } + } + } + // Finally resize scene, if needed + scene()->update(); + movingObj = NULL; + objectMoved = false; + vPan = QPoint(); + } + else + // maybe we moved View: set old cursor + setCursor(Qt::ArrowCursor); + + if (state != EditingHeading) + setState(Neutral); // Continue editing after double click! + + QGraphicsView::mouseReleaseEvent(e); +} + +void MapEditor::mouseDoubleClickEvent(QMouseEvent *e) +{ + // Allow selecting text in QLineEdit if necessary + if (model->isSelectionBlocked()) { + e->ignore(); + QGraphicsView::mouseDoubleClickEvent(e); + return; + } + + if (e->button() == Qt::LeftButton) { + QPointF p = mapToScene(e->pos()); + TreeItem *ti = findMapItem(p, NULL); + LinkableMapObj *lmo; + if (ti) { + if (state == EditingHeading) + editHeadingFinished(); + model->select(ti); + BranchItem *selbi = model->getSelectedBranch(); + if (selbi) { + lmo = ((MapItem *)ti)->getLMO(); + if (lmo) { + QUuid uid = ((BranchObj *)lmo)->findSystemFlagUidByPos(p); + + // Don't edit heading when double clicking system flag: + if (!uid.isNull()) + return; + } + } + e->accept(); + editHeading(); + } + } +} + +void MapEditor::wheelEvent(QWheelEvent *e) +{ + if (e->modifiers() & Qt::ControlModifier && + e->angleDelta().y() != 0) { + QPointF p = mapToScene(e->position().toPoint()); + if (e->angleDelta().y() > 0) + // setZoomFactorTarget (zoomFactorTarget*1.15); + setViewCenterTarget(p, zoomFactorTarget * 1.15, angleTarget); + else + // setZoomFactorTarget (zoomFactorTarget*0.85); + setViewCenterTarget(p, zoomFactorTarget * 0.85, angleTarget); + } + else { + scrollBarPosAnimation.stop(); + QGraphicsView::wheelEvent(e); + } +} + +void MapEditor::focusOutEvent(QFocusEvent *) +{ + // qDebug()<<"ME::focusOutEvent"<reason(); + if (state == EditingHeading) + editHeadingFinished(); +} + +void MapEditor::resizeEvent(QResizeEvent *e) { QGraphicsView::resizeEvent(e); } + +void MapEditor::dragEnterEvent(QDragEnterEvent *event) +{ + // for (unsigned int i=0;event->format(i);i++) // Debug mime type + // cerr << event->format(i) << endl; + + if (event->mimeData()->hasImage()) + event->acceptProposedAction(); + else if (event->mimeData()->hasUrls()) + event->acceptProposedAction(); +} + +void MapEditor::dragMoveEvent(QDragMoveEvent *) {} + +void MapEditor::dragLeaveEvent(QDragLeaveEvent *event) { event->accept(); } + +void MapEditor::dropEvent(QDropEvent *event) +{ + BranchItem *selbi = model->getSelectedBranch(); + if (selbi) { + if (debug) { + foreach (QString format, event->mimeData()->formats()) + qDebug() << "MapEditor: Dropped format: " << qPrintable(format); + foreach (QUrl url, event->mimeData()->urls()) { + qDebug() << " URL-path:" << url.path(); + qDebug() << "URL-string:" << url.toString(); + qDebug() << " enc:" << url.toEncoded(); + qDebug() << " valid:" << url.isValid(); + } + qDebug() << "============== mimeData ==================="; + qDebug() << "has-img : " << event->mimeData()->hasImage(); + qDebug() << "has-urls: " << event->mimeData()->hasUrls(); + qDebug() << " text: " << event->mimeData()->text(); + qDebug() << "==========================================="; + } + + if (event->mimeData()->hasUrls()) { + // Try text representation first, which works on windows, but in + // Linux only for https, not local images + QString url = event->mimeData()->text(); + if (url.isEmpty()) { + QByteArray ba = + event->mimeData()->urls().first().path().toLatin1(); + QByteArray ba2; + for (int i = 0; i < ba.count(); i++) + if (ba.at(i) != 0) + ba2.append(ba.at(i)); + url = ba2; + } + + BranchItem *bi = NULL; + // Workaround to avoid adding empty branches + if (!url.isEmpty()) { + if (url.startsWith("file://")) + url.remove(0, 7); + +#if defined(Q_OS_WIN32) + if (url.startsWith("/")) + url.remove(0, 1); +#endif + if (isImage(url)) { + if (debug) + qDebug() << "dropped url seems to be image: " << url; + // Image, try to download or set image from local file + if (url.startsWith("http")) + model->downloadImage(url); + else + model->loadImage(bi, url); + if (debug) + qDebug() << "finished loading image"; + } + else { + bi = model->addNewBranch(); + if (bi) { + model->select(bi); + if (url.endsWith(".vym", Qt::CaseInsensitive)) + model->setVymLink(url); + else { + model->setURL(url); + model->setHeadingPlainText(url); + } + + model->select(bi->parent()); + } + } + } + } + } + event->acceptProposedAction(); +} + +void MapEditor::setState(EditorState s) +{ + if (state != Neutral && s != Neutral) + qWarning() << "MapEditor::setState switching directly from " << state + << " to " << s; + state = s; + /* if (debug) + { + QString s; + switch (state) + { + case Neutral: + s = "Neutral"; + break; + case EditingHeading: + s = "EditingHeading"; + break; + case EditingLink: + s = "EditingLink"; + break; + case MovingObject: + s = "MovingObject"; + break; + case MovingObjectWithoutLinking: + s = "MovingObjectWithoutLinking"; + break; + case MovingView: + s = "MovingView"; + break; + case PickingColor: + s = "PickingColor"; + break; + case DrawingLink: + s = "DrawingLink"; + break; + } + qDebug() << "MapEditor: State " << s << " of " << model->getMapName(); + } + */ +} + +MapEditor::EditorState MapEditor::getState() { return state; } + +void MapEditor::updateSelection(QItemSelection nsel, QItemSelection dsel) +{ + Q_UNUSED(nsel); + + QList itemsSelected; + QList itemsDeselected; + + QItemSelection sel = model->getSelectionModel()->selection(); + + LinkableMapObj *lmo; + + // Add new selected objects + if (sel.indexes().count() > 1) + mainWindow->statusMessage( + tr("%1 items selected").arg(sel.indexes().count())); + + foreach (QModelIndex ix, sel.indexes()) { + MapItem *mi = static_cast(ix.internalPointer()); + if (mi->isBranchLikeType() || mi->getType() == TreeItem::Image || + mi->getType() == TreeItem::XLink) + if (!itemsSelected.contains(mi)) + itemsSelected.append(mi); + lmo = mi->getLMO(); + if (lmo) + mi->getLMO()->updateVisibility(); + } + + // Delete objects meanwhile removed from selection + foreach (QModelIndex ix, dsel.indexes()) { + MapItem *mi = static_cast(ix.internalPointer()); + if (mi->isBranchLikeType() || mi->getType() == TreeItem::Image || + mi->getType() == TreeItem::XLink) + if (!itemsDeselected.contains(mi)) + itemsDeselected.append(mi); + lmo = mi->getLMO(); // FIXME-2 xlink does return nullptr + if (lmo) + mi->getLMO()->updateVisibility(); + } + + // Trim list of selection paths + while (itemsSelected.count() < selPathList.count()) + delete selPathList.takeFirst(); + + // Reduce polygons + while (itemsSelected.count() < selPathList.count()) + delete selPathList.takeFirst(); + + // Add additonal polygons + QGraphicsPathItem *sp; + while (itemsSelected.count() > selPathList.count()) { + sp = mapScene->addPath(QPainterPath()); + sp->show(); + selPathList.append(sp); + } + + // Reposition polygons + for (int i = 0; i < itemsSelected.count(); ++i) { + MapObj *mo = itemsSelected.at(i)->getMO(); + sp = selPathList.at(i); + sp->setPath(mo->getSelectionPath()); + sp->setPen(selectionPen); + sp->setBrush(selectionBrush); + sp->setParentItem(mo); + sp->setZValue(dZ_SELBOX); + + // Reposition also LineEdit for heading during animation + if (lineEdit) + lineEdit->move(mo->getAbsPos().toPoint()); + } + + scene()->update(); +} + +void MapEditor::updateData(const QModelIndex &sel) +{ + TreeItem *ti = static_cast(sel.internalPointer()); + + /* testing + qDebug() << "ME::updateData"; + if (!ti) + { + qDebug() << " ti=NULL"; + return; + } + qDebug() << " ti="<