Eclipse SUMO - Simulation of Urban MObility
Loading...
Searching...
No Matches
GNEUndoList.cpp
Go to the documentation of this file.
1/****************************************************************************/
2// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3// Copyright (C) 2001-2025 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
22#include <netedit/GNEViewNet.h>
28
30#include "GNEUndoList.h"
31
32// ===========================================================================
33// FOX callback mapping
34// ===========================================================================
35FXDEFMAP(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
46FXIMPLEMENT_ABSTRACT(GNEUndoList, GNEChangeGroup, GNEUndoListMap, ARRAYNUMBER(GNEUndoListMap))
47
48
49// ===========================================================================
50// member method definitions
51// ===========================================================================
52
53// ---------------------------------------------------------------------------
54// GNEUndoList::Iterator
55// ---------------------------------------------------------------------------
56
58
59
60bool
62 return myCurrentChange == nullptr;
63}
64
65
66int
70
71
72const std::string
74 std::string redoName = myCurrentChange->redoName();
75 return redoName;
76}
77
78
79const std::string
83
84
85FXIcon*
87 const GNEChangeGroup* changeGroup = dynamic_cast<GNEChangeGroup*>(myCurrentChange);
88 if (changeGroup) {
89 return GUIIconSubSys::getIcon(changeGroup->getGroupIcon());
90 } else {
91 return nullptr;
92 }
93}
94
95
98 // move current change to next element
100 // update index
101 myIndex++;
102 return *this;
103}
104
105
110
111
116
117
121
122
126
127// ---------------------------------------------------------------------------
128// GNEUndoList
129// ---------------------------------------------------------------------------
130
135
136
138
139
140void
142 GNEChange* change = nullptr;
143 if (group) {
144 throw ProcessError("GNEUndoList::undo() cannot call undo inside begin-end block");
145 }
146 if (undoList) {
147 myWorking = true;
148 change = undoList;
149 // Remove from undoList BEFORE undo
150 undoList = undoList->next;
151 change->undo();
152 // Hang into redoList AFTER undo
153 change->next = redoList;
154 redoList = change;
155 myWorking = false;
156 // update view net (is called only if gViewUpdater.allowUpdate() is enable)
157 myGNEApplicationWindowParent->getViewNet()->updateViewNet(false);
158 }
159 // update specific controls
160 myGNEApplicationWindowParent->updateControls();
161}
162
163
164void
166 GNEChange* change = nullptr;
167 if (group) {
168 throw ProcessError("GNEUndoList::redo() cannot call undo inside begin-end block");
169 }
170 if (redoList) {
171 myWorking = true;
172 change = redoList;
173 // Remove from redoList BEFORE redo
174 redoList = redoList->next;
175 change->redo();
176 // Hang into undoList AFTER redo
177 change->next = undoList;
178 undoList = change;
179 myWorking = false;
180 // update view net (is called only if gViewUpdater.allowUpdate() is enable)
181 myGNEApplicationWindowParent->getViewNet()->updateViewNet(false);
182 }
183 // update specific controls
184 myGNEApplicationWindowParent->updateControls();
185}
186
187
188std::string
190 if (undoList) {
191 return undoList->undoName();
192 } else {
193 return "";
194 }
195}
196
197
198std::string
200 if (redoList) {
201 return redoList->redoName();
202 } else {
203 return "";
204 }
205}
206
207
208void
209GNEUndoList::begin(GUIIcon icon, const std::string& description) {
210 if (myGNEApplicationWindowParent->getViewNet()) {
211 begin(myGNEApplicationWindowParent->getViewNet()->getEditModes().currentSupermode, icon, description);
212 } else {
213 begin(Supermode::NETWORK, icon, description);
214 }
215}
216
217
218void
219GNEUndoList::begin(const GNEAttributeCarrier* AC, const std::string& description) {
220 begin(AC->getTagProperty()->getGUIIcon(), description);
221}
222
223
224void
225GNEUndoList::begin(Supermode supermode, GUIIcon icon, const std::string& description) {
226 myChangeGroups.push(new GNEChangeGroup(supermode, icon, description));
227 // get this reference
228 GNEChangeGroup* changeGroup = this;
229 // Calling begin while in the middle of doing something!
230 if (myWorking) {
231 throw ProcessError("GNEChangeGroup::begin: already working on undo or redo");
232 }
233 // Cut redo list
234 cut();
235 // Hunt for end of group chain
236 while (changeGroup->group) {
237 changeGroup = changeGroup->group;
238 }
239 // Add to end
240 changeGroup->group = myChangeGroups.top();
241 // disable update
242 gViewUpdater.disableUpdate();
243}
244
245
246void
248 myChangeGroups.pop();
249 // enable update
250 gViewUpdater.enableUpdate();
251 // update view without ignoring viewUpdater (used to avoid slowdows during massive edits)
252 myGNEApplicationWindowParent->getViewNet()->updateViewNet(false);
253 // check if net has to be updated (called only if this is the last end
254 if (myChangeGroups.empty() && myGNEApplicationWindowParent->getViewNet()) {
255 // check if we have to update selector frame
256 const auto& editModes = myGNEApplicationWindowParent->getViewNet()->getEditModes();
257 if ((editModes.isCurrentSupermodeNetwork() && editModes.networkEditMode == NetworkEditMode::NETWORK_INSPECT) ||
258 (editModes.isCurrentSupermodeDemand() && editModes.demandEditMode == DemandEditMode::DEMAND_INSPECT) ||
259 (editModes.isCurrentSupermodeData() && editModes.dataEditMode == DataEditMode::DATA_INSPECT)) {
260 // refresh inspect frame
261 myGNEApplicationWindowParent->getViewNet()->getViewParent()->getInspectorFrame()->refreshInspection();
262 } else if ((editModes.isCurrentSupermodeNetwork() && editModes.networkEditMode == NetworkEditMode::NETWORK_SELECT) ||
263 (editModes.isCurrentSupermodeDemand() && editModes.demandEditMode == DemandEditMode::DEMAND_SELECT) ||
264 (editModes.isCurrentSupermodeData() && editModes.dataEditMode == DataEditMode::DATA_SELECT)) {
265 // update informacion label in selection frame
266 myGNEApplicationWindowParent->getViewNet()->getViewParent()->getSelectorFrame()->getSelectionInformation()->updateInformationLabel();
267 }
268 }
269 // continue with end
270 GNEChangeGroup* change = nullptr;
271 GNEChangeGroup* changeGroup = this;
272 // Must have called begin
273 if (!changeGroup->group) {
274 throw ProcessError("GNEChangeGroup::end: no matching call to begin");
275 }
276 // Calling end while in the middle of doing something!
277 if (myWorking) {
278 throw ProcessError("GNEChangeGroup::end: already working on undo or redo");
279 }
280 // Hunt for one above end of group chain
281 while (changeGroup->group->group) {
282 changeGroup = changeGroup->group;
283 }
284 // Unlink from group chain
285 change = changeGroup->group;
286 changeGroup->group = nullptr;
287 // Add to group if non-empty
288 if (!change->empty()) {
289 // Append new change to undo list
290 change->next = changeGroup->undoList;
291 changeGroup->undoList = change;
292 } else {
293 // Delete bottom group
294 delete change;
295 }
296}
297
298
299void
301 // abort all change groups
303 // clear
304 GNEChange* change = nullptr;
305 while (redoList) {
306 change = redoList;
307 redoList = redoList->next;
308 delete change;
309 }
310 while (undoList) {
311 change = undoList;
312 undoList = undoList->next;
313 delete change;
314 }
315 delete group;
316 redoList = nullptr;
317 undoList = nullptr;
318 group = nullptr;
319}
320
321
322void
324 while (hasCommandGroup()) {
325 myChangeGroups.top()->undo();
326 myChangeGroups.pop();
327 // abort current subgroup
329 }
330}
331
332
333void
335 if (myChangeGroups.size() > 0) {
336 myChangeGroups.top()->undo();
337 myChangeGroups.pop();
338 // abort current subgroup
340 }
341}
342
343
344void
345GNEUndoList::add(GNEChange* change, bool doit, bool merge) {
346 GNEChangeGroup* changeGroup = this;
347 // Must pass a change
348 if (!change) {
349 throw ProcessError("GNEChangeGroup::add: nullptr change argument");
350 }
351 // Adding undo while in the middle of doing something!
352 if (myWorking) {
353 throw ProcessError("GNEChangeGroup::add: already working on undo or redo");
354 }
355 myWorking = true;
356 // Cut redo list
357 cut();
358 // Execute change
359 if (doit) {
360 change->redo();
361 }
362 // Hunt for end of group chain
363 while (changeGroup->group) {
364 changeGroup = changeGroup->group;
365 }
366 // Try to merge commands when desired and possible
367 if (merge && changeGroup->undoList && (group != nullptr) && change->canMerge() && changeGroup->undoList->mergeWith(change)) {
368 // Delete incoming change that was merged
369 delete change;
370 } else {
371 // Append incoming change
372 change->next = changeGroup->undoList;
373 changeGroup->undoList = change;
374 }
375 myWorking = false;
376}
377
378
379int
381 if (myChangeGroups.size() > 0) {
382 return myChangeGroups.top()->size();
383 } else {
384 return 0;
385 }
386}
387
388
391 if (undoList) {
392 // try to obtain Change Group
393 const GNEChangeGroup* begin = dynamic_cast<GNEChangeGroup*>(undoList);
394 if (begin) {
395 return begin->getGroupSupermode();
396 } else {
397 return undoList->getSupermode();
398 }
399 } else {
400 return Supermode::NETWORK;
401 }
402}
403
404
407 if (redoList) {
408 // try to obtain Change Group
409 const GNEChangeGroup* begin = dynamic_cast<GNEChangeGroup*>(redoList);
410 if (begin) {
411 return begin->getGroupSupermode();
412 } else {
413 return redoList->getSupermode();
414 }
415 } else {
416 return Supermode::NETWORK;
417 }
418}
419
420
421bool
423 return myChangeGroups.size() != 0;
424}
425
426
427bool
429 return myWorking;
430}
431
432
433long
434GNEUndoList::onCmdUndo(FXObject*, FXSelector, void*) {
435 undo();
436 return 1;
437}
438
439
440long
441GNEUndoList::onUpdUndo(FXObject* sender, FXSelector, void*) {
442 // first check if Undo Menu command or button has to be disabled
443 const bool buttonEnabled = canUndo() && !hasCommandGroup() &&
444 myGNEApplicationWindowParent->isUndoRedoEnabledTemporally().empty() &&
445 myGNEApplicationWindowParent->isUndoRedoAllowed();
446 // cast button (see flickering problem #6209)
447 const FXButton* button = dynamic_cast<FXButton*>(sender);
448 // enable or disable depending of "enable" flag
449 if (button) {
450 // avoid unnecessary enables/disables (due flickering)
451 if (buttonEnabled && !button->isEnabled()) {
452 sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE), nullptr);
453 button->update();
454 } else if (!buttonEnabled && button->isEnabled()) {
455 sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
456 button->update();
457 }
458 } else {
459 sender->handle(this, buttonEnabled ? FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE) : FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
460 }
461 // cast menu command
462 FXMenuCommand* menuCommand = dynamic_cast<FXMenuCommand*>(sender);
463 // only set caption on menu command item
464 if (menuCommand) {
465 // change caption of FXMenuCommand
466 std::string caption = undoName();
467 // set caption of FXmenuCommand edit/undo
468 if (!myGNEApplicationWindowParent->isUndoRedoAllowed()) {
469 caption = TL("Disabled undo");
470 } else if (myGNEApplicationWindowParent->isUndoRedoEnabledTemporally().size() > 0) {
471 caption = TL("Cannot Undo in the middle of ") + myGNEApplicationWindowParent->isUndoRedoEnabledTemporally();
472 } else if (hasCommandGroup()) {
473 caption = TL("Cannot Undo in the middle of ") + myChangeGroups.top()->getDescription();
474 } else if (!canUndo()) {
475 caption = TL("Undo");
476 }
477 menuCommand->setText(caption.c_str());
478 menuCommand->update();
479 }
480 return 1;
481}
482
483
484long
485GNEUndoList::onCmdRedo(FXObject*, FXSelector, void*) {
486 redo();
487 return 1;
488}
489
490
491long
492GNEUndoList::onUpdRedo(FXObject* sender, FXSelector, void*) {
493 // first check if Redo Menu command or button has to be disabled
494 const bool enable = canRedo() && !hasCommandGroup() &&
495 myGNEApplicationWindowParent->isUndoRedoEnabledTemporally().empty() &&
496 myGNEApplicationWindowParent->isUndoRedoAllowed();
497 // cast button (see #6209)
498 const FXButton* button = dynamic_cast<FXButton*>(sender);
499 // enable or disable depending of "enable" flag
500 if (button) {
501 // avoid unnecessary enables/disables (due flickering)
502 if (enable && !button->isEnabled()) {
503 sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE), nullptr);
504 button->update();
505 } else if (!enable && button->isEnabled()) {
506 sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
507 button->update();
508 }
509 } else {
510 sender->handle(this, enable ? FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE) : FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
511 }
512 // cast menu command
513 FXMenuCommand* menuCommand = dynamic_cast<FXMenuCommand*>(sender);
514 // only set caption on menu command item
515 if (menuCommand) {
516 // change caption of FXMenuCommand
517 std::string caption = redoName();
518 // set caption of FXmenuCommand edit/undo
519 if (!myGNEApplicationWindowParent->isUndoRedoAllowed()) {
520 caption = TL("Disabled redo");
521 } else if (myGNEApplicationWindowParent->isUndoRedoEnabledTemporally().size() > 0) {
522 caption = TL("Cannot Redo in the middle of ") + myGNEApplicationWindowParent->isUndoRedoEnabledTemporally();
523 } else if (hasCommandGroup()) {
524 caption = TL("Cannot Redo in the middle of ") + myChangeGroups.top()->getDescription();
525 } else if (!canRedo()) {
526 caption = TL("Redo");
527 }
528 menuCommand->setText(caption.c_str());
529 menuCommand->update();
530 }
531 return 1;
532}
533
534
535void
537 GNEChange* change = nullptr;
538 while (redoList) {
539 change = redoList;
540 redoList = redoList->next;
541 delete change;
542 }
543 redoList = nullptr;
544}
545
546
547void
549 // get reference to change group
550 GNEChangeGroup* changeGroup = this;
551 // Must be called after begin
552 if (!changeGroup->group) {
553 throw ProcessError("GNEChangeGroup::abort: no matching call to begin");
554 }
555 // Calling abort while in the middle of doing something!
556 if (myWorking) {
557 throw ProcessError("GNEChangeGroup::abort: already working on undo or redo");
558 }
559 // Hunt for one above end of group chain
560 while (changeGroup->group->group) {
561 changeGroup = changeGroup->group;
562 }
563 // Delete bottom group
564 delete changeGroup->group;
565 // New end of chain
566 changeGroup->group = nullptr;
567}
568
569
570bool
572 return (undoList != nullptr);
573}
574
575
576bool
578 return (redoList != nullptr);
579}
580
581/******************************/
FXDEFMAP(GNEUndoList) GNEUndoListMap[]
@ DATA_SELECT
mode for selecting data elements
@ DATA_INSPECT
mode for inspecting data elements
Supermode
@brie enum for supermodes
@ NETWORK
Network mode (Edges, junctions, etc..).
@ NETWORK_SELECT
mode for selecting network elements
@ NETWORK_INSPECT
mode for inspecting network elements
@ DEMAND_INSPECT
mode for inspecting demand elements
@ DEMAND_SELECT
mode for selecting demand elements
@ MID_HOTKEY_CTRL_Y_REDO
Undo.
Definition GUIAppEnum.h:133
@ MID_HOTKEY_CTRL_Z_UNDO
Redo.
Definition GUIAppEnum.h:135
GUIViewUpdater gViewUpdater
GUIIcon
An enumeration of icons used by the gui applications.
Definition GUIIcons.h:33
#define TL(string)
Definition MsgHandler.h:304
The main window of Netedit.
const GNETagProperties * getTagProperty() const
get tagProperty associated with this Attribute Carrier
GNEChange * undoList
undo list command (can be access by GNEUndoList)
const std::string & getTimeStamp()
get timeStamp
GNEChangeGroup(Supermode groupSupermode, GUIIcon icon, const std::string &description)
Construct initially empty undo command group.
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.
GNEChangeGroup()
FOX need this.
virtual void redo()=0
redo action/operation
virtual void undo()=0
undo action/operation
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:78
GNEChange(Supermode supermode, bool forward, const bool selectedElement)
Constructor.
Definition GNEChange.cpp:34
friend class GNEUndoList
Definition GNEChange.h:62
GNEChange * next
Definition GNEChange.h:187
bool canMerge() const
Return TRUE if this command can be merged with previous undo commands. This is useful to combine e....
Definition GNEChange.cpp:72
GUIIcon getGUIIcon() const
get GUI icon associated to this tag property
FOX declaration.
Definition GNEUndoList.h:48
const std::string getTimeStamp() const
get timeStamp
Iterator(GNEChange *change)
constructor for GNEUndoList
const std::string getDescription() const
get description
bool end() const
check if iterator is at the end
FXIcon * getIcon() const
get icon
Iterator()
default constructor
Iterator & operator++(int)
increment operator
GNEChange * myCurrentChange
current change
Definition GNEUndoList.h:81
int getIndex() const
get index
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
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.
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
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...
GNEUndoList(GNEApplicationWindow *parent)
constructor
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...
static FXIcon * getIcon(const GUIIcon which)
returns a icon previously defined in the enum GUIIcon