diff --git a/src/FileFormat/AkiraFile.vala b/src/FileFormat/AkiraFile.vala index 8b71ec7e2..c17674407 100644 --- a/src/FileFormat/AkiraFile.vala +++ b/src/FileFormat/AkiraFile.vala @@ -59,7 +59,7 @@ public class Akira.FileFormat.AkiraFile : Akira.FileFormat.ZipArchiveHandler { save_images.begin (); var content = new FileFormat.JsonContent (window); - content.save_content (); + content.inner_save_content (); var json = content.finalize_content (); write_content_to_file (content_file, json); diff --git a/src/FileFormat/JsonContent.vala b/src/FileFormat/JsonContent.vala index a11eaf02e..c165cc371 100644 --- a/src/FileFormat/JsonContent.vala +++ b/src/FileFormat/JsonContent.vala @@ -40,7 +40,20 @@ public class Akira.FileFormat.JsonContent : Object { canvas = window.main_window.main_canvas.canvas; } - public void save_content () { + public string finalize_content () { + builder.end_object (); + + Json.Node root = builder.get_root (); + generator.set_root (root); + + return generator.to_data (null); + } + + public void inner_save_content() { + save_content(builder, canvas); + } + + public static void save_content (Json.Builder builder, Akira.Lib.Canvas canvas) { // Save the current version of Akira. builder.set_member_name ("version"); builder.add_string_value (Constants.VERSION); @@ -49,22 +62,22 @@ public class Akira.FileFormat.JsonContent : Object { builder.set_member_name ("scale"); builder.add_double_value (canvas.get_scale ()); builder.set_member_name ("hadjustment"); - builder.add_double_value (window.main_window.main_canvas.main_scroll.hadjustment.value); + builder.add_double_value (0); builder.set_member_name ("vadjustment"); - builder.add_double_value (window.main_window.main_canvas.main_scroll.vadjustment.value); + builder.add_double_value (0); // Convert Artboards to JSON. - save_artboards (); + save_artboards (builder, canvas); // Convert Items to JSON. - save_items (); + save_items (builder, canvas); } - private void save_artboards () { + private static void save_artboards (Json.Builder builder, Akira.Lib.Canvas canvas) { builder.set_member_name ("artboards"); builder.begin_array (); - foreach (var artboard in window.items_manager.artboards) { + foreach (var artboard in canvas.window.items_manager.artboards) { var item = new JsonObject (artboard); builder.begin_object (); builder.set_member_name ("artboard"); @@ -75,11 +88,11 @@ public class Akira.FileFormat.JsonContent : Object { builder.end_array (); } - private void save_items () { + private static void save_items (Json.Builder builder, Akira.Lib.Canvas canvas) { builder.set_member_name ("items"); builder.begin_array (); - foreach (var _item in window.items_manager.free_items) { + foreach (var _item in canvas.window.items_manager.free_items) { var item = new JsonObject (_item); builder.begin_object (); builder.set_member_name ("item"); @@ -88,7 +101,7 @@ public class Akira.FileFormat.JsonContent : Object { } // Save all the items inside this Artboard. - foreach (var artboard in window.items_manager.artboards) { + foreach (var artboard in canvas.window.items_manager.artboards) { foreach (var _item in artboard.items) { var child_item = new JsonObject (_item); builder.begin_object (); @@ -101,12 +114,19 @@ public class Akira.FileFormat.JsonContent : Object { builder.end_array (); } - public string finalize_content () { - builder.end_object (); + public static string serialize_canvas (Akira.Lib.Canvas canvas) { + var inner_generator = new Json.Generator (); + inner_generator.pretty = true; + var inner_builder = new Json.Builder (); + inner_builder.begin_object (); - Json.Node root = builder.get_root (); - generator.set_root (root); + save_content(inner_builder, canvas); - return generator.to_data (null); + inner_builder.end_object (); + + Json.Node root = inner_builder.get_root (); + inner_generator.set_root (root); + + return inner_generator.to_data (null); } } diff --git a/src/FileFormat/JsonLoader.vala b/src/FileFormat/JsonLoader.vala index 8d145852d..f972f324c 100644 --- a/src/FileFormat/JsonLoader.vala +++ b/src/FileFormat/JsonLoader.vala @@ -33,47 +33,51 @@ public class Akira.FileFormat.JsonLoader : Object { construct { load_content (); } - public void load_content () { + inner_load_content(window.main_window.main_canvas.canvas, obj); + + } + + public static void inner_load_content (Akira.Lib.Canvas canvas, Json.Object json_object) { // Se the canvas to simulate a click + holding state to avoid triggering // redrawing methods connected to that state. - window.main_window.main_canvas.canvas.holding = true; + canvas.holding = true; // Load saved Artboards. - if (obj.get_member ("artboards") != null) { - Json.Array artboards = obj.get_member ("artboards").get_array (); + if (json_object.get_member ("artboards") != null) { + Json.Array artboards = json_object.get_member ("artboards").get_array (); var artboards_list = artboards.get_elements (); artboards_list.reverse (); foreach (unowned Json.Node node in artboards_list) { - load_item (node.get_object (), "artboard"); + load_item (canvas, node.get_object (), "artboard"); } } // Load saved Items. - if (obj.get_member ("items") != null) { - Json.Array items = obj.get_member ("items").get_array (); + if (json_object.get_member ("items") != null) { + Json.Array items = json_object.get_member ("items").get_array (); var items_list = items.get_elements (); items_list.reverse (); foreach (unowned Json.Node node in items_list) { - load_item (node.get_object (), "item"); + load_item (canvas, node.get_object (), "item"); } } - window.event_bus.set_scale (obj.get_double_member ("scale")); - window.main_window.main_canvas.main_scroll.hadjustment.value = obj.get_double_member ("hadjustment"); - window.main_window.main_canvas.main_scroll.vadjustment.value = obj.get_double_member ("vadjustment"); + //window.event_bus.set_scale (obj.get_double_member ("scale")); + //window.main_window.main_canvas.main_scroll.hadjustment.value = obj.get_double_member ("hadjustment"); + //window.main_window.main_canvas.main_scroll.vadjustment.value = obj.get_double_member ("vadjustment"); // Reset the holding state at the end of it. - window.main_window.main_canvas.canvas.holding = false; + canvas.holding = false; } - private void load_item (Json.Object obj, string type) { - var item = obj.get_member (type).get_object (); + private static void load_item (Akira.Lib.Canvas canvas, Json.Object json_object, string type) { + var item = json_object.get_member (type).get_object (); if (item != null) { // debug ("loading %s", type); - window.items_manager.load_item (item); + canvas.window.items_manager.load_item (item); } } } diff --git a/src/Lib/Canvas.vala b/src/Lib/Canvas.vala index fbb2b691c..1a85558d2 100644 --- a/src/Lib/Canvas.vala +++ b/src/Lib/Canvas.vala @@ -53,6 +53,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { MODE_PANNING, } + public Managers.UndoManager undo_manager; public Managers.ExportManager export_manager; public Managers.SelectedBoundManager selected_bound_manager; private Managers.NobManager nob_manager; @@ -85,6 +86,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { events |= Gdk.EventMask.TOUCHPAD_GESTURE_MASK; events |= Gdk.EventMask.TOUCH_MASK; + undo_manager = new Managers.UndoManager (); export_manager = new Managers.ExportManager (this); selected_bound_manager = new Managers.SelectedBoundManager (this); nob_manager = new Managers.NobManager (this); @@ -258,6 +260,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { switch (edit_mode) { case EditMode.MODE_INSERT: + undo_manager.add_undo(this); selected_bound_manager.reset_selection (); var new_item = window.items_manager.insert_item (event.x, event.y); @@ -375,6 +378,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { break; case EditMode.MODE_SELECTION: + undo_manager.add_undo(this); window.event_bus.detect_artboard_change (); window.event_bus.detect_image_size_change (); break; @@ -424,6 +428,8 @@ public class Akira.Lib.Canvas : Goo.Canvas { public void on_insert_item () { edit_mode = EditMode.MODE_INSERT; + + debug("add item"); } public void on_set_focus_on_canvas () { diff --git a/src/Lib/Managers/UndoManager.vala b/src/Lib/Managers/UndoManager.vala new file mode 100644 index 000000000..d8546a5eb --- /dev/null +++ b/src/Lib/Managers/UndoManager.vala @@ -0,0 +1,107 @@ + + + + + + + +public class Akira.Lib.Managers.UndoManager : Object { + + private GLib.Queue undos; + private GLib.Queue redos; + + public UndoManager () { + undos = new GLib.Queue(); + redos = new GLib.Queue(); + } + + + public void add_undo(Akira.Lib.Canvas canvas) { + redos.clear (); + inner_add_undo(Akira.FileFormat.JsonContent.serialize_canvas(canvas)); + debug("%d %d", (int)undos.get_length(), (int)redos.get_length()); + } + + public void apply_undo(Akira.Lib.Canvas canvas) { + if (undos.get_length () == 0) { + debug("undo empty"); + return; + } + + var old_undo = undos.pop_head(); + inner_add_redo(old_undo); + + try { + var parser = new Json.Parser (); + parser.load_from_data (old_undo); + var obj = parser.get_root ().get_object (); + + clear_canvas(canvas); + Akira.FileFormat.JsonLoader.inner_load_content(canvas, obj); + + } catch (Error e) { + debug("failed to read undo"); + return; + } + + debug("%d %d", (int)undos.get_length(), (int)redos.get_length()); + } + + private void inner_add_undo(string undo_to_add) { + undos.push_head(undo_to_add); + } + + private void inner_add_redo(string redo_to_add) { + redos.push_head(redo_to_add); + } + + public void apply_redo(Akira.Lib.Canvas canvas) { + if (redos.get_length () == 0) { + debug("redo empty"); + return; + } + + var old_redo = redos.pop_head(); + inner_add_undo(old_redo); + + + try { + var parser = new Json.Parser (); + parser.load_from_data (old_redo); + var obj = parser.get_root ().get_object (); + + clear_canvas(canvas); + Akira.FileFormat.JsonLoader.inner_load_content(canvas, obj); + + } catch (Error e) { + debug("failed to read redo"); + return; + } + + debug("%d %d", (int)undos.get_length(), (int)redos.get_length()); + + } + + private void clear_canvas(Akira.Lib.Canvas canvas) { + var item_manager = canvas.window.items_manager; + + var to_delete = new GLib.Queue(); + + foreach (var item in item_manager.free_items) { + to_delete.push_head(item); + } + + foreach (var item in item_manager.artboards) { + to_delete.push_head(item); + } + + foreach (var item in item_manager.images) { + to_delete.push_head(item); + } + + while (to_delete.get_length () != 0) { + canvas.window.event_bus.request_delete_item (to_delete.pop_head()); + } + } + +} diff --git a/src/Services/ActionManager.vala b/src/Services/ActionManager.vala index 6a87f4ce9..c1c4306f3 100644 --- a/src/Services/ActionManager.vala +++ b/src/Services/ActionManager.vala @@ -66,6 +66,8 @@ public class Akira.Services.ActionManager : Object { public const string ACTION_ESCAPE = "action_escape"; public const string ACTION_SHORTCUTS = "action_shortcuts"; public const string ACTION_PICK_COLOR = "action_pick_color"; + public const string ACTION_UNDO = "action_undo"; + public const string ACTION_REDO = "action_redo"; public static Gee.MultiMap action_accelerators = new Gee.HashMultiMap (); public static Gee.MultiMap typing_accelerators = new Gee.HashMultiMap (); @@ -104,6 +106,8 @@ public class Akira.Services.ActionManager : Object { { ACTION_ESCAPE, action_escape }, { ACTION_SHORTCUTS, action_shortcuts }, { ACTION_PICK_COLOR, action_pick_color }, + { ACTION_UNDO, do_undo }, + { ACTION_REDO, do_redo }, }; public ActionManager (Akira.Application akira_app, Akira.Window window) { @@ -149,6 +153,9 @@ public class Akira.Services.ActionManager : Object { typing_accelerators.set (ACTION_DELETE, "Delete"); typing_accelerators.set (ACTION_DELETE, "BackSpace"); typing_accelerators.set (ACTION_TOGGLE_PIXEL_GRID, "Tab"); + + typing_accelerators.set (ACTION_UNDO, "z"); + typing_accelerators.set (ACTION_REDO, "z"); } construct { @@ -509,6 +516,16 @@ public class Akira.Services.ActionManager : Object { }); } + private void do_undo () { + weak Akira.Lib.Canvas canvas = window.main_window.main_canvas.canvas; + canvas.undo_manager.apply_undo(canvas); + } + + private void do_redo () { + weak Akira.Lib.Canvas canvas = window.main_window.main_canvas.canvas; + canvas.undo_manager.apply_redo(canvas); + } + public static void action_from_group (string action_name, ActionGroup? action_group) { action_group.activate_action (action_name, null); } diff --git a/src/meson.build b/src/meson.build index 6867b6bb0..6fba4d67b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -108,6 +108,7 @@ sources = files( 'Lib/Managers/NobManager.vala', 'Lib/Managers/SelectedBoundManager.vala', 'Lib/Managers/SnapManager.vala', + 'Lib/Managers/UndoManager.vala', 'Lib/Selection/Nob.vala',