397 lines
9.6 KiB
C++
397 lines
9.6 KiB
C++
/*
|
||
* © 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
|