/*
 * Copyright (C) 2012 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

#include <sstream>
#include <boost/bind.hpp>

#include <ignition/math/Vector2.hh>

#include <ignition/math/Color.hh>

#include "gazebo/gui/Conversions.hh"
#include "gazebo/gui/building/ImportImageDialog.hh"
#include "gazebo/gui/building/GridLines.hh"
#include "gazebo/gui/building/EditorItem.hh"
#include "gazebo/gui/building/RectItem.hh"
#include "gazebo/gui/building/WindowItem.hh"
#include "gazebo/gui/building/DoorItem.hh"
#include "gazebo/gui/building/StairsItem.hh"
#include "gazebo/gui/building/FloorItem.hh"
#include "gazebo/gui/building/GrabberHandle.hh"
#include "gazebo/gui/building/WallSegmentItem.hh"
#include "gazebo/gui/building/BuildingMaker.hh"
#include "gazebo/gui/building/LevelInspectorDialog.hh"
#include "gazebo/gui/building/BuildingEditorEvents.hh"
#include "gazebo/gui/building/EditorView.hh"

using namespace gazebo;
using namespace gui;

/////////////////////////////////////////////////
EditorView::EditorView(QWidget *_parent)
: QGraphicsView(_parent)
{
  this->setObjectName("editorView");

  this->drawMode = NONE;
  this->drawInProgress = false;
  this->floorplanVisible = true;
  this->elementsVisible = true;

  this->connections.push_back(
      gui::editor::Events::ConnectCreateBuildingEditorItem(
      boost::bind(&EditorView::OnCreateEditorItem, this, _1)));

  this->connections.push_back(
      gui::editor::Events::ConnectColorSelected(
      boost::bind(&EditorView::OnColorSelected, this, _1)));

  this->connections.push_back(
      gui::editor::Events::ConnectTextureSelected(
      boost::bind(&EditorView::OnTextureSelected, this, _1)));

  this->connections.push_back(
      gui::editor::Events::ConnectNewBuildingModel(
      boost::bind(&EditorView::OnDiscardModel, this)));

  this->connections.push_back(
      gui::editor::Events::ConnectAddBuildingLevel(
      boost::bind(&EditorView::OnAddLevel, this)));

  this->connections.push_back(
      gui::editor::Events::ConnectDeleteBuildingLevel(
      boost::bind(&EditorView::OnDeleteLevel, this)));

  this->connections.push_back(
      gui::editor::Events::ConnectChangeBuildingLevel(
      boost::bind(&EditorView::OnChangeLevel, this, _1)));

  this->connections.push_back(
      gui::editor::Events::ConnectShowFloorplan(
      boost::bind(&EditorView::OnShowFloorplan, this)));

  this->connections.push_back(
      gui::editor::Events::ConnectShowElements(
      boost::bind(&EditorView::OnShowElements, this)));

  this->mousePressRotation = 0;

  this->buildingMaker = new BuildingMaker();
  this->currentLevel = 0;
  this->levelDefaultHeight = 250;

  Level *newLevel = new Level();
  newLevel->level = 0;
  newLevel->baseHeight = 0;
  newLevel->height = this->levelDefaultHeight;
  newLevel->name = "Level 1";
  this->levels.push_back(newLevel);

  this->levelInspector = new LevelInspectorDialog();
  this->levelInspector->setModal(false);
  connect(this->levelInspector, SIGNAL(Applied()), this, SLOT(OnLevelApply()));

  this->openLevelInspectorAct = new QAction(tr("&Open Level Inspector"), this);
  this->openLevelInspectorAct->setStatusTip(tr("Open Level Inspector"));
  connect(this->openLevelInspectorAct, SIGNAL(triggered()),
    this, SLOT(OnOpenLevelInspector()));

  this->addLevelAct = new QAction(tr("&Add Level"), this);
  this->addLevelAct->setStatusTip(tr("Add Level"));
  connect(this->addLevelAct, SIGNAL(triggered()),
    this, SLOT(OnAddLevel()));

  this->deleteLevelAct = new QAction(tr("&Delete Level"), this);
  this->deleteLevelAct->setStatusTip(tr("Delete Level"));
  connect(this->deleteLevelAct, SIGNAL(triggered()),
    this, SLOT(OnDeleteLevel()));

  this->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
//  this->setRenderHint(QPainter::SmoothPixmapTransform);
//  this->setRenderHint(QPainter::Antialiasing);
  this->gridLines = NULL;

  this->viewScale = 1.0;
  this->levelCounter = 0;

  this->mouseTooltip = new QGraphicsTextItem;
  this->mouseTooltip->setPlainText(
      "Oops! Color and texture can only be added in the 3D view.");
  this->mouseTooltip->setZValue(10);
}

/////////////////////////////////////////////////
EditorView::~EditorView()
{
  if (this->buildingMaker)
    delete this->buildingMaker;

  delete this->levelInspector;
}

/////////////////////////////////////////////////
void EditorView::scrollContentsBy(int _dx, int _dy)
{
  QGraphicsView::scrollContentsBy(_dx, _dy);

  if (this->gridLines && this->scene())
  {
    this->gridLines->setPos(this->mapToScene(QPoint(this->width()/2,
        this->height()/2)));
  }
}

/////////////////////////////////////////////////
void EditorView::resizeEvent(QResizeEvent *_event)
{
  if (this->scene())
  {
    if (!this->gridLines)
    {
      this->gridLines = new GridLines(_event->size().width(),
          _event->size().height());
      this->scene()->addItem(this->gridLines);
    }
    else
    {
      this->gridLines->SetSize(_event->size().width(),
          _event->size().height());
    }
    this->gridLines->setPos(this->mapToScene(
        QPoint(_event->size().width()/2, _event->size().height()/2)));
  }
}

/////////////////////////////////////////////////
void EditorView::contextMenuEvent(QContextMenuEvent *_event)
{
  if (this->drawMode == COLOR || this->drawMode == TEXTURE)
    return;

  if (this->drawInProgress)
  {
    this->CancelDrawMode();
    gui::editor::Events::createBuildingEditorItem(std::string());
    _event->accept();
    return;
  }

  QGraphicsItem *item = this->scene()->itemAt(
      this->mapToScene(_event->pos()), QTransform());
  if (item && item != this->levels[this->currentLevel]->backgroundPixmap)
  {
    _event->ignore();
    QGraphicsView::contextMenuEvent(_event);
    return;
  }

  QMenu menu(this);
  menu.addAction(this->addLevelAct);
  menu.addAction(this->deleteLevelAct);
  menu.addAction(this->openLevelInspectorAct);
  menu.exec(_event->globalPos());
  _event->accept();
}

/////////////////////////////////////////////////
void EditorView::wheelEvent(QWheelEvent *_event)
{
  int wheelIncr = 120;
  int sign = (_event->delta() > 0) ? 1 : -1;
  int delta = std::max(std::abs(_event->delta()), wheelIncr) * sign;
  int numSteps = delta / wheelIncr;

  QMatrix mat = matrix();
  QPointF mousePosition = _event->pos();

  mat.translate((width()/2) - mousePosition.x(), (height() / 2) -
    mousePosition.y());

  double scaleFactor = 1.15;

  if (numSteps > 0)
  {
    mat.scale(numSteps*scaleFactor, numSteps*scaleFactor);
    this->viewScale *= numSteps*scaleFactor;
  }
  else
  {
    mat.scale(-1/(numSteps*scaleFactor), -1/(numSteps*scaleFactor));
    this->viewScale *= -1/(numSteps*scaleFactor);
  }
  mat.translate(mousePosition.x() - (this->width()/2),
      mousePosition.y() -(this->height()/2));
  this->setMatrix(mat);

  if (this->gridLines)
  {
    this->gridLines->setPos(this->mapToScene(
        QPoint(this->width()/2, this->height()/2)));
  }

  gui::editor::Events::changeBuildingEditorZoom(this->viewScale);
  _event->accept();
}

/////////////////////////////////////////////////
void EditorView::mousePressEvent(QMouseEvent *_event)
{
  if (!this->drawInProgress && this->drawMode != WALL && this->drawMode != COLOR
      && this->drawMode != TEXTURE && (_event->button() != Qt::RightButton))
  {
    QGraphicsItem *mouseItem =
        this->scene()->itemAt(this->mapToScene(_event->pos()), QTransform());
    if (mouseItem && !mouseItem->isSelected())
    {
      EditorItem *editorItem = dynamic_cast<EditorItem*>(mouseItem);
      if (editorItem)
      {
        this->scene()->clearSelection();
        mouseItem->setSelected(true);
      }
    }
    QGraphicsView::mousePressEvent(_event);
  }
}

/////////////////////////////////////////////////
void EditorView::mouseReleaseEvent(QMouseEvent *_event)
{
  if (this->drawMode == WALL)
  {
    this->DrawWall(_event->pos());
  }
  else if (this->drawMode != NONE)
  {
    if (this->drawInProgress)
    {
      if (this->drawMode == WINDOW)
      {
        this->windowList.push_back(dynamic_cast<WindowItem *>(
            this->currentMouseItem));
      }
      else if (this->drawMode == DOOR)
      {
        this->doorList.push_back(dynamic_cast<DoorItem *>(
            this->currentMouseItem));
      }
      else if (this->drawMode == STAIRS)
      {
        StairsItem *stairsItem = dynamic_cast<StairsItem *>(
            this->currentMouseItem);
        stairsItem->SetTexture3d("");
        stairsItem->SetColor3d(ignition::math::Color::White);
        this->stairsList.push_back(stairsItem);
        if ((this->currentLevel) < static_cast<int>(floorList.size()))
        {
          this->buildingMaker->AttachManip(this->itemToVisualMap[stairsItem],
              this->itemToVisualMap[floorList[this->currentLevel]]);
        }
      }
      dynamic_cast<EditorItem *>(this->currentMouseItem)->
          SetHighlighted(false);

      this->drawMode = NONE;
      this->drawInProgress = false;
      gui::editor::Events::createBuildingEditorItem(std::string());
    }
  }

  if (!this->drawInProgress)
    this->currentMouseItem = NULL;

  QGraphicsView::mouseReleaseEvent(_event);
}

/////////////////////////////////////////////////
void EditorView::mouseMoveEvent(QMouseEvent *_event)
{
  switch (this->drawMode)
  {
    case NONE:
      break;
    case WALL:
    {
      WallSegmentItem *wallSegmentItem = dynamic_cast<WallSegmentItem *>(
          this->currentMouseItem);
      if (this->drawInProgress && wallSegmentItem)
      {
        this->snapToGrabber = false;
        this->snapGrabberOther = NULL;
        this->snapGrabberCurrent = NULL;

        QPointF p1 = wallSegmentItem->line().p1();
        QPointF p2 = this->mapToScene(_event->pos());
        QPointF pf;
        pf = p2;

        if (!(QApplication::keyboardModifiers() & Qt::ShiftModifier))
        {
          double distanceToClose = 30;

          // Snap to other walls' points
          QList<QGraphicsItem *> itemsList = this->scene()->items(QRectF(
              QPointF(p2.x() - distanceToClose/2, p2.y() - distanceToClose/2),
              QPointF(p2.x() + distanceToClose/2, p2.y() + distanceToClose/2)));
          for (QList<QGraphicsItem *>::iterator it = itemsList.begin();
              it  != itemsList.end(); ++it)
          {
            WallSegmentItem *anotherWall = dynamic_cast<WallSegmentItem *>
                (*it);
            if (anotherWall && anotherWall != wallSegmentItem)
            {
              if (ignition::math::Vector2d(Conversions::Convert(p2) -
                  anotherWall->StartPoint()).Length() <= distanceToClose)
              {
                pf = Conversions::Convert(anotherWall->StartPoint());
                this->snapGrabberOther = anotherWall->Grabbers()[0];
                this->snapGrabberCurrent = wallSegmentItem->Grabbers()[1];
                this->snapToGrabber = true;
                break;
              }
              else if (ignition::math::Vector2d(Conversions::Convert(p2) -
                  anotherWall->EndPoint()).Length() <= distanceToClose)
              {
                pf = Conversions::Convert(anotherWall->EndPoint());
                this->snapGrabberOther = anotherWall->Grabbers()[1];
                this->snapGrabberCurrent = wallSegmentItem->Grabbers()[1];
                this->snapToGrabber = true;
                break;
              }
            }
          }

          if (!this->snapToGrabber)
          {
            // Snap to angular increments
            QLineF newLine(p1, p2);
            double angle = IGN_DTOR(QLineF(p1, p2).angle());
            double range = IGN_DTOR(SegmentItem::SnapAngle);
            int angleIncrement = angle / range;

            if ((angle - range*angleIncrement) > range*0.5)
              angleIncrement++;
            angle = -range*angleIncrement;

            // Snap to length increments
            double newLength = newLine.length();
            double lengthIncrement = SegmentItem::SnapLength /
                wallSegmentItem->Scale();
            newLength  = round(newLength/lengthIncrement)*lengthIncrement-
                wallSegmentItem->Thickness();

            pf.setX(p1.x() + qCos(angle)*newLength);
            pf.setY(p1.y() + qSin(angle)*newLength);
          }
        }
        wallSegmentItem->SetEndPoint(Conversions::Convert(pf));
        wallSegmentItem->Update();
      }
      QApplication::setOverrideCursor(QCursor(Qt::CrossCursor));
      break;
    }
    case WINDOW:
      this->DrawWindow(_event->pos());
      break;
    case DOOR:
      this->DrawDoor(_event->pos());
      break;
    case STAIRS:
      this->DrawStairs(_event->pos());
      break;
    case COLOR:
    case TEXTURE:
    {
      if (!this->mouseTooltip->scene())
        this->scene()->addItem(this->mouseTooltip);

      this->mouseTooltip->setPos(this->mapToScene(_event->pos()) +
          QPointF(15, 15));
      break;
    }
    default:
      break;
  }

  // auto attach windows and doors to walls
  QGraphicsItem *grabber = this->scene()->mouseGrabberItem();
  if (!grabber)
    grabber = this->currentMouseItem;
  RectItem *editorItem = dynamic_cast<RectItem *>(grabber);
  if (grabber && editorItem && (editorItem->ItemType() == "Window"
      || editorItem->ItemType() == "Door") )
  {
    if (grabber->parentItem())
    {
      WallSegmentItem *wallSegmentItem =
            dynamic_cast<WallSegmentItem *>(grabber->parentItem());
      if (wallSegmentItem)
      {
        QLineF segmentLine(wallSegmentItem->line());
        segmentLine.setP1(wallSegmentItem->mapToScene(segmentLine.p1()));
        segmentLine.setP2(wallSegmentItem->mapToScene(segmentLine.p2()));
        QPointF mousePoint = this->mapToScene(_event->pos());
        QPointF deltaLine = segmentLine.p2() - segmentLine.p1();
        QPointF deltaMouse = mousePoint - segmentLine.p1();
        double deltaLineMouse2 = deltaLine.x()*(-deltaMouse.y())
            - (-deltaMouse.x())*deltaLine.y();
        double deltaLine2 = (deltaLine.x()*deltaLine.x())
            + deltaLine.y()*deltaLine.y();
        double mouseDotLine = deltaMouse.x()*deltaLine.x()
              + deltaMouse.y()*deltaLine.y();
        double t = mouseDotLine / deltaLine2;
        double distance = fabs(deltaLineMouse2) / sqrt(deltaLine2);
        if (distance > 30 || t > 1.0 || t < 0.0)
        {
          editorItem->DetachFromParent();
          wallSegmentItem->setZValue(wallSegmentItem->ZValueIdle());
          editorItem->SetPositionOnWall(0);
          editorItem->SetAngleOnWall(0);
          this->buildingMaker->DetachFromParent(
              this->itemToVisualMap[editorItem]);
          editorItem->SetRotation(editorItem->Rotation()
            - this->mousePressRotation);
          editorItem->SetPosition(Conversions::Convert(mousePoint));
        }
        else
        {
          QPointF closest(segmentLine.p1() + t*deltaLine);
          grabber->setPos(wallSegmentItem->mapFromScene(closest));
          grabber->setRotation(wallSegmentItem->rotation());

          QPointF absPositionOnWall = grabber->pos() -
              wallSegmentItem->line().p1();
          double positionLength = sqrt(absPositionOnWall.x()*
                                       absPositionOnWall.x() +
                                       absPositionOnWall.y()*
                                       absPositionOnWall.y());
          editorItem->SetPositionOnWall(positionLength /
              wallSegmentItem->line().length());
          editorItem->SetRotation(editorItem->Rotation());
        }
        return;
      }
    }
    else
    {
      QList<QGraphicsItem *> overlaps = this->scene()->collidingItems(
          grabber, Qt::IntersectsItemBoundingRect);
      for (int i = 0; i < overlaps.size(); ++i)
      {
        WallSegmentItem *wallSegmentItem =
            dynamic_cast<WallSegmentItem *>(overlaps[i]);
        if (wallSegmentItem)
        {
          QPointF scenePos =  grabber->scenePos();
          if (wallSegmentItem->contains(wallSegmentItem->mapFromScene(
              scenePos)))
          {
            editorItem->setParentItem(wallSegmentItem);
            wallSegmentItem->setZValue(wallSegmentItem->ZValueIdle()+5);
            this->buildingMaker->AttachManip(
                this->itemToVisualMap[editorItem],
                this->itemToVisualMap[wallSegmentItem]);
            editorItem->SetPosition(
                Conversions::Convert(wallSegmentItem->mapFromScene(scenePos)));
            this->mousePressRotation = -wallSegmentItem->line().angle();
            editorItem->SetRotation(this->mousePressRotation);

            QPointF absPositionOnWall = editorItem->pos() -
                wallSegmentItem->line().p1();
            double positionLength = sqrt(absPositionOnWall.x()*
                                         absPositionOnWall.x() +
                                         absPositionOnWall.y()*
                                         absPositionOnWall.y());
            editorItem->SetPositionOnWall(positionLength /
                wallSegmentItem->line().length());
            return;
          }
        }
      }
    }
  }

  if (!drawInProgress)
  {
    QGraphicsView::mouseMoveEvent(_event);
  }
}

/////////////////////////////////////////////////
void EditorView::leaveEvent(QEvent */*_event*/)
{
  if (this->mouseTooltip &&
      this->scene()->items().contains(this->mouseTooltip))
  {
    this->scene()->removeItem(this->mouseTooltip);
  }
}

