#include <netedit/GNETagProperties.h>
#include <netedit/GNEViewNet.h>
#include <netedit/GNEViewParent.h>
#include <netedit/changes/GNEChange_Attribute.h>
#include <netedit/frames/common/GNEInspectorFrame.h>
#include <netedit/frames/common/GNESelectorFrame.h>
#include <utils/gui/div/GUIGlobalViewUpdater.h>
#include "GNEApplicationWindow.h"
#include "GNEUndoList.h"
FXDEFMAP(GNEUndoList) GNEUndoListMap[] = {
FXMAPFUNC(SEL_COMMAND, MID_HOTKEY_CTRL_Z_UNDO, GNEUndoList::onCmdUndo),
FXMAPFUNC(SEL_UPDATE, MID_HOTKEY_CTRL_Z_UNDO, GNEUndoList::onUpdUndo),
FXMAPFUNC(SEL_COMMAND, MID_HOTKEY_CTRL_Y_REDO, GNEUndoList::onCmdRedo),
FXMAPFUNC(SEL_UPDATE, MID_HOTKEY_CTRL_Y_REDO, GNEUndoList::onUpdRedo),
};
FXIMPLEMENT_ABSTRACT(GNEUndoList, GNEChangeGroup, GNEUndoListMap, ARRAYNUMBER(GNEUndoListMap))
GNEUndoList::Iterator::~Iterator() {}
bool
GNEUndoList::Iterator::end() const {
return myCurrentChange == nullptr;
}
int
GNEUndoList::Iterator::getIndex() const {
return myIndex;
}
const std::string
GNEUndoList::Iterator::getDescription() const {
std::string redoName = myCurrentChange->redoName();
return redoName;
}
const std::string
GNEUndoList::Iterator::getTimeStamp() const {
return dynamic_cast<GNEChangeGroup*>(myCurrentChange)->getTimeStamp();
}
FXIcon*
GNEUndoList::Iterator::getIcon() const {
const GNEChangeGroup* changeGroup = dynamic_cast<GNEChangeGroup*>(myCurrentChange);
if (changeGroup) {
return GUIIconSubSys::getIcon(changeGroup->getGroupIcon());
} else {
return nullptr;
}
}
GNEUndoList::Iterator&
GNEUndoList::Iterator::operator++(int) {
myCurrentChange = myCurrentChange->next;
myIndex++;
return *this;
}
GNEUndoList::Iterator::Iterator(GNEChange* change) :
myCurrentChange(change),
myIndex(0) {
}
GNEUndoList::Iterator::Iterator() :
myCurrentChange(nullptr),
myIndex(0) {
}
GNEUndoList::UndoIterator::UndoIterator(const GNEUndoList* undoList) :
Iterator(undoList->undoList) {
}
GNEUndoList::RedoIterator::RedoIterator(const GNEUndoList* undoList) :
Iterator(undoList->redoList) {
}
GNEUndoList::GNEUndoList(GNEApplicationWindow* parent) :
myWorking(false),
myGNEApplicationWindowParent(parent) {
}
GNEUndoList::~GNEUndoList() {}
void
GNEUndoList::undo() {
GNEChange* change = nullptr;
if (group) {
throw ProcessError("GNEUndoList::undo() cannot call undo inside begin-end block");
}
if (undoList) {
myWorking = true;
change = undoList;
undoList = undoList->next;
change->undo();
change->next = redoList;
redoList = change;
myWorking = false;
myGNEApplicationWindowParent->getViewNet()->updateViewNet(false);
}
myGNEApplicationWindowParent->updateControls();
}
void
GNEUndoList::redo() {
GNEChange* change = nullptr;
if (group) {
throw ProcessError("GNEUndoList::redo() cannot call undo inside begin-end block");
}
if (redoList) {
myWorking = true;
change = redoList;
redoList = redoList->next;
change->redo();
change->next = undoList;
undoList = change;
myWorking = false;
myGNEApplicationWindowParent->getViewNet()->updateViewNet(false);
}
myGNEApplicationWindowParent->updateControls();
}
std::string
GNEUndoList::undoName() const {
if (undoList) {
return undoList->undoName();
} else {
return "";
}
}
std::string
GNEUndoList::redoName() const {
if (redoList) {
return redoList->redoName();
} else {
return "";
}
}
void
GNEUndoList::begin(GUIIcon icon, const std::string& description) {
if (myGNEApplicationWindowParent->getViewNet()) {
begin(myGNEApplicationWindowParent->getViewNet()->getEditModes().currentSupermode, icon, description);
} else {
begin(Supermode::NETWORK, icon, description);
}
}
void
GNEUndoList::begin(const GNEAttributeCarrier* AC, const std::string& description) {
begin(AC->getTagProperty()->getGUIIcon(), description);
}
void
GNEUndoList::begin(Supermode supermode, GUIIcon icon, const std::string& description) {
myChangeGroups.push(new GNEChangeGroup(supermode, icon, description));
GNEChangeGroup* changeGroup = this;
if (myWorking) {
throw ProcessError("GNEChangeGroup::begin: already working on undo or redo");
}
cut();
while (changeGroup->group) {
changeGroup = changeGroup->group;
}
changeGroup->group = myChangeGroups.top();
gViewUpdater.disableUpdate();
}
void
GNEUndoList::end() {
myChangeGroups.pop();
gViewUpdater.enableUpdate();
myGNEApplicationWindowParent->getViewNet()->updateViewNet(false);
if (myChangeGroups.empty() && myGNEApplicationWindowParent->getViewNet()) {
const auto& editModes = myGNEApplicationWindowParent->getViewNet()->getEditModes();
if ((editModes.isCurrentSupermodeNetwork() && editModes.networkEditMode == NetworkEditMode::NETWORK_INSPECT) ||
(editModes.isCurrentSupermodeDemand() && editModes.demandEditMode == DemandEditMode::DEMAND_INSPECT) ||
(editModes.isCurrentSupermodeData() && editModes.dataEditMode == DataEditMode::DATA_INSPECT)) {
myGNEApplicationWindowParent->getViewNet()->getViewParent()->getInspectorFrame()->refreshInspection();
} else if ((editModes.isCurrentSupermodeNetwork() && editModes.networkEditMode == NetworkEditMode::NETWORK_SELECT) ||
(editModes.isCurrentSupermodeDemand() && editModes.demandEditMode == DemandEditMode::DEMAND_SELECT) ||
(editModes.isCurrentSupermodeData() && editModes.dataEditMode == DataEditMode::DATA_SELECT)) {
myGNEApplicationWindowParent->getViewNet()->getViewParent()->getSelectorFrame()->getSelectionInformation()->updateInformationLabel();
}
}
GNEChangeGroup* change = nullptr;
GNEChangeGroup* changeGroup = this;
if (!changeGroup->group) {
throw ProcessError("GNEChangeGroup::end: no matching call to begin");
}
if (myWorking) {
throw ProcessError("GNEChangeGroup::end: already working on undo or redo");
}
while (changeGroup->group->group) {
changeGroup = changeGroup->group;
}
change = changeGroup->group;
changeGroup->group = nullptr;
if (!change->empty()) {
change->next = changeGroup->undoList;
changeGroup->undoList = change;
} else {
delete change;
}
}
void
GNEUndoList::clear() {
abortAllChangeGroups();
GNEChange* change = nullptr;
while (redoList) {
change = redoList;
redoList = redoList->next;
delete change;
}
while (undoList) {
change = undoList;
undoList = undoList->next;
delete change;
}
delete group;
redoList = nullptr;
undoList = nullptr;
group = nullptr;
}
void
GNEUndoList::abortAllChangeGroups() {
while (hasCommandGroup()) {
myChangeGroups.top()->undo();
myChangeGroups.pop();
abortCurrentSubGroup();
}
}
void
GNEUndoList::abortLastChangeGroup() {
if (myChangeGroups.size() > 0) {
myChangeGroups.top()->undo();
myChangeGroups.pop();
abortCurrentSubGroup();
}
}
void
GNEUndoList::add(GNEChange* change, bool doit, bool merge) {
GNEChangeGroup* changeGroup = this;
if (!change) {
throw ProcessError("GNEChangeGroup::add: nullptr change argument");
}
if (myWorking) {
throw ProcessError("GNEChangeGroup::add: already working on undo or redo");
}
myWorking = true;
cut();
if (doit) {
change->redo();
}
while (changeGroup->group) {
changeGroup = changeGroup->group;
}
if (merge && changeGroup->undoList && (group != nullptr) && change->canMerge() && changeGroup->undoList->mergeWith(change)) {
delete change;
} else {
change->next = changeGroup->undoList;
changeGroup->undoList = change;
}
myWorking = false;
}
int
GNEUndoList::currentCommandGroupSize() const {
if (myChangeGroups.size() > 0) {
return myChangeGroups.top()->size();
} else {
return 0;
}
}
Supermode
GNEUndoList::getUndoSupermode() const {
if (undoList) {
const GNEChangeGroup* begin = dynamic_cast<GNEChangeGroup*>(undoList);
if (begin) {
return begin->getGroupSupermode();
} else {
return undoList->getSupermode();
}
} else {
return Supermode::NETWORK;
}
}
Supermode
GNEUndoList::getRedoSupermode() const {
if (redoList) {
const GNEChangeGroup* begin = dynamic_cast<GNEChangeGroup*>(redoList);
if (begin) {
return begin->getGroupSupermode();
} else {
return redoList->getSupermode();
}
} else {
return Supermode::NETWORK;
}
}
bool
GNEUndoList::hasCommandGroup() const {
return myChangeGroups.size() != 0;
}
bool
GNEUndoList::busy() const {
return myWorking;
}
long
GNEUndoList::onCmdUndo(FXObject*, FXSelector, void*) {
undo();
return 1;
}
long
GNEUndoList::onUpdUndo(FXObject* sender, FXSelector, void*) {
const bool buttonEnabled = canUndo() && !hasCommandGroup() &&
myGNEApplicationWindowParent->isUndoRedoEnabledTemporally().empty() &&
myGNEApplicationWindowParent->isUndoRedoAllowed();
const FXButton* button = dynamic_cast<FXButton*>(sender);
if (button) {
if (buttonEnabled && !button->isEnabled()) {
sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE), nullptr);
button->update();
} else if (!buttonEnabled && button->isEnabled()) {
sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
button->update();
}
} else {
sender->handle(this, buttonEnabled ? FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE) : FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
}
FXMenuCommand* menuCommand = dynamic_cast<FXMenuCommand*>(sender);
if (menuCommand) {
std::string caption = undoName();
if (!myGNEApplicationWindowParent->isUndoRedoAllowed()) {
caption = TL("Disabled undo");
} else if (myGNEApplicationWindowParent->isUndoRedoEnabledTemporally().size() > 0) {
caption = TL("Cannot Undo in the middle of ") + myGNEApplicationWindowParent->isUndoRedoEnabledTemporally();
} else if (hasCommandGroup()) {
caption = TL("Cannot Undo in the middle of ") + myChangeGroups.top()->getDescription();
} else if (!canUndo()) {
caption = TL("Undo");
}
menuCommand->setText(caption.c_str());
menuCommand->update();
}
return 1;
}
long
GNEUndoList::onCmdRedo(FXObject*, FXSelector, void*) {
redo();
return 1;
}
long
GNEUndoList::onUpdRedo(FXObject* sender, FXSelector, void*) {
const bool enable = canRedo() && !hasCommandGroup() &&
myGNEApplicationWindowParent->isUndoRedoEnabledTemporally().empty() &&
myGNEApplicationWindowParent->isUndoRedoAllowed();
const FXButton* button = dynamic_cast<FXButton*>(sender);
if (button) {
if (enable && !button->isEnabled()) {
sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE), nullptr);
button->update();
} else if (!enable && button->isEnabled()) {
sender->handle(this, FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
button->update();
}
} else {
sender->handle(this, enable ? FXSEL(SEL_COMMAND, FXWindow::ID_ENABLE) : FXSEL(SEL_COMMAND, FXWindow::ID_DISABLE), nullptr);
}
FXMenuCommand* menuCommand = dynamic_cast<FXMenuCommand*>(sender);
if (menuCommand) {
std::string caption = redoName();
if (!myGNEApplicationWindowParent->isUndoRedoAllowed()) {
caption = TL("Disabled redo");
} else if (myGNEApplicationWindowParent->isUndoRedoEnabledTemporally().size() > 0) {
caption = TL("Cannot Redo in the middle of ") + myGNEApplicationWindowParent->isUndoRedoEnabledTemporally();
} else if (hasCommandGroup()) {
caption = TL("Cannot Redo in the middle of ") + myChangeGroups.top()->getDescription();
} else if (!canRedo()) {
caption = TL("Redo");
}
menuCommand->setText(caption.c_str());
menuCommand->update();
}
return 1;
}
void
GNEUndoList::cut() {
GNEChange* change = nullptr;
while (redoList) {
change = redoList;
redoList = redoList->next;
delete change;
}
redoList = nullptr;
}
void
GNEUndoList::abortCurrentSubGroup() {
GNEChangeGroup* changeGroup = this;
if (!changeGroup->group) {
throw ProcessError("GNEChangeGroup::abort: no matching call to begin");
}
if (myWorking) {
throw ProcessError("GNEChangeGroup::abort: already working on undo or redo");
}
while (changeGroup->group->group) {
changeGroup = changeGroup->group;
}
delete changeGroup->group;
changeGroup->group = nullptr;
}
bool
GNEUndoList::canUndo() const {
return (undoList != nullptr);
}
bool
GNEUndoList::canRedo() const {
return (redoList != nullptr);
}