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>
21 #include <netedit/GNEViewParent.h>
27 
28 #include "GNEApplicationWindow.h"
29 #include "GNEUndoList.h"
30 
31 
32 // ===========================================================================
33 // FOX callback mapping
34 // ===========================================================================
35 FXDEFMAP(GNEUndoList) GNEUndoListMap[] = {
36  FXMAPFUNC(SEL_COMMAND, MID_HOTKEY_CTRL_Z_UNDO, GNEUndoList::onCmdUndo),
37  FXMAPFUNC(SEL_UPDATE, MID_HOTKEY_CTRL_Z_UNDO, GNEUndoList::onUpdUndo),
38  FXMAPFUNC(SEL_COMMAND, MID_HOTKEY_CTRL_Y_REDO, GNEUndoList::onCmdRedo),
39  FXMAPFUNC(SEL_UPDATE, MID_HOTKEY_CTRL_Y_REDO, GNEUndoList::onUpdRedo),
40 };
41 
42 // ===========================================================================
43 // FOX-declarations
44 // ===========================================================================
45 
46 FXIMPLEMENT_ABSTRACT(GNEUndoList, GNEChangeGroup, GNEUndoListMap, ARRAYNUMBER(GNEUndoListMap))
47 
48 
49 // ===========================================================================
50 // member method definitions
51 // ===========================================================================
52 
53 // ---------------------------------------------------------------------------
54 // GNEUndoList::Iterator
55 // ---------------------------------------------------------------------------
56 
58  myCurrentChange(undoList->undoList),
59  myIndex(0) {
60 }
61 
62 
64 
65 
66 bool
68  return myCurrentChange == nullptr;
69 }
70 
71 
72 int
74  return myIndex;
75 }
76 
77 
78 const std::string
80  std::string redoName = myCurrentChange->redoName();
81  // remove "redo "
82  if (redoName.size() >= 5) {
83  redoName.erase(0, 5);
84  }
85  return redoName;
86 }
87 
88 
89 FXIcon*
91  const GNEChangeGroup* changeGroup = dynamic_cast<GNEChangeGroup*>(myCurrentChange);
92  if (changeGroup) {
93  return GUIIconSubSys::getIcon(changeGroup->getGroupIcon());
94  } else {
95  return nullptr;
96  }
97 }
98 
99 
102  // move current change to next element
103  myCurrentChange = myCurrentChange->next;
104  // update index
105  myIndex++;
106  return *this;
107 }
108 
109 // ---------------------------------------------------------------------------
110 // GNEUndoList
111 // ---------------------------------------------------------------------------
112 
114  myWorking(false),
116 }
117 
118 
120 
121 
122 void
124  WRITE_DEBUG("Calling GNEUndoList::undo()");
125  GNEChange* change = nullptr;
126  if (group) {
127  throw ProcessError("GNEChangeGroup::undo: cannot call undo inside begin-end block");
128  }
129  if (undoList) {
130  myWorking = true;
131  change = undoList;
132  // Remove from undoList BEFORE undo
134  change->undo();
135  // Hang into redoList AFTER undo
136  change->next = redoList;
137  redoList = change;
138  myWorking = false;
139  }
140  // update specific controls
142 }
143 
144 
145 void
147  WRITE_DEBUG("Calling GNEUndoList::redo()");
148  GNEChange* change = nullptr;
149  if (group) {
150  throw ProcessError("GNEChangeGroup::redo: cannot call undo inside begin-end block");
151  }
152  if (redoList) {
153  myWorking = true;
154  change = redoList;
155  // Remove from redoList BEFORE redo
157  change->redo();
158  // Hang into undoList AFTER redo
159  change->next = undoList;
160  undoList = change;
161  myWorking = false;
162  }
163  // update specific controls
165 }
166 
167 
168 std::string
170  if (undoList) {
171  return undoList->undoName();
172  } else {
173  return "";
174  }
175 }
176 
177 
178 std::string
180  if (redoList) {
181  return redoList->redoName();
182  } else {
183  return "";
184  }
185 }
186 
187 
188 void
189 GNEUndoList::begin(GUIIcon icon, const std::string& description) {
192  } else {
193  begin(Supermode::NETWORK, icon, description);
194  }
195 }
196 
197 
198 void
199 GNEUndoList::begin(Supermode supermode, GUIIcon icon, const std::string& description) {
200  myChangeGroups.push(new GNEChangeGroup(supermode, icon, description));
201  // get this reference
202  GNEChangeGroup* changeGroup = this;
203  // Calling begin while in the middle of doing something!
204  if (myWorking) {
205  throw ProcessError("GNEChangeGroup::begin: already working on undo or redo");
206  }
207  // Cut redo list
208  cut();
209  // Hunt for end of group chain
210  while (changeGroup->group) {
211  changeGroup = changeGroup->group;
212  }
213  // Add to end
214  changeGroup->group = myChangeGroups.top();
215 }
216 
217 
218 void
220  myChangeGroups.pop();
221  // check if net has to be updated
223  // update view
225  // check if we have to update selector frame
226  const auto& editModes = myGNEApplicationWindowParent->getViewNet()->getEditModes();
227  if ((editModes.isCurrentSupermodeNetwork() && editModes.networkEditMode == NetworkEditMode::NETWORK_SELECT) ||
228  (editModes.isCurrentSupermodeDemand() && editModes.demandEditMode == DemandEditMode::DEMAND_SELECT) ||
229  (editModes.isCurrentSupermodeData() && editModes.dataEditMode == DataEditMode::DATA_SELECT)) {
231  }
232  }
233  // continue with end
234  GNEChangeGroup* change = nullptr;
235  GNEChangeGroup* changeGroup = this;
236  // Must have called begin
237  if (!changeGroup->group) {
238  throw ProcessError("GNEChangeGroup::end: no matching call to begin");
239  }
240  // Calling end while in the middle of doing something!
241  if (myWorking) {
242  throw ProcessError("GNEChangeGroup::end: already working on undo or redo");
243  }
244  // Hunt for one above end of group chain
245  while (changeGroup->group->group) {
246  changeGroup = changeGroup->group;
247  }
248  // Unlink from group chain
249  change = changeGroup->group;
250  changeGroup->group = nullptr;
251  // Add to group if non-empty
252  if (!change->empty()) {
253  // Append new change to undo list
254  change->next = changeGroup->undoList;
255  changeGroup->undoList = change;
256  } else {
257  // Delete bottom group
258  delete change;
259  }
260  // check if update undoRedo dialog
263  if (undoRedoDialog->shown()) {
264  undoRedoDialog->updateList();
265  }
266  }
267 }
268 
269 
270 void
272  // disable updating of interval bar (check viewNet due #7252)
275  }
276  // abort all change groups
278  // clear
279  GNEChange* change = nullptr;
280  while (redoList) {
281  change = redoList;
283  delete change;
284  }
285  while (undoList) {
286  change = undoList;
288  delete change;
289  }
290  delete group;
291  redoList = nullptr;
292  undoList = nullptr;
293  group = nullptr;
294  // enable updating of interval bar again (check viewNet due #7252)
297  }
298 }
299 
300 
301 void
303  while (hasCommandGroup()) {
304  myChangeGroups.top()->undo();
305  myChangeGroups.pop();
306  // abort current subgroup
308  }
309 }
310 
311 
312 void
314  if (myChangeGroups.size() > 0) {
315  myChangeGroups.top()->undo();
316  myChangeGroups.pop();
317  // abort current subgroup
319  }
320 }
321 
322 
323 void
324 GNEUndoList::add(GNEChange* change, bool doit, bool merge) {
325  GNEChangeGroup* changeGroup = this;
326  // Must pass a change
327  if (!change) {
328  throw ProcessError("GNEChangeGroup::add: nullptr change argument");
329  }
330  // Adding undo while in the middle of doing something!
331  if (myWorking) {
332  throw ProcessError("GNEChangeGroup::add: already working on undo or redo");
333  }
334  myWorking = true;
335  // Cut redo list
336  cut();
337  // Execute change
338  if (doit) {
339  change->redo();
340  }
341  // Hunt for end of group chain
342  while (changeGroup->group) {
343  changeGroup = changeGroup->group;
344  }
345  // Try to merge commands when desired and possible
346  if (merge && changeGroup->undoList && (group != nullptr) && change->canMerge() && changeGroup->undoList->mergeWith(change)) {
347  // Delete incoming change that was merged
348  delete change;
349  } else {
350  // Append incoming change
351  change->next = changeGroup->undoList;
352  changeGroup->undoList = change;
353  }
354  myWorking = false;
355 }
356 
357 
358 void
360  if (change->trueChange()) {
361  add(change, true);
362  } else {
363  delete change;
364  }
365 }
366 
367 
368 int
370  if (myChangeGroups.size() > 0) {
371  return myChangeGroups.top()->size();
372  } else {
373  return 0;
374  }
375 }
376 
377 
378 Supermode
380  if (undoList) {
381  // try to obtain Change Group
382  const GNEChangeGroup* begin = dynamic_cast<GNEChangeGroup*>(undoList);
383  if (begin) {
384  return begin->getGroupSupermode();
385  } else {
386  return undoList->getSupermode();
387  }
388  } else {
389  return Supermode::NETWORK;
390  }
391 }
392 
393 
394 Supermode
396  if (redoList) {
397  // try to obtain Change Group
398  const GNEChangeGroup* begin = dynamic_cast<GNEChangeGroup*>(redoList);
399  if (begin) {
400  return begin->getGroupSupermode();
401  } else {
402  return redoList->getSupermode();
403  }
404  } else {
405  return Supermode::NETWORK;
406  }
407 }
408 
409 
410 bool
412  return myChangeGroups.size() != 0;
413 }
414 
415 
416 bool
418  return myWorking;
419 }
420 
421 
422 long
423 GNEUndoList::onCmdUndo(FXObject*, FXSelector, void*) {
424  undo();
425  return 1;
426 }
427 
428 
429 long
430 GNEUndoList::onUpdUndo(FXObject* sender, FXSelector, void*) {
431  // first check if Undo Menu command or button has to be disabled
432  const bool enable = canUndo() && !hasCommandGroup() && myGNEApplicationWindowParent->isUndoRedoEnabled().empty();
433  // cast button (see #6209)
434  const FXButton* button = dynamic_cast<FXButton*>(sender);
435  // enable or disable depending of "enable" flag
436  if (button) {
437  // avoid unnnecesary enables/disables (due flickering)
438  if (enable && !button->isEnabled()) {
439  sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE), nullptr);
440  button->update();
441  } else if (!enable && button->isEnabled()) {
442  sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
443  button->update();
444  }
445  } else {
446  sender->handle(this, enable ? FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE) : FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
447  }
448  // cast menu command
449  FXMenuCommand* menuCommand = dynamic_cast<FXMenuCommand*>(sender);
450  // only set caption on menu command item
451  if (menuCommand) {
452  // change caption of FXMenuCommand
453  std::string caption = undoName();
454  // set caption of FXmenuCommand edit/undo
456  caption = "Cannot Undo in the middle of " + myGNEApplicationWindowParent->isUndoRedoEnabled();
457  } else if (hasCommandGroup()) {
458  caption = "Cannot Undo in the middle of " + myChangeGroups.top()->getDescription();
459  } else if (!canUndo()) {
460  caption = "Undo";
461  }
462  menuCommand->setText(caption.c_str());
463  menuCommand->update();
464  }
465  return 1;
466 }
467 
468 
469 long
470 GNEUndoList::onCmdRedo(FXObject*, FXSelector, void*) {
471  redo();
472  return 1;
473 }
474 
475 
476 long
477 GNEUndoList::onUpdRedo(FXObject* sender, FXSelector, void*) {
478  // first check if Redo Menu command or button has to be disabled
479  const bool enable = canRedo() && !hasCommandGroup() && myGNEApplicationWindowParent->isUndoRedoEnabled().empty();
480  // cast button (see #6209)
481  const FXButton* button = dynamic_cast<FXButton*>(sender);
482  // enable or disable depending of "enable" flag
483  if (button) {
484  // avoid unnnecesary enables/disables (due flickering)
485  if (enable && !button->isEnabled()) {
486  sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE), nullptr);
487  button->update();
488  } else if (!enable && button->isEnabled()) {
489  sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
490  button->update();
491  }
492  } else {
493  sender->handle(this, enable ? FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE) : FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
494  }
495  // cast menu command
496  FXMenuCommand* menuCommand = dynamic_cast<FXMenuCommand*>(sender);
497  // only set caption on menu command item
498  if (menuCommand) {
499  // change caption of FXMenuCommand
500  std::string caption = redoName();
501  // set caption of FXmenuCommand edit/undo
503  caption = "Cannot Redo in the middle of " + myGNEApplicationWindowParent->isUndoRedoEnabled();
504  } else if (hasCommandGroup()) {
505  caption = "Cannot Redo in the middle of " + myChangeGroups.top()->getDescription();
506  } else if (!canRedo()) {
507  caption = "Redo";
508  }
509  menuCommand->setText(caption.c_str());
510  menuCommand->update();
511  }
512  return 1;
513 }
514 
515 
516 void
518  GNEChange* change = nullptr;
519  while (redoList) {
520  change = redoList;
522  delete change;
523  }
524  redoList = nullptr;
525 }
526 
527 
528 void
530  // get reference to change group
531  GNEChangeGroup* changeGroup = this;
532  // Must be called after begin
533  if (!changeGroup->group) {
534  throw ProcessError("GNEChangeGroup::abort: no matching call to begin");
535  }
536  // Calling abort while in the middle of doing something!
537  if (myWorking) {
538  throw ProcessError("GNEChangeGroup::abort: already working on undo or redo");
539  }
540  // Hunt for one above end of group chain
541  while (changeGroup->group->group) {
542  changeGroup = changeGroup->group;
543  }
544  // Delete bottom group
545  delete changeGroup->group;
546  // New end of chain
547  changeGroup->group = nullptr;
548 }
549 
550 
551 bool
553  return (undoList != nullptr);
554 }
555 
556 
557 bool
559  return (redoList != nullptr);
560 }
561 
562 /******************************/
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:117
@ MID_HOTKEY_CTRL_Z_UNDO
Redo.
Definition: GUIAppEnum.h:119
GUIIcon
An enumeration of icons used by the gui applications.
Definition: GUIIcons.h:33
#define WRITE_DEBUG(msg)
Definition: MsgHandler.h:290
The main window of the Netedit.
void updateControls()
update control contents after undo/redo or recompute
GNEViewNet * getViewNet()
get pointer to viewNet
GNEUndoListDialog * getUndoListDialog()
get pointer to undoList dialog
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:64
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:257
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
getmodul for selection information
FOX declaration.
Definition: GNEUndoList.h:48
const std::string getDescription() const
get description
Definition: GNEUndoList.cpp:79
bool end() const
check if iterator is at the end
Definition: GNEUndoList.cpp:67
FXIcon * getIcon() const
get icon
Definition: GNEUndoList.cpp:90
Iterator & operator++(int)
increment operator
int getIndex() const
get index
Definition: GNEUndoList.cpp:73
void updateList()
update data table
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:209
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:203
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:206
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
void enableIntervalBarUpdate()
enable interval bar update
void disableIntervalBarUpdate()
enable interval bar update
const GNEViewNetHelper::EditModes & getEditModes() const
get edit modes
Definition: GNEViewNet.cpp:513
GNEViewNetHelper::IntervalBar & getIntervalBar()
get interval bar
GNEViewParent * getViewParent() const
get the net object
void updateViewNet() const
Mark the entire GNEViewNet to be repainted later.
Definition: GNEViewNet.cpp:372
GNESelectorFrame * getSelectorFrame() const
get frame for select elements
GNEApplicationWindow * getGNEAppWindows() const
get GNE Application Windows
static FXIcon * getIcon(const GUIIcon which)
returns a icon previously defined in the enum GUIIcon
Supermode currentSupermode
the current supermode