/////////////////////////////////////////////////
void EditorView::keyPressEvent(QKeyEvent *_event)
{
  if (_event->key() == Qt::Key_Delete || _event->key() == Qt::Key_Backspace)
  {
    QList<QGraphicsItem *> selectedItems = this->scene()->selectedItems();

    for (int i = 0; i < selectedItems.size(); ++i)
    {
      EditorItem *item = dynamic_cast<EditorItem *>(selectedItems[i]);
      if (item)
      {
        this->DeleteItem(item);
      }
    }
    this->drawMode = NONE;
    this->drawInProgress = false;
    this->currentMouseItem = NULL;
    this->releaseKeyboard();
    QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
    gui::editor::Events::createBuildingEditorItem(std::string());
  }
  else if (_event->key() == Qt::Key_Escape)
  {
    if (this->mouseTooltip &&
        this->scene()->items().contains(this->mouseTooltip))
      this->scene()->removeItem(this->mouseTooltip);
    this->CancelDrawMode();
    gui::editor::Events::createBuildingEditorItem(std::string());
    this->releaseKeyboard();
  }
}

/////////////////////////////////////////////////
void EditorView::mouseDoubleClickEvent(QMouseEvent *_event)
{
  if (this->drawMode == WALL)
  {
    WallSegmentItem *wallSegmentItem = dynamic_cast<WallSegmentItem *>(
        this->currentMouseItem);
    this->itemToVisualMap.erase(wallSegmentItem);

    this->UnlinkGrabbers(wallSegmentItem->Grabbers()[0]);

    this->scene()->removeItem(this->currentMouseItem);
    delete this->currentMouseItem;

    this->snapToGrabber = false;
    this->snapGrabberOther = NULL;
    this->snapGrabberCurrent = NULL;
    this->currentMouseItem = NULL;
    this->drawMode = NONE;
    this->drawInProgress = false;
    this->releaseKeyboard();
    QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
    gui::editor::Events::createBuildingEditorItem(std::string());
  }
  else if (this->drawMode == COLOR || this->drawMode == TEXTURE)
  {
    return;
  }
  else
  {
    if (!this->scene()->itemAt(this->mapToScene(_event->pos()), QTransform()))
      this->OnOpenLevelInspector();
  }

  if (!this->drawInProgress)
    QGraphicsView::mouseDoubleClickEvent(_event);
}

