#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); } private: void processUnfocus(MapView2D* m, MapModelElement* elem) { MV2DElement* me = elem->getMV2D(); if (!me) {return;} // elements has selectedable nodes? unselect them if (dynamic_cast(me)) { dynamic_cast(me)->onNodeUnselect(m); } // let the element itself process the unfocus me->unfocus(); } void processPress(MapView2D* m, const Point2 p, MapModelElement* elem) { MV2DElement* me = elem->getMV2D(); if (!me) {return;} // let the element itself process all events me->mousePressed(m, p); } void processMove(MapView2D* m, const Point2 p, MapModelElement* elem) { MV2DElement* me = elem->getMV2D(); if (!me) {return;} // elements has selectedable nodes? try to process them if (dynamic_cast(me)) { if (moveNode(m, p, dynamic_cast(me))) {return;} } // otherwise: let the element itself process all events me->mouseMove(m, p); } void processRelease(MapView2D* m, const Point2 p, MapModelElement* elem) { MV2DElement* me = elem->getMV2D(); if (!me) {return;} // element has selectedable nodes? try to process them if (dynamic_cast(me)) { if (selectNode(m, p, dynamic_cast(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(); 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; } 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) if (focused) {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) // get all elements with bounding-box matchings std::vector possible; for (MapModelElement* el : m->getModel()->getSelectedLayerElements()) { 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(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(m, p2, focused);} } return false; } virtual bool mouseReleaseEvent(MapView2D* m, QMouseEvent* e) override { const Scaler& s = m->getScaler(); Point2 p2(s.xsm(e->x()), s.ysm(e->y())); // provide the focused element with the mouse event? if (focused) {processRelease(m, p2, focused);} mouseIsDown = false; return false; } virtual bool keyPressEvent(MapView2D* m, QKeyEvent* e) override { (void) m; 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; } } return false; } signals: /** map element was selected */ void onMapElementSelected(MapModelElement* el); }; #endif // TOOLSELECTOR_H