3 #include <QGraphicsProxyWidget>
6 #include <QPrintDialog>
10 #include "branchitem.h"
12 #include "mainwindow.h"
14 #include "shortcuts.h"
15 #include "warningdialog.h"
17 #include "xlinkitem.h"
19 extern Main *mainWindow;
20 extern QString clipboardDir;
21 extern QString clipboardFile;
23 extern QPrinter *printer;
25 extern QMenu *branchContextMenu;
26 extern QMenu *canvasContextMenu;
27 extern QMenu *floatimageContextMenu;
28 extern QMenu *taskContextMenu;
30 extern Switchboard switchboard;
31 extern Settings settings;
33 extern QTextStream vout;
35 extern QString editorFocusStyle;
37 extern FlagRowMaster *systemFlagsMaster;
39 ///////////////////////////////////////////////////////////////////////
40 ///////////////////////////////////////////////////////////////////////
41 MapEditor::MapEditor(VymModel *vm)
43 // qDebug() << "Constructor ME " << this;
45 QString shortcutScope = tr("Map Editor", "Shortcut scope");
46 mapScene = new QGraphicsScene(NULL);
47 mapScene->setBackgroundBrush(QBrush(Qt::white, Qt::SolidPattern));
48 mapScene->setItemIndexMethod(QGraphicsScene::NoIndex); // FIXME-2 Avoiding crashes...
49 // Alternatively call removeFromIndex() in destructor
50 // or maybe also prepareGeometryChange()
52 zoomFactor = zoomFactorTarget = 1;
53 angle = angleTarget = 0;
56 model->registerMapEditor(this);
60 setStyleSheet("QGraphicsView:focus {" + editorFocusStyle + "}");
62 // Create bitmap cursors, platform dependant
63 HandOpenCursor = QCursor(QPixmap(":/mode-move-view.png"), 1, 1);
64 PickColorCursor = QCursor(QPixmap(":/cursorcolorpicker.png"), 5, 27);
65 XLinkCursor = QCursor(QPixmap(":/cursorxlink.png"), 1, 7);
74 // Shortcuts and actions
77 a = new QAction("Select upper branch", this);
78 a->setShortcut(Qt::Key_Up);
79 a->setShortcutContext(Qt::WidgetShortcut);
80 connect(a, SIGNAL(triggered()), this, SLOT(cursorUp()));
83 a = new QAction("Add upper branch to selection", this);
84 a->setShortcut(Qt::Key_Up + Qt::SHIFT);
85 a->setShortcutContext(Qt::WidgetShortcut);
87 connect(a, SIGNAL(triggered()), this, SLOT(cursorUpToggleSelection()));
89 a = new QAction("Select lower branch", this);
90 a->setShortcut(Qt::Key_Down);
91 a->setShortcutContext(Qt::WidgetShortcut);
93 connect(a, SIGNAL(triggered()), this, SLOT(cursorDown()));
95 a = new QAction("Add lower branch to selection", this);
96 a->setShortcut(Qt::Key_Down + Qt::SHIFT);
97 a->setShortcutContext(Qt::WidgetShortcut);
99 connect(a, SIGNAL(triggered()), this, SLOT(cursorDownToggleSelection()));
101 a = new QAction("Select left branch", this);
102 a->setShortcut(Qt::Key_Left);
103 // a->setShortcutContext (Qt::WidgetWithChildrenShortcut);
105 connect(a, SIGNAL(triggered()), this, SLOT(cursorLeft()));
107 a = new QAction("Select child branch", this);
108 a->setShortcut(Qt::Key_Right);
109 // a->setShortcutContext (Qt::WidgetWithChildrenShortcut);
111 connect(a, SIGNAL(triggered()), this, SLOT(cursorRight()));
113 a = new QAction("Select first branch", this);
114 a->setShortcut(Qt::Key_Home);
115 a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
117 connect(a, SIGNAL(triggered()), this, SLOT(cursorFirst()));
119 a = new QAction("Select last branch", this);
120 a->setShortcut(Qt::Key_End);
121 a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
123 connect(a, SIGNAL(triggered()), this, SLOT(cursorLast()));
125 // Action to embed LineEdit for heading in Scene
128 a = new QAction(tr("Edit heading", "MapEditor"), this);
129 a->setShortcut(Qt::Key_Return); // Edit heading
130 a->setShortcutContext(Qt::WidgetShortcut);
132 connect(a, SIGNAL(triggered()), this, SLOT(editHeading()));
133 a = new QAction(tr("Edit heading", "MapEditor"), this);
134 a->setShortcut(Qt::Key_Enter); // Edit heading
135 a->setShortcutContext(Qt::WidgetShortcut);
137 connect(a, SIGNAL(triggered()), this, SLOT(editHeading()));
140 selectionPen = QPen(QColor(255,255,0), 1);
141 selectionBrush = QBrush(QColor(255,255,0));
144 panningTimer = new QTimer(this);
146 connect(panningTimer, SIGNAL(timeout()), this, SLOT(panView()));
148 // Clone actions defined in MainWindow
149 foreach (QAction *qa, mainWindow->mapEditorActions) {
150 a = new QAction(this);
151 a->setShortcut(qa->shortcut());
152 a->setShortcutContext(qa->shortcutContext());
153 connect(a, SIGNAL(triggered()), qa, SLOT(trigger()));
162 MapEditor::~MapEditor()
164 // qDebug ()<<"Destr MapEditor this="<<this;
172 VymModel *MapEditor::getModel() { return model; }
174 QGraphicsScene *MapEditor::getScene() { return mapScene; }
176 void MapEditor::panView()
178 if (!vPan.isNull()) {
180 // To avoid jumping of the sceneView, only
181 // show selection, if not tmp linked
186 else if (vPan.x() > 0)
187 px = width() + vPan.x();
190 else if (vPan.y() > 0)
191 py = height() + vPan.y();
193 QPointF q = mapToScene(QPoint(px, py));
194 QRectF r = QRectF(q, QPointF(q.x() + 1, q.y() + 1));
196 // Expand view if necessary
197 setScrollBarPosTarget(r); // FIXME-2 mapToScene first?
199 // Stop possible other animations
200 if (scrollBarPosAnimation.state() == QAbstractAnimation::Running)
201 scrollBarPosAnimation.stop();
203 // Do linear animation
204 horizontalScrollBar()->setValue(horizontalScrollBar()->value() +
206 verticalScrollBar()->setValue(verticalScrollBar()->value() + vPan.y());
208 // Update currently moving object
213 void MapEditor::ensureAreaVisibleAnimated(const QRectF &area, bool maximizeArea) // FIXME-2 zooming in not working yet (fit to selection)
215 // Changes viewCenter to make sure that
216 // r is within the margins of the viewport
218 // Only zooms, if r NOT fit into viewport
219 // view is centered then on bounding box.
221 // Similar to QGraphicsItem::ensureVisible,
222 // but with animation and (if necessary)
225 int xmargin = settings.value("/mapeditor/scrollToMarginX/", 50).toInt();
226 int ymargin = settings.value("/mapeditor/scrollToMarginY/", 50).toInt();
228 // Do we need to zoom out to show area?
229 QRect areaViewCoord = mapFromScene(area).boundingRect();
231 // Visible area within margins
232 QRect visibleViewCoord = rect();
233 visibleViewCoord -= QMargins(xmargin, ymargin, xmargin, ymargin);
236 // Calculate required width and height considering rotation of view
237 qreal a = angle / 180 * M_PI;
238 qreal area_w_viewCoord = abs(sin(a) * area.height()) + abs(cos(a) * area.width());
239 qreal area_h_viewCoord = abs(sin(a) * area.width()) + abs(cos(a) * area.height());
240 qreal z_x = 1.0 * visibleViewCoord.width() / area_w_viewCoord;
241 qreal z_y = 1.0 * visibleViewCoord.height() / area_h_viewCoord;
243 qreal zf = min (z_x, z_y);
245 bool zoomOutRequired =
246 (visibleViewCoord.width() < areaViewCoord.width() ||
247 visibleViewCoord.height() < areaViewCoord.height());
248 bool zoomInRequired =
249 (visibleViewCoord.width() > areaViewCoord.width() &&
250 visibleViewCoord.height() > areaViewCoord.height());
252 //qDebug() << " zoom out: " << zoomOutRequired;
253 //qDebug() << " zoom in: " << zoomInRequired << " zoomFactor=" << zoomFactor << " zf=" << zf;
254 if (zoomOutRequired || maximizeArea) {
255 setViewCenterTarget(area.center(), zf, angle);
260 // After zooming bbox would fit into margins of viewport
263 if (areaViewCoord.left() < xmargin)
265 view_dx = areaViewCoord.left() - xmargin;
266 else if (areaViewCoord.right() > viewport()->width())
268 view_dx = areaViewCoord.x() + areaViewCoord.width() - viewport()->width() + xmargin;
270 if (areaViewCoord.top() < ymargin)
272 view_dy = areaViewCoord.top() - ymargin;
273 else if (areaViewCoord.bottom() > viewport()->height() - ymargin)
275 view_dy = areaViewCoord.y() + areaViewCoord.height() - viewport()->height() + ymargin;
277 if (abs(view_dx) > 5 || abs(view_dy) > 5)
279 mapToScene(viewport()->geometry().center() + QPoint (view_dx, view_dy)),
283 QEasingCurve::OutQuint);
286 void MapEditor::ensureSelectionVisibleAnimated(bool maximizeArea)
288 // Changes viewCenter to make sure that bounding box of all currently
289 // selected items is within the margins of the viewport
291 // Only zooms, if bounding box of items does NOT fit into viewport
292 // view is centered then on bounding box. (Useful also for big images)
294 // Similar to QGraphicsItem::ensureVisible, but with animation and (if necessary)
297 QList <TreeItem*> selis = model->getSelectedItems();
299 // Nothing to do, if nothing is selected
300 if (selis.isEmpty()) return;
302 // Calculate total bounding box
304 bool firstIteration = true;
306 foreach (TreeItem *ti, selis) {
307 LinkableMapObj *lmo = nullptr;
308 if (ti->getType() == TreeItem::Image || ti->isBranchLikeType())
309 lmo = ((MapItem *)ti)->getLMO();
311 if (firstIteration) {
312 bbox = lmo->getBBox();
313 firstIteration = false;
315 bbox = bbox.united(lmo->getBBox());
319 ensureAreaVisibleAnimated(bbox, maximizeArea);
322 void MapEditor::scrollTo(const QModelIndex &index)
324 if (index.isValid()) {
325 LinkableMapObj *lmo = NULL;
326 TreeItem *ti = static_cast<TreeItem *>(index.internalPointer());
327 if (ti->getType() == TreeItem::Image || ti->isBranchLikeType())
328 lmo = ((MapItem *)ti)->getLMO();
330 QRectF r = lmo->getBBox();
331 setScrollBarPosTarget(r);
337 void MapEditor::setScrollBarPosTarget(QRectF rect)
339 // Expand viewport, if rect is not contained
340 if (!sceneRect().contains(rect))
341 setSceneRect(sceneRect().united(rect));
343 int xmargin = settings.value("/mapeditor/scrollToMarginX/", 80).toInt();
344 int ymargin = settings.value("/mapeditor/scrollToMarginX/", 80).toInt();
347 qreal width = viewport()->width();
348 qreal height = viewport()->height();
349 QRectF viewRect = transform().scale(zoomFactorTarget, zoomFactorTarget).mapRect(rect);
351 qreal left = horizontalScrollBar()->value();
352 qreal right = left + width;
353 qreal top = verticalScrollBar()->value();
354 qreal bottom = top + height;
356 scrollBarPosTarget = getScrollBarPos();
358 if (viewRect.left() <= left + xmargin) {
359 // need to scroll from the left
360 scrollBarPosTarget.setX(int(viewRect.left() - xmargin - 0.5));
362 if (viewRect.right() >= right - xmargin) {
363 // need to scroll from the right
364 scrollBarPosTarget.setX(int(viewRect.right() - width + xmargin + 0.5));
366 if (viewRect.top() <= top + ymargin) {
367 // need to scroll from the top
368 scrollBarPosTarget.setY(int(viewRect.top() - ymargin - 0.5));
370 if (viewRect.bottom() >= bottom - ymargin) {
371 // need to scroll from the bottom
372 scrollBarPosTarget.setY(
373 int(viewRect.bottom() - height + ymargin + 0.5));
377 QPointF MapEditor::getScrollBarPosTarget() { return scrollBarPosTarget; }
379 void MapEditor::setScrollBarPos(const QPointF &p)
382 horizontalScrollBar()->setValue(int(p.x()));
383 verticalScrollBar()->setValue(int(p.y()));
386 QPointF MapEditor::getScrollBarPos()
388 return QPointF(horizontalScrollBar()->value(),
389 verticalScrollBar()->value());
390 // return scrollBarPos;
393 void MapEditor::animateScrollBars()
395 if (scrollBarPosAnimation.state() == QAbstractAnimation::Running)
396 scrollBarPosAnimation.stop();
398 if (settings.value("/animation/use/", true).toBool()) {
399 scrollBarPosAnimation.setTargetObject(this);
400 scrollBarPosAnimation.setPropertyName("scrollBarPos");
401 scrollBarPosAnimation.setDuration(
402 settings.value("/animation/duration/scrollbar", 2000).toInt());
403 scrollBarPosAnimation.setEasingCurve(QEasingCurve::OutQuint);
404 scrollBarPosAnimation.setStartValue(QPointF(
405 horizontalScrollBar()->value(), verticalScrollBar()->value()));
406 scrollBarPosAnimation.setEndValue(scrollBarPosTarget);
407 scrollBarPosAnimation.start();
410 setScrollBarPos(scrollBarPosTarget);
413 void MapEditor::setZoomFactorTarget(const qreal &zft)
415 zoomFactorTarget = zft;
416 if (zoomAnimation.state() == QAbstractAnimation::Running)
417 zoomAnimation.stop();
418 if (settings.value("/animation/use/", true).toBool()) {
419 zoomAnimation.setTargetObject(this);
420 zoomAnimation.setPropertyName("zoomFactor");
421 zoomAnimation.setDuration(
422 settings.value("/animation/duration/zoom", 2000).toInt());
423 zoomAnimation.setEasingCurve(QEasingCurve::OutQuint);
424 zoomAnimation.setStartValue(zoomFactor);
425 zoomAnimation.setEndValue(zft);
426 zoomAnimation.start();
432 qreal MapEditor::getZoomFactorTarget() { return zoomFactorTarget; }
434 void MapEditor::setZoomFactor(const qreal &zf)
440 qreal MapEditor::getZoomFactor() { return zoomFactor; }
442 void MapEditor::setAngleTarget(const qreal &at)
445 if (rotationAnimation.state() == QAbstractAnimation::Running)
446 rotationAnimation.stop();
447 if (settings.value("/animation/use/", true).toBool()) {
448 rotationAnimation.setTargetObject(this);
449 rotationAnimation.setPropertyName("angle");
450 rotationAnimation.setDuration(
451 settings.value("/animation/duration/rotation", 2000).toInt());
452 rotationAnimation.setEasingCurve(QEasingCurve::OutQuint);
453 rotationAnimation.setStartValue(angle);
454 rotationAnimation.setEndValue(at);
455 rotationAnimation.start();
458 setAngle(angleTarget);
461 qreal MapEditor::getAngleTarget() { return angleTarget; }
463 void MapEditor::setAngle(const qreal &a)
468 winter->updateView();
471 qreal MapEditor::getAngle() { return angle; }
473 void MapEditor::setViewCenterTarget(const QPointF &p, const qreal &zft,
474 const qreal &at, const int duration,
475 const QEasingCurve &easingCurve)
477 viewCenterTarget = p;
478 zoomFactorTarget = zft;
481 viewCenter = mapToScene(viewport()->geometry()).boundingRect().center();
483 if (viewCenterAnimation.state() == QAbstractAnimation::Running)
484 viewCenterAnimation.stop();
485 if (rotationAnimation.state() == QAbstractAnimation::Running)
486 rotationAnimation.stop();
487 if (zoomAnimation.state() == QAbstractAnimation::Running)
488 zoomAnimation.stop();
490 if (settings.value("/animation/use/", true).toBool()) {
491 viewCenterAnimation.setTargetObject(this);
492 viewCenterAnimation.setPropertyName("viewCenter");
493 viewCenterAnimation.setDuration(
494 settings.value("/animation/duration/scrollbar", duration).toInt());
495 viewCenterAnimation.setEasingCurve(easingCurve);
496 viewCenterAnimation.setStartValue(viewCenter);
497 viewCenterAnimation.setEndValue(viewCenterTarget);
498 viewCenterAnimation.start();
500 rotationAnimation.setTargetObject(this);
501 rotationAnimation.setPropertyName("angle");
502 rotationAnimation.setDuration(
503 settings.value("/animation/duration/rotation", duration).toInt());
504 rotationAnimation.setEasingCurve(easingCurve);
505 rotationAnimation.setStartValue(angle);
506 rotationAnimation.setEndValue(angleTarget);
507 rotationAnimation.start();
509 zoomAnimation.setTargetObject(this);
510 zoomAnimation.setPropertyName("zoomFactor");
511 zoomAnimation.setDuration(
512 settings.value("/animation/duration/zoom", duration).toInt());
513 zoomAnimation.setEasingCurve(easingCurve);
514 zoomAnimation.setStartValue(zoomFactor);
515 zoomAnimation.setEndValue(zoomFactorTarget);
516 zoomAnimation.start();
519 setAngle(angleTarget);
521 setViewCenter(viewCenterTarget);
525 void MapEditor::setViewCenterTarget()
527 MapItem *selti = (MapItem *)(model->getSelectedItem());
529 LinkableMapObj *lmo = selti->getLMO();
531 setViewCenterTarget(lmo->getBBox().center(), 1, 0);
535 QPointF MapEditor::getViewCenterTarget() { return viewCenterTarget; }
537 void MapEditor::setViewCenter(const QPointF &vc) { centerOn(vc); }
539 QPointF MapEditor::getViewCenter() { return viewCenter; }
541 void MapEditor::updateMatrix()
544 t_zoom.scale(zoomFactor, zoomFactor);
547 setTransform(t_zoom * t_rot);
550 void MapEditor::minimizeView() {
551 // If we only would set scene rectangle to existing items, then
552 // view fould "jump", when Qt automatically tries to center.
553 // Better consider the currently visible viewport (with slight offset)
554 QRectF r = mapToScene(viewport()->geometry()).boundingRect();
556 setSceneRect(scene()->itemsBoundingRect().united(r));
559 void MapEditor::print()
561 QRectF totalBBox = getTotalBBox();
564 printer = mainWindow->setupPrinter();
566 // Try to set orientation automagically
567 // Note: Interpretation of generated postscript is amibiguous, if
568 // there are problems with landscape mode, see
569 // http://sdb.suse.de/de/sdb/html/jsmeix_print-cups-landscape-81.html
571 if (totalBBox.width() > totalBBox.height())
572 // recommend landscape
573 printer->setPageOrientation(QPageLayout::Landscape);
575 // recommend portrait
576 printer->setPageOrientation(QPageLayout::Portrait);
578 QPrintDialog dialog(printer, this);
579 dialog.setWindowTitle(tr("Print vym map", "MapEditor"));
580 if (dialog.exec() == QDialog::Accepted) {
581 QPainter pp(printer);
583 pp.setRenderHint(QPainter::Antialiasing, true);
585 // Don't print the visualisation of selection
586 model->unselectAll();
588 QRectF mapRect = totalBBox;
589 QGraphicsRectItem *frame = NULL;
592 // Print frame around map
593 mapRect.setRect(totalBBox.x() - 10, totalBBox.y() - 10,
594 totalBBox.width() + 20, totalBBox.height() + 20);
595 frame = mapScene->addRect(mapRect, QPen(Qt::black),
596 QBrush(Qt::NoBrush));
602 (double)printer->width() / (double)printer->height();
603 double mapAspect = (double)mapRect.width() / (double)mapRect.height();
605 if (mapAspect >= paperAspect) {
606 // Fit horizontally to paper width
607 // pp.setViewport(0,0,
608 // printer->width(),(int)(printer->width()/mapAspect) );
609 viewBottom = (int)(printer->width() / mapAspect);
612 // Fit vertically to paper height
613 // pp.setViewport(0,0,(int)(printer->height()*mapAspect),printer->height());
614 viewBottom = printer->height();
618 // Print footer below map
620 font.setPointSize(10);
622 QRectF footerBox(0, viewBottom, printer->width(), 15);
623 pp.drawText(footerBox, Qt::AlignLeft,
624 "VYM - " + model->getFileName());
625 pp.drawText(footerBox, Qt::AlignRight,
626 QDate::currentDate().toString(Qt::TextDate));
628 mapScene->render(&pp,
629 QRectF(0, 0, printer->width(), printer->height() - 15),
630 QRectF(mapRect.x(), mapRect.y(), mapRect.width(),
633 // Viewport has paper dimension
642 QRectF MapEditor::getTotalBBox() // FIXME-2 really needed? Overlaps with scene and VM...
648 QImage MapEditor::getImage(QPointF &offset)
650 QRectF mapRect = getTotalBBox(); // minimized sceneRect
652 int d = 10; // border
653 offset = QPointF(mapRect.x() - d / 2, mapRect.y() - d / 2);
654 QImage pix(mapRect.width() + d, mapRect.height() + d, QImage::Format_RGB32);
657 pp.setRenderHints(renderHints());
658 mapScene->render(&pp,
660 QRectF(0, 0, mapRect.width() + d, mapRect.height() + d),
662 QRectF(mapRect.x() - d / 2, mapRect.y() - d / 2,
663 mapRect.width() + d, mapRect.height() + d));
667 void MapEditor::setAntiAlias(bool b)
669 setRenderHint(QPainter::Antialiasing, b);
672 void MapEditor::setSmoothPixmap(bool b)
674 setRenderHint(QPainter::SmoothPixmapTransform, b);
677 void MapEditor::autoLayout()
679 // Create list with all bounding polygons
680 QList<LinkableMapObj *> mapobjects;
681 QList<ConvexPolygon> polys;
683 QList<Vector> vectors;
684 QList<Vector> orgpos;
685 QStringList headings; // FIXME-3 testing only
691 // Outer loop: Iterate until we no more changes in orientation
692 bool orientationChanged = true;
693 while (orientationChanged) {
694 BranchItem *ri = model->getRootItem();
695 for (int i = 0; i < ri->branchCount(); ++i) {
696 bi = ri->getBranchNum(i);
697 bo = (BranchObj *)bi->getLMO();
699 mapobjects.append(bo);
700 p = bo->getBoundingPolygon();
703 vectors.append(QPointF(0, 0));
704 orgpos.append(p.at(0));
705 headings.append(bi->getHeadingPlain());
707 for (int j = 0; j < bi->branchCount(); ++j) {
708 bi2 = bi->getBranchNum(j);
709 bo = (BranchObj *)bi2->getLMO();
711 mapobjects.append(bo);
712 p = bo->getBoundingPolygon();
715 vectors.append(QPointF(0, 0));
716 orgpos.append(p.at(0));
717 headings.append(bi2->getHeadingPlain());
722 // Iterate moving bounding polygons until we have no more collisions
724 while (collisions > 0) {
726 for (int i = 0; i < polys.size() - 1; ++i) {
727 for (int j = i + 1; j < polys.size(); ++j) {
728 if (polygonCollision(polys.at(i), polys.at(j),
733 qDebug() << "Collision: " << headings[i] << " - "
735 v = polys.at(j).centroid() - polys.at(i).centroid();
737 // Add random direction, if only two polygons with
739 if (v.x() == 0 || v.y() == 0) {
740 Vector w(cos(double((int)rand() % 1000)),
741 sin(double((int)rand() % 1000)));
746 // Scale translation vector by area of polygons
747 vectors[j] = v * 10000 / polys.at(j).weight();
748 vectors[i] = v * 10000 / polys.at(i).weight();
750 // FIXME-3 outer loop, "i" get's changed several
752 // Better not move away from centroid of 2 colliding
753 // polys, but from centroid of _all_
757 for (int i = 0; i < vectors.size(); i++) {
758 // qDebug() << " v="<<vectors[i]<<" "<<headings[i];
759 if (!vectors[i].isNull())
760 polys[i].translate(vectors[i]);
762 // if (debug) qDebug()<< "Collisions total: "<<collisions;
766 // Finally move the real objects and update
767 QList<LinkableMapObj::Orientation> orients;
768 for (int i = 0; i < polys.size(); i++) {
769 Vector v = polys[i].at(0) - orgpos[i];
770 orients.append(mapobjects[i]->getOrientation());
773 qDebug() << " Moving " << polys.at(i).weight() << " "
774 << mapobjects[i]->getAbsPos() << " -> "
775 << mapobjects[i]->getAbsPos() + v << " "
777 // mapobjects[i]->moveBy(v.x(),v.y() );
778 // mapobjects[i]->setRelPos();
779 model->startAnimation((BranchObj *)mapobjects[i], v);
781 qDebug() << i << " Weight: " << polys.at(i).weight() << " "
782 << v << " " << headings.at(i);
787 orientationChanged=false;
788 for (int i=0;i<polys.size();i++)
789 if (orients[i]!=mapobjects[i]->getOrientation())
791 orientationChanged=true;
798 // orientationChanged=false;
799 } // loop if orientation has changed
801 model->emitSelectionChanged();
804 TreeItem *MapEditor::findMapItem(QPointF p, TreeItem *exclude)
808 for (int i = 0; i < model->xlinkCount(); i++) {
809 link = model->getXLinkNum(i);
811 XLinkObj *xlo = link->getXLinkObj();
812 if (xlo && xlo->isInClickBox(p)) {
813 // Found XLink, now return the nearest XLinkItem of p
814 qreal d0 = Geometry::distance(p, xlo->getBeginPos());
815 qreal d1 = Geometry::distance(p, xlo->getEndPos());
817 return link->getBeginLinkItem();
819 return link->getEndLinkItem();
824 // Search branches (and their childs, e.g. images
825 // Start with mapcenter, no images allowed at rootItem
827 BranchItem *bi = model->getRootItem()->getFirstBranch();
828 TreeItem *found = NULL;
830 found = bi->findMapItem(p, exclude);
834 bi = model->getRootItem()->getBranchNum(i);
839 void MapEditor::testFunction1() {}
841 void MapEditor::testFunction2() { autoLayout(); }
843 void MapEditor::toggleWinter()
850 winter = new Winter(this);
851 QList<QRectF> obstacles;
853 BranchItem *cur = NULL;
854 BranchItem *prev = NULL;
855 model->nextBranch(cur, prev);
857 if (!cur->hasHiddenExportParent()) {
859 bo = (BranchObj *)(cur->getLMO());
860 if (bo && bo->isVisibleObj())
861 obstacles.append(bo->getBBox());
863 model->nextBranch(cur, prev);
865 winter->setObstacles(obstacles);
869 BranchItem *MapEditor::getBranchDirectAbove(BranchItem *bi)
874 return bi->parent()->getBranchNum(i - 1);
879 BranchItem *MapEditor::getBranchAbove(BranchItem *selbi)
882 int dz = selbi->depth(); // original depth
884 if (selbi->getLMO()->getOrientation() == LinkableMapObj::LeftOfCenter)
889 // Look for branch with same parent but directly above
890 if (dz == 1 && invert)
891 bi = getBranchDirectBelow(selbi);
893 bi = getBranchDirectAbove(selbi);
896 // direct predecessor
899 // Go towards center and look for predecessor
900 while (selbi->depth() > 0) {
901 selbi = (BranchItem *)(selbi->parent());
902 if (selbi->depth() == 1 && invert)
903 bi = getBranchDirectBelow(selbi);
905 bi = getBranchDirectAbove(selbi);
909 while (selbi->depth() < dz) {
910 // try to get back to original depth dz
911 bi = selbi->getLastBranch();
924 BranchItem *MapEditor::getBranchDirectBelow(BranchItem *bi)
928 if (i + 1 < bi->parent()->branchCount())
929 return bi->parent()->getBranchNum(i + 1);
934 BranchItem *MapEditor::getBranchBelow(BranchItem *selbi)
938 int dz = selbi->depth(); // original depth
940 if (selbi->getLMO()->getOrientation() == LinkableMapObj::LeftOfCenter)
943 // Look for branch with same parent but directly below
944 if (dz == 1 && invert)
945 bi = getBranchDirectAbove(selbi);
947 bi = getBranchDirectBelow(selbi);
952 // Go towards center and look for neighbour
953 while (selbi->depth() > 0) {
954 selbi = (BranchItem *)(selbi->parent());
955 if (selbi->depth() == 1 && invert)
956 bi = getBranchDirectAbove(selbi);
958 bi = getBranchDirectBelow(selbi);
962 while (selbi->depth() < dz) {
963 // try to get back to original depth dz
964 bi = selbi->getFirstBranch();
977 BranchItem *MapEditor::getLeftBranch(TreeItem *ti)
982 if (ti->isBranchLikeType()) {
983 BranchItem *bi = (BranchItem *)ti;
984 if (bi->depth() == 0) {
985 // Special case: use alternative selection index
986 BranchItem *newbi = bi->getLastSelectedBranchAlt();
989 // Try to find a mainbranch left of center
990 for (int i = 0; i < bi->branchCount(); i++) {
991 newbi = bi->getBranchNum(i);
992 bo = newbi->getBranchObj();
994 bo->getOrientation() == LinkableMapObj::LeftOfCenter)
1000 if (bi->getBranchObj()->getOrientation() ==
1001 LinkableMapObj::RightOfCenter)
1003 return (BranchItem *)(bi->parent());
1006 if (bi->getType() == TreeItem::Branch)
1007 return bi->getLastSelectedBranch();
1010 if (ti->parent() && ti->parent()->isBranchLikeType())
1011 return (BranchItem *)(ti->parent());
1015 BranchItem *MapEditor::getRightBranch(TreeItem *ti)
1020 if (ti->isBranchLikeType()) {
1021 BranchItem *bi = (BranchItem *)ti;
1022 if (bi->depth() == 0) {
1023 // Special case: use alternative selection index
1024 BranchItem *newbi = bi->getLastSelectedBranch();
1027 // Try to find a mainbranch right of center
1028 for (int i = 0; i < bi->branchCount(); i++) {
1029 newbi = bi->getBranchNum(i);
1030 bo = newbi->getBranchObj();
1032 bo->getOrientation() == LinkableMapObj::RightOfCenter)
1034 << "BI found right: " << newbi->getHeadingPlain();
1039 if (bi->getBranchObj()->getOrientation() ==
1040 LinkableMapObj::LeftOfCenter)
1042 return (BranchItem *)(bi->parent());
1045 if (bi->getType() == TreeItem::Branch)
1046 return (BranchItem *)bi->getLastSelectedBranch();
1049 if (ti->parent() && ti->parent()->isBranchLikeType())
1050 return (BranchItem *)(ti->parent());
1055 void MapEditor::cursorUp()
1057 if (state == MapEditor::EditingHeading)
1060 BranchItem *selbi = model->getSelectedBranch();
1063 // Exactly one branch is currently selected
1064 bi = getBranchAbove(selbi);
1069 // Nothing selected or already multiple selections
1070 TreeItem *ti = model->lastToggledItem();
1071 if (ti && ti->isBranchLikeType()) {
1072 bi = getBranchAbove( (BranchItem*)ti);
1079 void MapEditor::cursorUpToggleSelection()
1081 if (state == MapEditor::EditingHeading)
1084 BranchItem *selbi = model->getSelectedBranch();
1088 // Exactly one branch is currently selected
1089 bi = getBranchAbove(selbi);
1090 if (bi) model->selectToggle(bi);
1092 // Nothing selected or already multiple selections
1093 TreeItem *ti = model->lastToggledItem();
1094 if (ti && ti->isBranchLikeType()) {
1095 if (lastToggleDirection == toggleUp)
1096 bi = getBranchAbove( (BranchItem*)ti);
1098 bi = (BranchItem*)ti;
1101 model->selectToggle(bi);
1104 lastToggleDirection = toggleUp;
1107 void MapEditor::cursorDown()
1109 if (state == MapEditor::EditingHeading)
1112 BranchItem *selbi = model->getSelectedBranch();
1115 // Exactly one branch is currently selected
1116 bi = getBranchBelow(selbi);
1121 // Nothing selected or already multiple selections
1122 TreeItem *ti = model->lastToggledItem();
1123 if (ti && ti->isBranchLikeType()) {
1124 bi = getBranchBelow( (BranchItem*)ti);
1132 void MapEditor::cursorDownToggleSelection()
1134 if (state == MapEditor::EditingHeading)
1137 BranchItem *selbi = model->getSelectedBranch();
1140 // Exactly one branch is currently selected
1141 bi = getBranchBelow(selbi);
1143 model->selectToggle(bi);
1146 // Nothing selected or already multiple selections
1147 TreeItem *ti = model->lastToggledItem();
1148 if (ti && ti->isBranchLikeType()) {
1149 if (lastToggleDirection == toggleDown)
1150 bi = getBranchBelow( (BranchItem*)ti);
1152 bi = (BranchItem*)ti;
1155 model->selectToggle(bi);
1158 lastToggleDirection = toggleDown;
1161 void MapEditor::cursorLeft()
1163 TreeItem *ti = model->getSelectedItem();
1165 ti = model->lastToggledItem();
1169 BranchItem *bi = getLeftBranch(ti);
1173 ImageItem *ii = ti->getFirstImage();
1179 void MapEditor::cursorRight()
1181 TreeItem *ti = model->getSelectedItem();
1183 ti = model->lastToggledItem();
1187 BranchItem *bi = getRightBranch(ti);
1191 ImageItem *ii = ti->getFirstImage();
1197 void MapEditor::cursorFirst() { model->selectFirstBranch(); }
1199 void MapEditor::cursorLast() { model->selectLastBranch(); }
1201 void MapEditor::editHeading()
1203 if (state == EditingHeading) {
1204 editHeadingFinished();
1208 BranchObj *bo = model->getSelectedBranchObj();
1209 BranchItem *bi = model->getSelectedBranch();
1211 VymText heading = bi->getHeading();
1212 if (heading.isRichText() || bi->getHeadingPlain().contains("\n")) {
1213 mainWindow->windowShowHeadingEditor();
1214 ensureSelectionVisibleAnimated();
1217 model->setSelectionBlocked(true);
1219 lineEdit = new QLineEdit;
1220 QGraphicsProxyWidget *pw = mapScene->addWidget(lineEdit);
1221 pw->setZValue(Z_LINEEDIT);
1222 lineEdit->setCursor(Qt::IBeamCursor);
1223 lineEdit->setCursorPosition(1);
1225 #if defined(Q_OS_WINDOWS)
1226 QFont font = lineEdit->font();
1227 font.setPointSize(font.pointSize() + 4);
1228 lineEdit->setFont(font);
1235 if (bo->getOrientation() != LinkableMapObj::LeftOfCenter) {
1236 tl = bo->getOrnamentsBBox().topLeft();
1237 br = tl + QPointF(w, h);
1240 br = bo->getOrnamentsBBox().bottomRight();
1241 tl = br - QPointF(w, h);
1244 lineEdit->setGeometry(r.toRect());
1245 pw->setGeometry(r.toRect());
1249 // Set focus to MapEditor first
1250 // To avoid problems with Cursor up/down
1253 ensureAreaVisibleAnimated(r);
1255 if (heading.getTextASCII() == " ")
1256 heading.setPlainText("");
1257 lineEdit->setText(heading.getTextASCII());
1258 lineEdit->setFocus();
1259 lineEdit->selectAll(); // Hack to enable cursor in lineEdit
1260 lineEdit->deselect(); // probably a Qt bug...
1262 setState(EditingHeading);
1266 void MapEditor::editHeadingFinished()
1268 if (state != EditingHeading || !lineEdit ) {
1269 qWarning() << "ME::editHeadingFinished not editing heading!";
1271 lineEdit->clearFocus();
1272 QString s = lineEdit->text();
1273 s.replace(QRegExp("\\n"), " "); // Don't paste newline chars
1274 if (s.length() == 0)
1275 s = " "; // Don't allow empty lines, which would screw up drawing
1276 model->setHeadingPlainText(s);
1280 // FIXME-2 ensureAreaVisible like in starting editing?
1282 // Maybe reselect previous branch
1283 mainWindow->editHeadingFinished(model);
1285 // Autolayout to avoid overlapping branches with longer headings
1286 if (settings.value("/mainwindow/autoLayout/use", "true") == "true")
1290 model->setSelectionBlocked(false);
1294 void MapEditor::contextMenuEvent(QContextMenuEvent *e)
1296 // Lineedits are already closed by preceding
1297 // mouseEvent, we don't need to close here.
1299 QPointF p = mapToScene(e->pos());
1300 TreeItem *ti = findMapItem(p, NULL);
1302 if (ti) { // MapObj was found
1305 LinkableMapObj *lmo = NULL;
1306 BranchItem *selbi = model->getSelectedBranch();
1308 lmo = ((MapItem *)ti)->getLMO();
1312 QString sysFlagName;
1313 QUuid uid = ((BranchObj *)lmo)->findSystemFlagUidByPos(p);
1314 if (!uid.isNull()) {
1315 Flag *flag = systemFlagsMaster->findFlagByUid(uid);
1317 sysFlagName = flag->getName();
1320 if (sysFlagName.startsWith("system-task"))
1321 taskContextMenu->popup(e->globalPos());
1323 // Context Menu on branch or mapcenter
1324 branchContextMenu->popup(e->globalPos());
1327 if (model->getSelectedImage()) {
1328 // Context Menu on floatimage
1329 floatimageContextMenu->popup(e->globalPos());
1332 if (model->getSelectedXLink())
1333 // Context Menu on XLink
1338 else { // No MapObj found, we are on the Canvas itself
1339 // Context Menu on scene
1341 // Open context menu synchronously to position new mapcenter
1342 model->setContextPos(p);
1343 canvasContextMenu->exec(e->globalPos());
1344 model->unsetContextPos();
1349 void MapEditor::keyPressEvent(QKeyEvent *e)
1351 if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown)
1352 // Ignore PageUP/Down to avoid scrolling with keys
1355 if (e->modifiers() & Qt::ShiftModifier) {
1356 switch (mainWindow->getModMode()) {
1357 case Main::ModModePoint:
1358 setCursor(Qt::ArrowCursor);
1360 case Main::ModModeColor:
1361 setCursor(PickColorCursor);
1363 case Main::ModModeXLink:
1364 setCursor(XLinkCursor);
1366 case Main::ModModeMoveObject:
1367 setCursor(Qt::PointingHandCursor);
1369 case Main::ModModeMoveView:
1370 setCursor(QPixmap(":/mode-move-view.png"));
1373 setCursor(Qt::ArrowCursor);
1377 QGraphicsView::keyPressEvent(e);
1380 void MapEditor::keyReleaseEvent(QKeyEvent *e)
1382 if (!(e->modifiers() & Qt::ControlModifier))
1383 setCursor(Qt::ArrowCursor);
1386 void MapEditor::startMovingView(QMouseEvent *e)
1388 setState(MovingView);
1389 movingObj = NULL; // move Content not Obj
1390 movingObj_offset = e->globalPos();
1392 QPointF(horizontalScrollBar()->value(), verticalScrollBar()->value());
1393 movingVec = QPointF(0, 0);
1394 setCursor(HandOpenCursor);
1397 void MapEditor::mousePressEvent(QMouseEvent *e)
1399 // Ignore right clicks
1400 if (e->button() == Qt::RightButton) {
1402 QGraphicsView::mousePressEvent(e);
1406 // Check if we need to reset zoomFactor for middle button + Ctrl
1407 if (e->button() == Qt::MidButton && e->modifiers() & Qt::ControlModifier) {
1408 setZoomFactorTarget(1);
1413 QPointF p = mapToScene(e->pos());
1414 TreeItem *ti_found = findMapItem(p, NULL);
1415 LinkableMapObj *lmo_found = NULL;
1417 lmo_found = ((MapItem *)ti_found)->getLMO();
1420 // Allow selecting text in QLineEdit if necessary
1421 if (model->isSelectionBlocked()) {
1423 QGraphicsView::mousePressEvent(e);
1427 // Stop editing in LineEdit
1428 if (state == EditingHeading) editHeadingFinished();
1430 QString sysFlagName;
1433 uid = ((BranchObj *)lmo_found)->findSystemFlagUidByPos(p);
1434 if (!uid.isNull()) {
1435 Flag *flag = systemFlagsMaster->findFlagByUid(uid);
1437 sysFlagName = flag->getName();
1442 qDebug() << "ME::mouse pressed\n";
1443 qDebug() << " lmo_found=" << lmo_found;
1444 qDebug() << " ti_found=" << ti_found;
1445 //if (ti_found) qDebug() << " ti_found="<<ti_found->getHeading();
1446 qDebug() << " flag=" << sysFlagName;
1449 // Check modifier key (before selecting object!)
1450 if (ti_found && (e->modifiers() & Qt::ShiftModifier)) {
1451 if (mainWindow->getModMode() == Main::ModModeColor) {
1452 setState(PickingColor);
1453 mainWindow->setCurrentColor(ti_found->getHeadingColor());
1454 if (e->modifiers() & Qt::ControlModifier)
1455 model->colorBranch(ti_found->getHeadingColor());
1457 model->colorSubtree(ti_found->getHeadingColor());
1461 if (mainWindow->getModMode() == Main::ModModeMoveView) {
1467 // Check vymlink modifier (before selecting object!)
1468 if (ti_found && sysFlagName == "system-vymLink") {
1469 model->select(ti_found);
1470 if (e->modifiers() & Qt::ControlModifier) {
1471 if (e->modifiers() & Qt::ShiftModifier)
1472 model->deleteVymLink();
1474 mainWindow->editOpenVymLink(true);
1476 mainWindow->editOpenVymLink(false);
1480 // Select the clicked object, if not moving without linking
1481 if (ti_found && (e->modifiers() & Qt::ShiftModifier)) {
1482 if (mainWindow->getModMode() == Main::ModModePoint) {
1483 model->selectToggle(ti_found);
1484 lastToggleDirection = toggleUndefined;
1488 model->select(ti_found);
1492 // Take care of remaining system flags _or_ modifier modes
1494 if (!sysFlagName.isEmpty()) {
1495 // systemFlag clicked
1496 if (sysFlagName.contains("system-url")) {
1497 if (e->modifiers() & Qt::ControlModifier)
1498 mainWindow->editOpenURLTab();
1500 mainWindow->editOpenURL();
1502 else if (sysFlagName == "system-note")
1503 mainWindow->windowToggleNoteEditor();
1504 else if (sysFlagName == "hideInExport")
1505 model->toggleHideExport();
1506 else if (sysFlagName.startsWith("system-task-"))
1507 model->cycleTaskStatus();
1511 // Take care of xLink: Open context menu with targets
1512 // if clicked near to begin of xlink
1513 if (ti_found->xlinkCount() > 0 &&
1514 ti_found->getType() != TreeItem::MapCenter &&
1515 lmo_found->getBBox().width() > 30) {
1516 if ((lmo_found->getOrientation() !=
1517 LinkableMapObj::RightOfCenter &&
1518 p.x() < lmo_found->getBBox().left() + 10) ||
1519 (lmo_found->getOrientation() !=
1520 LinkableMapObj::LeftOfCenter &&
1521 p.x() > lmo_found->getBBox().right() - 10)) {
1522 // FIXME-4 similar code in mainwindow::updateActions
1524 QList<QAction *> alist;
1525 QList<BranchItem *> blist;
1526 for (int i = 0; i < ti_found->xlinkCount(); i++) {
1527 XLinkItem *xli = ti_found->getXLinkItemNum(i);
1528 BranchItem *bit = xli->getPartnerBranch();
1531 new QAction(ti_found->getXLinkItemNum(i)
1532 ->getPartnerBranch()
1533 ->getHeadingPlain(),
1536 menu.addActions(alist);
1537 QAction *ra = menu.exec(e->globalPos());
1539 model->select(blist.at(alist.indexOf(ra)));
1540 while (!alist.isEmpty()) {
1541 QAction *a = alist.takeFirst();
1550 // XLink modifier, create new XLink
1551 BranchItem *selbi = model->getSelectedBranch();
1552 if (selbi && mainWindow->getModMode() == Main::ModModeXLink &&
1553 (e->modifiers() & Qt::ShiftModifier)) {
1554 setState(DrawingLink);
1555 tmpLink = new Link(model);
1556 tmpLink->setBeginBranch(selbi);
1557 tmpLink->createMapObj();
1558 tmpLink->setStyleBegin("None");
1559 tmpLink->setStyleEnd("None");
1560 tmpLink->setEndPoint(mapToScene(e->pos()));
1561 tmpLink->updateLink();
1565 // Start moving around
1567 // Left Button Move Branches
1568 if (e->button() == Qt::LeftButton) {
1569 // No system flag clicked, take care of moving modes or simply
1571 movingObj_offset.setX(p.x() - lmo_found->x());
1572 movingObj_offset.setY(p.y() - lmo_found->y());
1573 movingObj_orgPos.setX(lmo_found->x());
1574 movingObj_orgPos.setY(lmo_found->y());
1575 if (ti_found->depth() > 0) {
1576 lmo_found->setRelPos();
1577 movingObj_orgRelPos = lmo_found->getRelPos();
1580 if (mainWindow->getModMode() == Main::ModModeMoveObject &&
1581 e->modifiers() & Qt::ShiftModifier) {
1582 setState(MovingObjectWithoutLinking);
1585 setState(MovingObject);
1587 movingObj = model->getSelectedLMO();
1590 // Middle Button Toggle Scroll
1591 // (On Mac OS X this won't work, but we still have
1592 // a button in the toolbar)
1593 if (e->button() == Qt::MidButton)
1594 model->toggleScroll();
1596 else { // No lmo found, check XLinks
1598 if (ti_found->getType() == TreeItem::XLink) {
1599 XLinkObj *xlo = (XLinkObj *)((MapItem *)ti_found)->getMO();
1601 setState(DrawingXLink);
1602 int i = xlo->ctrlPointInClickBox(p);
1604 xlo->setSelection(i);
1605 movingObj_offset.setX(p.x() - xlo->x());
1606 movingObj_offset.setY(p.y() - xlo->y());
1607 movingObj_orgPos.setX(xlo->x());
1608 movingObj_orgPos.setY(xlo->y());
1612 else { // No MapObj found, we are on the scene itself
1613 // Left Button move Pos of sceneView
1614 if (e->button() == Qt::LeftButton ||
1615 e->button() == Qt::MiddleButton) {
1623 void MapEditor::mouseMoveEvent(QMouseEvent *e)
1625 // Show mouse position for debugging in statusBar
1626 if (debug && e->modifiers() & Qt::ControlModifier)
1627 mainWindow->statusMessage(
1628 QString("ME::mousePressEvent Scene: %1 widget: %2")
1629 .arg(qpointFToString(mapToScene(e->pos())))
1630 .arg(qpointFToString(e->pos())));
1632 // Allow selecting text in QLineEdit if necessary
1633 if (model->isSelectionBlocked()) {
1635 QGraphicsView::mouseMoveEvent(e);
1640 if (state == MovingView &&
1641 (e->buttons() == Qt::LeftButton || e->buttons() == Qt::MiddleButton)) {
1642 QPointF p = e->globalPos();
1643 movingVec.setX(-p.x() + movingObj_offset.x());
1644 movingVec.setY(-p.y() + movingObj_offset.y());
1645 horizontalScrollBar()->setSliderPosition(
1646 (int)(movingCont_start.x() + movingVec.x()));
1647 verticalScrollBar()->setSliderPosition(
1648 (int)(movingCont_start.y() + movingVec.y()));
1650 scrollBarPosAnimation.stop();
1651 viewCenterAnimation.stop();
1652 rotationAnimation.stop();
1653 // zoomAnimation.stop();
1658 TreeItem *seli = model->getSelectedItem();
1660 MapObj *mosel = NULL;
1662 mosel = ((MapItem *)seli)->getMO();
1664 // If not already happened during mousepress, we might need to switch state
1665 if (mainWindow->getModMode() == Main::ModModeMoveObject &&
1666 e->modifiers() & Qt::ShiftModifier && e->buttons() == Qt::LeftButton) {
1667 state = MovingObjectWithoutLinking;
1670 // Move the selected MapObj
1672 (state == MovingObject || state == MovingObjectWithoutLinking ||
1673 state == DrawingXLink)) {
1676 // Check if we have to scroll
1679 if (e->y() >= 0 && e->y() <= margin)
1680 vPan.setY(e->y() - margin);
1681 else if (e->y() <= height() && e->y() > height() - margin)
1682 vPan.setY(e->y() - height() + margin);
1683 if (e->x() >= 0 && e->x() <= margin)
1684 vPan.setX(e->x() - margin);
1685 else if (e->x() <= width() && e->x() > width() - margin)
1686 vPan.setX(e->x() - width() + margin);
1688 pointerPos = e->pos();
1689 pointerMod = e->modifiers();
1691 } // selection && moving_obj
1693 // Draw a link from one branch to another
1694 if (state == DrawingLink) {
1695 tmpLink->setEndPoint(mapToScene(e->pos()));
1696 tmpLink->updateLink();
1700 void MapEditor::moveObject()
1702 if (!panningTimer->isActive())
1703 panningTimer->start(50);
1705 QPointF p = mapToScene(pointerPos);
1706 TreeItem *seli = model->getSelectedItem();
1707 LinkableMapObj *lmosel = NULL;
1709 lmosel = ((MapItem *)seli)->getLMO();
1713 // reset cursor if we are moving and don't copy
1715 // Check if we could link
1716 TreeItem *ti_found = findMapItem(p, seli);
1717 BranchItem *bi_dst = NULL;
1718 LinkableMapObj *lmo_dst = NULL;
1719 if (ti_found && ti_found != seli && ti_found->isBranchLikeType()) {
1720 bi_dst = (BranchItem *)ti_found;
1721 lmo_dst = bi_dst->getLMO();
1727 if (seli->getType() == TreeItem::Image) {
1728 FloatImageObj *fio = (FloatImageObj *)lmosel;
1729 fio->moveCenter(p.x() - movingObj_offset.x(),
1730 p.y() - movingObj_offset.y());
1732 fio->updateLinkGeometry(); // no need for reposition, if we update
1734 model->emitSelectionChanged(); // position has changed
1736 // Relink float to new mapcenter or branch, if shift is pressed
1737 // Only relink, if selection really has a new parent
1738 if (pointerMod == Qt::ShiftModifier && bi_dst &&
1739 bi_dst != seli->parent()) {
1740 // Also save the move which was done so far
1741 QString pold = qpointFToString(movingObj_orgRelPos);
1742 QString pnow = qpointFToString(fio->getRelPos());
1743 model->saveState(seli, "moveRel " + pold, seli,
1745 QString("Move %1 to relative position %2")
1746 .arg(model->getObjectName(lmosel))
1748 model->reposition();
1750 model->relinkImage((ImageItem *)seli, bi_dst);
1751 model->select(seli);
1754 else if (seli->isBranchLikeType()) { // selection != a FloatObj
1755 if (seli->depth() == 0) {
1757 lmosel->move(p - movingObj_offset);
1758 if (pointerMod == Qt::ControlModifier) {
1759 // Move only mapcenter, leave its children where they are
1761 v = lmosel->getAbsPos();
1762 for (int i = 0; i < seli->branchCount(); ++i) {
1763 seli->getBranchObjNum(i)->setRelPos();
1764 seli->getBranchObjNum(i)->setOrientation();
1769 if (seli->depth() == 1) {
1771 if (!lmosel->hasParObjTmp())
1772 lmosel->move(p - movingObj_offset);
1773 lmosel->setRelPos();
1776 // d>1, move ordinary branch
1777 if (lmosel->getOrientation() ==
1778 LinkableMapObj::LeftOfCenter)
1779 // Add width of bbox here, otherwise alignRelTo will
1780 // cause jumping around
1781 lmosel->move(p.x() - movingObj_offset.x(),
1782 p.y() - movingObj_offset.y() +
1783 lmosel->getTopPad());
1785 lmosel->move(p.x() - movingObj_offset.x(),
1786 p.y() - movingObj_offset.y() -
1787 lmosel->getTopPad());
1788 BranchItem *selbi = ((BranchItem *)seli);
1789 if (selbi->parentBranch()->getChildrenLayout() ==
1790 BranchItem::FreePositioning)
1791 lmosel->setRelPos();
1796 // Maybe we can relink temporary?
1797 if (bi_dst && state != MovingObjectWithoutLinking) {
1798 if (pointerMod == Qt::ControlModifier) {
1799 // Special case: CTRL to link below dst
1800 lmosel->setParObjTmp(lmo_dst, p, +1);
1802 else if (pointerMod == Qt::ShiftModifier)
1803 lmosel->setParObjTmp(lmo_dst, p, -1);
1805 lmosel->setParObjTmp(lmo_dst, p, 0);
1808 lmosel->unsetParObjTmp();
1810 // reposition subbranch
1811 lmosel->reposition();
1813 QItemSelection sel = model->getSelectionModel()->selection();
1814 updateSelection(sel, sel); // position has changed
1816 // In winter mode shake snow from heading
1818 model->emitDataChanged(seli);
1819 } // Moving branchLikeType
1820 } // End of lmosel != NULL
1821 else if (seli && seli->getType() == TreeItem::XLink) {
1822 // Move XLink control point
1823 MapObj *mosel = ((MapItem *)seli)->getMO();
1825 mosel->move(p - movingObj_offset); // FIXME-3 Missing savestate
1826 model->setChanged();
1827 model->emitSelectionChanged();
1831 qWarning("ME::moveObject Huh? I'm confused.");
1838 void MapEditor::mouseReleaseEvent(QMouseEvent *e)
1840 // Allow selecting text in QLineEdit if necessary
1841 if (model->isSelectionBlocked()) {
1843 QGraphicsView::mouseReleaseEvent(e);
1847 QPointF p = mapToScene(e->pos());
1848 TreeItem *seli = model->getSelectedItem();
1850 TreeItem *dsti = NULL;
1852 dsti = findMapItem(p, seli);
1853 LinkableMapObj *dst = NULL;
1854 BranchItem *selbi = model->getSelectedBranch();
1855 if (dsti && dsti->isBranchLikeType())
1856 dst = ((MapItem *)dsti)->getLMO();
1860 // Have we been picking color?
1861 if (state == PickingColor) {
1862 setCursor(Qt::ArrowCursor);
1863 // Check if we are over another branch
1865 if (e->modifiers() & Qt::ShiftModifier)
1866 model->colorBranch(mainWindow->getCurrentColor());
1868 model->colorSubtree(mainWindow->getCurrentColor());
1874 // Have we been drawing a link?
1875 if (state == DrawingLink) {
1877 // Check if we are over another branch
1879 tmpLink->setEndBranch(((BranchItem *)dsti));
1880 tmpLink->activate();
1881 tmpLink->updateLink();
1882 if (model->createLink(tmpLink)) {
1884 tmpLink->getBeginLinkItem(), "remove ()", seli,
1885 QString("addXLink (\"%1\",\"%2\",%3,\"%4\",\"%5\")")
1886 .arg(model->getSelectString(tmpLink->getBeginBranch()))
1887 .arg(model->getSelectString(tmpLink->getEndBranch()))
1888 .arg(tmpLink->getPen().width())
1889 .arg(tmpLink->getPen().color().name())
1890 .arg(penStyleToString(tmpLink->getPen().style())),
1891 QString("Adding Link from %1 to %2")
1892 .arg(model->getObjectName(seli))
1893 .arg(model->getObjectName(dsti)));
1902 // Have we been moving something?
1903 if (seli && state == MovingObject) {
1904 panningTimer->stop();
1905 if (seli->getType() == TreeItem::Image) {
1906 FloatImageObj *fio = (FloatImageObj *)(((MapItem *)seli)->getLMO());
1908 // Moved Image, we need to reposition
1909 QString pold = qpointFToString(movingObj_orgRelPos);
1910 QString pnow = qpointFToString(fio->getRelPos());
1911 model->saveState(seli, "moveRel " + pold, seli,
1913 QString("Move %1 to relative position %2")
1914 .arg(model->getObjectName(seli))
1917 model->emitDataChanged(
1918 seli->parent()); // Parent of image has changed
1919 model->reposition();
1923 if (selbi && selbi->depth() == 0) {
1924 if (movingObj_orgPos != selbi->getBranchObj()->getAbsPos()) {
1925 QString pold = qpointFToString(movingObj_orgPos);
1927 qpointFToString(selbi->getBranchObj()->getAbsPos());
1929 model->saveState(selbi, "move " + pold, selbi, "move " + pnow,
1930 QString("Move mapcenter %1 to position %2")
1931 .arg(model->getObjectName(selbi))
1936 if (seli->isBranchLikeType()) //(seli->getType() == TreeItem::Branch )
1937 { // A branch was moved
1938 LinkableMapObj *lmosel = NULL;
1939 lmosel = ((MapItem *)seli)->getLMO();
1941 // save the position in case we link to mapcenter
1942 QPointF savePos = QPointF(lmosel->getAbsPos());
1944 // Reset the temporary drawn link to the original one
1945 lmosel->unsetParObjTmp();
1947 // For Redo we may need to save original selection
1948 QString preSelStr = model->getSelectString(seli);
1950 if (dsti && objectMoved && state != MovingObjectWithoutLinking) {
1951 // We have a destination, relink to that
1952 BranchObj *selbo = model->getSelectedBranchObj();
1954 QString preParStr = model->getSelectString(seli->parent());
1955 QString preNum = QString::number(seli->num(), 10);
1956 QString preDstParStr;
1958 if (e->modifiers() & Qt::ShiftModifier &&
1959 dsti->parent()) { // Link above dst
1960 preDstParStr = model->getSelectString(dsti->parent());
1961 model->relinkBranch((BranchItem *)seli,
1962 (BranchItem *)dsti->parent(),
1963 ((BranchItem *)dsti)->num(), true);
1965 else if (e->modifiers() & Qt::ControlModifier &&
1968 preDstParStr = model->getSelectString(dsti->parent());
1969 model->relinkBranch((BranchItem *)seli,
1970 (BranchItem *)dsti->parent(),
1971 ((BranchItem *)dsti)->num() + 1, true);
1973 else { // Append to dst
1974 preDstParStr = model->getSelectString(dsti);
1975 model->relinkBranch((BranchItem *)seli, (BranchItem *)dsti,
1976 -1, true, movingObj_orgPos);
1977 if (dsti->depth() == 0)
1978 selbo->move(savePos);
1982 // No destination, undo temporary move
1984 if (seli->depth() == 1) {
1985 // The select string might be different _after_ moving
1986 // around. Therefor reposition and then use string of old
1988 model->reposition();
1990 QPointF rp(lmosel->getRelPos());
1991 if (rp != movingObj_orgRelPos) {
1992 QString ps = qpointFToString(rp);
1994 model->getSelectString(lmosel),
1995 "moveRel " + qpointFToString(movingObj_orgRelPos),
1996 preSelStr, "moveRel " + ps,
1997 QString("Move %1 to relative position %2")
1998 .arg(model->getObjectName(lmosel))
2003 if (selbi->parentBranch()->getChildrenLayout() ==
2004 BranchItem::FreePositioning) {
2005 lmosel->setRelPos();
2006 model->reposition();
2010 // Draw the original link, before selection was moved around
2011 if (settings.value("/animation/use", true).toBool() &&
2014 //(lmosel->getRelPos(),movingObj_orgRelPos)<3
2016 lmosel->setRelPos(); // calc relPos first for starting
2019 model->startAnimation((BranchObj *)lmosel,
2020 lmosel->getRelPos(),
2021 movingObj_orgRelPos);
2024 model->reposition();
2028 // Finally resize scene, if needed
2031 objectMoved = false;
2035 // maybe we moved View: set old cursor
2036 setCursor(Qt::ArrowCursor);
2038 if (state != EditingHeading)
2039 setState(Neutral); // Continue editing after double click!
2041 QGraphicsView::mouseReleaseEvent(e);
2044 void MapEditor::mouseDoubleClickEvent(QMouseEvent *e)
2046 // Allow selecting text in QLineEdit if necessary
2047 if (model->isSelectionBlocked()) {
2049 QGraphicsView::mouseDoubleClickEvent(e);
2053 if (e->button() == Qt::LeftButton) {
2054 QPointF p = mapToScene(e->pos());
2055 TreeItem *ti = findMapItem(p, NULL);
2056 LinkableMapObj *lmo;
2058 if (state == EditingHeading)
2059 editHeadingFinished();
2061 BranchItem *selbi = model->getSelectedBranch();
2063 lmo = ((MapItem *)ti)->getLMO();
2065 QUuid uid = ((BranchObj *)lmo)->findSystemFlagUidByPos(p);
2067 // Don't edit heading when double clicking system flag:
2078 void MapEditor::wheelEvent(QWheelEvent *e)
2080 if (e->modifiers() & Qt::ControlModifier &&
2081 e->angleDelta().y() != 0) {
2082 QPointF p = mapToScene(e->position().toPoint());
2083 if (e->angleDelta().y() > 0)
2084 // setZoomFactorTarget (zoomFactorTarget*1.15);
2085 setViewCenterTarget(p, zoomFactorTarget * 1.15, angleTarget);
2087 // setZoomFactorTarget (zoomFactorTarget*0.85);
2088 setViewCenterTarget(p, zoomFactorTarget * 0.85, angleTarget);
2091 scrollBarPosAnimation.stop();
2092 QGraphicsView::wheelEvent(e);
2096 void MapEditor::focusOutEvent(QFocusEvent *)
2098 // qDebug()<<"ME::focusOutEvent"<<e->reason();
2099 if (state == EditingHeading)
2100 editHeadingFinished();
2103 void MapEditor::resizeEvent(QResizeEvent *e) { QGraphicsView::resizeEvent(e); }
2105 void MapEditor::dragEnterEvent(QDragEnterEvent *event)
2107 // for (unsigned int i=0;event->format(i);i++) // Debug mime type
2108 // cerr << event->format(i) << endl;
2110 if (event->mimeData()->hasImage())
2111 event->acceptProposedAction();
2112 else if (event->mimeData()->hasUrls())
2113 event->acceptProposedAction();
2116 void MapEditor::dragMoveEvent(QDragMoveEvent *) {}
2118 void MapEditor::dragLeaveEvent(QDragLeaveEvent *event) { event->accept(); }
2120 void MapEditor::dropEvent(QDropEvent *event)
2122 BranchItem *selbi = model->getSelectedBranch();
2125 foreach (QString format, event->mimeData()->formats())
2126 qDebug() << "MapEditor: Dropped format: " << qPrintable(format);
2127 foreach (QUrl url, event->mimeData()->urls()) {
2128 qDebug() << " URL-path:" << url.path();
2129 qDebug() << "URL-string:" << url.toString();
2130 qDebug() << " enc:" << url.toEncoded();
2131 qDebug() << " valid:" << url.isValid();
2133 qDebug() << "============== mimeData ===================";
2134 qDebug() << "has-img : " << event->mimeData()->hasImage();
2135 qDebug() << "has-urls: " << event->mimeData()->hasUrls();
2136 qDebug() << " text: " << event->mimeData()->text();
2137 qDebug() << "===========================================";
2140 if (event->mimeData()->hasUrls()) {
2141 // Try text representation first, which works on windows, but in
2142 // Linux only for https, not local images
2143 QString url = event->mimeData()->text();
2144 if (url.isEmpty()) {
2146 event->mimeData()->urls().first().path().toLatin1();
2148 for (int i = 0; i < ba.count(); i++)
2150 ba2.append(ba.at(i));
2154 BranchItem *bi = NULL;
2155 // Workaround to avoid adding empty branches
2156 if (!url.isEmpty()) {
2157 if (url.startsWith("file://"))
2160 #if defined(Q_OS_WIN32)
2161 if (url.startsWith("/"))
2166 qDebug() << "dropped url seems to be image: " << url;
2167 // Image, try to download or set image from local file
2168 if (url.startsWith("http"))
2169 model->downloadImage(url);
2171 model->loadImage(bi, url);
2173 qDebug() << "finished loading image";
2176 bi = model->addNewBranch();
2179 if (url.endsWith(".vym", Qt::CaseInsensitive))
2180 model->setVymLink(url);
2184 // Shorten long URLs for heading
2185 int i = url.indexOf("?");
2186 QString url_short = url.left(i);
2188 url_short = url_short + "...";
2189 model->setHeadingPlainText(url_short);
2192 model->select(bi->parent());
2198 event->acceptProposedAction();
2201 void MapEditor::setState(EditorState s)
2203 if (state != Neutral && s != Neutral)
2204 qWarning() << "MapEditor::setState switching directly from " << state
2215 case EditingHeading:
2216 s = "EditingHeading";
2224 case MovingObjectWithoutLinking:
2225 s = "MovingObjectWithoutLinking";
2237 qDebug() << "MapEditor: State " << s << " of " << model->getMapName();
2242 MapEditor::EditorState MapEditor::getState() { return state; }
2244 void MapEditor::updateSelection(QItemSelection nsel, QItemSelection dsel)
2248 QList<MapItem *> itemsSelected;
2249 QList<MapItem *> itemsDeselected;
2251 QItemSelection sel = model->getSelectionModel()->selection();
2253 LinkableMapObj *lmo;
2255 // Add new selected objects
2256 if (sel.indexes().count() > 1)
2257 mainWindow->statusMessage(
2258 tr("%1 items selected").arg(sel.indexes().count()));
2260 foreach (QModelIndex ix, sel.indexes()) {
2261 MapItem *mi = static_cast<MapItem *>(ix.internalPointer());
2262 if (mi->isBranchLikeType() || mi->getType() == TreeItem::Image ||
2263 mi->getType() == TreeItem::XLink)
2264 if (!itemsSelected.contains(mi))
2265 itemsSelected.append(mi);
2268 mi->getLMO()->updateVisibility();
2271 // Delete objects meanwhile removed from selection
2272 foreach (QModelIndex ix, dsel.indexes()) {
2273 MapItem *mi = static_cast<MapItem *>(ix.internalPointer());
2274 if (mi->isBranchLikeType() || mi->getType() == TreeItem::Image ||
2275 mi->getType() == TreeItem::XLink)
2276 if (!itemsDeselected.contains(mi))
2277 itemsDeselected.append(mi);
2278 lmo = mi->getLMO(); // FIXME-2 xlink does return nullptr
2280 mi->getLMO()->updateVisibility();
2283 // Trim list of selection paths
2284 while (itemsSelected.count() < selPathList.count())
2285 delete selPathList.takeFirst();
2288 while (itemsSelected.count() < selPathList.count())
2289 delete selPathList.takeFirst();
2291 // Add additonal polygons
2292 QGraphicsPathItem *sp;
2293 while (itemsSelected.count() > selPathList.count()) {
2294 sp = mapScene->addPath(QPainterPath());
2296 selPathList.append(sp);
2299 // Reposition polygons
2300 for (int i = 0; i < itemsSelected.count(); ++i) {
2301 MapObj *mo = itemsSelected.at(i)->getMO();
2302 sp = selPathList.at(i);
2303 sp->setPath(mo->getSelectionPath());
2304 sp->setPen(selectionPen);
2305 sp->setBrush(selectionBrush);
2306 sp->setParentItem(mo);
2307 sp->setZValue(dZ_SELBOX);
2309 // Reposition also LineEdit for heading during animation
2311 lineEdit->move(mo->getAbsPos().toPoint());
2317 void MapEditor::updateData(const QModelIndex &sel)
2319 TreeItem *ti = static_cast<TreeItem *>(sel.internalPointer());
2322 qDebug() << "ME::updateData";
2325 qDebug() << " ti=NULL";
2328 qDebug() << " ti="<<ti;
2329 qDebug() << " h="<<ti->getHeadingPlain();
2332 if (ti && ti->isBranchLikeType()) {
2333 BranchObj *bo = (BranchObj *)(((MapItem *)ti)->getLMO());
2334 bo->updateVisuals();
2338 QList<QRectF> obstacles;
2340 BranchItem *cur = NULL;
2341 BranchItem *prev = NULL;
2342 model->nextBranch(cur, prev);
2344 if (!cur->hasHiddenExportParent()) {
2346 bo = (BranchObj *)(cur->getLMO());
2347 if (bo && bo->isVisibleObj())
2348 obstacles.append(bo->getBBox());
2350 model->nextBranch(cur, prev);
2352 winter->setObstacles(obstacles);
2356 void MapEditor::togglePresentationMode()
2358 mainWindow->togglePresentationMode();
2361 void MapEditor::setSelectionPen(const QPen &p)
2364 QItemSelection sel = model->getSelectionModel()->selection();
2365 updateSelection(sel, sel);
2368 QPen MapEditor::getSelectionPen() { return selectionPen; }
2370 void MapEditor::setSelectionBrush(const QBrush &b)
2373 QItemSelection sel = model->getSelectionModel()->selection();
2374 updateSelection(sel, sel);
2377 QBrush MapEditor::getSelectionBrush() { return selectionBrush; }