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/2D/tools/ToolSelector.h
2018-10-25 12:23:40 +02:00

397 lines
9.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* © Copyright 2014 Urheberrechtshinweis
* Alle Rechte vorbehalten / All Rights Reserved
*
* Programmcode ist urheberrechtlich geschuetzt.
* Das Urheberrecht liegt, soweit nicht ausdruecklich anders gekennzeichnet, bei Frank Ebner.
* Keine Verwendung ohne explizite Genehmigung.
* (vgl. § 106 ff UrhG / § 97 UrhG)
*/
#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();
}
bool processPress(QMouseEvent* e, MapView2D* m, const Point2 p, MapModelElement* elem) {
MV2DElement* me = elem->getMV2D();
if (!me) {return false;}
// 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 true;}
}
}
// let the element itself process all events
me->mousePressed(m, p);
return false;
}
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)
if (focused && processPress(e, m, p2, focused)) {
;
} else {
// 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 the new element, unfocus the old one
if (setFocused(m, el)) {
;
}
}
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