390 lines
9.4 KiB
C++
390 lines
9.4 KiB
C++
#ifndef TOOLSELECTOR_H
|
|
#define TOOLSELECTOR_H
|
|
|
|
|
|
#include "Tool.h"
|
|
#include "../MapView2D.h"
|
|
|
|
#include "../../model/MapModelElement.h"
|
|
#include "../../model/MapModel.h"
|
|
|
|
|
|
/**
|
|
* this tool allows:
|
|
* - selecting elements within the 2D view (focus/unfocus)
|
|
* - selecting and moving nodes of elements inheriting from HasMoveableNodes
|
|
*
|
|
*/
|
|
class ToolSelector : public Tool {
|
|
|
|
Q_OBJECT
|
|
|
|
private:
|
|
|
|
/** the currently focused MapElement (if any) */
|
|
MapModelElement* focused = nullptr;
|
|
|
|
/** whether the mouse-button is currently pressed */
|
|
bool mouseIsDown = false;
|
|
|
|
public:
|
|
|
|
virtual ~ToolSelector() {;}
|
|
|
|
|
|
const std::string getName() const override {
|
|
return "Selection";
|
|
}
|
|
|
|
void becomesActive() override {
|
|
showHelp();
|
|
}
|
|
|
|
/** needed eg. when the focused element gets invalid (switching visible layers) */
|
|
void layerChange(MapView2D* m) override {
|
|
(void) m;
|
|
setFocused(m, nullptr);
|
|
}
|
|
|
|
/**
|
|
* @brief change the currently focused element. throws an event.
|
|
* @param el the to-be-focused element or null
|
|
* @return true if the focused element has changed. false otherwise
|
|
*/
|
|
bool focus(MapView2D* v, MapModelElement* el) {
|
|
return setFocused(v, el);
|
|
}
|
|
|
|
private:
|
|
|
|
void processUnfocus(MapView2D* m, MapModelElement* elem) {
|
|
|
|
MV2DElement* me = elem->getMV2D();
|
|
if (!me) {return;}
|
|
|
|
// elements has selectedable nodes? unselect them
|
|
if (dynamic_cast<HasMoveableNodes*>(me)) {
|
|
dynamic_cast<HasMoveableNodes*>(me)->onNodeUnselect(m);
|
|
}
|
|
|
|
// let the element itself process the unfocus
|
|
me->unfocus();
|
|
|
|
}
|
|
|
|
void processPress(QMouseEvent* e, MapView2D* m, const Point2 p, MapModelElement* elem) {
|
|
|
|
MV2DElement* me = elem->getMV2D();
|
|
if (!me) {return;}
|
|
|
|
// left mouse button?
|
|
if (e->button() == Qt::MouseButton::LeftButton) {
|
|
// element has selectedable nodes? try to select one
|
|
if (dynamic_cast<HasMoveableNodes*>(me)) {
|
|
if (selectNode(m, p, dynamic_cast<HasMoveableNodes*>(me))) {return;}
|
|
}
|
|
}
|
|
|
|
// let the element itself process all events
|
|
me->mousePressed(m, p);
|
|
|
|
}
|
|
|
|
void processMove(QMouseEvent* e, MapView2D* m, const Point2 p, MapModelElement* elem) {
|
|
|
|
(void) e;
|
|
|
|
MV2DElement* me = elem->getMV2D();
|
|
if (!me) {return;}
|
|
|
|
// left mouse button? [does not work for move-events?!]
|
|
//if (e->button() == Qt::MouseButton::LeftButton) {
|
|
// elements has selectedable nodes? try to move the selected one (if any)
|
|
if (dynamic_cast<HasMoveableNodes*>(me)) {
|
|
if (moveNode(m, p, dynamic_cast<HasMoveableNodes*>(me))) {return;}
|
|
}
|
|
//}
|
|
|
|
// otherwise: let the element itself process all events
|
|
me->mouseMove(m, p);
|
|
|
|
}
|
|
|
|
void processRelease(QMouseEvent* e, MapView2D* m, const Point2 p, MapModelElement* elem) {
|
|
|
|
(void) e;
|
|
|
|
MV2DElement* me = elem->getMV2D();
|
|
if (!me) {return;}
|
|
|
|
// // left mouse button?
|
|
// if (e->button() == Qt::MouseButton::LeftButton) {
|
|
// // element has selectedable nodes? try to select one
|
|
if (dynamic_cast<HasMoveableNodes*>(me)) {
|
|
//if (selectNode(m, p, dynamic_cast<HasMoveableNodes*>(me))) {return;}
|
|
if (nodeMoved(m, p, dynamic_cast<HasMoveableNodes*>(me))) {return;}
|
|
}
|
|
// }
|
|
|
|
// otherwise: let the element itself process all events
|
|
me->mouseReleased(m, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
/** the given element has selectable nodes. try to select the one near to the mouse. true if successful */
|
|
bool selectNode(MapView2D* v, Point2 p, HasMoveableNodes* elem) {
|
|
|
|
// select a new point on mouse-release (more robust than on mouse-press)
|
|
|
|
// find the node nearest to p
|
|
auto comp = [&] (const MoveableNode& n1, const MoveableNode& n2) {return n1.pos.getDistance(p) < n2.pos.getDistance(p);};
|
|
auto lst = elem->getMoveableNodes();
|
|
if (lst.empty()) {return false;} // nothing available!
|
|
auto it = std::min_element(lst.begin(), lst.end(), comp);
|
|
|
|
// is the nearest point below the threshold?
|
|
const float t = v->getScaler().sm(CFG::SEL_THRESHOLD_SIZE_PX);
|
|
const float d = it->pos.getDistance(p);
|
|
if (d < t) {
|
|
elem->onNodeSelect(v, it->userIdx);
|
|
return true;
|
|
} else {
|
|
elem->onNodeSelect(v, -1);
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
/** move the currently selected node. true if successful */
|
|
bool moveNode(MapView2D* v, Point2 p, HasMoveableNodes* elem) {
|
|
|
|
// no node selected? -> done
|
|
if (elem->getSelectedNode() == -1) {return false;}
|
|
|
|
// snap the node
|
|
const Point2 pSnapped = v->getScaler().snap(p);
|
|
|
|
// move
|
|
elem->onNodeMove(v, elem->getSelectedNode(), pSnapped);
|
|
return true;
|
|
|
|
}
|
|
|
|
/** moved the currently selected node (now done). true if successful */
|
|
bool nodeMoved(MapView2D* v, Point2 p, HasMoveableNodes* elem) {
|
|
|
|
// no node selected? -> done
|
|
if (elem->getSelectedNode() == -1) {return false;}
|
|
|
|
// snap the node
|
|
const Point2 pSnapped = v->getScaler().snap(p);
|
|
|
|
// moved (done)
|
|
elem->onNodeMoved(v, elem->getSelectedNode(), pSnapped);
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void showHelp() {
|
|
if (focused) {emit onHelpTextChange("select one of the element's nodes");}
|
|
if (!focused) {emit onHelpTextChange("select an element by clicking on it");}
|
|
}
|
|
|
|
/**
|
|
* @brief change the currently focused element. throws an event.
|
|
* @param el the to-be-focused element or null
|
|
* @return true if the focused element has changed. false otherwise
|
|
*/
|
|
bool setFocused(MapView2D* v, MapModelElement* el) {
|
|
|
|
// whether the focus has changed or not
|
|
const bool focusChanged = (focused != el);
|
|
|
|
if (focusChanged) {
|
|
|
|
// unfocus the old one (if any)
|
|
if (focused) {processUnfocus(v, focused);}
|
|
|
|
// set the currently focused object (if any)
|
|
focused = el;
|
|
|
|
// focus the new one (if any, and focus supported)
|
|
if (focused && focused->getMV2D()) {
|
|
focused->getMV2D()->focus();
|
|
}
|
|
|
|
// update the help-text
|
|
showHelp();
|
|
|
|
emit onMapElementSelected(el);
|
|
|
|
}
|
|
|
|
// done
|
|
return focusChanged;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
virtual bool mousePressEvent(MapView2D* m, QMouseEvent* e) override {
|
|
|
|
if (e->button() != Qt::MouseButton::LeftButton) {return false;}
|
|
|
|
mouseIsDown = true;
|
|
|
|
const Scaler& s = m->getScaler();
|
|
const Point2 p2(s.xsm(e->x()), s.ysm(e->y()));
|
|
|
|
const float g = m->getScaler().sm(15); // increase each BBox by 15 px (needed mainly for hor/ver lines)
|
|
|
|
#pragma message "which elements to select? among all currently visible? or only among the selected layer?"
|
|
// get all elements with bounding-box matchings
|
|
std::vector<MapModelElement*> possible;
|
|
// for (MapModelElement* el : m->getModel()->getSelectedLayerElements()) {
|
|
for (MapModelElement* el : m->getModel()->getVisibleElements()) {
|
|
if (!el->getMV2D()) {continue;}
|
|
BBox2 bbox = el->getMV2D()->getBoundingBox(); // elements 2D bbox
|
|
bbox.grow(Point2(g, g)); // grow a little (needed for straight lines)
|
|
if (bbox.contains(p2)) {possible.push_back(el);} // intersection?
|
|
}
|
|
|
|
// among those, find the best-matching one (smallest distance to an intersection)
|
|
auto lambda = [&] (const MapModelElement* el1, const MapModelElement* el2) {return el1->getMV2D()->getMinDistanceXY(p2) < el2->getMV2D()->getMinDistanceXY(p2);};
|
|
auto it = std::min_element(possible.begin(), possible.end(), lambda);
|
|
MapModelElement* el = (it == possible.end()) ? (nullptr) : (*it);
|
|
|
|
|
|
|
|
// focus changed? -> unfocus the old one (if any)
|
|
if (setFocused(m, el)) {
|
|
|
|
;
|
|
|
|
} else {
|
|
|
|
// focus kept. provide the currently focused element with events
|
|
if (focused) {
|
|
processPress(e, m, p2, focused);
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
virtual bool mouseMoveEvent(MapView2D* m, QMouseEvent* e) override {
|
|
|
|
const Scaler& s = m->getScaler();
|
|
Point2 p2(s.xsm(e->x()), s.ysm(e->y()));
|
|
|
|
// mouse pressed?
|
|
if (mouseIsDown) {
|
|
|
|
// provide the focused element with the mouse event?
|
|
if (focused) {processMove(e, m, p2, focused);}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
virtual bool mouseReleaseEvent(MapView2D* m, QMouseEvent* e) override {
|
|
|
|
const Scaler& s = m->getScaler();
|
|
const Point2 p2(s.xsm(e->x()), s.ysm(e->y()));
|
|
|
|
// provide the focused element with the mouse event?
|
|
if (focused) {processRelease(e, m, p2, focused);}
|
|
|
|
mouseIsDown = false;
|
|
|
|
// not consumed
|
|
return false;
|
|
|
|
}
|
|
|
|
virtual bool keyPressEvent(MapView2D* m, QKeyEvent* e) override {
|
|
|
|
(void) m;
|
|
|
|
// currently: only accept keys when something is focused
|
|
if (focused) {
|
|
|
|
// pass it to the element which may consume this event
|
|
if (focused->getMV2D()->keyPressEvent(m, e)) {return false;}
|
|
|
|
// not consumed -> additional options
|
|
|
|
// ESCAPE -> unfocus
|
|
if (e->key() == Qt::Key_Escape) {setFocused(m, nullptr);}
|
|
|
|
// DELETE -> delete
|
|
if (e->key() == Qt::Key_Delete) {
|
|
focused->deleteMe();
|
|
setFocused(m, nullptr);
|
|
return true;
|
|
}
|
|
|
|
if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down || e->key() == Qt::Key_Left || e->key() == Qt::Key_Right) {
|
|
moveNodes(m, focused, e->key());
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
void moveNodes(MapView2D* v, MapModelElement* focused, const int key) {
|
|
|
|
MV2DElement* me = focused->getMV2D();
|
|
if (!me) {return;}
|
|
|
|
HasMoveableNodes* elem = dynamic_cast<HasMoveableNodes*>(me);
|
|
if (!elem) {return;}
|
|
|
|
// no node selected? -> done
|
|
if (elem->getSelectedNode() == -1) {return;}
|
|
|
|
// get current value
|
|
MoveableNode& node = elem->getMoveableNodes()[elem->getSelectedNode()];
|
|
Point2 pos = node.pos;
|
|
|
|
// adjust the node
|
|
switch(key) {
|
|
case Qt::Key_Up: pos.y += 0.1; break;
|
|
case Qt::Key_Down: pos.y -= 0.1; break;
|
|
case Qt::Key_Left: pos.x -= 0.1; break;
|
|
case Qt::Key_Right: pos.x += 0.1; break;
|
|
}
|
|
|
|
// snap the position
|
|
pos = v->getScaler().snap(pos);
|
|
|
|
// move
|
|
elem->onNodeMove(v, elem->getSelectedNode(), pos);
|
|
|
|
}
|
|
|
|
signals:
|
|
|
|
/** map element was selected */
|
|
void onMapElementSelected(MapModelElement* el);
|
|
|
|
};
|
|
|
|
#endif // TOOLSELECTOR_H
|