This repository has been archived on 2020-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
Files
IndoorMap/mapview/tools/ToolSelector.h
2016-07-04 15:11:10 +02:00

279 lines
6.4 KiB
C++

#ifndef TOOLSELECTOR_H
#define TOOLSELECTOR_H
#include "Tool.h"
#include "../model/MapModelElement.h"
#include "../MapView2D.h"
#include "../model/MapModel.h"
/**
* this tool allows:
* - selecting 2D elements within the 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() {;}
/** 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<HasMoveableNodes*>(me)) {
dynamic_cast<HasMoveableNodes*>(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<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(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<HasMoveableNodes*>(me)) {
if (selectNode(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();
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:
/**
* @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();}
emit onMapElementSelected(el);
}
// done
return focusChanged;
}
private:
virtual void mousePressEvent(MapView2D* m, QMouseEvent* e) override {
if (e->button() != Qt::MouseButton::LeftButton) {return;}
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<MapModelElement*> 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);
}
}
}
virtual void 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);}
}
}
virtual void 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;
}
virtual void 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;}
// 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);
}
}
}
signals:
/** map element was selected */
void onMapElementSelected(MapModelElement* el);
};
#endif // TOOLSELECTOR_H