added support for meta-data editing improved element selection changed zooming fixed some issues with layer events fixed issue with 3D outline fixed loading issue for old maps some interface changes
316 lines
7.3 KiB
C++
316 lines
7.3 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) {
|
|
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(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:
|
|
|
|
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)
|
|
|
|
#warning "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(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
|