Skip to content

Commit

Permalink
Add quick and dirty undo/redo actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Lecrapouille committed Dec 17, 2023
1 parent ff8ca9b commit 0d27b56
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ endif
# Make the list of compiled files for the application
#
LIB_OBJS += Path.o Howard.o Utils.o TimedTokens.o Receptivities.o
LIB_OBJS += PetriNet.o Algorithms.o Simulation.o
LIB_OBJS += PetriNet.o Algorithms.o Simulation.o History.o
LIB_OBJS += ImportJSON.o ExportJSON.o ExportSymfony.o ExportPnEditor.o
LIB_OBJS += ExportPetriLaTeX.o ExportJulia.o ExportGraphviz.o ExportDrawIO.o
LIB_OBJS += ExportGrafcetCpp.o
Expand Down
4 changes: 4 additions & 0 deletions src/Editor/DearImGui/KeyBindings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
# define KEY_BINDINGS_HPP

// -----------------------------------------------------------------------------
// FIXME: The backend raylib does not support other than US keyboard meaning that
// other keyboard mapping are fucked up.
# define KEY_QUIT_APPLICATION ImGuiKey_Escape
# define KEY_RUN_SIMULATION ImGuiKey_Space
# define KEY_RUN_SIMULATION_ALT ImGuiKey_R
Expand All @@ -30,5 +32,7 @@
# define KEY_MOVE_PETRI_NODE ImGuiKey_Semicolon
# define KEY_INCREMENT_TOKENS ImGuiKey_KeypadAdd
# define KEY_DECREMENT_TOKENS ImGuiKey_KeypadSubtract
# define KEY_UNDO ImGuiKey_W // remapping for Z
# define KEY_REDO ImGuiKey_Y

#endif
81 changes: 80 additions & 1 deletion src/Editor/PetriEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,19 @@ void Editor::menu()
switchOfNet(TypeOfNet(current_type));
ImGui::EndMenu();
}

ImGui::Separator();
if (ImGui::MenuItem("Undo", "Ctrl+Z", false))
{
undo();
}

if (ImGui::MenuItem("Redo", "Ctrl+Y", false))
{
redo();
}

ImGui::Separator();
if (ImGui::MenuItem("Clear net", nullptr, false))
{
clearNet();
Expand Down Expand Up @@ -960,6 +971,34 @@ void Editor::clearLogs()
m_messages.clear();
}

//--------------------------------------------------------------------------
void Editor::undo()
{
if (!m_history.undo())
{
m_messages.setInfo("Cannot do more undos!");
}
else
{
m_messages.setInfo("Undo!");
}
m_net.modified = true;
}

//--------------------------------------------------------------------------
void Editor::redo()
{
if (!m_history.redo())
{
m_messages.setInfo("Cannot do more redos!");
}
else
{
m_messages.setInfo("Redo!");
}
m_net.modified = true;
}

//--------------------------------------------------------------------------
Editor::PetriView::PetriView(Editor& editor)
: m_editor(editor)
Expand Down Expand Up @@ -1135,6 +1174,8 @@ void Editor::PetriView::handleAddNode(ImGuiMouseButton button)
// present.
if (m_editor.getNode(m_mouse.position) == nullptr)
{
auto action = std::make_unique<NetModifaction>(m_editor);
action->before(m_editor.m_net);
if (button == ImGuiMouseButton_Left)
{
m_editor.m_net.addPlace(m_mouse.position.x,
Expand All @@ -1145,6 +1186,8 @@ void Editor::PetriView::handleAddNode(ImGuiMouseButton button)
m_editor.m_net.addTransition(m_mouse.position.x,
m_mouse.position.y);
}
action->after(m_editor.m_net);
m_editor.m_history.add(std::move(action));
}
}
else if (m_editor.m_net.type() == TypeOfNet::PetriNet)
Expand Down Expand Up @@ -1185,6 +1228,9 @@ void Editor::PetriView::handleArcDestination()
float x = m_mouse.to->x + (m_mouse.from->x - m_mouse.to->x) / 2.0f;
float y = m_mouse.to->y + (m_mouse.from->y - m_mouse.to->y) / 2.0f;
float duration = random(1, 5);

