1 /* 2 * Copyright 2012, 2014 Thomas Schöps 3 * Copyright 2013-2017 Kai Pastor 4 * 5 * This file is part of OpenOrienteering. 6 * 7 * OpenOrienteering is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * OpenOrienteering is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with OpenOrienteering. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 22 #ifndef OPENORIENTEERING_MAP_EDITOR_TOOL_BASE_H 23 #define OPENORIENTEERING_MAP_EDITOR_TOOL_BASE_H 24 25 #include <memory> 26 #include <set> 27 #include <utility> 28 #include <vector> 29 30 #include <Qt> 31 #include <QCursor> 32 #include <QObject> 33 #include <QPoint> 34 #include <QPointF> 35 #include <QString> 36 37 #include <QPointer> 38 39 #include "core/map_coord.h" 40 #include "core/objects/object.h" 41 #include "tools/tool.h" 42 43 class QAction; 44 class QKeyEvent; 45 class QMouseEvent; 46 class QPainter; 47 class QRectF; 48 49 namespace OpenOrienteering { 50 51 class ConstrainAngleToolHelper; 52 class KeyButtonBar; 53 class MapEditorController; 54 class MapRenderables; 55 class MapWidget; 56 class SnappingToolHelper; 57 58 59 /** 60 * Provides a simple interface to base tools on. 61 * 62 * Look at the top of the protected section for the methods to override, 63 * and below that for some helper methods. 64 */ 65 class MapEditorToolBase : public MapEditorTool 66 { 67 Q_OBJECT 68 69 /** 70 * An edited item consist of an active object and a corresponding duplicate. 71 * 72 * The active object is owned by the map. 73 * The duplicate is owned by this item. 74 * The duplicate may be used when for restoring the active's object's data 75 * when aborting editing, 76 * or it can be transferred to an undo step when committing the changes. 77 */ 78 struct EditedItem 79 { 80 Object* active_object; 81 std::unique_ptr<Object> duplicate; 82 83 // Convenience constructor 84 EditedItem(Object* active_object); 85 86 // All methods which are needed for efficient containers 87 EditedItem() noexcept = default; 88 EditedItem(const EditedItem& prototype); 89 EditedItem(EditedItem&& prototype) noexcept; 90 EditedItem& operator=(const EditedItem& prototype); 91 EditedItem& operator=(EditedItem&& prototype) noexcept; 92 93 ~EditedItem() = default; 94 95 bool isModified() const; 96 }; 97 98 public: 99 /** 100 * An accessor to the active objects in a std::vector<EditedItem>. 101 * 102 * This makes the edited objects available in range-for loops 103 * by providing forward iteration, but hides the implementation details. 104 */ 105 class ObjectsRange 106 { 107 private: 108 using container = std::vector<EditedItem>; 109 container::iterator range_begin; 110 container::iterator range_end; 111 112 public: ObjectsRange(container & items)113 explicit ObjectsRange(container& items) : range_begin { items.begin() }, range_end { items.end() } {} 114 struct iterator : private container::iterator 115 { iteratoriterator116 explicit iterator(const container::iterator& it) noexcept : container::iterator { it } {} iteratoriterator117 explicit iterator(container::iterator&& it) noexcept : container::iterator { std::move(it) } {} 118 Object* operator*() noexcept { return container::iterator::operator*().active_object; } 119 Object* operator->() noexcept { return operator*(); } 120 iterator& operator++() noexcept { container::iterator::operator++(); return *this; } 121 bool operator==(const iterator& rhs) noexcept { return static_cast<container::iterator>(*this)==static_cast<container::iterator>(rhs); } 122 bool operator!=(const iterator& rhs) noexcept { return !operator==(rhs); } 123 }; begin()124 iterator begin() noexcept { return iterator { range_begin }; } end()125 iterator end() noexcept { return iterator { range_end }; } 126 }; 127 128 129 MapEditorToolBase(const QCursor& cursor, MapEditorTool::Type type, MapEditorController* editor, QAction* tool_action); 130 ~MapEditorToolBase() override; 131 132 void init() override; 133 const QCursor& getCursor() const override; 134 135 /** 136 * Updates the saved current position (raw and constrained), map widget, and modifiers. 137 * 138 * This function is called by the other mouse*Event handlers of this class. 139 * Derived classes which override the handlers may wish to call this, too. 140 */ 141 void mousePositionEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget); 142 143 bool mousePressEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget) override; 144 bool mouseMoveEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget) override; 145 bool mouseReleaseEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget) override; 146 147 bool keyPressEvent(QKeyEvent* event) override; 148 bool keyReleaseEvent(QKeyEvent* event) override; 149 150 /// Draws the preview renderables. Should be overridden to draw custom elements. 151 void draw(QPainter* painter, MapWidget* widget) override; 152 153 void finishEditing() override; 154 155 protected slots: 156 void updateDirtyRect(); 157 void objectSelectionChanged(); 158 void updatePreviewObjectsSlot(); 159 160 protected: 161 // Virtual methods to be implemented by the derived class. 162 // To access the cursor position, use [click/cur]_pos(_map) to get the mouse press 163 // respectively current position on the screen or on the map, or constrained_pos(_map) 164 // to get the position constrained by the snap and angle tool helpers, if activated. 165 166 /// Can do additional initializations at a time where no other tool is active (in contrast to the constructor) 167 virtual void initImpl(); 168 /// Must include the area of all custom drawings into the rect, 169 /// which already contains the area of the selection preview and activated tool helpers when this method is called. 170 /// Must return the size of the pixel border, or -1 to clear the drawing. 171 virtual int updateDirtyRectImpl(QRectF& rect); 172 /// Must draw the tool's graphics. 173 /// The implementation draws the preview renderables. 174 /// MapEditorToolBase::draw() draws the activated tool helpers afterwards. 175 /// If this is not desired, you can override draw() directly. 176 virtual void drawImpl(QPainter* painter, MapWidget* widget); 177 /// Must update the status bar text 178 virtual void updateStatusText() = 0; 179 /// Called when the object selection in the map is changed. 180 /// This can happen for example as result of an undo/redo operation. 181 virtual void objectSelectionChangedImpl() = 0; 182 183 /// Called when the left mouse button is pressed 184 virtual void clickPress(); 185 /// Called when the left mouse button is released without a drag operation before 186 virtual void clickRelease(); 187 188 /// Called when the mouse is moved without the left mouse button being pressed 189 virtual void mouseMove(); 190 191 void gestureStarted() override; 192 193 void startDragging(); 194 void updateDragging(); 195 void finishDragging(); 196 void cancelDragging(); 197 /// Is the left mouse button pressed and has a drag move been started (by moving the mouse a minimum amount of pixels)? isDragging()198 bool isDragging() const { return dragging; } 199 200 /// Called when a drag operation is started. This happens when dragging the mouse some pixels 201 /// away from the mouse press position. The distance is determined by start_drag_distance. 202 /// dragMove() is called immediately after this call to account for the already moved distance. 203 virtual void dragStart(); 204 /// Called when the mouse is moved with the left mouse button being pressed 205 virtual void dragMove(); 206 /// Called when a drag operation is finished. There is no need to update the edit operation with the 207 /// current cursor coordinates in this call as it is ensured that dragMove() is called before. 208 virtual void dragFinish(); 209 210 virtual void dragCanceled(); 211 212 /// Called when a key is pressed down. Return true if the key was processed by the tool. 213 virtual bool keyPress(QKeyEvent* event); 214 /// Called when a key is released. Return true if the key was processed by the tool. 215 virtual bool keyRelease(QKeyEvent* event); 216 217 // Helper methods 218 219 /** 220 * Applies the constraint helpers and generates a pointer event. 221 * 222 * This function re-applies the constraint helpers to the current pointer 223 * position. Then it calls either mouseMove() or dragMove(), depending on 224 * the current state. 225 */ 226 void reapplyConstraintHelpers(); 227 228 /// Call this before editing the selected objects, and finish/abortEditing() afterwards. 229 /// Takes care of the preview renderables handling, map dirty flag, and objects edited signal. 230 void startEditing(); 231 void startEditing(Object* object); 232 void startEditing(const std::set<Object*>& objects); 233 void abortEditing(); 234 editedObjects()235 ObjectsRange editedObjects() { return ObjectsRange { edited_items }; } 236 bool editedObjectsModified() const; 237 void resetEditedObjects(); 238 239 /// Call this to display changes to the preview objects between startEditing() and finish/abortEditing(). 240 virtual void updatePreviewObjects(); 241 242 /// Call this to display changes to the preview objects between startEditing() and finish/abortEditing(). 243 /// This method delays the actual redraw by a short amount of time to reduce the load when editing many objects. 244 void updatePreviewObjectsAsynchronously(); 245 246 /// If the tool created custom renderables (e.g. with updatePreviewObjects()), draws the preview renderables, 247 /// else draws the renderables of the selected map objects. 248 void drawSelectionOrPreviewObjects(QPainter* painter, MapWidget* widget, bool draw_opaque = false); 249 250 /// Activates or deactivates the angle helper, recalculates (un-)constrained cursor position, 251 /// and calls mouseMove() or dragMove() to update the tool. 252 void activateAngleHelperWhileEditing(bool enable = true); 253 254 /// Activates or deactivates the snap helper, recalculates (un-)constrained cursor position, 255 /// and calls mouseMove() or dragMove() to update the tool. 256 void activateSnapHelperWhileEditing(bool enable = true); 257 258 /** 259 * Calculates the constrained cursor position for the current position and map widget. 260 * 261 * Key event handlers which change constraint helper activation must call 262 * this to be able to visualize the effect of the change. Overrides of 263 * mouse*Event handlers shall call mousePositionEvent() instead. This is 264 * also done by the default implementation of these handlers. Thus it is not 265 * necessary to call this explicitly from clickPress(), clickRelease(), 266 * mouseMove(), dragStart(), dragMove(), or dragFinish() handlers. 267 */ 268 void updateConstrainedPositions(); 269 270 #ifdef MAPPER_DEVELOPMENT_BUILD 271 272 protected slots: 273 /** 274 * Sends simulated sequences of mouse button-press, move, button-release. 275 * 276 * The mouse position is modified randomly for every single event. 277 * After each event, there is a small delay. 278 * 279 * This a debugging aid. The implementation is meant to be modified as 280 * needed with regard to modifier keys, stopping the simulation, etc. 281 * It is to be started from an event handler via a single-shot timer: 282 * 283 * QTimer::singleShot(200, this, &MapEditorToolBase::generateNextSimulatedEvent); 284 */ 285 void generateNextSimulatedEvent(); 286 287 private: 288 int simulation_state = 0; 289 290 #endif // MAPPER_DEVELOPMENT_BUILD 291 292 protected: 293 // Mouse handling 294 295 /// Position where the left mouse button was pressed, with no constraints applied. 296 QPoint click_pos; 297 MapCoordF click_pos_map; 298 /// Position where the left mouse button was pressed, constrained by tool helpers, if active. 299 QPointF constrained_click_pos; 300 MapCoordF constrained_click_pos_map; 301 /// Position where the cursor is currently 302 QPoint cur_pos; 303 MapCoordF cur_pos_map; 304 /// Position where the cursor is currently, constrained by tool helpers, if active 305 QPointF constrained_pos; 306 MapCoordF constrained_pos_map; 307 /// This is set to true when constrained_pos(_map) is a snapped position 308 bool snapped_to_pos; 309 /// The amount of pixels the mouse has to be moved to start dragging. 310 /// Defaults to Settings::getInstance().getStartDragDistancePx(), but can be modified. 311 int effective_start_drag_distance; 312 313 /// Angle tool helper. If activated, it is included in the dirty rect and drawn automatically. 314 std::unique_ptr<ConstrainAngleToolHelper> angle_helper; 315 /// Snapping tool helper. If a non-null filter is set, it is included in the dirty rect and drawn automatically. 316 std::unique_ptr<SnappingToolHelper> snap_helper; 317 /// An object to exclude from snapping, or nullptr to snap to all objects. 318 Object* snap_exclude_object = nullptr; 319 320 // Key handling 321 322 /// Bitmask containing active modifiers 323 Qt::KeyboardModifiers active_modifiers; 324 325 // Other 326 327 /// The map widget in which the tool was last used 328 MapWidget* cur_map_widget; 329 330 /// Must be set by derived classes if a key button bar is used. 331 /// MapEditorToolBase will take care of including its modifiers into 332 /// active_modifiers and destruct it when the tool is destructed. 333 QPointer<KeyButtonBar> key_button_bar; 334 335 private: 336 // Miscellaneous internals 337 QCursor cursor; 338 bool preview_update_triggered = false; 339 bool dragging = false; 340 bool dragging_canceled = false; 341 std::unique_ptr<MapRenderables> renderables; 342 std::unique_ptr<MapRenderables> old_renderables; 343 std::vector<EditedItem> edited_items; 344 }; 345 346 347 } // namespace OpenOrienteering 348 #endif 349