/////////////////////////////////////////////////
void EditorView::DeleteItem(EditorItem *_item)
{
  if (!_item)
    return;

  // To make holes in the final model, windows and doors are attached to walls,
  // and stairs are attached to floors above them.
  // Detach 3D manip, but 2D items may remain as children.
  this->buildingMaker->DetachAllChildren(this->itemToVisualMap[_item]);

  _item->SetHighlighted(false);

  if (_item->ItemType() == "WallSegment")
  {
    WallSegmentItem *wallSegmentItem = dynamic_cast<WallSegmentItem *>(_item);

    // Delete item's child doors and windows before deleting item
    for (int i = wallSegmentItem->childItems().size()-1; i >=0; --i)
    {
      // WallSegmentItems have other children besides RectItems
      RectItem *rectItem = dynamic_cast<RectItem *>(
          wallSegmentItem->childItems().at(i));

      if (rectItem)
      {
        this->DeleteItem(rectItem);
      }
    }

    this->UnlinkGrabbers(wallSegmentItem->Grabbers()[0]);
    this->UnlinkGrabbers(wallSegmentItem->Grabbers()[1]);

    this->wallSegmentList.erase(std::remove(this->wallSegmentList.begin(),
        this->wallSegmentList.end(), dynamic_cast<WallSegmentItem *>(_item)),
        this->wallSegmentList.end());
  }
  else if (_item->ItemType() == "Window")
  {
    this->windowList.erase(std::remove(this->windowList.begin(),
        this->windowList.end(), dynamic_cast<WindowItem *>(_item)),
        this->windowList.end());
  }
  else if (_item->ItemType() == "Door")
  {
    this->doorList.erase(std::remove(this->doorList.begin(),
        this->doorList.end(), dynamic_cast<DoorItem *>(_item)),
        this->doorList.end());
  }
  else if (_item->ItemType() == "Stairs")
  {
    this->stairsList.erase(std::remove(this->stairsList.begin(),
        this->stairsList.end(), dynamic_cast<StairsItem *>(_item)),
        this->stairsList.end());
  }
  else if (_item->ItemType() == "Floor")
  {
    this->floorList.erase(std::remove(this->floorList.begin(),
        this->floorList.end(), dynamic_cast<FloorItem *>(_item)),
        this->floorList.end());
  }

  this->itemToVisualMap.erase(_item);
  delete _item;
}

