Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/netedit/GNEUndoList.cpp
169666 views
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
/****************************************************************************/
14
/// @file GNEUndoList.cpp
15
/// @author Jakob Erdmann
16
/// @author Pablo Alvarez Lopez
17
/// @date Mar 2011
18
///
19
/****************************************************************************/
20
21
#include <netedit/GNETagProperties.h>
22
#include <netedit/GNEViewNet.h>
23
#include <netedit/GNEViewParent.h>
24
#include <netedit/changes/GNEChange_Attribute.h>
25
#include <netedit/frames/common/GNEInspectorFrame.h>
26
#include <netedit/frames/common/GNESelectorFrame.h>
27
#include <utils/gui/div/GUIGlobalViewUpdater.h>
28
29
#include "GNEApplicationWindow.h"
30
#include "GNEUndoList.h"
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
57
GNEUndoList::Iterator::~Iterator() {}
58
59
60
bool
61
GNEUndoList::Iterator::end() const {
62
return myCurrentChange == nullptr;
63
}
64
65
66
int
67
GNEUndoList::Iterator::getIndex() const {
68
return myIndex;
69
}
70
71
72
const std::string
73
GNEUndoList::Iterator::getDescription() const {
74
std::string redoName = myCurrentChange->redoName();
75
return redoName;
76
}
77
78
79
const std::string
80
GNEUndoList::Iterator::getTimeStamp() const {
81
return dynamic_cast<GNEChangeGroup*>(myCurrentChange)->getTimeStamp();
82
}
83
84
85
FXIcon*
86
GNEUndoList::Iterator::getIcon() const {
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
96
GNEUndoList::Iterator&
97
GNEUndoList::Iterator::operator++(int) {
98
// move current change to next element
99
myCurrentChange = myCurrentChange->next;
100
// update index
101
myIndex++;
102
return *this;
103
}
104
105
106
GNEUndoList::Iterator::Iterator(GNEChange* change) :
107
myCurrentChange(change),
108
myIndex(0) {
109
}
110
111
112
GNEUndoList::Iterator::Iterator() :
113
myCurrentChange(nullptr),
114
myIndex(0) {
115
}
116
117
118
GNEUndoList::UndoIterator::UndoIterator(const GNEUndoList* undoList) :
119
Iterator(undoList->undoList) {
120
}
121
122
123
GNEUndoList::RedoIterator::RedoIterator(const GNEUndoList* undoList) :
124
Iterator(undoList->redoList) {
125
}
126
127
// ---------------------------------------------------------------------------
128
// GNEUndoList
129
// ---------------------------------------------------------------------------
130
131
GNEUndoList::GNEUndoList(GNEApplicationWindow* parent) :
132
myWorking(false),
133
myGNEApplicationWindowParent(parent) {
134
}
135
136
137
GNEUndoList::~GNEUndoList() {}
138
139
140
void
141
GNEUndoList::undo() {
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
164
void
165
GNEUndoList::redo() {
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
188
std::string
189
GNEUndoList::undoName() const {
190
if (undoList) {
191
return undoList->undoName();
192
} else {
193
return "";
194
}
195
}
196
197
198
std::string
199
GNEUndoList::redoName() const {
200
if (redoList) {
201
return redoList->redoName();
202
} else {
203
return "";
204
}
205
}
206
207
208
void
209
GNEUndoList::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
218
void
219
GNEUndoList::begin(const GNEAttributeCarrier* AC, const std::string& description) {
220
begin(AC->getTagProperty()->getGUIIcon(), description);
221
}
222
223
224
void
225
GNEUndoList::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
246
void
247
GNEUndoList::end() {
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
299
void
300
GNEUndoList::clear() {
301
// abort all change groups
302
abortAllChangeGroups();
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
322
void
323
GNEUndoList::abortAllChangeGroups() {
324
while (hasCommandGroup()) {
325
myChangeGroups.top()->undo();
326
myChangeGroups.pop();
327
// abort current subgroup
328
abortCurrentSubGroup();
329
}
330
}
331
332
333
void
334
GNEUndoList::abortLastChangeGroup() {
335
if (myChangeGroups.size() > 0) {
336
myChangeGroups.top()->undo();
337
myChangeGroups.pop();
338
// abort current subgroup
339
abortCurrentSubGroup();
340
}
341
}
342
343
344
void
345
GNEUndoList::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
379
int
380
GNEUndoList::currentCommandGroupSize() const {
381
if (myChangeGroups.size() > 0) {
382
return myChangeGroups.top()->size();
383
} else {
384
return 0;
385
}
386
}
387
388
389
Supermode
390
GNEUndoList::getUndoSupermode() const {
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
405
Supermode
406
GNEUndoList::getRedoSupermode() const {
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
421
bool
422
GNEUndoList::hasCommandGroup() const {
423
return myChangeGroups.size() != 0;
424
}
425
426
427
bool
428
GNEUndoList::busy() const {
429
return myWorking;
430
}
431
432
433
long
434
GNEUndoList::onCmdUndo(FXObject*, FXSelector, void*) {
435
undo();
436
return 1;
437
}
438
439
440
long
441
GNEUndoList::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
484
long
485
GNEUndoList::onCmdRedo(FXObject*, FXSelector, void*) {
486
redo();
487
return 1;
488
}
489
490
491
long
492
GNEUndoList::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
535
void
536
GNEUndoList::cut() {
537
GNEChange* change = nullptr;
538
while (redoList) {
539
change = redoList;
540
redoList = redoList->next;
541
delete change;
542
}
543
redoList = nullptr;
544
}
545
546
547
void
548
GNEUndoList::abortCurrentSubGroup() {
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
570
bool
571
GNEUndoList::canUndo() const {
572
return (undoList != nullptr);
573
}
574
575
576
bool
577
GNEUndoList::canRedo() const {
578
return (redoList != nullptr);
579
}
580
581
/******************************/
582
583