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));
49 zoomFactor = zoomFactorTarget = 1;
50 angle = angleTarget = 0;
53 model->registerMapEditor(this);
57 setStyleSheet("QGraphicsView:focus {" + editorFocusStyle + "}");
59 // Create bitmap cursors, platform dependant
60 HandOpenCursor = QCursor(QPixmap(":/mode-move-view.png"), 1, 1);
61 PickColorCursor = QCursor(QPixmap(":/cursorcolorpicker.png"), 5, 27);
62 XLinkCursor = QCursor(QPixmap(":/cursorxlink.png"), 1, 7);
71 // Shortcuts and actions
74 a = new QAction("Select upper branch", this);
75 a->setShortcut(Qt::Key_Up);
76 a->setShortcutContext(Qt::WidgetShortcut);
77 connect(a, SIGNAL(triggered()), this, SLOT(cursorUp()));
80 a = new QAction("Add upper branch to selection", this);
81 a->setShortcut(Qt::Key_Up + Qt::SHIFT);
82 a->setShortcutContext(Qt::WidgetShortcut);
84 connect(a, SIGNAL(triggered()), this, SLOT(cursorUpToggleSelection()));
86 a = new QAction("Select lower branch", this);
87 a->setShortcut(Qt::Key_Down);
88 a->setShortcutContext(Qt::WidgetShortcut);
90 connect(a, SIGNAL(triggered()), this, SLOT(cursorDown()));
92 a = new QAction("Add lower branch to selection", this);
93 a->setShortcut(Qt::Key_Down + Qt::SHIFT);
94 a->setShortcutContext(Qt::WidgetShortcut);
96 connect(a, SIGNAL(triggered()), this, SLOT(cursorDownToggleSelection()));
98 a = new QAction("Select left branch", this);
99 a->setShortcut(Qt::Key_Left);
100 // a->setShortcutContext (Qt::WidgetWithChildrenShortcut);
102 connect(a, SIGNAL(triggered()), this, SLOT(cursorLeft()));
104 a = new QAction("Select child branch", this);
105 a->setShortcut(Qt::Key_Right);
106 // a->setShortcutContext (Qt::WidgetWithChildrenShortcut);
108 connect(a, SIGNAL(triggered()), this, SLOT(cursorRight()));
110 a = new QAction("Select first branch", this);
111 a->setShortcut(Qt::Key_Home);
112 a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
114 connect(a, SIGNAL(triggered()), this, SLOT(cursorFirst()));
116 a = new QAction("Select last branch", this);
117 a->setShortcut(Qt::Key_End);
118 a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
120 connect(a, SIGNAL(triggered()), this, SLOT(cursorLast()));
122 // Action to embed LineEdit for heading in Scene
125 a = new QAction(tr("Edit heading", "MapEditor"), this);
126 a->setShortcut(Qt::Key_Return); // Edit heading
127 a->setShortcutContext(Qt::WidgetShortcut);
129 connect(a, SIGNAL(triggered()), this, SLOT(editHeading()));
130 a = new QAction(tr("Edit heading", "MapEditor"), this);
131 a->setShortcut(Qt::Key_Enter); // Edit heading
132 a->setShortcutContext(Qt::WidgetShortcut);
134 connect(a, SIGNAL(triggered()), this, SLOT(editHeading()));
137 selectionPen = QPen(QColor(255,255,0), 1);
138 selectionBrush = QBrush(QColor(255,255,0));
141 panningTimer = new QTimer(this);
143 connect(panningTimer, SIGNAL(timeout()), this, SLOT(panView()));
145 // Clone actions defined in MainWindow
146 foreach (QAction *qa, mainWindow->mapEditorActions) {
147 a = new QAction(this);
148 a->setShortcut(qa->shortcut());
149 a->setShortcutContext(qa->shortcutContext());
150 connect(a, SIGNAL(triggered()), qa, SLOT(trigger()));
159 MapEditor::~MapEditor()
161 // qDebug ()<<"Destr MapEditor this="<<this;
169 VymModel *MapEditor::getModel() { return model; }
171 QGraphicsScene *MapEditor::getScene() { return mapScene; }
173 void MapEditor::panView()
175 if (!vPan.isNull()) {
177 // To avoid jumping of the sceneView, only
178 // show selection, if not tmp linked
183 else if (vPan.x() > 0)
184 px = width() + vPan.x();
187 else if (vPan.y() > 0)
188 py = height() + vPan.y();
190 QPointF q = mapToScene(QPoint(px, py));
191 QRectF r = QRectF(q, QPointF(q.x() + 1, q.y() + 1));
193 // Expand view if necessary
194 setScrollBarPosTarget(r); // FIXME-2 mapToScene first?
196 // Stop possible other animations
197 if (scrollBarPosAnimation.state() == QAbstractAnimation::Running)
198 scrollBarPosAnimation.stop();
200 // Do linear animation
201 horizontalScrollBar()->setValue(horizontalScrollBar()->value() +
203 verticalScrollBar()->setValue(verticalScrollBar()->value() + vPan.y());
205 // Update currently moving object
210 void MapEditor::ensureAreaVisibleAnimated(const QRectF &area, bool maximizeArea) // FIXME-2 zooming in not working yet (fit to selection)
212 // Changes viewCenter to make sure that
213 // r is within the margins of the viewport
215 // Only zooms, if r NOT fit into viewport
216 // view is centered then on bounding box.
218 // Similar to QGraphicsItem::ensureVisible,
219 // but with animation and (if necessary)
222 int xmargin = settings.value("/mapeditor/scrollToMarginX/", 50).toInt();
223 int ymargin = settings.value("/mapeditor/scrollToMarginY/", 50).toInt();
225 // Do we need to zoom out to show area?
226 QRect areaViewCoord = mapFromScene(area).boundingRect();
228 // Visible area within margins
229 QRect visibleViewCoord = rect();
230 visibleViewCoord -= QMargins(xmargin, ymargin, xmargin, ymargin);
233 // Calculate required width and height considering rotation of view
234 qreal a = angle / 180 * M_PI;
235 qreal area_w_viewCoord = abs(sin(a) * area.height()) + abs(cos(a) * area.width());
236 qreal area_h_viewCoord = abs(sin(a) * area.width()) + abs(cos(a) * area.height());
237 qreal z_x = 1.0 * visibleViewCoord.width() / area_w_viewCoord;
238 qreal z_y = 1.0 * visibleViewCoord.height() / area_h_viewCoord;
240 qreal zf = min (z_x, z_y);
242 bool zoomOutRequired =
243 (visibleViewCoord.width() < areaViewCoord.width() ||
244 visibleViewCoord.height() < areaViewCoord.height());
245 bool zoomInRequired =
246 (visibleViewCoord.width() > areaViewCoord.width() &&
247 visibleViewCoord.height() > areaViewCoord.height());
249 //qDebug() << " zoom out: " << zoomOutRequired;
250 //qDebug() << " zoom in: " << zoomInRequired << " zoomFactor=" << zoomFactor << " zf=" << zf;
251 if (zoomOutRequired || maximizeArea) {
252 setViewCenterTarget(area.center(), zf, angle);
257 // After zooming bbox would fit into margins of viewport
260 if (areaViewCoord.left() < xmargin)
262 view_dx = areaViewCoord.left() - xmargin;
263 else if (areaViewCoord.right() > viewport()->width())
265 view_dx = areaViewCoord.x() + areaViewCoord.width() - viewport()->width() + xmargin;
267 if (areaViewCoord.top() < ymargin)
269 view_dy = areaViewCoord.top() - ymargin;
270 else if (areaViewCoord.bottom() > viewport()->height() - ymargin)
272 view_dy = areaViewCoord.y() + areaViewCoord.height() - viewport()->height() + ymargin;
274 if (abs(view_dx) > 5 || abs(view_dy) > 5)
276 mapToScene(viewport()->geometry().center() + QPoint (view_dx, view_dy)),
280 QEasingCurve::OutQuint);
283 void MapEditor::ensureSelectionVisibleAnimated(bool maximizeArea)
285 // Changes viewCenter to make sure that bounding box of all currently
286 // selected items is within the margins of the viewport
288 // Only zooms, if bounding box of items does NOT fit into viewport
289 // view is centered then on bounding box. (Useful also for big images)
291 // Similar to QGraphicsItem::ensureVisible, but with animation and (if necessary)
294 QList <TreeItem*> selis = model->getSelectedItems();
296 // Nothing to do, if nothing is selected
297 if (selis.isEmpty()) return;
299 // Calculate total bounding box
301 bool firstIteration = true;
303 foreach (TreeItem *ti, selis) {
304 LinkableMapObj *lmo = nullptr;
305 if (ti->getType() == TreeItem::Image || ti->isBranchLikeType())
306 lmo = ((MapItem *)ti)->getLMO();
308 if (firstIteration) {
309 bbox = lmo->getBBox();
310 firstIteration = false;
312 bbox = bbox.united(lmo->getBBox());
316 ensureAreaVisibleAnimated(bbox, maximizeArea);
319 void MapEditor::scrollTo(const QModelIndex &index)
321 if (index.isValid()) {
322 LinkableMapObj *lmo = NULL;
323 TreeItem *ti = static_cast<TreeItem *>(index.internalPointer());
324 if (ti->getType() == TreeItem::Image || ti->isBranchLikeType())
325 lmo = ((MapItem *)ti)->getLMO();
327 QRectF r = lmo->getBBox();
328 setScrollBarPosTarget(r);
334 void MapEditor::setScrollBarPosTarget(QRectF rect)
336 // Expand viewport, if rect is not contained
337 if (!sceneRect().contains(rect))
338 setSceneRect(sceneRect().united(rect));
340 int xmargin = settings.value("/mapeditor/scrollToMarginX/", 80).toInt();
341 int ymargin = settings.value("/mapeditor/scrollToMarginX/", 80).toInt();
344 qreal width = viewport()->width();
345 qreal height = viewport()->height();
346 QRectF viewRect = transform().scale(zoomFactorTarget, zoomFactorTarget).mapRect(rect);
348 qreal left = horizontalScrollBar()->value();
349 qreal right = left + width;
350 qreal top = verticalScrollBar()->value();
351 qreal bottom = top + height;
353 scrollBarPosTarget = getScrollBarPos();
355 if (viewRect.left() <= left + xmargin) {
356 // need to scroll from the left
357 scrollBarPosTarget.setX(int(viewRect.left() - xmargin - 0.5));
359 if (viewRect.right() >= right - xmargin) {
360 // need to scroll from the right
361 scrollBarPosTarget.setX(int(viewRect.right() - width + xmargin + 0.5));
363 if (viewRect.top() <= top + ymargin) {
364 // need to scroll from the top
365 scrollBarPosTarget.setY(int(viewRect.top() - ymargin - 0.5));
367 if (viewRect.bottom() >= bottom - ymargin) {
368 // need to scroll from the bottom
369 scrollBarPosTarget.setY(
370 int(viewRect.bottom() - height + ymargin + 0.5));
374 QPointF MapEditor::getScrollBarPosTarget() { return scrollBarPosTarget; }
376 void MapEditor::setScrollBarPos(const QPointF &p)
379 horizontalScrollBar()->setValue(int(p.x()));
380 verticalScrollBar()->setValue(int(p.y()));
383 QPointF MapEditor::getScrollBarPos()
385 return QPointF(horizontalScrollBar()->value(),
386 verticalScrollBar()->value());
387 // return scrollBarPos;
390 void MapEditor::animateScrollBars()
392 if (scrollBarPosAnimation.state() == QAbstractAnimation::Running)
393 scrollBarPosAnimation.stop();
395 if (settings.value("/animation/use/", true).toBool()) {
396 scrollBarPosAnimation.setTargetObject(this);
397 scrollBarPosAnimation.setPropertyName("scrollBarPos");
398 scrollBarPosAnimation.setDuration(
399 settings.value("/animation/duration/scrollbar", 2000).toInt());
400 scrollBarPosAnimation.setEasingCurve(QEasingCurve::OutQuint);
401 scrollBarPosAnimation.setStartValue(QPointF(
402 horizontalScrollBar()->value(), verticalScrollBar()->value()));
403 scrollBarPosAnimation.setEndValue(scrollBarPosTarget);
404 scrollBarPosAnimation.start();
407 setScrollBarPos(scrollBarPosTarget);
410 void MapEditor::setZoomFactorTarget(const qreal &zft)
412 zoomFactorTarget = zft;
413 if (zoomAnimation.state() == QAbstractAnimation::Running)
414 zoomAnimation.stop();
415 if (settings.value("/animation/use/", true).toBool()) {
416 zoomAnimation.setTargetObject(this);
417 zoomAnimation.setPropertyName("zoomFactor");
418 zoomAnimation.setDuration(
419 settings.value("/animation/duration/zoom", 2000).toInt());
420 zoomAnimation.setEasingCurve(QEasingCurve::OutQuint);
421 zoomAnimation.setStartValue(zoomFactor);
422 zoomAnimation.setEndValue(zft);
423 zoomAnimation.start();
429 qreal MapEditor::getZoomFactorTarget() { return zoomFactorTarget; }
431 void MapEditor::setZoomFactor(const qreal &zf)
437 qreal MapEditor::getZoomFactor() { return zoomFactor; }
439 void MapEditor::setAngleTarget(const qreal &at)
442 if (rotationAnimation.state() == QAbstractAnimation::Running)
443 rotationAnimation.stop();
444 if (settings.value("/animation/use/", true).toBool()) {
445 rotationAnimation.setTargetObject(this);
446 rotationAnimation.setPropertyName("angle");
447 rotationAnimation.setDuration(
448 settings.value("/animation/duration/rotation", 2000).toInt());
449 rotationAnimation.setEasingCurve(QEasingCurve::OutQuint);
450 rotationAnimation.setStartValue(angle);
451 rotationAnimation.setEndValue(at);
452 rotationAnimation.start();
455 setAngle(angleTarget);
458 qreal MapEditor::getAngleTarget() { return angleTarget; }
460 void MapEditor::setAngle(const qreal &a)
465 winter->updateView();
468 qreal MapEditor::getAngle() { return angle; }
470 void MapEditor::setViewCenterTarget(const QPointF &p, const qreal &zft,
471 const qreal &at, const int duration,
472 const QEasingCurve &easingCurve)
474 viewCenterTarget = p;
475 zoomFactorTarget = zft;
478 viewCenter = mapToScene(viewport()->geometry()).boundingRect().center();
480 if (viewCenterAnimation.state() == QAbstractAnimation::Running)
481 viewCenterAnimation.stop();
482 if (rotationAnimation.state() == QAbstractAnimation::Running)
483 rotationAnimation.stop();
484 if (zoomAnimation.state() == QAbstractAnimation::Running)
485 zoomAnimation.stop();
487 if (settings.value("/animation/use/", true).toBool()) {
488 viewCenterAnimation.setTargetObject(this);
489 viewCenterAnimation.setPropertyName("viewCenter");
490 viewCenterAnimation.setDuration(
491 settings.value("/animation/duration/scrollbar", duration).toInt());
492 viewCenterAnimation.setEasingCurve(easingCurve);
493 viewCenterAnimation.setStartValue(viewCenter);
494 viewCenterAnimation.setEndValue(viewCenterTarget);
495 viewCenterAnimation.start();
497 rotationAnimation.setTargetObject(this);
498 rotationAnimation.setPropertyName("angle");
499 rotationAnimation.setDuration(
500 settings.value("/animation/duration/rotation", duration).toInt());
501 rotationAnimation.setEasingCurve(easingCurve);
502 rotationAnimation.setStartValue(angle);
503 rotationAnimation.setEndValue(angleTarget);
504 rotationAnimation.start();
506 zoomAnimation.setTargetObject(this);
507 zoomAnimation.setPropertyName("zoomFactor");
508 zoomAnimation.setDuration(
509 settings.value("/animation/duration/zoom", duration).toInt());
510 zoomAnimation.setEasingCurve(easingCurve);
511 zoomAnimation.setStartValue(zoomFactor);
512 zoomAnimation.setEndValue(zoomFactorTarget);
513 zoomAnimation.start();
516 setAngle(angleTarget);
518 setViewCenter(viewCenterTarget);
522 void MapEditor::setViewCenterTarget()
524 MapItem *selti = (MapItem *)(model->getSelectedItem());
526 LinkableMapObj *lmo = selti->getLMO();
528 setViewCenterTarget(lmo->getBBox().center(), 1, 0);
532 QPointF MapEditor::getViewCenterTarget() { return viewCenterTarget; }
534 void MapEditor::setViewCenter(const QPointF &vc) { centerOn(vc); }
536 QPointF MapEditor::getViewCenter() { return viewCenter; }
538 void MapEditor::updateMatrix()
541 t_zoom.scale(zoomFactor, zoomFactor);
544 setTransform(t_zoom * t_rot);
547 void MapEditor::minimizeView() {
548 // If we only would set scene rectangle to existing items, then
549 // view fould "jump", when Qt automatically tries to center.
550 // Better consider the currently visible viewport (with slight offset)
551 QRectF r = mapToScene(viewport()->geometry()).boundingRect();
553 setSceneRect(scene()->itemsBoundingRect().united(r));
556 void MapEditor::print()
558 QRectF totalBBox = getTotalBBox();
561 printer = mainWindow->setupPrinter();
563 // Try to set orientation automagically
564 // Note: Interpretation of generated postscript is amibiguous, if
565 // there are problems with landscape mode, see
566 // http://sdb.suse.de/de/sdb/html/jsmeix_print-cups-landscape-81.html
568 if (totalBBox.width() > totalBBox.height())
569 // recommend landscape
570 printer->setPageOrientation(QPageLayout::Landscape);
572 // recommend portrait
573 printer->setPageOrientation(QPageLayout::Portrait);
575 QPrintDialog dialog(printer, this);
576 dialog.setWindowTitle(tr("Print vym map", "MapEditor"));
577 if (dialog.exec() == QDialog::Accepted) {
578 QPainter pp(printer);
580 pp.setRenderHint(QPainter::Antialiasing, true);
582 // Don't print the visualisation of selection
583 model->unselectAll();
585 QRectF mapRect = totalBBox;
586 QGraphicsRectItem *frame = NULL;
589 // Print frame around map
590 mapRect.setRect(totalBBox.x() - 10, totalBBox.y() - 10,
591 totalBBox.width() + 20, totalBBox.height() + 20);
592 frame = mapScene->addRect(mapRect, QPen(Qt::black),
593 QBrush(Qt::NoBrush));
599 (double)printer->width() / (double)printer->height();
600 double mapAspect = (double)mapRect.width() / (double)mapRect.height();
602 if (mapAspect >= paperAspect) {
603 // Fit horizontally to paper width
604 // pp.setViewport(0,0,
605 // printer->width(),(int)(printer->width()/mapAspect) );
606 viewBottom = (int)(printer->width() / mapAspect);
609 // Fit vertically to paper height
610 // pp.setViewport(0,0,(int)(printer->height()*mapAspect),printer->height());
611 viewBottom = printer->height();
615 // Print footer below map
617 font.setPointSize(10);
619 QRectF footerBox(0, viewBottom, printer->width(), 15);
620 pp.drawText(footerBox, Qt::AlignLeft,
621 "VYM - " + model->getFileName());
622 pp.drawText(footerBox, Qt::AlignRight,
623 QDate::currentDate().toString(Qt::TextDate));
625 mapScene->render(&pp,
626 QRectF(0, 0, printer->width(), printer->height() - 15),
627 QRectF(mapRect.x(), mapRect.y(), mapRect.width(),
630 // Viewport has paper dimension
639 QRectF MapEditor::getTotalBBox() // FIXME-2 really needed? Overlaps with scene and VM...
645 QImage MapEditor::getImage(QPointF &offset)
647 QRectF mapRect = getTotalBBox(); // minimized sceneRect
649 int d = 10; // border
650 offset = QPointF(mapRect.x() - d / 2, mapRect.y() - d / 2);
651 QImage pix(mapRect.width() + d, mapRect.height() + d, QImage::Format_RGB32);
654 pp.setRenderHints(renderHints());
655 mapScene->render(&pp,
657 QRectF(0, 0, mapRect.width() + d, mapRect.height() + d),
659 QRectF(mapRect.x() - d / 2, mapRect.y() - d / 2,
660 mapRect.width() + d, mapRect.height() + d));
664 void MapEditor::setAntiAlias(bool b)
666 setRenderHint(QPainter::Antialiasing, b);
669 void MapEditor::setSmoothPixmap(bool b)
671 setRenderHint(QPainter::SmoothPixmapTransform, b);
674 void MapEditor::autoLayout()
676 // Create list with all bounding polygons
677 QList<LinkableMapObj *> mapobjects;
678 QList<ConvexPolygon> polys;
680 QList<Vector> vectors;
681 QList<Vector> orgpos;
682 QStringList headings; // FIXME-3 testing only
688 // Outer loop: Iterate until we no more changes in orientation
689 bool orientationChanged = true;
690 while (orientationChanged) {
691 BranchItem *ri = model->getRootItem();
692 for (int i = 0; i < ri->branchCount(); ++i) {
693 bi = ri->getBranchNum(i);
694 bo = (BranchObj *)bi->getLMO();
696 mapobjects.append(bo);
697 p = bo->getBoundingPolygon();
700 vectors.append(QPointF(0, 0));
701 orgpos.append(p.at(0));
702 headings.append(bi->getHeadingPlain());
704 for (int j = 0; j < bi->branchCount(); ++j) {
705 bi2 = bi->getBranchNum(j);
706 bo = (BranchObj *)bi2->getLMO();
708 mapobjects.append(bo);
709 p = bo->getBoundingPolygon();
712 vectors.append(QPointF(0, 0));
713 orgpos.append(p.at(0));
714 headings.append(bi2->getHeadingPlain());
719 // Iterate moving bounding polygons until we have no more collisions
721 while (collisions > 0) {
723 for (int i = 0; i < polys.size() - 1; ++i) {
724 for (int j = i + 1; j < polys.size(); ++j) {
725 if (polygonCollision(polys.at(i), polys.at(j),
730 qDebug() << "Collision: " << headings[i] << " - "
732 v = polys.at(j).centroid() - polys.at(i).centroid();
734 // Add random direction, if only two polygons with
736 if (v.x() == 0 || v.y() == 0) {
737 Vector w(cos(double((int)rand() % 1000)),
738 sin(double((int)rand() % 1000)));
743 // Scale translation vector by area of polygons
744 vectors[j] = v * 10000 / polys.at(j).weight();
745 vectors[i] = v * 10000 / polys.at(i).weight();
747 // FIXME-3 outer loop, "i" get's changed several
749 // Better not move away from centroid of 2 colliding
750 // polys, but from centroid of _all_
754 for (int i = 0; i < vectors.size(); i++) {
755 // qDebug() << " v="<<vectors[i]<<" "<<headings[i];
756 if (!vectors[i].isNull())
757 polys[i].translate(vectors[i]);
759 // if (debug) qDebug()<< "Collisions total: "<<collisions;
763 // Finally move the real objects and update
764 QList<LinkableMapObj::Orientation> orients;
765 for (int i = 0; i < polys.size(); i++) {
766 Vector v = polys[i].at(0) - orgpos[i];
767 orients.append(mapobjects[i]->getOrientation());
770 qDebug() << " Moving " << polys.at(i).weight() << " "
771 << mapobjects[i]->getAbsPos() << " -> "
772 << mapobjects[i]->getAbsPos() + v << " "
774 // mapobjects[i]->moveBy(v.x(),v.y() );
775 // mapobjects[i]->setRelPos();
776 model->startAnimation((BranchObj *)mapobjects[i], v);
778 qDebug() << i << " Weight: " << polys.at(i).weight() << " "
779 << v << " " << headings.at(i);
784 orientationChanged=false;
785 for (int i=0;i<polys.size();i++)
786 if (orients[i]!=mapobjects[i]->getOrientation())
788 orientationChanged=true;
795 // orientationChanged=false;
796 } // loop if orientation has changed
798 model->emitSelectionChanged();
801 TreeItem *MapEditor::findMapItem(QPointF p, TreeItem *exclude)
805 for (int i = 0; i < model->xlinkCount(); i++) {
806 link = model->getXLinkNum(i);
808 XLinkObj *xlo = link->getXLinkObj();
809 if (xlo && xlo->isInClickBox(p)) {
810 // Found XLink, now return the nearest XLinkItem of p
811 qreal d0 = Geometry::distance(p, xlo->getBeginPos());
812 qreal d1 = Geometry::distance(p, xlo->getEndPos());
814 return link->getBeginLinkItem();
816 return link->getEndLinkItem();
821 // Search branches (and their childs, e.g. images
822 // Start with mapcenter, no images allowed at rootItem
824 BranchItem *bi = model->getRootItem()->getFirstBranch();
825 TreeItem *found = NULL;
827 found = bi->findMapItem(p, exclude);
831 bi = model->getRootItem()->getBranchNum(i);
836 void MapEditor::testFunction1() {}
838 void MapEditor::testFunction2() { autoLayout(); }
840 void MapEditor::toggleWinter()
847 winter = new Winter(this);
848 QList<QRectF> obstacles;
850 BranchItem *cur = NULL;
851 BranchItem *prev = NULL;
852 model->nextBranch(cur, prev);
854 if (!cur->hasHiddenExportParent()) {
856 bo = (BranchObj *)(cur->getLMO());
857 if (bo && bo->isVisibleObj())
858 obstacles.append(bo->getBBox());
860 model->nextBranch(cur, prev);
862 winter->setObstacles(obstacles);
866 BranchItem *MapEditor::getBranchDirectAbove(BranchItem *bi)
871 return bi->parent()->getBranchNum(i - 1);
876 BranchItem *MapEditor::getBranchAbove(BranchItem *selbi)
879 int dz = selbi->depth(); // original depth
881 if (selbi->getLMO()->getOrientation() == LinkableMapObj::LeftOfCenter)
886 // Look for branch with same parent but directly above
887 if (dz == 1 && invert)
888 bi = getBranchDirectBelow(selbi);
890 bi = getBranchDirectAbove(selbi);
893 // direct predecessor
896 // Go towards center and look for predecessor
897 while (selbi->depth() > 0) {
898 selbi = (BranchItem *)(selbi->parent());
899 if (selbi->depth() == 1 && invert)
900 bi = getBranchDirectBelow(selbi);
902 bi = getBranchDirectAbove(selbi);
906 while (selbi->depth() < dz) {
907 // try to get back to original depth dz
908 bi = selbi->getLastBranch();
921 BranchItem *MapEditor::getBranchDirectBelow(BranchItem *bi)
925 if (i + 1 < bi->parent()->branchCount())
926 return bi->parent()->getBranchNum(i + 1);
931 BranchItem *MapEditor::getBranchBelow(BranchItem *selbi)
935 int dz = selbi->depth(); // original depth
937 if (selbi->getLMO()->getOrientation() == LinkableMapObj::LeftOfCenter)
940 // Look for branch with same parent but directly below
941 if (dz == 1 && invert)
942 bi = getBranchDirectAbove(selbi);
944 bi = getBranchDirectBelow(selbi);
949 // Go towards center and look for neighbour
950 while (selbi->depth() > 0) {
951 selbi = (BranchItem *)(selbi->parent());
952 if (selbi->depth() == 1 && invert)
953 bi = getBranchDirectAbove(selbi);
955 bi = getBranchDirectBelow(selbi);
959 while (selbi->depth() < dz) {
960 // try to get back to original depth dz
961 bi = selbi->getFirstBranch();
974 BranchItem *MapEditor::getLeftBranch(TreeItem *ti)
979 if (ti->isBranchLikeType()) {
980 BranchItem *bi = (BranchItem *)ti;
981 if (bi->depth() == 0) {
982 // Special case: use alternative selection index
983 BranchItem *newbi = bi->getLastSelectedBranchAlt();
986 // Try to find a mainbranch left of center
987 for (int i = 0; i < bi->branchCount(); i++) {
988 newbi = bi->getBranchNum(i);
989 bo = newbi->getBranchObj();
991 bo->getOrientation() == LinkableMapObj::LeftOfCenter)
997 if (bi->getBranchObj()->getOrientation() ==
998 LinkableMapObj::RightOfCenter)
1000 return (BranchItem *)(bi->parent());
1003 if (bi->getType() == TreeItem::Branch)
1004 return bi->getLastSelectedBranch();
1007 if (ti->parent() && ti->parent()->isBranchLikeType())
1008 return (BranchItem *)(ti->parent());
1012 BranchItem *MapEditor::getRightBranch(TreeItem *ti)
1017 if (ti->isBranchLikeType()) {
1018 BranchItem *bi = (BranchItem *)ti;
1019 if (bi->depth() == 0) {
1020 // Special case: use alternative selection index
1021 BranchItem *newbi = bi->getLastSelectedBranch();
1024 // Try to find a mainbranch right of center
1025 for (int i = 0; i < bi->branchCount(); i++) {
1026 newbi = bi->getBranchNum(i);
1027 bo = newbi->getBranchObj();
1029 bo->getOrientation() == LinkableMapObj::RightOfCenter)
1031 << "BI found right: " << newbi->getHeadingPlain();
1036 if (bi->getBranchObj()->getOrientation() ==
1037 LinkableMapObj::LeftOfCenter)
1039 return (BranchItem *)(bi->parent());
1042 if (bi->getType() == TreeItem::Branch)
1043 return (BranchItem *)bi->getLastSelectedBranch();
1046 if (ti->parent() && ti->parent()->isBranchLikeType())
1047 return (BranchItem *)(ti->parent());
1052 void MapEditor::cursorUp()
1054 if (state == MapEditor::EditingHeading)
1057 BranchItem *selbi = model->getSelectedBranch();
1060 // Exactly one branch is currently selected
1061 bi = getBranchAbove(selbi);
1066 // Nothing selected or already multiple selections
1067 TreeItem *ti = model->lastToggledItem();
1068 if (ti && ti->isBranchLikeType()) {
1069 bi = getBranchAbove( (BranchItem*)ti);
1076 void MapEditor::cursorUpToggleSelection()
1078 if (state == MapEditor::EditingHeading)
1081 BranchItem *selbi = model->getSelectedBranch();
1085 // Exactly one branch is currently selected
1086 bi = getBranchAbove(selbi);
1087 if (bi) model->selectToggle(bi);
1089 // Nothing selected or already multiple selections
1090 TreeItem *ti = model->lastToggledItem();
1091 if (ti && ti->isBranchLikeType()) {
1092 if (lastToggleDirection == toggleUp)
1093 bi = getBranchAbove( (BranchItem*)ti);
1095 bi = (BranchItem*)ti;
1098 model->selectToggle(bi);
1101 lastToggleDirection = toggleUp;
1104 void MapEditor::cursorDown()
1106 if (state == MapEditor::EditingHeading)
1109 BranchItem *selbi = model->getSelectedBranch();
1112 // Exactly one branch is currently selected
1113 bi = getBranchBelow(selbi);
1118 // Nothing selected or already multiple selections
1119 TreeItem *ti = model->lastToggledItem();
1120 if (ti && ti->isBranchLikeType()) {
1121 bi = getBranchBelow( (BranchItem*)ti);
1129 void MapEditor::cursorDownToggleSelection()
1131 if (state == MapEditor::EditingHeading)
1134 BranchItem *selbi = model->getSelectedBranch();
1137 // Exactly one branch is currently selected
1138 bi = getBranchBelow(selbi);
1140 model->selectToggle(bi);
1143 // Nothing selected or already multiple selections
1144 TreeItem *ti = model->lastToggledItem();
1145 if (ti && ti->isBranchLikeType()) {
1146 if (lastToggleDirection == toggleDown)
1147 bi = getBranchBelow( (BranchItem*)ti);
1149 bi = (BranchItem*)ti;
1152 model->selectToggle(bi);
1155 lastToggleDirection = toggleDown;
1158 void MapEditor::cursorLeft()
1160 TreeItem *ti = model->getSelectedItem();
1162 ti = model->lastToggledItem();
1166 BranchItem *bi = getLeftBranch(ti);
1170 ImageItem *ii = ti->getFirstImage();
1176 void MapEditor::cursorRight()
1178 TreeItem *ti = model->getSelectedItem();
1180 ti = model->lastToggledItem();
1184 BranchItem *bi = getRightBranch(ti);
1188 ImageItem *ii = ti->getFirstImage();
1194 void MapEditor::cursorFirst() { model->selectFirstBranch(); }
1196 void MapEditor::cursorLast() { model->selectLastBranch(); }
1198 void MapEditor::editHeading()
1200 if (state == EditingHeading) {
1201 editHeadingFinished();
1205 BranchObj *bo = model->getSelectedBranchObj();
1206 BranchItem *bi = model->getSelectedBranch();
1208 VymText heading = bi->getHeading();
1209 if (heading.isRichText() || bi->getHeadingPlain().contains("\n")) {
1210 mainWindow->windowShowHeadingEditor();
1211 ensureSelectionVisibleAnimated();
1214 model->setSelectionBlocked(true);
1216 lineEdit = new QLineEdit;
1217 QGraphicsProxyWidget *pw = mapScene->addWidget(lineEdit);
1218 pw->setZValue(Z_LINEEDIT);
1219 lineEdit->setCursor(Qt::IBeamCursor);
1220 lineEdit->setCursorPosition(1);
1222 #if defined(Q_OS_WINDOWS)
1223 QFont font = lineEdit->font();
1224 font.setPointSize(font.pointSize() + 4);
1225 lineEdit->setFont(font);
1232 if (bo->getOrientation() != LinkableMapObj::LeftOfCenter) {
1233 tl = bo->getOrnamentsBBox().topLeft();
1234 br = tl + QPointF(w, h);
1237 br = bo->getOrnamentsBBox().bottomRight();
1238 tl = br - QPointF(w, h);
1241 lineEdit->setGeometry(r.toRect());
1242 pw->setGeometry(r.toRect());
1246 // Set focus to MapEditor first
1247 // To avoid problems with Cursor up/down
1250 ensureAreaVisibleAnimated(r);
1252 lineEdit->setText(heading.getTextASCII());
1253 lineEdit->setFocus();
1254 lineEdit->selectAll(); // Hack to enable cursor in lineEdit
1255 lineEdit->deselect(); // probably a Qt bug...
1257 setState(EditingHeading);
1261 void MapEditor::editHeadingFinished()
1263 if (state != EditingHeading || !lineEdit ) {
1264 qWarning() << "ME::editHeadingFinished not editing heading!";
1266 lineEdit->clearFocus();
1267 QString s = lineEdit->text();
1268 s.replace(QRegExp("\\n"), " "); // Don't paste newline chars
1269 if (s.length() == 0)
1270 s = " "; // Don't allow empty lines, which would screw up drawing
1271 model->setHeadingPlainText(s);
1275 // FIXME-2 ensureAreaVisible like in starting editing?
1277 // Maybe reselect previous branch
1278 mainWindow->editHeadingFinished(model);
1280 // Autolayout to avoid overlapping branches with longer headings
1281 if (settings.value("/mainwindow/autoLayout/use", "true") == "true")
1285 model->setSelectionBlocked(false);
1289 void MapEditor::contextMenuEvent(QContextMenuEvent *e)
1291 // Lineedits are already closed by preceding
1292 // mouseEvent, we don't need to close here.
1294 QPointF p = mapToScene(e->pos());
1295 TreeItem *ti = findMapItem(p, NULL);
1297 if (ti) { // MapObj was found
1300 LinkableMapObj *lmo = NULL;
1301 BranchItem *selbi = model->getSelectedBranch();
1303 lmo = ((MapItem *)ti)->getLMO();
1307 QString sysFlagName;
1308 QUuid uid = ((BranchObj *)lmo)->findSystemFlagUidByPos(p);
1309 if (!uid.isNull()) {
1310 Flag *flag = systemFlagsMaster->findFlagByUid(uid);
1312 sysFlagName = flag->getName();
1315 if (sysFlagName.startsWith("system-task"))
1316 taskContextMenu->popup(e->globalPos());
1318 // Context Menu on branch or mapcenter
1319 branchContextMenu->popup(e->globalPos());
1322 if (model->getSelectedImage()) {
1323 // Context Menu on floatimage
1324 floatimageContextMenu->popup(e->globalPos());
1327 if (model->getSelectedXLink())
1328 // Context Menu on XLink
1333 else { // No MapObj found, we are on the Canvas itself
1334 // Context Menu on scene
1336 // Open context menu synchronously to position new mapcenter
1337 model->setContextPos(p);
1338 canvasContextMenu->exec(e->globalPos());
1339 model->unsetContextPos();
1344 void MapEditor::keyPressEvent(QKeyEvent *e)
1346 if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown)
1347 // Ignore PageUP/Down to avoid scrolling with keys
1350 if (e->modifiers() & Qt::ShiftModifier) {
1351 switch (mainWindow->getModMode()) {
1352 case Main::ModModePoint:
1353 setCursor(Qt::ArrowCursor);
1355 case Main::ModModeColor:
1356 setCursor(PickColorCursor);
1358 case Main::ModModeXLink:
1359 setCursor(XLinkCursor);
1361 case Main::ModModeMoveObject:
1362 setCursor(Qt::PointingHandCursor);
1364 case Main::ModModeMoveView:
1365 setCursor(QPixmap(":/mode-move-view.png"));
1368 setCursor(Qt::ArrowCursor);
1372 QGraphicsView::keyPressEvent(e);
1375 void MapEditor::keyReleaseEvent(QKeyEvent *e)
1377 if (!(e->modifiers() & Qt::ControlModifier))
1378 setCursor(Qt::ArrowCursor);
1381 void MapEditor::startMovingView(QMouseEvent *e)
1383 setState(MovingView);
1384 movingObj = NULL; // move Content not Obj
1385 movingObj_offset = e->globalPos();
1387 QPointF(horizontalScrollBar()->value(), verticalScrollBar()->value());
1388 movingVec = QPointF(0, 0);
1389 setCursor(HandOpenCursor);
1392 void MapEditor::mousePressEvent(QMouseEvent *e)
1394 // Ignore right clicks
1395 if (e->button() == Qt::RightButton) {
1397 QGraphicsView::mousePressEvent(e);
1401 // Check if we need to reset zoomFactor for middle button + Ctrl
1402 if (e->button() == Qt::MidButton && e->modifiers() & Qt::ControlModifier) {
1403 setZoomFactorTarget(1);
1408 QPointF p = mapToScene(e->pos());
1409 TreeItem *ti_found = findMapItem(p, NULL);
1410 LinkableMapObj *lmo_found = NULL;
1412 lmo_found = ((MapItem *)ti_found)->getLMO();
1415 // Allow selecting text in QLineEdit if necessary
1416 if (model->isSelectionBlocked()) {
1418 QGraphicsView::mousePressEvent(e);
1422 // Stop editing in LineEdit
1423 if (state == EditingHeading) editHeadingFinished();
1425 QString sysFlagName;
1428 uid = ((BranchObj *)lmo_found)->findSystemFlagUidByPos(p);
1429 if (!uid.isNull()) {
1430 Flag *flag = systemFlagsMaster->findFlagByUid(uid);
1432 sysFlagName = flag->getName();
1437 qDebug() << "ME::mouse pressed\n";
1438 qDebug() << " lmo_found=" << lmo_found;
1439 qDebug() << " ti_found=" << ti_found;
1440 //if (ti_found) qDebug() << " ti_found="<<ti_found->getHeading();
1441 qDebug() << " flag=" << sysFlagName;
1444 // Check modifier key (before selecting object!)
1445 if (ti_found && (e->modifiers() & Qt::ShiftModifier)) {
1446 if (mainWindow->getModMode() == Main::ModModeColor) {
1447 setState(PickingColor);
1448 mainWindow->setCurrentColor(ti_found->getHeadingColor());
1449 if (e->modifiers() & Qt::ControlModifier)
1450 model->colorBranch(ti_found->getHeadingColor());
1452 model->colorSubtree(ti_found->getHeadingColor());
1456 if (mainWindow->getModMode() == Main::ModModeMoveView) {
1462 // Check vymlink modifier (before selecting object!)
1463 if (ti_found && sysFlagName == "system-vymLink") {
1464 model->select(ti_found);
1465 if (e->modifiers() & Qt::ControlModifier) {
1466 if (e->modifiers() & Qt::ShiftModifier)
1467 model->deleteVymLink();
1469 mainWindow->editOpenVymLink(true);
1471 mainWindow->editOpenVymLink(false);
1475 // Select the clicked object, if not moving without linking
1476 if (ti_found && (e->modifiers() & Qt::ShiftModifier)) {
1477 if (mainWindow->getModMode() == Main::ModModePoint) {
1478 model->selectToggle(ti_found);
1479 lastToggleDirection = toggleUndefined;
1483 model->select(ti_found);
1487 // Take care of remaining system flags _or_ modifier modes
1489 if (!sysFlagName.isEmpty()) {
1490 // systemFlag clicked
1491 if (sysFlagName.contains("system-url")) {
1492 if (e->modifiers() & Qt::ControlModifier)
1493 mainWindow->editOpenURLTab();
1495 mainWindow->editOpenURL();
1497 else if (sysFlagName == "system-note")
1498 mainWindow->windowToggleNoteEditor();
1499 else if (sysFlagName == "hideInExport")
1500 model->toggleHideExport();
1501 else if (sysFlagName.startsWith("system-task-"))
1502 model->cycleTaskStatus();
1506 // Take care of xLink: Open context menu with targets
1507 // if clicked near to begin of xlink
1508 if (ti_found->xlinkCount() > 0 &&
1509 ti_found->getType() != TreeItem::MapCenter &&
1510 lmo_found->getBBox().width() > 30) {
1511 if ((lmo_found->getOrientation() !=
1512 LinkableMapObj::RightOfCenter &&
1513 p.x() < lmo_found->getBBox().left() + 10) ||
1514 (lmo_found->getOrientation() !=
1515 LinkableMapObj::LeftOfCenter &&
1516 p.x() > lmo_found->getBBox().right() - 10)) {
1517 // FIXME-4 similar code in mainwindow::updateActions
1519 QList<QAction *> alist;
1520 QList<BranchItem *> blist;
1521 for (int i = 0; i < ti_found->xlinkCount(); i++) {
1522 XLinkItem *xli = ti_found->getXLinkItemNum(i);
1523 BranchItem *bit = xli->getPartnerBranch();
1526 new QAction(ti_found->getXLinkItemNum(i)
1527 ->getPartnerBranch()
1528 ->getHeadingPlain(),
1531 menu.addActions(alist);
1532 QAction *ra = menu.exec(e->globalPos());
1534 model->select(blist.at(alist.indexOf(ra)));
1535 while (!alist.isEmpty()) {
1536 QAction *a = alist.takeFirst();
1545 // XLink modifier, create new XLink
1546 BranchItem *selbi = model->getSelectedBranch();
1547 if (selbi && mainWindow->getModMode() == Main::ModModeXLink &&
1548 (e->modifiers() & Qt::ShiftModifier)) {
1549 setState(DrawingLink);
1550 tmpLink = new Link(model);
1551 tmpLink->setBeginBranch(selbi);
1552 tmpLink->createMapObj();
1553 tmpLink->setStyleBegin("None");
1554 tmpLink->setStyleEnd("None");
1555 tmpLink->setEndPoint(mapToScene(e->pos()));
1556 tmpLink->updateLink();
1560 // Start moving around
1562 // Left Button Move Branches
1563 if (e->button() == Qt::LeftButton) {
1564 // No system flag clicked, take care of moving modes or simply
1566 movingObj_offset.setX(p.x() - lmo_found->x());
1567 movingObj_offset.setY(p.y() - lmo_found->y());
1568 movingObj_orgPos.setX(lmo_found->x());
1569 movingObj_orgPos.setY(lmo_found->y());
1570 if (ti_found->depth() > 0) {
1571 lmo_found->setRelPos();
1572 movingObj_orgRelPos = lmo_found->getRelPos();
1575 if (mainWindow->getModMode() == Main::ModModeMoveObject &&
1576 e->modifiers() & Qt::ShiftModifier) {
1577 setState(MovingObjectWithoutLinking);
1580 setState(MovingObject);
1582 movingObj = model->getSelectedLMO();
1585 // Middle Button Toggle Scroll
1586 // (On Mac OS X this won't work, but we still have
1587 // a button in the toolbar)
1588 if (e->button() == Qt::MidButton)
1589 model->toggleScroll();
1591 else { // No lmo found, check XLinks
1593 if (ti_found->getType() == TreeItem::XLink) {
1594 XLinkObj *xlo = (XLinkObj *)((MapItem *)ti_found)->getMO();
1596 setState(DrawingXLink);
1597 int i = xlo->ctrlPointInClickBox(p);
1599 xlo->setSelection(i);
1600 movingObj_offset.setX(p.x() - xlo->x());
1601 movingObj_offset.setY(p.y() - xlo->y());
1602 movingObj_orgPos.setX(xlo->x());
1603 movingObj_orgPos.setY(xlo->y());
1607 else { // No MapObj found, we are on the scene itself
1608 // Left Button move Pos of sceneView
1609 if (e->button() == Qt::LeftButton ||
1610 e->button() == Qt::MiddleButton) {
1618 void MapEditor::mouseMoveEvent(QMouseEvent *e)
1620 // Show mouse position for debugging in statusBar
1621 if (debug && e->modifiers() & Qt::ControlModifier)
1622 mainWindow->statusMessage(
1623 QString("ME::mousePressEvent Scene: %1 widget: %2")
1624 .arg(qpointFToString(mapToScene(e->pos())))
1625 .arg(qpointFToString(e->pos())));
1627 // Allow selecting text in QLineEdit if necessary
1628 if (model->isSelectionBlocked()) {
1630 QGraphicsView::mouseMoveEvent(e);
1635 if (state == MovingView &&
1636 (e->buttons() == Qt::LeftButton || e->buttons() == Qt::MiddleButton)) {
1637 QPointF p = e->globalPos();
1638 movingVec.setX(-p.x() + movingObj_offset.x());
1639 movingVec.setY(-p.y() + movingObj_offset.y());
1640 horizontalScrollBar()->setSliderPosition(
1641 (int)(movingCont_start.x() + movingVec.x()));
1642 verticalScrollBar()->setSliderPosition(
1643 (int)(movingCont_start.y() + movingVec.y()));
1645 scrollBarPosAnimation.stop();
1646 viewCenterAnimation.stop();
1647 rotationAnimation.stop();
1648 // zoomAnimation.stop();
1653 TreeItem *seli = model->getSelectedItem();
1655 MapObj *mosel = NULL;
1657 mosel = ((MapItem *)seli)->getMO();
1659 // If not already happened during mousepress, we might need to switch state
1660 if (mainWindow->getModMode() == Main::ModModeMoveObject &&
1661 e->modifiers() & Qt::ShiftModifier && e->buttons() == Qt::LeftButton) {
1662 state = MovingObjectWithoutLinking;
1665 // Move the selected MapObj
1667 (state == MovingObject || state == MovingObjectWithoutLinking ||
1668 state == DrawingXLink)) {
1671 // Check if we have to scroll
1674 if (e->y() >= 0 && e->y() <= margin)
1675 vPan.setY(e->y() - margin);
1676 else if (e->y() <= height() && e->y() > height() - margin)
1677 vPan.setY(e->y() - height() + margin);
1678 if (e->x() >= 0 && e->x() <= margin)
1679 vPan.setX(e->x() - margin);
1680 else if (e->x() <= width() && e->x() > width() - margin)
1681 vPan.setX(e->x() - width() + margin);
1683 pointerPos = e->pos();
1684 pointerMod = e->modifiers();
1686 } // selection && moving_obj
1688 // Draw a link from one branch to another
1689 if (state == DrawingLink) {
1690 tmpLink->setEndPoint(mapToScene(e->pos()));
1691 tmpLink->updateLink();
1695 void MapEditor::moveObject()
1697 if (!panningTimer->isActive())
1698 panningTimer->start(50);
1700 QPointF p = mapToScene(pointerPos);
1701 TreeItem *seli = model->getSelectedItem();
1702 LinkableMapObj *lmosel = NULL;
1704 lmosel = ((MapItem *)seli)->getLMO();
1708 // reset cursor if we are moving and don't copy
1710 // Check if we could link
1711 TreeItem *ti_found = findMapItem(p, seli);
1712 BranchItem *bi_dst = NULL;
1713 LinkableMapObj *lmo_dst = NULL;
1714 if (ti_found && ti_found != seli && ti_found->isBranchLikeType()) {
1715 bi_dst = (BranchItem *)ti_found;
1716 lmo_dst = bi_dst->getLMO();
1722 if (seli->getType() == TreeItem::Image) {
1723 FloatImageObj *fio = (FloatImageObj *)lmosel;
1724 fio->moveCenter(p.x() - movingObj_offset.x(),
1725 p.y() - movingObj_offset.y());
1727 fio->updateLinkGeometry(); // no need for reposition, if we update
1729 model->emitSelectionChanged(); // position has changed
1731 // Relink float to new mapcenter or branch, if shift is pressed
1732 // Only relink, if selection really has a new parent
1733 if (pointerMod == Qt::ShiftModifier && bi_dst &&
1734 bi_dst != seli->parent()) {
1735 // Also save the move which was done so far
1736 QString pold = qpointFToString(movingObj_orgRelPos);
1737 QString pnow = qpointFToString(fio->getRelPos());
1738 model->saveState(seli, "moveRel " + pold, seli,
1740 QString("Move %1 to relative position %2")
1741 .arg(model->getObjectName(lmosel))
1743 model->reposition();
1745 model->relinkImage((ImageItem *)seli, bi_dst);
1746 model->select(seli);
1749 else if (seli->isBranchLikeType()) { // selection != a FloatObj
1750 if (seli->depth() == 0) {
1752 lmosel->move(p - movingObj_offset);
1753 if (pointerMod == Qt::ControlModifier) {
1754 // Move only mapcenter, leave its children where they are
1756 v = lmosel->getAbsPos();
1757 for (int i = 0; i < seli->branchCount(); ++i) {
1758 seli->getBranchObjNum(i)->setRelPos();
1759 seli->getBranchObjNum(i)->setOrientation();
1764 if (seli->depth() == 1) {
1766 if (!lmosel->hasParObjTmp())
1767 lmosel->move(p - movingObj_offset);
1768 lmosel->setRelPos();
1771 // d>1, move ordinary branch
1772 if (lmosel->getOrientation() ==
1773 LinkableMapObj::LeftOfCenter)
1774 // Add width of bbox here, otherwise alignRelTo will
1775 // cause jumping around
1776 lmosel->move(p.x() - movingObj_offset.x(),
1777 p.y() - movingObj_offset.y() +
1778 lmosel->getTopPad());
1780 lmosel->move(p.x() - movingObj_offset.x(),
1781 p.y() - movingObj_offset.y() -
1782 lmosel->getTopPad());
1783 BranchItem *selbi = ((BranchItem *)seli);
1784 if (selbi->parentBranch()->getChildrenLayout() ==
1785 BranchItem::FreePositioning)
1786 lmosel->setRelPos();
1791 // Maybe we can relink temporary?
1792 if (bi_dst && state != MovingObjectWithoutLinking) {
1793 if (pointerMod == Qt::ControlModifier) {
1794 // Special case: CTRL to link below dst
1795 lmosel->setParObjTmp(lmo_dst, p, +1);
1797 else if (pointerMod == Qt::ShiftModifier)
1798 lmosel->setParObjTmp(lmo_dst, p, -1);
1800 lmosel->setParObjTmp(lmo_dst, p, 0);
1803 lmosel->unsetParObjTmp();
1805 // reposition subbranch
1806 lmosel->reposition();
1808 QItemSelection sel = model->getSelectionModel()->selection();
1809 updateSelection(sel, sel); // position has changed
1811 // In winter mode shake snow from heading
1813 model->emitDataChanged(seli);
1814 } // Moving branchLikeType
1815 } // End of lmosel != NULL
1816 else if (seli && seli->getType() == TreeItem::XLink) {
1817 // Move XLink control point
1818 MapObj *mosel = ((MapItem *)seli)->getMO();
1820 mosel->move(p - movingObj_offset); // FIXME-3 Missing savestate
1821 model->setChanged();
1822 model->emitSelectionChanged();
1826 qWarning("ME::moveObject Huh? I'm confused.");
1833 void MapEditor::mouseReleaseEvent(QMouseEvent *e)
1835 // Allow selecting text in QLineEdit if necessary
1836 if (model->isSelectionBlocked()) {
1838 QGraphicsView::mouseReleaseEvent(e);
1842 QPointF p = mapToScene(e->pos());
1843 TreeItem *seli = model->getSelectedItem();
1845 TreeItem *dsti = NULL;
1847 dsti = findMapItem(p, seli);
1848 LinkableMapObj *dst = NULL;
1849 BranchItem *selbi = model->getSelectedBranch();
1850 if (dsti && dsti->isBranchLikeType())
1851 dst = ((MapItem *)dsti)->getLMO();
1855 // Have we been picking color?
1856 if (state == PickingColor) {
1857 setCursor(Qt::ArrowCursor);
1858 // Check if we are over another branch
1860 if (e->modifiers() & Qt::ShiftModifier)
1861 model->colorBranch(mainWindow->getCurrentColor());
1863 model->colorSubtree(mainWindow->getCurrentColor());
1869 // Have we been drawing a link?
1870 if (state == DrawingLink) {
1872 // Check if we are over another branch
1874 tmpLink->setEndBranch(((BranchItem *)dsti));
1875 tmpLink->activate();
1876 tmpLink->updateLink();
1877 if (model->createLink(tmpLink)) {
1879 tmpLink->getBeginLinkItem(), "remove ()", seli,
1880 QString("addXLink (\"%1\",\"%2\",%3,\"%4\",\"%5\")")
1881 .arg(model->getSelectString(tmpLink->getBeginBranch()))
1882 .arg(model->getSelectString(tmpLink->getEndBranch()))
1883 .arg(tmpLink->getPen().width())
1884 .arg(tmpLink->getPen().color().name())
1885 .arg(penStyleToString(tmpLink->getPen().style())),
1886 QString("Adding Link from %1 to %2")
1887 .arg(model->getObjectName(seli))
1888 .arg(model->getObjectName(dsti)));
1897 // Have we been moving something?
1898 if (seli && state == MovingObject) {
1899 panningTimer->stop();
1900 if (seli->getType() == TreeItem::Image) {
1901 FloatImageObj *fio = (FloatImageObj *)(((MapItem *)seli)->getLMO());
1903 // Moved Image, we need to reposition
1904 QString pold = qpointFToString(movingObj_orgRelPos);
1905 QString pnow = qpointFToString(fio->getRelPos());
1906 model->saveState(seli, "moveRel " + pold, seli,
1908 QString("Move %1 to relative position %2")
1909 .arg(model->getObjectName(seli))
1912 model->emitDataChanged(
1913 seli->parent()); // Parent of image has changed
1914 model->reposition();
1918 if (selbi && selbi->depth() == 0) {
1919 if (movingObj_orgPos != selbi->getBranchObj()->getAbsPos()) {
1920 QString pold = qpointFToString(movingObj_orgPos);
1922 qpointFToString(selbi->getBranchObj()->getAbsPos());
1924 model->saveState(selbi, "move " + pold, selbi, "move " + pnow,
1925 QString("Move mapcenter %1 to position %2")
1926 .arg(model->getObjectName(selbi))
1931 if (seli->isBranchLikeType()) //(seli->getType() == TreeItem::Branch )
1932 { // A branch was moved
1933 LinkableMapObj *lmosel = NULL;
1934 lmosel = ((MapItem *)seli)->getLMO();
1936 // save the position in case we link to mapcenter
1937 QPointF savePos = QPointF(lmosel->getAbsPos());
1939 // Reset the temporary drawn link to the original one
1940 lmosel->unsetParObjTmp();
1942 // For Redo we may need to save original selection
1943 QString preSelStr = model->getSelectString(seli);
1945 if (dsti && objectMoved && state != MovingObjectWithoutLinking) {
1946 // We have a destination, relink to that
1947 BranchObj *selbo = model->getSelectedBranchObj();
1949 QString preParStr = model->getSelectString(seli->parent());
1950 QString preNum = QString::number(seli->num(), 10);
1951 QString preDstParStr;
1953 if (e->modifiers() & Qt::ShiftModifier &&
1954 dsti->parent()) { // Link above dst
1955 preDstParStr = model->getSelectString(dsti->parent());
1956 model->relinkBranch((BranchItem *)seli,
1957 (BranchItem *)dsti->parent(),
1958 ((BranchItem *)dsti)->num(), true);
1960 else if (e->modifiers() & Qt::ControlModifier &&
1963 preDstParStr = model->getSelectString(dsti->parent());
1964 model->relinkBranch((BranchItem *)seli,
1965 (BranchItem *)dsti->parent(),
1966 ((BranchItem *)dsti)->num() + 1, true);
1968 else { // Append to dst
1969 preDstParStr = model->getSelectString(dsti);
1970 model->relinkBranch((BranchItem *)seli, (BranchItem *)dsti,
1971 -1, true, movingObj_orgPos);
1972 if (dsti->depth() == 0)
1973 selbo->move(savePos);
1977 // No destination, undo temporary move
1979 if (seli->depth() == 1) {
1980 // The select string might be different _after_ moving
1981 // around. Therefor reposition and then use string of old
1983 model->reposition();
1985 QPointF rp(lmosel->getRelPos());
1986 if (rp != movingObj_orgRelPos) {
1987 QString ps = qpointFToString(rp);
1989 model->getSelectString(lmosel),
1990 "moveRel " + qpointFToString(movingObj_orgRelPos),
1991 preSelStr, "moveRel " + ps,
1992 QString("Move %1 to relative position %2")
1993 .arg(model->getObjectName(lmosel))
1998 if (selbi->parentBranch()->getChildrenLayout() ==
1999 BranchItem::FreePositioning) {
2000 lmosel->setRelPos();
2001 model->reposition();
2005 // Draw the original link, before selection was moved around
2006 if (settings.value("/animation/use", true).toBool() &&
2009 //(lmosel->getRelPos(),movingObj_orgRelPos)<3
2011 lmosel->setRelPos(); // calc relPos first for starting
2014 model->startAnimation((BranchObj *)lmosel,
2015 lmosel->getRelPos(),
2016 movingObj_orgRelPos);
2019 model->reposition();
2023 // Finally resize scene, if needed
2026 objectMoved = false;
2030 // maybe we moved View: set old cursor
2031 setCursor(Qt::ArrowCursor);
2033 if (state != EditingHeading)
2034 setState(Neutral); // Continue editing after double click!
2036 QGraphicsView::mouseReleaseEvent(e);
2039 void MapEditor::mouseDoubleClickEvent(QMouseEvent *e)
2041 // Allow selecting text in QLineEdit if necessary
2042 if (model->isSelectionBlocked()) {
2044 QGraphicsView::mouseDoubleClickEvent(e);
2048 if (e->button() == Qt::LeftButton) {
2049 QPointF p = mapToScene(e->pos());
2050 TreeItem *ti = findMapItem(p, NULL);
2051 LinkableMapObj *lmo;
2053 if (state == EditingHeading)
2054 editHeadingFinished();
2056 BranchItem *selbi = model->getSelectedBranch();
2058 lmo = ((MapItem *)ti)->getLMO();
2060 QUuid uid = ((BranchObj *)lmo)->findSystemFlagUidByPos(p);
2062 // Don't edit heading when double clicking system flag:
2073 void MapEditor::wheelEvent(QWheelEvent *e)
2075 if (e->modifiers() & Qt::ControlModifier &&
2076 e->angleDelta().y() != 0) {
2077 QPointF p = mapToScene(e->position().toPoint());
2078 if (e->angleDelta().y() > 0)
2079 // setZoomFactorTarget (zoomFactorTarget*1.15);
2080 setViewCenterTarget(p, zoomFactorTarget * 1.15, angleTarget);
2082 // setZoomFactorTarget (zoomFactorTarget*0.85);
2083 setViewCenterTarget(p, zoomFactorTarget * 0.85, angleTarget);
2086 scrollBarPosAnimation.stop();
2087 QGraphicsView::wheelEvent(e);
2091 void MapEditor::focusOutEvent(QFocusEvent *)
2093 // qDebug()<<"ME::focusOutEvent"<<e->reason();
2094 if (state == EditingHeading)
2095 editHeadingFinished();
2098 void MapEditor::resizeEvent(QResizeEvent *e) { QGraphicsView::resizeEvent(e); }
2100 void MapEditor::dragEnterEvent(QDragEnterEvent *event)
2102 // for (unsigned int i=0;event->format(i);i++) // Debug mime type
2103 // cerr << event->format(i) << endl;
2105 if (event->mimeData()->hasImage())
2106 event->acceptProposedAction();
2107 else if (event->mimeData()->hasUrls())
2108 event->acceptProposedAction();
2111 void MapEditor::dragMoveEvent(QDragMoveEvent *) {}
2113 void MapEditor::dragLeaveEvent(QDragLeaveEvent *event) { event->accept(); }
2115 void MapEditor::dropEvent(QDropEvent *event)
2117 BranchItem *selbi = model->getSelectedBranch();
2120 foreach (QString format, event->mimeData()->formats())
2121 qDebug() << "MapEditor: Dropped format: " << qPrintable(format);
2122 foreach (QUrl url, event->mimeData()->urls()) {
2123 qDebug() << " URL-path:" << url.path();
2124 qDebug() << "URL-string:" << url.toString();
2125 qDebug() << " enc:" << url.toEncoded();
2126 qDebug() << " valid:" << url.isValid();
2128 qDebug() << "============== mimeData ===================";
2129 qDebug() << "has-img : " << event->mimeData()->hasImage();
2130 qDebug() << "has-urls: " << event->mimeData()->hasUrls();
2131 qDebug() << " text: " << event->mimeData()->text();
2132 qDebug() << "===========================================";
2135 if (event->mimeData()->hasUrls()) {
2136 // Try text representation first, which works on windows, but in
2137 // Linux only for https, not local images
2138 QString url = event->mimeData()->text();
2139 if (url.isEmpty()) {
2141 event->mimeData()->urls().first().path().toLatin1();
2143 for (int i = 0; i < ba.count(); i++)
2145 ba2.append(ba.at(i));
2149 BranchItem *bi = NULL;
2150 // Workaround to avoid adding empty branches
2151 if (!url.isEmpty()) {
2152 if (url.startsWith("file://"))
2155 #if defined(Q_OS_WIN32)
2156 if (url.startsWith("/"))
2161 qDebug() << "dropped url seems to be image: " << url;
2162 // Image, try to download or set image from local file
2163 if (url.startsWith("http"))
2164 model->downloadImage(url);
2166 model->loadImage(bi, url);
2168 qDebug() << "finished loading image";
2171 bi = model->addNewBranch();
2174 if (url.endsWith(".vym", Qt::CaseInsensitive))
2175 model->setVymLink(url);
2178 model->setHeadingPlainText(url);
2181 model->select(bi->parent());
2187 event->acceptProposedAction();
2190 void MapEditor::setState(EditorState s)
2192 if (state != Neutral && s != Neutral)
2193 qWarning() << "MapEditor::setState switching directly from " << state
2204 case EditingHeading:
2205 s = "EditingHeading";
2213 case MovingObjectWithoutLinking:
2214 s = "MovingObjectWithoutLinking";
2226 qDebug() << "MapEditor: State " << s << " of " << model->getMapName();
2231 MapEditor::EditorState MapEditor::getState() { return state; }
2233 void MapEditor::updateSelection(QItemSelection nsel, QItemSelection dsel)
2237 QList<MapItem *> itemsSelected;
2238 QList<MapItem *> itemsDeselected;
2240 QItemSelection sel = model->getSelectionModel()->selection();
2242 LinkableMapObj *lmo;
2244 // Add new selected objects
2245 if (sel.indexes().count() > 1)
2246 mainWindow->statusMessage(
2247 tr("%1 items selected").arg(sel.indexes().count()));
2249 foreach (QModelIndex ix, sel.indexes()) {
2250 MapItem *mi = static_cast<MapItem *>(ix.internalPointer());
2251 if (mi->isBranchLikeType() || mi->getType() == TreeItem::Image ||
2252 mi->getType() == TreeItem::XLink)
2253 if (!itemsSelected.contains(mi))
2254 itemsSelected.append(mi);
2257 mi->getLMO()->updateVisibility();
2260 // Delete objects meanwhile removed from selection
2261 foreach (QModelIndex ix, dsel.indexes()) {
2262 MapItem *mi = static_cast<MapItem *>(ix.internalPointer());
2263 if (mi->isBranchLikeType() || mi->getType() == TreeItem::Image ||
2264 mi->getType() == TreeItem::XLink)
2265 if (!itemsDeselected.contains(mi))
2266 itemsDeselected.append(mi);
2267 lmo = mi->getLMO(); // FIXME-2 xlink does return nullptr
2269 mi->getLMO()->updateVisibility();
2272 // Trim list of selection paths
2273 while (itemsSelected.count() < selPathList.count())
2274 delete selPathList.takeFirst();
2277 while (itemsSelected.count() < selPathList.count())
2278 delete selPathList.takeFirst();
2280 // Add additonal polygons
2281 QGraphicsPathItem *sp;
2282 while (itemsSelected.count() > selPathList.count()) {
2283 sp = mapScene->addPath(QPainterPath());
2285 selPathList.append(sp);
2288 // Reposition polygons
2289 for (int i = 0; i < itemsSelected.count(); ++i) {
2290 MapObj *mo = itemsSelected.at(i)->getMO();
2291 sp = selPathList.at(i);
2292 sp->setPath(mo->getSelectionPath());
2293 sp->setPen(selectionPen);
2294 sp->setBrush(selectionBrush);
2295 sp->setParentItem(mo);
2296 sp->setZValue(dZ_SELBOX);
2298 // Reposition also LineEdit for heading during animation
2300 lineEdit->move(mo->getAbsPos().toPoint());
2306 void MapEditor::updateData(const QModelIndex &sel)
2308 TreeItem *ti = static_cast<TreeItem *>(sel.internalPointer());
2311 qDebug() << "ME::updateData";
2314 qDebug() << " ti=NULL";
2317 qDebug() << " ti="<<ti;
2318 qDebug() << " h="<<ti->getHeadingPlain();
2321 if (ti && ti->isBranchLikeType()) {
2322 BranchObj *bo = (BranchObj *)(((MapItem *)ti)->getLMO());
2323 bo->updateVisuals();
2327 QList<QRectF> obstacles;
2329 BranchItem *cur = NULL;
2330 BranchItem *prev = NULL;
2331 model->nextBranch(cur, prev);
2333 if (!cur->hasHiddenExportParent()) {
2335 bo = (BranchObj *)(cur->getLMO());
2336 if (bo && bo->isVisibleObj())
2337 obstacles.append(bo->getBBox());
2339 model->nextBranch(cur, prev);
2341 winter->setObstacles(obstacles);
2345 void MapEditor::togglePresentationMode()
2347 mainWindow->togglePresentationMode();
2350 void MapEditor::setSelectionPen(const QPen &p)
2353 QItemSelection sel = model->getSelectionModel()->selection();
2354 updateSelection(sel, sel);
2357 QPen MapEditor::getSelectionPen() { return selectionPen; }
2359 void MapEditor::setSelectionBrush(const QBrush &b)
2362 QItemSelection sel = model->getSelectionModel()->selection();
2363 updateSelection(sel, sel);
2366 QBrush MapEditor::getSelectionBrush() { return selectionBrush; }