/////////////////////////////////////////////////
void EditorView::DrawWall(const QPoint &_pos)
{
  WallSegmentItem *wallSegmentItem = NULL;

  // First point on the chain
  if (!this->drawInProgress)
  {
    auto pointStart = Conversions::Convert(this->mapToScene(_pos));
    auto pointEnd = pointStart + ignition::math::Vector2d(1, 0);

    wallSegmentItem = new WallSegmentItem(pointStart, pointEnd,
        this->levelDefaultHeight);

    if (!(QApplication::keyboardModifiers() & Qt::ShiftModifier))
    {
      double distanceToClose = 30;

      // Snap to other walls' points
      QList<QGraphicsItem *> itemsList = this->scene()->items(QRectF(
          QPointF(pointStart.X() - distanceToClose/2,
                  pointStart.Y() - distanceToClose/2),
          QPointF(pointStart.X() + distanceToClose/2,
                  pointStart.Y() + distanceToClose/2)));
      for (QList<QGraphicsItem *>::iterator it = itemsList.begin();
          it  != itemsList.end(); ++it)
      {
        WallSegmentItem *anotherWall = dynamic_cast<WallSegmentItem *>(*it);
        if (anotherWall)
        {
          if (ignition::math::Vector2d(pointStart -
              anotherWall->StartPoint()).Length() <= distanceToClose)
          {
            wallSegmentItem->SetStartPoint(anotherWall->StartPoint());
            this->LinkGrabbers(anotherWall->Grabbers()[0],
                wallSegmentItem->Grabbers()[0]);
            break;
          }
          else if (ignition::math::Vector2d(pointStart -
              anotherWall->EndPoint()).Length() <= distanceToClose)
          {
            wallSegmentItem->SetStartPoint(anotherWall->EndPoint());
            this->LinkGrabbers(anotherWall->Grabbers()[1],
                wallSegmentItem->Grabbers()[0]);
            break;
          }
        }
      }
    }

    wallSegmentItem->SetLevel(this->currentLevel);
    wallSegmentItem->SetLevelBaseHeight(this->levels[this->currentLevel]->
        baseHeight);
    this->scene()->addItem(wallSegmentItem);
    this->currentMouseItem = wallSegmentItem;
    this->drawInProgress = true;
  }
  // Subsequent points get linked in the chain
  else
  {
    // Snap currently finishing segment to a grabber in scene
    if (this->snapToGrabber && this->snapGrabberOther &&
        this->snapGrabberCurrent)
    {
      this->LinkGrabbers(this->snapGrabberOther, this->snapGrabberCurrent);
    }

    // Reset and start a new segment
    this->snapToGrabber = false;
    this->snapGrabberOther = NULL;
    this->snapGrabberCurrent = NULL;

    wallSegmentItem = dynamic_cast<WallSegmentItem*>(this->currentMouseItem);
    wallSegmentItem->SetTexture3d("");
    wallSegmentItem->SetColor3d(ignition::math::Color::White);
    wallSegmentItem->SetHighlighted(false);
    this->wallSegmentList.push_back(wallSegmentItem);
    if (wallSegmentItem->Level() > 0)
    {
      floorList[wallSegmentItem->Level()-1]->AttachWallSegment(
          wallSegmentItem);
    }

    // Start from previous segment's end
    auto newPointStart = wallSegmentItem->EndPoint();
    auto newPointEnd = newPointStart + ignition::math::Vector2d(1, 0);
    GrabberHandle *grabberStart = wallSegmentItem->Grabbers()[1];

    wallSegmentItem = new WallSegmentItem(newPointStart,
        newPointEnd, this->levelDefaultHeight);
    wallSegmentItem->SetLevel(this->currentLevel);
    wallSegmentItem->SetLevelBaseHeight(this->levels[
       this->currentLevel]->baseHeight);
    this->scene()->addItem(wallSegmentItem);
    this->currentMouseItem = wallSegmentItem;

    this->LinkGrabbers(grabberStart, wallSegmentItem->Grabbers()[0]);
  }

  // 3D counterpart
  if (wallSegmentItem)
    this->Create3DVisual(wallSegmentItem);
}

