Eclipse SUMO - Simulation of Urban MObility
GNEUndoList.cpp
Go to the documentation of this file.
1/****************************************************************************/
2// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
3// Copyright (C) 2001-2022 German Aerospace Center (DLR) and others.
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// https://www.eclipse.org/legal/epl-2.0/
7// This Source Code may also be made available under the following Secondary
8// Licenses when the conditions for such availability set forth in the Eclipse
9// Public License 2.0 are satisfied: GNU General Public License, version 2
10// or later which is available at
11// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13/****************************************************************************/
19/****************************************************************************/
20#include <netedit/GNEViewNet.h>
24
26#include "GNEUndoList.h"
27
28
29// ===========================================================================
30// FOX callback mapping
31// ===========================================================================
32FXDEFMAP(GNEUndoList) GNEUndoListMap[] = {
33 FXMAPFUNC(SEL_COMMAND, MID_HOTKEY_CTRL_Z_UNDO, GNEUndoList::onCmdUndo),
34 FXMAPFUNC(SEL_UPDATE, MID_HOTKEY_CTRL_Z_UNDO, GNEUndoList::onUpdUndo),
35 FXMAPFUNC(SEL_COMMAND, MID_HOTKEY_CTRL_Y_REDO, GNEUndoList::onCmdRedo),
36 FXMAPFUNC(SEL_UPDATE, MID_HOTKEY_CTRL_Y_REDO, GNEUndoList::onUpdRedo),
37};
38
39// ===========================================================================
40// FOX-declarations
41// ===========================================================================
42
43FXIMPLEMENT_ABSTRACT(GNEUndoList, GNEChangeGroup, GNEUndoListMap, ARRAYNUMBER(GNEUndoListMap))
44
45
46// ===========================================================================
47// member method definitions
48// ===========================================================================
49
50// ---------------------------------------------------------------------------
51// GNEUndoList::Iterator
52// ---------------------------------------------------------------------------
53
55
56
57bool
59 return myCurrentChange == nullptr;
60}
61
62
63int
65 return myIndex;
66}
67
68
69const std::string
71 std::string redoName = myCurrentChange->redoName();
72 // remove "redo "
73 if (redoName.size() >= 5) {
74 redoName.erase(0, 5);
75 }
76 return redoName;
77}
78
79
80FXIcon*
82 const GNEChangeGroup* changeGroup = dynamic_cast<GNEChangeGroup*>(myCurrentChange);
83 if (changeGroup) {
84 return GUIIconSubSys::getIcon(changeGroup->getGroupIcon());
85 } else {
86 return nullptr;
87 }
88}
89
90
93 // move current change to next element
94 myCurrentChange = myCurrentChange->next;
95 // update index
96 myIndex++;
97 return *this;
98}
99
100
102 myCurrentChange(change),
103 myIndex(0) {
104}
105
106
108 myCurrentChange(nullptr),
109 myIndex(0) {
110}
111
112
115}
116
117
120}
121
122// ---------------------------------------------------------------------------
123// GNEUndoList
124// ---------------------------------------------------------------------------
125
127 myWorking(false),
129}
130
131
133
134
135void
137 WRITE_DEBUG("Calling GNEUndoList::undo()");
138 GNEChange* change = nullptr;
139 if (group) {
140 throw ProcessError("GNEUndoList::undo() cannot call undo inside begin-end block");
141 }
142 if (undoList) {
143 myWorking = true;
144 change = undoList;
145 // Remove from undoList BEFORE undo
147 change->undo();
148 // Hang into redoList AFTER undo
149 change->next = redoList;
150 redoList = change;
151 myWorking = false;
152 }
153 // update specific controls
155}
156
157
158void
160 WRITE_DEBUG("Calling GNEUndoList::redo()");
161 GNEChange* change = nullptr;
162 if (group) {
163 throw ProcessError("GNEUndoList::redo() cannot call undo inside begin-end block");
164 }
165 if (redoList) {
166 myWorking = true;
167 change = redoList;
168 // Remove from redoList BEFORE redo
170 change->redo();
171 // Hang into undoList AFTER redo
172 change->next = undoList;
173 undoList = change;
174 myWorking = false;
175 }
176 // update specific controls
178}
179
180
181std::string
183 if (undoList) {
184 return undoList->undoName();
185 } else {
186 return "";
187 }
188}
189
190
191std::string
193 if (redoList) {
194 return redoList->redoName();
195 } else {
196 return "";
197 }
198}
199
200
201void
202GNEUndoList::begin(GUIIcon icon, const std::string& description) {
205 } else {
206 begin(Supermode::NETWORK, icon, description);
207 }
208}
209
210
211void
212GNEUndoList::begin(Supermode supermode, GUIIcon icon, const std::string& description) {
213 myChangeGroups.push(new GNEChangeGroup(supermode, icon, description));
214 // get this reference
215 GNEChangeGroup* changeGroup = this;
216 // Calling begin while in the middle of doing something!
217 if (myWorking) {
218 throw ProcessError("GNEChangeGroup::begin: already working on undo or redo");
219 }
220 // Cut redo list
221 cut();
222 // Hunt for end of group chain
223 while (changeGroup->group) {
224 changeGroup = changeGroup->group;
225 }
226 // Add to end
227 changeGroup->group = myChangeGroups.top();
228}
229
230
231void
233 myChangeGroups.pop();
234 // check if net has to be updated
236 // update view
238 // check if we have to update selector frame
239 const auto& editModes = myGNEApplicationWindowParent->getViewNet()->getEditModes();
240 if ((editModes.isCurrentSupermodeNetwork() && editModes.networkEditMode == NetworkEditMode::NETWORK_SELECT) ||
241 (editModes.isCurrentSupermodeDemand() && editModes.demandEditMode == DemandEditMode::DEMAND_SELECT) ||
242 (editModes.isCurrentSupermodeData() && editModes.dataEditMode == DataEditMode::DATA_SELECT)) {
244 }
245 }
246 // continue with end
247 GNEChangeGroup* change = nullptr;
248 GNEChangeGroup* changeGroup = this;
249 // Must have called begin
250 if (!changeGroup->group) {
251 throw ProcessError("GNEChangeGroup::end: no matching call to begin");
252 }
253 // Calling end while in the middle of doing something!
254 if (myWorking) {
255 throw ProcessError("GNEChangeGroup::end: already working on undo or redo");
256 }
257 // Hunt for one above end of group chain
258 while (changeGroup->group->group) {
259 changeGroup = changeGroup->group;
260 }
261 // Unlink from group chain
262 change = changeGroup->group;
263 changeGroup->group = nullptr;
264 // Add to group if non-empty
265 if (!change->empty()) {
266 // Append new change to undo list
267 change->next = changeGroup->undoList;
268 changeGroup->undoList = change;
269 } else {
270 // Delete bottom group
271 delete change;
272 }
273}
274
275
276void
278 // abort all change groups
280 // clear
281 GNEChange* change = nullptr;
282 while (redoList) {
283 change = redoList;
285 delete change;
286 }
287 while (undoList) {
288 change = undoList;
290 delete change;
291 }
292 delete group;
293 redoList = nullptr;
294 undoList = nullptr;
295 group = nullptr;
296}
297
298
299void
301 while (hasCommandGroup()) {
302 myChangeGroups.top()->undo();
303 myChangeGroups.pop();
304 // abort current subgroup
306 }
307}
308
309
310void
312 if (myChangeGroups.size() > 0) {
313 myChangeGroups.top()->undo();
314 myChangeGroups.pop();
315 // abort current subgroup
317 }
318}
319
320
321void
322GNEUndoList::add(GNEChange* change, bool doit, bool merge) {
323 GNEChangeGroup* changeGroup = this;
324 // Must pass a change
325 if (!change) {
326 throw ProcessError("GNEChangeGroup::add: nullptr change argument");
327 }
328 // Adding undo while in the middle of doing something!
329 if (myWorking) {
330 throw ProcessError("GNEChangeGroup::add: already working on undo or redo");
331 }
332 myWorking = true;
333 // Cut redo list
334 cut();
335 // Execute change
336 if (doit) {
337 change->redo();
338 }
339 // Hunt for end of group chain
340 while (changeGroup->group) {
341 changeGroup = changeGroup->group;
342 }
343 // Try to merge commands when desired and possible
344 if (merge && changeGroup->undoList && (group != nullptr) && change->canMerge() && changeGroup->undoList->mergeWith(change)) {
345 // Delete incoming change that was merged
346 delete change;
347 } else {
348 // Append incoming change
349 change->next = changeGroup->undoList;
350 changeGroup->undoList = change;
351 }
352 myWorking = false;
353}
354
355
356void
358 if (change->trueChange()) {
359 add(change, true);
360 } else {
361 delete change;
362 }
363}
364
365
366int
368 if (myChangeGroups.size() > 0) {
369 return myChangeGroups.top()->size();
370 } else {
371 return 0;
372 }
373}
374
375
378 if (undoList) {
379 // try to obtain Change Group
380 const GNEChangeGroup* begin = dynamic_cast<GNEChangeGroup*>(undoList);
381 if (begin) {
382 return begin->getGroupSupermode();
383 } else {
384 return undoList->getSupermode();
385 }
386 } else {
387 return Supermode::NETWORK;
388 }
389}
390
391
394 if (redoList) {
395 // try to obtain Change Group
396 const GNEChangeGroup* begin = dynamic_cast<GNEChangeGroup*>(redoList);
397 if (begin) {
398 return begin->getGroupSupermode();
399 } else {
400 return redoList->getSupermode();
401 }
402 } else {
403 return Supermode::NETWORK;
404 }
405}
406
407
408bool
410 return myChangeGroups.size() != 0;
411}
412
413
414bool
416 return myWorking;
417}
418
419
420long
421GNEUndoList::onCmdUndo(FXObject*, FXSelector, void*) {
422 undo();
423 return 1;
424}
425
426
427long
428GNEUndoList::onUpdUndo(FXObject* sender, FXSelector, void*) {
429 // first check if Undo Menu command or button has to be disabled
430 const bool enable = canUndo() && !hasCommandGroup() && myGNEApplicationWindowParent->isUndoRedoEnabled().empty();
431 // cast button (see #6209)
432 const FXButton* button = dynamic_cast<FXButton*>(sender);
433 // enable or disable depending of "enable" flag
434 if (button) {
435 // avoid unnecessary enables/disables (due flickering)
436 if (enable && !button->isEnabled()) {
437 sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE), nullptr);
438 button->update();
439 } else if (!enable && button->isEnabled()) {
440 sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
441 button->update();
442 }
443 } else {
444 sender->handle(this, enable ? FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE) : FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
445 }
446 // cast menu command
447 FXMenuCommand* menuCommand = dynamic_cast<FXMenuCommand*>(sender);
448 // only set caption on menu command item
449 if (menuCommand) {
450 // change caption of FXMenuCommand
451 std::string caption = undoName();
452 // set caption of FXmenuCommand edit/undo
454 caption = "Cannot Undo in the middle of " + myGNEApplicationWindowParent->isUndoRedoEnabled();
455 } else if (hasCommandGroup()) {
456 caption = "Cannot Undo in the middle of " + myChangeGroups.top()->getDescription();
457 } else if (!canUndo()) {
458 caption = "Undo";
459 }
460 menuCommand->setText(caption.c_str());
461 menuCommand->update();
462 }
463 return 1;
464}
465
466
467long
468GNEUndoList::onCmdRedo(FXObject*, FXSelector, void*) {
469 redo();
470 return 1;
471}
472
473
474long
475GNEUndoList::onUpdRedo(FXObject* sender, FXSelector, void*) {
476 // first check if Redo Menu command or button has to be disabled
477 const bool enable = canRedo() && !hasCommandGroup() && myGNEApplicationWindowParent->isUndoRedoEnabled().empty();
478 // cast button (see #6209)
479 const FXButton* button = dynamic_cast<FXButton*>(sender);
480 // enable or disable depending of "enable" flag
481 if (button) {
482 // avoid unnecessary enables/disables (due flickering)
483 if (enable && !button->isEnabled()) {
484 sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE), nullptr);
485 button->update();
486 } else if (!enable && button->isEnabled()) {
487 sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
488 button->update();
489 }
490 } else {
491 sender->handle(this, enable ? FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE) : FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
492 }
493 // cast menu command
494 FXMenuCommand* menuCommand = dynamic_cast<FXMenuCommand*>(sender);
495 // only set caption on menu command item
496 if (menuCommand) {
497 // change caption of FXMenuCommand
498 std::string caption = redoName();
499 // set caption of FXmenuCommand edit/undo
501 caption = "Cannot Redo in the middle of " + myGNEApplicationWindowParent->isUndoRedoEnabled();
502 } else if (hasCommandGroup()) {
503 caption = "Cannot Redo in the middle of " + myChangeGroups.top()->getDescription();
504 } else if (!canRedo()) {
505 caption = "Redo";
506 }
507 menuCommand->setText(caption.c_str());
508 menuCommand->update();
509 }
510 return 1;
511}
512
513
514void
516 GNEChange* change = nullptr;
517 while (redoList) {
518 change = redoList;
520 delete change;
521 }
522 redoList = nullptr;
523}
524
525
526void
528 // get reference to change group
529 GNEChangeGroup* changeGroup = this;
530 // Must be called after begin
531 if (!changeGroup->group) {
532 throw ProcessError("GNEChangeGroup::abort: no matching call to begin");
533 }
534 // Calling abort while in the middle of doing something!
535 if (myWorking) {
536 throw ProcessError("GNEChangeGroup::abort: already working on undo or redo");
537 }
538 // Hunt for one above end of group chain
539 while (changeGroup->group->group) {
540 changeGroup = changeGroup->group;
541 }
542 // Delete bottom group
543 delete changeGroup->group;
544 // New end of chain
545 changeGroup->group = nullptr;
546}
547
548
549bool
551 return (undoList != nullptr);
552}
553
554
555bool
557 return (redoList != nullptr);
558}
559
560/******************************/
FXDEFMAP(GNEUndoList) GNEUndoListMap[]
@ DATA_SELECT
mode for selecting data elements
Supermode
@brie enum for supermodes
@ NETWORK
Network mode (Edges, junctions, etc..)
@ NETWORK_SELECT
mode for selecting network elements
@ DEMAND_SELECT
mode for selecting demand elements
@ MID_HOTKEY_CTRL_Y_REDO
Undo.
Definition: GUIAppEnum.h:121
@ MID_HOTKEY_CTRL_Z_UNDO
Redo.
Definition: GUIAppEnum.h:123
GUIIcon
An enumeration of icons used by the gui applications.
Definition: GUIIcons.h:33
#define WRITE_DEBUG(msg)
Definition: MsgHandler.h:276
The main window of the Netedit.
void updateControls()
update control contents after undo/redo or recompute
GNEViewNet * getViewNet()
get pointer to viewNet
const std::string & isUndoRedoEnabled() const
check if undo-redo is enabled
the function-object for an editing operation (abstract base)
bool trueChange()
wether original and new value differ
GNEChange * undoList
undo list command (can be access by GNEUndoList)
GUIIcon getGroupIcon() const
get icon associated with this ChangeGroup
GNEChange * redoList
redo list command (can be access by GNEUndoList)
GNEChangeGroup * group
group (can be access by GNEUndoList)
bool empty() const
Return TRUE if empty.
friend class GNEUndoList
GNEChangeGroup()
FOX need this.
the function-object for an editing operation (abstract base)
Definition: GNEChange.h:60
virtual void redo()=0
redo action/operation
virtual void undo()=0
undo action/operation
Supermode getSupermode() const
get supermode
Definition: GNEChange.cpp:68
bool mergeWith(GNEChange *command)
Called by the undo system to try and merge the new incoming command with this command; should return ...
Definition: GNEChange.cpp:80
virtual std::string redoName() const =0
return rendoName
GNEChange * next
Definition: GNEChange.h:229
virtual std::string undoName() const =0
return undoName
bool canMerge() const
Return TRUE if this command can be merged with previous undo commands. This is useful to combine e....
Definition: GNEChange.cpp:74
void updateInformationLabel()
update information label
SelectionInformation * getSelectionInformation() const
get modul for selection information
FOX declaration.
Definition: GNEUndoList.h:48
const std::string getDescription() const
get description
Definition: GNEUndoList.cpp:70
bool end() const
check if iterator is at the end
Definition: GNEUndoList.cpp:58
FXIcon * getIcon() const
get icon
Definition: GNEUndoList.cpp:81
Iterator()
default constructor
Iterator & operator++(int)
increment operator
Definition: GNEUndoList.cpp:92
GNEChange * myCurrentChange
current change
Definition: GNEUndoList.h:78
int getIndex() const
get index
Definition: GNEUndoList.cpp:64
RedoIterator(const GNEUndoList *undoList)
constructor for GNEUndoList
UndoIterator(const GNEUndoList *undoList)
constructor for GNEUndoList
void abortCurrentSubGroup()
Abort the current command sub-group being compiled. All commands already added to the sub-groups undo...
void end()
End undo command sub-group. If the sub-group is still empty, it will be deleted; otherwise,...
bool hasCommandGroup() const
Check if undoList has command group.
void undo()
undo the last command group
long onUpdUndo(FXObject *, FXSelector, void *)
event after Undo
void begin(GUIIcon icon, const std::string &description)
Begin undo command sub-group with current supermode. This begins a new group of commands that are tre...
std::string undoName() const
Return name of the first undo command available; if no undo command available this will return the em...
~GNEUndoList()
destructor
GNEApplicationWindow *const myGNEApplicationWindowParent
Definition: GNEUndoList.h:228
void abortAllChangeGroups()
reverts and discards ALL active chained change groups
Supermode getRedoSupermode() const
get redo supermode
long onUpdRedo(FXObject *, FXSelector, void *)
event after Redo
bool myWorking
Currently busy with undo or redo.
Definition: GNEUndoList.h:222
void cut()
Cut the redo list. This is automatically invoked when a new undo command is added.
bool canRedo() const
Can we redo more commands.
Supermode getUndoSupermode() const
get undo supermode
bool canUndo() const
Can we undo more commands.
std::stack< GNEChangeGroup * > myChangeGroups
Definition: GNEUndoList.h:225
void redo()
redo the last command group
long onCmdUndo(FXObject *, FXSelector, void *)
int currentCommandGroupSize() const
get size of current CommandGroup
void abortLastChangeGroup()
reverts last active chained change group
long onCmdRedo(FXObject *, FXSelector, void *)
redo change
bool busy() const
Return TRUE if currently inside undo or redo operation; this is useful to avoid generating another un...
std::string redoName() const
Return name of the first redo command available; if no Redo command available this will return the em...
void add(GNEChange *command, bool doit=false, bool merge=true)
Add new command, executing it if desired. The new command will be merged with the previous command if...
void changeAttribute(GNEChange_Attribute *change)
special method for change attributes, avoid empty changes, always execute
const GNEViewNetHelper::EditModes & getEditModes() const
get edit modes
Definition: GNEViewNet.cpp:632
GNEViewParent * getViewParent() const
get the net object
void updateViewNet() const
Mark the entire GNEViewNet to be repainted later.
Definition: GNEViewNet.cpp:414
GNESelectorFrame * getSelectorFrame() const
get frame for select elements
static FXIcon * getIcon(const GUIIcon which)
returns a icon previously defined in the enum GUIIcon
Supermode currentSupermode
the current supermode