auto action = std::make_unique<NetModifaction>(m_editor);
action->before(m_editor.m_net);
if (m_mouse.to->type == Node::Type::Place)
{
Transition &n = m_editor.m_net.addTransition(x, y);
Expand All @@ -1203,6 +1249,8 @@ void Editor::PetriView::handleArcDestination()
}
m_mouse.from = &n;
}
action->after(m_editor.m_net);
m_editor.m_history.add(std::move(action));
}
}
else
Expand All @@ -1211,6 +1259,8 @@ void Editor::PetriView::handleArcDestination()
// create the origin node before creating the arc.
if (m_mouse.arc_from_unknown_node)
{
auto action = std::make_unique<NetModifaction>(m_editor);
action->before(m_editor.m_net);
if (m_mouse.to->type == Node::Type::Place)
{
m_mouse.from = &m_editor.m_net.addTransition(
Expand All @@ -1221,6 +1271,8 @@ void Editor::PetriView::handleArcDestination()
m_mouse.from = &m_editor.m_net.addPlace(
m_mouse.click_position.x, m_mouse.click_position.y);
}
action->after(m_editor.m_net);
m_editor.m_history.add(std::move(action));
}
}
}
Expand All @@ -1236,29 +1288,45 @@ void Editor::PetriView::handleArcDestination()
float px = x + (m_mouse.from->x - x) / 2.0f;
float py = y + (m_mouse.from->y - y) / 2.0f;
float duration = random(1, 5);
auto action = std::make_unique<NetModifaction>(m_editor);
action->before(m_editor.m_net);
Place &n = m_editor.m_net.addPlace(px, py);
if (!m_editor.m_net.addArc(*m_mouse.from, n, duration))
{
// m_message_bar.setError(m_net.message());
}
m_mouse.from = &n;
action->after(m_editor.m_net);
m_editor.m_history.add(std::move(action));
}
if (m_mouse.from->type == Node::Type::Place)
{
auto action = std::make_unique<NetModifaction>(m_editor);
action->before(m_editor.m_net);
m_mouse.to = &m_editor.m_net.addTransition(x, y);
action->after(m_editor.m_net);
m_editor.m_history.add(std::move(action));
}
else
{
auto action = std::make_unique<NetModifaction>(m_editor);
action->before(m_editor.m_net);
m_mouse.to = &m_editor.m_net.addPlace(x, y);
action->after(m_editor.m_net);
m_editor.m_history.add(std::move(action));
}
}
// Create the arc. Note: the duration value is only used
// for arc Transition --> Place.
auto action = std::make_unique<NetModifaction>(m_editor);
action->before(m_editor.m_net);
float duration = random(1, 5);
if (!m_editor.m_net.addArc(*m_mouse.from, *m_mouse.to, duration))
{
// m_message_bar.setError(m_petri_net.message());
}
action->after(m_editor.m_net);
m_editor.m_history.add(std::move(action));

// Reset states
m_mouse.from = m_mouse.to = nullptr;
Expand Down Expand Up @@ -1334,7 +1402,18 @@ void Editor::PetriView::onHandleInput()

if (ImGui::IsItemHovered())
{
if (ImGui::IsKeyPressed(KEY_MOVE_PETRI_NODE, false))
if (ImGui::GetIO().KeyCtrl)
{
if (ImGui::IsKeyPressed(KEY_UNDO, false))
{
m_editor.undo();
}
else if (ImGui::IsKeyPressed(KEY_REDO, false))
{
m_editor.redo();
}
}
else if (ImGui::IsKeyPressed(KEY_MOVE_PETRI_NODE, false))
{
handleMoveNode();
}
Expand Down
37 changes: 37 additions & 0 deletions src/Editor/PetriEditor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
# include "Net/Simulation.hpp"
# include "Net/Exports/Exports.hpp"
# include "Net/Imports/Imports.hpp"
# include "Utils/History.hpp"
# include "Utils/Path.hpp"

namespace tpne {
Expand Down Expand Up @@ -85,6 +86,8 @@ class Editor: public Application
void toogleStartSimulation();
void takeScreenshot();
void clearNet();
void undo();
void redo();

private: // Error logs

Expand Down Expand Up @@ -196,6 +199,38 @@ class Editor: public Application
// create.
ImVec2 click_position; bool arc_from_unknown_node = false;
} m_mouse;
}; // class PetriView

// ************************************************************************
//! \brief Quick and dirty net memorization for performing undo/redo.
//! \fixme this is memory usage consuption by saving two nets. It's better
//! to memorize only command. but the remove command make change nodes ID
//! so history will become false.
// ************************************************************************
class NetModifaction : public History::Action
{
public:
NetModifaction(Editor& editor) : m_editor(editor) {}
void before(Net& net) { m_before = net; }
void after(Net& net) { m_after = net; }

virtual bool undo() override
{
m_editor.m_net = m_before;
return true;
}

virtual bool redo() override
{
m_editor.m_net = m_after;
return true;
}

private:

Editor& m_editor;
Net m_before;
Net m_after;
};

private:
Expand All @@ -210,6 +245,8 @@ class Editor: public Application
//! \brief Single Petri net the editor can edit.
//! \fixme Manage several nets (like done with GEMMA).
Net m_net;
//! \brief History of modifications of the net.
History m_history;
//! \brief Instance allowing to do timed simulation.
Simulation m_simulation;
//! \brief Visualize the net and do the interaction with the user.
Expand Down
81 changes: 81 additions & 0 deletions src/Utils/History.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "Utils/History.hpp"

namespace tpne {

bool History::canUndo() const
{
return m_undoList.size() > 0u;
}

bool History::canRedo() const
{
return m_redoList.size() > 0u;
}

bool History::isDirty() const
{
return m_nCleanCount != 0u;
}

bool History::undo()
{
if (!canUndo())
return false;

m_nCleanCount--;
History::Action::Ptr action = std::move(m_undoList.back());
m_undoList.pop_back();
if (action->undo())
{
addRedo(std::move(action));
return true;
}
return false;
}

bool History::redo()
{
if (!canRedo())
return false;

m_nCleanCount++;
History::Action::Ptr action = std::move(m_redoList.back());
m_redoList.pop_back();
if (action->redo())
{
addUndo(std::move(action));
return true;
}
return false;
}

void History::clear()
{
m_undoList.clear();
m_redoList.clear();
m_nCleanCount = 0u;
}

void History::addUndo(History::Action::Ptr action)
{
if (m_undoList.size() >= m_nUndoLevel)
{
m_undoList.pop_front();
}
m_undoList.push_back(std::move(action));
if ((m_nCleanCount < 0u) && (m_redoList.size() > 0u))
{
m_nCleanCount = m_undoList.size() + m_redoList.size() + 1u;
}
else
{
m_nCleanCount++;
}
}

void History::addRedo(History::Action::Ptr action)
{
m_redoList.push_back(std::move(action));
}

} // namespace tpne
Loading

0 comments on commit 0d27b56

Please sign in to comment.