/////////////////////////////////////////////////
void EditorView::DrawWindow(const QPoint &_pos)
{
  WindowItem *windowItem = NULL;
  if (!drawInProgress)
  {
    windowItem = new WindowItem();
    windowItem->SetLevel(this->currentLevel);
    windowItem->SetLevelBaseHeight(
        this->levels[this->currentLevel]->baseHeight);
    this->scene()->addItem(windowItem);
    this->currentMouseItem = windowItem;
    this->Create3DVisual(windowItem);
    this->drawInProgress = true;
  }
  windowItem = dynamic_cast<WindowItem*>(this->currentMouseItem);
  if (windowItem)
  {
    QPointF scenePos = this->mapToScene(_pos);
    windowItem->SetPosition(scenePos.x(), scenePos.y());
  }
}

/////////////////////////////////////////////////
void EditorView::DrawDoor(const QPoint &_pos)
{
  DoorItem *doorItem = NULL;
  if (!drawInProgress)
  {
    doorItem = new DoorItem();
    doorItem->SetLevel(this->currentLevel);
    doorItem->SetLevelBaseHeight(this->levels[this->currentLevel]->baseHeight);
    this->scene()->addItem(doorItem);
    this->currentMouseItem = doorItem;
    this->Create3DVisual(doorItem);
    this->drawInProgress = true;
  }
  doorItem = dynamic_cast<DoorItem*>(currentMouseItem);
  if (doorItem)
  {
    QPointF scenePos = this->mapToScene(_pos);
    doorItem->SetPosition(scenePos.x(), scenePos.y());
  }
}

/////////////////////////////////////////////////
void EditorView::DrawStairs(const QPoint &_pos)
{
  StairsItem *stairsItem = NULL;
  if (!drawInProgress)
  {
    stairsItem = new StairsItem();
    stairsItem->SetLevel(this->currentLevel);
    stairsItem->SetLevelBaseHeight(
        this->levels[this->currentLevel]->baseHeight);
    this->scene()->addItem(stairsItem);
    this->currentMouseItem = stairsItem;

    this->Create3DVisual(stairsItem);
    this->drawInProgress = true;
  }
  stairsItem = dynamic_cast<StairsItem*>(this->currentMouseItem);
  if (stairsItem)
  {
    QPointF scenePos = this->mapToScene(_pos);
    stairsItem->SetPosition(scenePos.x(), scenePos.y());
  }
}

/////////////////////////////////////////////////
void EditorView::Create3DVisual(EditorItem *_item)
{
  QVector3D itemPosition = Conversions::Convert(_item->ScenePosition());
  itemPosition.setZ(_item->LevelBaseHeight() + itemPosition.z());
  QVector3D itemSize = Conversions::Convert(_item->Size());
  std::string itemName;

  if (_item->ItemType() == "Stairs")
  {
    StairsItem *stairsItem = dynamic_cast<StairsItem *>(_item);
    itemName = this->buildingMaker->AddStairs(
        itemSize, itemPosition, _item->SceneRotation(),
        stairsItem->Steps());
    if (stairsItem->Level() < static_cast<int>(floorList.size()))
    {
      this->buildingMaker->AttachManip(itemName,
          this->itemToVisualMap[floorList[stairsItem->Level()]]);
    }
  }
  else if (_item->ItemType() == "WallSegment")
  {
    WallSegmentItem *wallSegmentItem = dynamic_cast<WallSegmentItem *>(_item);
    itemSize.setZ(wallSegmentItem->Height());
    itemName = this->buildingMaker->AddWall(
        itemSize, itemPosition, _item->SceneRotation());
  }
  else if (_item->ItemType() == "Window")
  {
    itemName = this->buildingMaker->AddWindow(
        itemSize, itemPosition, _item->SceneRotation());
  }
  else if (_item->ItemType() == "Door")
  {
    itemName = this->buildingMaker->AddDoor(
        itemSize, itemPosition, _item->SceneRotation());
  }
  else if (_item->ItemType() == "Floor")
  {
    itemName = this->buildingMaker->AddFloor(
        itemSize, Conversions::Convert(_item->ScenePosition()), 0);
  }
  this->buildingMaker->ConnectItem(itemName, _item);
  this->itemToVisualMap[_item] = itemName;
  _item->SetName(itemName);
}

/////////////////////////////////////////////////
void EditorView::SetBackgroundImage(const std::string &_filename,
    double _resolution)
{
  QImage img(QString::fromStdString(_filename));
  int newHeight = (img.height() * 100) / _resolution;
  img = img.scaledToHeight(newHeight);

  if (!this->levels[this->currentLevel]->backgroundPixmap)
  {
    this->levels[this->currentLevel]->backgroundPixmap =
      new QGraphicsPixmapItem(QPixmap::fromImage(img));
    this->scene()->addItem(this->levels[this->currentLevel]->backgroundPixmap);
  }
  else
  {
    this->levels[this->currentLevel]->backgroundPixmap->setPixmap(
        QPixmap::fromImage(img));
  }

  this->levels[this->currentLevel]->backgroundPixmap->
      setX(img.width() * -0.5);
  this->levels[this->currentLevel]->backgroundPixmap->
      setY(img.height() * -0.5);
  this->setSceneRect(img.width() * -0.5, img.height() * -0.5,
                     img.width(), img.height());

  if (!this->floorplanVisible)
  {
    this->levels[this->currentLevel]->backgroundPixmap->setVisible(false);
    gui::editor::Events::triggerShowFloorplan();
  }
  gui::editor::Events::createBuildingEditorItem(std::string());
}

/////////////////////////////////////////////////
void EditorView::OnCreateEditorItem(const std::string &_type)
{
  this->CancelDrawMode();

  if (_type == "wall")
    this->drawMode = WALL;
  else if (_type == "window")
    this->drawMode = WINDOW;
  else if (_type == "door")
    this->drawMode = DOOR;
  else if (_type == "stairs")
    this->drawMode = STAIRS;
  else if (_type == "image")
  {
    this->drawMode = NONE;
    ImportImageDialog *importImage = new ImportImageDialog(this);
    importImage->show();
  }

  if (this->drawInProgress && this->currentMouseItem)
  {
    this->scene()->removeItem(this->currentMouseItem);
    this->currentMouseItem = NULL;
    this->drawInProgress = false;
  }

  this->scene()->clearSelection();

  if (this->drawMode == WALL)
    QApplication::setOverrideCursor(QCursor(Qt::CrossCursor));

  if (!this->elementsVisible)
    gui::editor::Events::triggerShowElements();

  // this->grabKeyboard();
}

/////////////////////////////////////////////////
void EditorView::OnColorSelected(QColor _color)
{
  if (!_color.isValid())
    return;

  this->CancelDrawMode();
  this->scene()->clearSelection();
  this->drawMode = COLOR;
}

/////////////////////////////////////////////////
void EditorView::OnTextureSelected(QString _texture)
{
  if (_texture == QString(""))
    return;

  this->CancelDrawMode();
  this->scene()->clearSelection();
  this->drawMode = TEXTURE;
}

/////////////////////////////////////////////////
void EditorView::OnDiscardModel()
{
  this->wallSegmentList.clear();
  this->windowList.clear();
  this->doorList.clear();
  this->stairsList.clear();
  this->floorList.clear();
  this->itemToVisualMap.clear();
  this->buildingMaker->Reset();

  for (unsigned int i = 0; i < this->levels.size(); ++i)
    delete this->levels[i];
  this->levels.clear();
  this->currentLevel = 0;

  Level *newLevel = new Level();
  newLevel->level = 0;
  newLevel->baseHeight = 0;
  newLevel->height = this->levelDefaultHeight;
  newLevel->name = "Level 1";
  this->levels.push_back(newLevel);
  this->levelCounter = 0;

  // clear the scene but add the grid back in
  this->scene()->clear();
  this->gridLines = new GridLines(this->scene()->sceneRect().width(),
      this->scene()->sceneRect().height());
  this->scene()->addItem(this->gridLines);

  this->currentMouseItem = NULL;
  this->drawInProgress = false;
  this->drawMode = NONE;
  gui::editor::Events::createBuildingEditorItem(std::string());
}

/////////////////////////////////////////////////
void EditorView::OnAddLevel()
{
  if (this->wallSegmentList.empty())
  {
    QMessageBox msgBox;
    msgBox.setText("Create new levels after adding walls.");
    msgBox.exec();
    return;
  }

  if (!this->elementsVisible)
    gui::editor::Events::triggerShowElements();

  if (this->levels[this->currentLevel]->backgroundPixmap)
    this->levels[this->currentLevel]->backgroundPixmap->setVisible(false);

  int newLevelNum = this->levels.size();
  std::stringstream levelNameStr;
  levelNameStr << "Level " << ++this->levelCounter + 1;
  std::string levelName = levelNameStr.str();
  this->currentLevel = newLevelNum;
  Level *newLevel = new Level();
  newLevel->name = levelName;
  newLevel->level = newLevelNum;
  newLevel->height = this->levelDefaultHeight;
  this->levels.push_back(newLevel);
  gui::editor::Events::updateLevelWidget(this->currentLevel, levelName);

  std::vector<WallSegmentItem *>::iterator wallIt =
      this->wallSegmentList.begin();
  double wallHeight = (*wallIt)->Height() + (*wallIt)->LevelBaseHeight();
  double maxHeight = wallHeight;
  int wallLevel = 0;

  ++wallIt;
  for (wallIt; wallIt != this->wallSegmentList.end(); ++wallIt)
  {
    wallHeight = (*wallIt)->Height() + (*wallIt)->LevelBaseHeight();
    if ( wallHeight > maxHeight)
    {
      maxHeight = wallHeight;
      wallLevel = (*wallIt)->Level();
    }
  }
  newLevel->baseHeight = maxHeight;

  FloorItem *floorItem = new FloorItem();
  this->levels[this->currentLevel]->floorItem = floorItem;
  std::vector<WallSegmentItem *> newWalls;
  std::map<WallSegmentItem *, WallSegmentItem *> clonedWallMap;
  for (std::vector<WallSegmentItem *>::iterator it = wallSegmentList.begin();
      it  != this->wallSegmentList.end(); ++it)
  {
    if ((*it)->Level() != wallLevel)
      continue;

    WallSegmentItem *wallSegmentItem = (*it)->Clone();
    clonedWallMap[(*it)] = wallSegmentItem;
    wallSegmentItem->SetLevel(newLevelNum);
    wallSegmentItem->SetLevelBaseHeight(this->levels[this->currentLevel]->
        baseHeight);
    this->scene()->addItem(wallSegmentItem);
    newWalls.push_back(wallSegmentItem);

    this->Create3DVisual(wallSegmentItem);

    floorItem->AttachWallSegment(wallSegmentItem);
    wallSegmentItem->SetTexture3d("");
    wallSegmentItem->SetColor3d(ignition::math::Color::White);
    wallSegmentItem->SetHighlighted(false);
  }

  // Clone linked grabber relations
  typedef std::map<WallSegmentItem *, WallSegmentItem *>::iterator clonedIt;
  for (clonedIt iterator = clonedWallMap.begin(); iterator !=
      clonedWallMap.end(); ++iterator)
  {
    WallSegmentItem *oldWall = iterator->first;
    WallSegmentItem *newWall = iterator->second;

    // start / end
    for (int g = 0; g < 2; ++g)
    {
      for (auto oldGrabber : oldWall->Grabbers()[g]->LinkedGrabbers())
      {
        WallSegmentItem *parentItem = dynamic_cast<WallSegmentItem*>(
            oldGrabber->parentItem());

        int index = oldGrabber->Index();

        newWall->Grabbers()[g]->PushLinkedGrabber(
            clonedWallMap[parentItem]->Grabbers()[index]);
      }
    }
  }

  this->wallSegmentList.insert(this->wallSegmentList.end(), newWalls.begin(),
      newWalls.end());

  floorItem->SetLevel(this->currentLevel);
  floorItem->SetLevelBaseHeight(this->levels[this->currentLevel]->baseHeight);
  this->Create3DVisual(floorItem);
  for (std::vector<StairsItem *>::iterator it = this->stairsList.begin();
      it != this->stairsList.end(); ++it)
  {
    if ((*it)->Level() == (newLevelNum - 1))
    {
      buildingMaker->AttachManip(this->itemToVisualMap[(*it)],
          floorItem->Name());
    }
  }
  this->scene()->addItem(floorItem);
  this->floorList.push_back(floorItem);
  floorItem->SetTexture3d("");
  floorItem->SetColor3d(ignition::math::Color::White);
  floorItem->SetHighlighted(false);
}

/////////////////////////////////////////////////
void EditorView::OnDeleteLevel()
{
  this->DeleteLevel(this->currentLevel);
}

/////////////////////////////////////////////////
void EditorView::DeleteLevel(int _level)
{
  if (this->levels.size() == 1
      || _level >= static_cast<int>(this->levels.size()))
    return;

  // Delete current level and move to level below or above
  int newLevelIndex = _level - 1;
  if (newLevelIndex < 0)
    newLevelIndex = _level + 1;

  if (this->levels[_level]->backgroundPixmap)
  {
    this->scene()->removeItem(this->levels[_level]->backgroundPixmap);
    delete this->levels[_level]->backgroundPixmap;
    this->levels[_level]->backgroundPixmap = NULL;
  }

  this->OnChangeLevel(newLevelIndex);

  double deletedHeight = this->levels[_level]->height;
  std::vector<EditorItem *> toBeDeleted;
  for (std::vector<WindowItem *>::iterator it = this->windowList.begin();
      it != this->windowList.end(); ++it)
  {
    if ((*it)->Level() == _level)
      toBeDeleted.push_back(*it);
    else if ((*it)->Level() > _level)
    {
      (*it)->SetLevel((*it)->Level()-1);
      (*it)->SetLevelBaseHeight((*it)->LevelBaseHeight() - deletedHeight);
      (*it)->WindowChanged();
    }
  }
  for (std::vector<DoorItem *>::iterator it = this->doorList.begin();
      it != this->doorList.end(); ++it)
  {
    if ((*it)->Level() == _level)
      toBeDeleted.push_back(*it);
    else if ((*it)->Level() > _level)
    {
      (*it)->SetLevel((*it)->Level()-1);
      (*it)->SetLevelBaseHeight((*it)->LevelBaseHeight()-deletedHeight);
      (*it)->DoorChanged();
    }
  }
  for (std::vector<StairsItem *>::iterator it = this->stairsList.begin();
      it != this->stairsList.end(); ++it)
  {
    if ((*it)->Level() == _level)
      toBeDeleted.push_back(*it);
    else if ((*it)->Level() > _level)
    {
      (*it)->SetLevel((*it)->Level()-1);
      (*it)->SetLevelBaseHeight((*it)->LevelBaseHeight()-deletedHeight);
      (*it)->StairsChanged();
    }
  }
  for (std::vector<FloorItem *>::iterator it = this->floorList.begin();
      it != this->floorList.end(); ++it)
  {
    if ((*it)->Level() == _level)
      toBeDeleted.push_back(*it);
    else if ((*it)->Level() > _level)
    {
      (*it)->SetLevel((*it)->Level()-1);
      (*it)->SetLevelBaseHeight((*it)->LevelBaseHeight()-deletedHeight);
      (*it)->FloorChanged();
    }
  }
  for (std::vector<WallSegmentItem *>::iterator it =
      this->wallSegmentList.begin();
      it != this->wallSegmentList.end(); ++it)
  {
    if ((*it)->Level() == _level)
      toBeDeleted.push_back(*it);
    else if ((*it)->Level() > _level)
    {
      (*it)->SetLevel((*it)->Level()-1);
      (*it)->SetLevelBaseHeight((*it)->LevelBaseHeight()-deletedHeight);
      (*it)->WallSegmentChanged();
    }
  }

  for (unsigned int i = 0; i < toBeDeleted.size(); ++i)
  {
    this->DeleteItem(toBeDeleted[i]);
  }

  int levelNum = 0;
  for (unsigned int i = 0; i < this->levels.size(); ++i)
  {
    if (this->levels[i]->level == _level)
    {
      delete this->levels[i];
      levelNum = i;
    }
    else if (this->levels[i]->level > _level)
    {
      this->levels[i]->level--;
      this->levels[i]->baseHeight -= deletedHeight;
    }
  }
  this->levels.erase(this->levels.begin() + levelNum);
  this->currentLevel = newLevelIndex;

  gui::editor::Events::updateLevelWidget(_level, "");
}

/////////////////////////////////////////////////
void EditorView::OnChangeLevel(int _level)
{
  if (_level < 0)
    return;

  if (this->currentLevel >= static_cast<int>(this->levels.size()))
    return;

  if (this->levels[this->currentLevel]->backgroundPixmap)
    this->levels[this->currentLevel]->backgroundPixmap->setVisible(false);

  if (_level < static_cast<int>(this->levels.size()) &&
      this->levels[_level]->backgroundPixmap &&
      this->floorplanVisible)
  {
    this->levels[_level]->backgroundPixmap->setVisible(true);
  }

  this->currentLevel = _level;
  this->ShowCurrentLevelItems();
}

/////////////////////////////////////////////////
void EditorView::OnOpenLevelInspector()
{
  this->levelInspector->SetLevelName(this->levels[this->currentLevel]->name);
  FloorItem *floorItem = this->levels[this->currentLevel]->floorItem;
  if (floorItem)
  {
    this->levelInspector->ShowFloorWidget(true);
    this->levelInspector->SetColor(floorItem->Color3d());
    this->levelInspector->SetTexture(floorItem->Texture3d());
  }
  else
  {
    this->levelInspector->ShowFloorWidget(false);
  }
  this->levelInspector->move(QCursor::pos());
  this->levelInspector->show();
}

/////////////////////////////////////////////////
void EditorView::OnLevelApply()
{
  LevelInspectorDialog *dialog =
      qobject_cast<LevelInspectorDialog *>(QObject::sender());

  std::string newLevelName = dialog->LevelName();
  this->levels[this->currentLevel]->name = newLevelName;
  FloorItem *floorItem = this->levels[this->currentLevel]->floorItem;
  if (floorItem)
  {
    floorItem->SetTexture3d(dialog->Texture());
    floorItem->SetColor3d(dialog->Color());
    floorItem->Set3dTransparency(0.4);
    floorItem->FloorChanged();
  }
  gui::editor::Events::updateLevelWidget(this->currentLevel, newLevelName);
}

/////////////////////////////////////////////////
void EditorView::CancelDrawMode()
{
  if (this->drawMode != NONE)
  {
    if (this->currentMouseItem)
    {
      EditorItem *item = dynamic_cast<EditorItem *>(this->currentMouseItem);
      this->itemToVisualMap.erase(item);

      WallSegmentItem *wallSegmentItem = dynamic_cast<WallSegmentItem *>(item);

      if (wallSegmentItem)
      {
        this->UnlinkGrabbers(wallSegmentItem->Grabbers()[0]);
      }
      this->scene()->removeItem(this->currentMouseItem);
      delete this->currentMouseItem;
    }
    this->snapToGrabber = false;
    this->snapGrabberOther = NULL;
    this->snapGrabberCurrent = NULL;
    this->drawMode = NONE;
    this->drawInProgress = false;
    this->currentMouseItem = NULL;
    QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
  }
}

/////////////////////////////////////////////////
void EditorView::OnShowFloorplan()
{
  this->floorplanVisible = !this->floorplanVisible;

  if (this->levels[this->currentLevel]->backgroundPixmap)
    this->levels[this->currentLevel]->backgroundPixmap->setVisible(
        !this->levels[this->currentLevel]->backgroundPixmap->isVisible());
}

/////////////////////////////////////////////////
void EditorView::OnShowElements()
{
  this->elementsVisible = !this->elementsVisible;

  this->ShowCurrentLevelItems();
}

/////////////////////////////////////////////////
void EditorView::ShowCurrentLevelItems()
{
  for (std::vector<WallSegmentItem *>::iterator it =
      this->wallSegmentList.begin(); it != this->wallSegmentList.end(); ++it)
  {
    if ((*it)->Level() != this->currentLevel)
      (*it)->setVisible(false);
    else
      (*it)->setVisible(this->elementsVisible);
  }
  for (std::vector<WindowItem *>::iterator it = this->windowList.begin();
      it != this->windowList.end(); ++it)
  {
    if ((*it)->Level() != this->currentLevel)
      (*it)->setVisible(false);
    else
      (*it)->setVisible(this->elementsVisible);
  }
  for (std::vector<DoorItem *>::iterator it = this->doorList.begin();
      it != this->doorList.end(); ++it)
  {
    if ((*it)->Level() != this->currentLevel)
      (*it)->setVisible(false);
    else
      (*it)->setVisible(this->elementsVisible);
  }
  for (std::vector<StairsItem *>::iterator it = this->stairsList.begin();
      it != this->stairsList.end(); ++it)
  {
    if ((*it)->Level() != this->currentLevel && (*it)->Level() !=
        (this->currentLevel - 1))
      (*it)->setVisible(false);
    else
      (*it)->setVisible(this->elementsVisible);
  }
  for (std::vector<FloorItem *>::iterator it = this->floorList.begin();
      it != this->floorList.end(); ++it)
  {
    if ((*it)->Level() != this->currentLevel)
      (*it)->setVisible(false);
    else
      (*it)->setVisible(this->elementsVisible);
  }
}

/////////////////////////////////////////////////
void EditorView::LinkGrabbers(GrabberHandle *_grabber1,
    GrabberHandle *_grabber2)
{
  if (_grabber1 && _grabber2 && _grabber1 != _grabber2)
  {
    // if _grabber2 is not yet linked to _grabber1
    auto linked1 = _grabber1->LinkedGrabbers();
    auto linked2 = _grabber2->LinkedGrabbers();
    if (std::find(linked1.begin(), linked1.end(), _grabber2) == linked1.end())
    {
      // Add _grabber2 so it moves when _grabber1 is moved
      _grabber1->PushLinkedGrabber(_grabber2);
      // also link _grabber1 to all grabbers already linked to _grabber2
      for (auto it : linked2)
      {
        this->LinkGrabbers(_grabber1, it);
      }
    }
    // if _grabber1 is not yet linked to _grabber2
    if (std::find(linked2.begin(), linked2.end(), _grabber1) == linked2.end())
    {
      // Add _grabber1 so it moves when _grabber2 is moved
      _grabber2->PushLinkedGrabber(_grabber1);
      // also link _grabber2 to all grabbers already linked to _grabber1
      for (auto it : linked1)
      {
        this->LinkGrabbers(_grabber2, it);
      }
    }
  }
}

/////////////////////////////////////////////////
void EditorView::UnlinkGrabbers(GrabberHandle *_grabber1,
    GrabberHandle *_grabber2)
{
  // If only one grabber, erase it from all grabbers it is linked
  if (!_grabber2)
  {
    for (auto linked : _grabber1->LinkedGrabbers())
    {
      linked->EraseLinkedGrabber(_grabber1);
    }
  }

  // TODO: add option to unlink grabbers besides when deleting one of them,
  // perhaps using a hot-key or at the wall inspector
}
