From c65af03ba1c27fe92ef8591cec498e15fad6cbdc Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Sun, 23 Mar 2025 08:26:07 +0000 Subject: [PATCH 01/25] tools : add can create basic folder to the tree view --- tools/level_editor/level_tree_view.vala | 114 ++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 6 deletions(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index ff4a475803..d74f6fbd41 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -225,6 +225,8 @@ public class LevelTreeView : Gtk.Box private bool on_button_pressed(Gdk.EventButton ev) { if (ev.button == Gdk.BUTTON_SECONDARY) { + Gtk.Menu menu = new Gtk.Menu(); + Gtk.MenuItem mi = null; Gtk.TreePath path; Gtk.TreeViewColumn column; if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, out column, null, null)) { @@ -233,12 +235,67 @@ public class LevelTreeView : Gtk.Box _tree_selection.select_path(path); } } else { // Clicked on empty space. - return Gdk.EVENT_PROPAGATE; + mi = new Gtk.MenuItem.with_label("Create New Folder"); + mi.activate.connect(() => { + create_new_folder(); + }); + menu.add(mi); + menu.show_all(); + menu.popup_at_pointer(ev); + return Gdk.EVENT_STOP; } - - Gtk.Menu menu = new Gtk.Menu(); - Gtk.MenuItem mi; - + _tree_selection.selected_foreach((model, path, iter) => { + Value type; + model.get_value(iter, Column.TYPE, out type); + + if ((int)type == ItemType.FOLDER) { + Gtk.TreeIter parent_iter = Gtk.TreeIter(); + Gtk.TreeStore base_store = null; + bool valid_conversion = true; + + if (model is Gtk.TreeModelSort) { + Gtk.TreeModelSort sort_model = (Gtk.TreeModelSort)model; + Gtk.TreeModel base_model = sort_model.model; + + if (base_model is Gtk.TreeModelFilter) { + Gtk.TreeModelFilter filter_model = (Gtk.TreeModelFilter)base_model; + Gtk.TreeModel inner_model = filter_model.get_model(); + if (inner_model is Gtk.TreeStore) { + base_store = (Gtk.TreeStore)inner_model; + } else { + print("The internal model of the filter is not a TreeStore.\n"); + return; + } + + Gtk.TreePath child_path = sort_model.convert_path_to_child_path(path); + if (child_path != null) { + valid_conversion = base_store.get_iter(out parent_iter, child_path); + } else { + valid_conversion = false; + } + } else { + valid_conversion = false; + } + } else { + valid_conversion = false; + } + + if (valid_conversion && base_store.iter_is_valid(parent_iter)) { + var menu_item = new Gtk.MenuItem.with_label("Create Subfolder"); + menu_item.activate.connect(() => { + if (base_store.iter_is_valid(parent_iter)) { + create_new_folder(parent_iter); + } else { + create_new_folder(null); + } + }); + menu.add(menu_item); + } else { + print("Failed to convert path to base template\n"); + } + } + }); + mi = new Gtk.MenuItem.with_label("Rename..."); mi.activate.connect(() => { Gtk.Dialog dg = new Gtk.Dialog.with_buttons("New Name" @@ -313,7 +370,52 @@ public class LevelTreeView : Gtk.Box return Gdk.EVENT_PROPAGATE; } - + private void create_new_folder(Gtk.TreeIter? parent_iter = null) + { + Gtk.Dialog dialog = new Gtk.Dialog.with_buttons( + "New Folder Name", + ((Gtk.Application)GLib.Application.get_default()).active_window, + Gtk.DialogFlags.MODAL, + "Cancel", Gtk.ResponseType.CANCEL, + "OK", Gtk.ResponseType.OK + ); + + Gtk.Entry entry = new Gtk.Entry(); + entry.set_placeholder_text("Enter folder name"); + dialog.get_content_area().pack_start(entry, true, true, 0); + dialog.show_all(); + + if (dialog.run() == Gtk.ResponseType.OK) { + string folder_name = entry.get_text().strip(); + if (folder_name == "") { + folder_name = "Folder Name"; + } + dialog.destroy(); + add_folder_to_tree(parent_iter, folder_name); + } else { + dialog.destroy(); + } + } + + private void add_folder_to_tree(Gtk.TreeIter? parent_iter, string folder_name) + { + Guid folder_guid = Guid.new_guid(); + Gtk.TreeIter iter; + + bool parent_valid = parent_iter != null && _tree_store.iter_is_valid(parent_iter); + + _tree_store.insert_with_values(out iter, parent_valid ? parent_iter : null, -1, + Column.TYPE, ItemType.FOLDER, + Column.GUID, folder_guid, + Column.NAME, folder_name + ); + + if (parent_valid) { + Gtk.TreePath parent_path = _tree_store.get_path(parent_iter); + _tree_view.expand_row(parent_path, false); + } + } + private void on_tree_selection_changed() { _level.selection_changed.disconnect(on_level_selection_changed); From 6e8d626e85684469707602d4b73e24264c28e025 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Sun, 23 Mar 2025 19:51:43 +0000 Subject: [PATCH 02/25] tools : try adding a drag and drop feature .... --- tools/level_editor/level_tree_view.vala | 97 ++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index d74f6fbd41..28939e44fb 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -10,6 +10,10 @@ namespace Crown { public class LevelTreeView : Gtk.Box { + private const Gtk.TargetEntry[] TARGET_ENTRIES = { + { "GTK_TREE_MODEL_ROW", Gtk.TargetFlags.SAME_APP, 0 } + }; + private enum ItemType { FOLDER, @@ -191,6 +195,12 @@ public class LevelTreeView : Gtk.Box _tree_view.model = _tree_sort; _tree_view.button_press_event.connect(on_button_pressed); + // Enable drag and drop + _tree_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, TARGET_ENTRIES, Gdk.DragAction.MOVE); + _tree_view.enable_model_drag_dest(TARGET_ENTRIES, Gdk.DragAction.MOVE); + _tree_view.drag_data_get.connect(on_drag_data_get); + _tree_view.drag_data_received.connect(on_drag_data_received); + _tree_selection = _tree_view.get_selection(); _tree_selection.set_mode(Gtk.SelectionMode.MULTIPLE); _tree_selection.changed.connect(on_tree_selection_changed); @@ -286,7 +296,7 @@ public class LevelTreeView : Gtk.Box if (base_store.iter_is_valid(parent_iter)) { create_new_folder(parent_iter); } else { - create_new_folder(null); + create_new_folder(); } }); menu.add(menu_item); @@ -416,6 +426,91 @@ public class LevelTreeView : Gtk.Box } } + private void on_drag_data_get(Gtk.Widget widget, Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time) { + Gtk.TreeModel model; + GLib.List paths = _tree_selection.get_selected_rows(out model); + string source_set = null; + StringBuilder data_builder = new StringBuilder(); + + foreach (Gtk.TreePath path in paths) { + Gtk.TreeIter iter; + model.get_iter(out iter, path); + + Gtk.TreeIter parent_iter; + if (model.iter_parent(out parent_iter, iter)) { + Value parent_name_val; + model.get_value(parent_iter, Column.NAME, out parent_name_val); + string parent_name = (string)parent_name_val; + + if (parent_name == "Units") { + source_set = "units"; + } else if (parent_name == "Sounds") { + source_set = "sounds"; + } + + Value guid_val; + model.get_value(iter, Column.GUID, out guid_val); + Guid guid = (Guid)guid_val; + data_builder.append(guid.to_string() + ","); + } + } + + if (source_set != null) { + string data_str = source_set + ";" + data_builder.str; + selection_data.set(selection_data.get_target(), 8, data_str.data); + } + } + private void on_drag_data_received(Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { + // 1. Stop IMMÉDIATEMENT le handler par défaut + Signal.stop_emission_by_name(_tree_view, "drag-data-received"); + + // 2. Traitement des données + string[] data = ((string)selection_data.get_data()).split(";"); + if (data.length != 2) return; + + string source_set = data[0]; + string[] guids = data[1].split(","); + + // 3. Détection de la cible + Gtk.TreePath path; + Gtk.TreeViewDropPosition pos; + if (!_tree_view.get_dest_row_at_pos(x, y, out path, out pos)) return; + + // 4. Détermination du set cible + Gtk.TreeIter target_iter; + _tree_sort.get_iter(out target_iter, path); + + string target_set = null; + Gtk.TreeIter parent_iter; + if (_tree_sort.iter_parent(out parent_iter, target_iter)) { + Value parent_name_val; + _tree_sort.get_value(parent_iter, Column.NAME, out parent_name_val); + string parent_name = (string)parent_name_val; + + target_set = (parent_name == "Units") ? "units" : "sounds"; + } else { + Value name_val; + _tree_sort.get_value(target_iter, Column.NAME, out name_val); + string name = (string)name_val; + + target_set = (name == "Units") ? "units" : "sounds"; + } + + // 5. Validation finale + if (target_set == null || target_set == source_set) return; + + // 6. Mise à jour de la base + foreach (string guid_str in guids) { + if (guid_str == "") continue; + Guid guid = Guid.parse(guid_str); + _db.remove_from_set(_level._id, source_set, guid); + _db.add_to_set(_level._id, target_set, guid); + } + + // 7. Finalisation du drag + Gtk.drag_finish(context, true, false, time); + } + private void on_tree_selection_changed() { _level.selection_changed.disconnect(on_level_selection_changed); From dfb1d5dc307f1df3bfe8de0a6d1c3ac183b8e68a Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Sun, 23 Mar 2025 20:34:51 +0000 Subject: [PATCH 03/25] tools: replace logic in LevelTreeView.on_database_key_changed() to avoid recreating datas from zero every time --- tools/level_editor/level_tree_view.vala | 139 ++++++++++++------------ 1 file changed, 72 insertions(+), 67 deletions(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index 28939e44fb..3c054408fd 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -604,81 +604,86 @@ public class LevelTreeView : Gtk.Box else return ItemType.UNIT; } - - private void on_database_key_changed(Guid id, string key) - { - if (id != _level._id) - return; - - if (key != "units" && key != "sounds") - return; - + + private void on_database_key_changed(Guid id, string key) { + if (id != _level._id || (key != "units" && key != "sounds")) return; + _tree_selection.changed.disconnect(on_tree_selection_changed); - _tree_view.model = null; - _tree_store.clear(); - - Gtk.TreeIter units_iter; - _tree_store.insert_with_values(out units_iter - , null - , -1 - , Column.TYPE - , ItemType.FOLDER - , Column.GUID - , GUID_ZERO - , Column.NAME - , "Units" - , -1 - ); - Gtk.TreeIter sounds_iter; - _tree_store.insert_with_values(out sounds_iter - , null - , -1 - , Column.TYPE - , ItemType.FOLDER - , Column.GUID - , GUID_ZERO - , Column.NAME - , "Sounds" - , -1 - ); - - HashSet units = _db.get_property_set(_level._id, "units", new HashSet()); - HashSet sounds = _db.get_property_set(_level._id, "sounds", new HashSet()); - - foreach (Guid unit_id in units) { - Unit u = Unit(_level._db, unit_id); - + + string[] required_sections = {"Units", "Sounds"}; + Gtk.TreeIter[] section_iters = new Gtk.TreeIter[2]; + + for (int i = 0; i < required_sections.length; i++) { + bool exists = false; Gtk.TreeIter iter; - _tree_store.insert_with_values(out iter - , units_iter - , -1 - , Column.TYPE - , item_type(u) - , Column.GUID - , unit_id - , Column.NAME - , _level.object_editor_name(unit_id) - , -1 + + // Search folder + if (_tree_store.get_iter_first(out iter)) { + do { + Value v; + _tree_store.get_value(iter, Column.NAME, out v); + if ((string)v == required_sections[i]) { + section_iters[i] = iter; + exists = true; + break; + } + } while (_tree_store.iter_next(ref iter)); + } + + // Create folder if not exist + if (!exists) { + _tree_store.append(out section_iters[i], null); + _tree_store.set( + section_iters[i], + Column.TYPE, ItemType.FOLDER, + Column.GUID, GUID_ZERO, + Column.NAME, required_sections[i], + -1 ); + } } - foreach (Guid sound in sounds) { - Gtk.TreeIter iter; - _tree_store.insert_with_values(out iter - , sounds_iter - , -1 - , Column.TYPE - , ItemType.SOUND - , Column.GUID - , sound - , Column.NAME - , _level.object_editor_name(sound) - , -1 + + // Synchronize only modified keys + int target_index = (key == "units") ? 0 : 1; + Gtk.TreeIter target_iter = section_iters[target_index]; + + var current_guids = _db.get_property_set(id, key, new HashSet()); + var existing_guids = new HashTable(Guid.hash_func, Guid.equal_func); + + // Update elements + Gtk.TreeIter child; + if (_tree_store.iter_children(out child, target_iter)) { + do { + Value v; + _tree_store.get_value(child, Column.GUID, out v); + if (v.holds(typeof(Guid))) { + existing_guids[(Guid)v] = child; + } + } while (_tree_store.iter_next(ref child)); + } + + existing_guids.foreach((guid, iter) => { + if (!current_guids.contains(guid)) { + _tree_store.remove(ref iter); + } + }); + + foreach (Guid guid in current_guids) { + if (!existing_guids.contains(guid)) { + Gtk.TreeIter new_iter; + ItemType type = (key == "units") ? item_type(Unit(_level._db, guid)) : ItemType.SOUND; + _tree_store.append(out new_iter, target_iter); + _tree_store.set( + new_iter, + Column.TYPE, type, + Column.GUID, guid, + Column.NAME, _level.object_editor_name(guid), + -1 ); + } } - _tree_view.model = _tree_sort; _tree_view.expand_all(); - _tree_selection.changed.connect(on_tree_selection_changed); } From 9e25501f0144531394ad4b5406c459f2bab83d41 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Sun, 23 Mar 2025 20:56:54 +0000 Subject: [PATCH 04/25] tools: fix creating an unit remove manually created folders --- tools/level_editor/level_tree_view.vala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index 3c054408fd..9adf136693 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -654,14 +654,19 @@ public class LevelTreeView : Gtk.Box Gtk.TreeIter child; if (_tree_store.iter_children(out child, target_iter)) { do { - Value v; - _tree_store.get_value(child, Column.GUID, out v); - if (v.holds(typeof(Guid))) { - existing_guids[(Guid)v] = child; + Value type_val; + _tree_store.get_value(child, Column.TYPE, out type_val); + + if ((ItemType)type_val.get_int() == ItemType.FOLDER) continue; + + Value guid_val; + _tree_store.get_value(child, Column.GUID, out guid_val); + if (guid_val.holds(typeof(Guid))) { + existing_guids[(Guid)guid_val] = child; } } while (_tree_store.iter_next(ref child)); } - + existing_guids.foreach((guid, iter) => { if (!current_guids.contains(guid)) { _tree_store.remove(ref iter); From 912e0cab8f7f72fb61ca0d90abf9b9ceacfadace Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Sun, 23 Mar 2025 21:03:41 +0000 Subject: [PATCH 05/25] tools: made folders sorted before units in level tree --- tools/level_editor/level_tree_view.vala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index 9adf136693..7e9ab86b04 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -124,9 +124,14 @@ public class LevelTreeView : Gtk.Box Value type_b; model.get_value(iter_a, Column.TYPE, out type_a); model.get_value(iter_b, Column.TYPE, out type_b); - if ((int)type_a == ItemType.FOLDER || (int)type_b == ItemType.FOLDER) - return -1; - + + // Ensure folders come first (prioritize folders over units/sounds) + if ((ItemType)type_a == ItemType.FOLDER && (ItemType)type_b != ItemType.FOLDER) + return -1; // folder should be before non-folder + if ((ItemType)type_b == ItemType.FOLDER && (ItemType)type_a != ItemType.FOLDER) + return 1; // folder should be after non-folder + + // If both are folders, or both are non-folders, sort by the usual criteria switch (_sort_mode) { case SortMode.NAME_AZ: case SortMode.NAME_ZA: { @@ -134,22 +139,20 @@ public class LevelTreeView : Gtk.Box Value name_b; model.get_value(iter_a, Column.NAME, out name_a); model.get_value(iter_b, Column.NAME, out name_b); - + int cmp = strcmp((string)name_a, (string)name_b); return _sort_mode == SortMode.NAME_AZ ? cmp : -cmp; } - case SortMode.TYPE_AZ: case SortMode.TYPE_ZA: { int cmp = (int)type_a - (int)type_b; return _sort_mode == SortMode.TYPE_AZ ? cmp : -cmp; } - default: return 0; } }); - + Gtk.TreeViewColumn column = new Gtk.TreeViewColumn(); Gtk.CellRendererPixbuf cell_pixbuf = new Gtk.CellRendererPixbuf(); Gtk.CellRendererText cell_text = new Gtk.CellRendererText(); @@ -687,7 +690,6 @@ public class LevelTreeView : Gtk.Box ); } } - _tree_view.model = _tree_sort; _tree_view.expand_all(); _tree_selection.changed.connect(on_tree_selection_changed); } From b101279bdc54c44f3b4350fa177511f2078667ae Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Mon, 24 Mar 2025 03:34:33 +0000 Subject: [PATCH 06/25] tools: add persistant folder saving , now need to can drag and drop units/sounds in it --- tools/core/guid.vala | 41 ++++-- tools/level_editor/level.vala | 79 +++++++++-- tools/level_editor/level_tree_view.vala | 177 +++++++++++++++++++----- tools/resource/global_guids.vala | 10 ++ 4 files changed, 245 insertions(+), 62 deletions(-) create mode 100644 tools/resource/global_guids.vala diff --git a/tools/core/guid.vala b/tools/core/guid.vala index 50f00ec9eb..8383c5af56 100644 --- a/tools/core/guid.vala +++ b/tools/core/guid.vala @@ -13,23 +13,34 @@ public struct Guid public static Guid new_guid() { - // FIXME: Replace Rand with something better. - Rand rnd = new Rand(); - uint64 a = rnd.next_int(); - uint64 b = rnd.next_int(); - uint64 c = rnd.next_int(); - uint64 d = rnd.next_int(); + Guid new_guid = { 0, 0 }; - uint64 d1; - d1 = a << 32; - d1 |= b << 0; - uint64 d2; - d2 = c << 32; - d2 |= d << 0; + do + { + // FIXME: Replace Rand with something better. + Rand rnd = new Rand(); + uint64 a = rnd.next_int(); + uint64 b = rnd.next_int(); + uint64 c = rnd.next_int(); + uint64 d = rnd.next_int(); + + uint64 d1; + d1 = a << 32; + d1 |= b << 0; + uint64 d2; + d2 = c << 32; + d2 |= d << 0; + + d1 = (d1 & 0xffffffffffff4fffu) | 0x4000u; + d2 = (d2 & 0x3fffffffffffffffu) | 0x8000000000000000u; + + new_guid = { d1, d2 }; + } + while (new_guid == GUID_NONE_FOLDER + || new_guid == GUID_UNIT_FOLDER + || new_guid == GUID_SOUND_FOLDER); - d1 = (d1 & 0xffffffffffff4fffu) | 0x4000u; - d2 = (d2 & 0x3fffffffffffffffu) | 0x8000000000000000u; - return { d1, d2 }; + return new_guid; } public static Guid parse(string guid) diff --git a/tools/level_editor/level.vala b/tools/level_editor/level.vala index 85a57c7200..d15e3e673b 100644 --- a/tools/level_editor/level.vala +++ b/tools/level_editor/level.vala @@ -2,7 +2,6 @@ * Copyright (c) 2012-2025 Daniele Bartolini et al. * SPDX-License-Identifier: GPL-3.0-or-later */ - using Gee; namespace Crown @@ -10,6 +9,21 @@ namespace Crown /// Manages objects in a level. public class Level { + public class Folder + { + public Guid id { get; set; } + public string name { get; set; } + public Guid parent_id { get; set; } + + // Constructor + public Folder(Guid id, string name, Guid parent_id = GUID_ZERO) + { + this.id = id; + this.name = name; + this.parent_id = parent_id; + } + } + public Project _project; // Engine connections @@ -31,13 +45,17 @@ public class Level public double _camera_orthographic_size; public double _camera_target_distance; public CameraViewType _camera_view_type; + public Gee.ArrayList _folders; // Signals public signal void selection_changed(Gee.ArrayList selection); public signal void object_editor_name_changed(Guid object_id, string name); + public signal void level_loaded(); public Level(Database db, RuntimeInstance runtime, Project project) { + _folders = new Gee.ArrayList(); + _project = project; // Engine connections @@ -86,9 +104,11 @@ public class Level if (ret != 0) return ret; + load_folders(); camera_decode(); GLib.Application.get_default().activate_action("camera-view", new GLib.Variant.int32(_camera_view_type)); + _name = name; _path = path; @@ -103,19 +123,56 @@ public class Level // FIXME: hack to keep the LevelTreeView working. _db.key_changed(_id, "units"); + level_loaded(); + return 0; } + public void load_folders() + { + _folders.clear(); + + HashSet folder_ids = _db.get_property_set(_id, "folders", new HashSet()); + + foreach (Guid folder_id in folder_ids) { + if (!_db.has_object(folder_id)) + continue; + + _folders.add(new Folder( + folder_id, + _db.get_property_string(folder_id, "editor.name"), + _db.get_property_guid(folder_id, "parent_folder") + )); + } + } public void save(string name) { string path = Path.build_filename(_project.source_dir(), name + ".level"); + save_folders(); + camera_encode(); _db.save(path, _id); _path = path; _name = name; } + public void save_folders() + { + foreach (Folder folder in _folders) { + _db.set_property_string(folder.id, "editor.name", folder.name); + _db.set_property_guid(folder.id, "parent_folder", folder.parent_id); + _db.add_to_set(_id, "folders", folder.id); + } + } + public void add_folder(Guid id, string name, Guid parent_id = GUID_ZERO) + { + _db.create(id, OBJECT_TYPE_UNIT); + _db.set_property_string(id, "editor.name", name); + _db.set_property_guid(id, "parent_folder", parent_id); + _db.add_to_set(_id, "folders", id); + _folders.add(new Folder(id, name, parent_id)); + } public void spawn_empty_unit() { Guid id = Guid.new_guid(); @@ -176,14 +233,16 @@ public class Level selection_set(ids); } - public void on_unit_spawned(Guid id, string? name, Vector3 pos, Quaternion rot, Vector3 scl) - { - Unit unit = Unit(_db, id); - unit.create(name, pos, rot, scl); - - _db.set_property_string(id, "editor.name", "unit_%04u".printf(_num_units++)); - _db.add_to_set(_id, "units", id); - } + public void on_unit_spawned(Guid id, string? name, Vector3 pos, Quaternion rot, Vector3 scl) + { + Unit unit = Unit(_db, id); + unit.create(name, pos, rot, scl); + + _db.set_property_string(id, "editor.name", "unit_%04u".printf(_num_units++)); + _db.add_to_set(_id, "units", id); + + _db.set_property_guid(id, "parent_folder", GUID_UNIT_FOLDER); + } public void on_sound_spawned(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl, double range, double volume, bool loop) { @@ -192,6 +251,8 @@ public class Level _db.set_property_string (id, "editor.name", "sound_%04u".printf(_num_sounds++)); _db.add_to_set(_id, "sounds", id); + + _db.set_property_guid(id, "parent_folder", GUID_SOUND_FOLDER); } public void on_move_objects(Guid?[] ids, Vector3[] positions, Quaternion[] rotations, Vector3[] scales) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index 7e9ab86b04..a866aa1fd3 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -14,7 +14,7 @@ public class LevelTreeView : Gtk.Box { "GTK_TREE_MODEL_ROW", Gtk.TargetFlags.SAME_APP, 0 } }; - private enum ItemType + public enum ItemType { FOLDER, CAMERA, @@ -23,7 +23,7 @@ public class LevelTreeView : Gtk.Box UNIT } - private enum Column + public enum Column { TYPE, GUID, @@ -233,13 +233,15 @@ public class LevelTreeView : Gtk.Box this.pack_start(tree_control, false, true, 0); this.pack_start(_scrolled_window, true, true, 0); + + _level.level_loaded.connect(() => { + rebuild_tree(); + }); } private bool on_button_pressed(Gdk.EventButton ev) { if (ev.button == Gdk.BUTTON_SECONDARY) { - Gtk.Menu menu = new Gtk.Menu(); - Gtk.MenuItem mi = null; Gtk.TreePath path; Gtk.TreeViewColumn column; if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, out column, null, null)) { @@ -248,15 +250,10 @@ public class LevelTreeView : Gtk.Box _tree_selection.select_path(path); } } else { // Clicked on empty space. - mi = new Gtk.MenuItem.with_label("Create New Folder"); - mi.activate.connect(() => { - create_new_folder(); - }); - menu.add(mi); - menu.show_all(); - menu.popup_at_pointer(ev); return Gdk.EVENT_STOP; } + Gtk.Menu menu = new Gtk.Menu(); + Gtk.MenuItem mi = null; _tree_selection.selected_foreach((model, path, iter) => { Value type; model.get_value(iter, Column.TYPE, out type); @@ -404,31 +401,59 @@ public class LevelTreeView : Gtk.Box folder_name = "Folder Name"; } dialog.destroy(); - add_folder_to_tree(parent_iter, folder_name); + Guid folder_guid = Guid.new_guid(); + add_folder_to_tree(true,parent_iter, folder_name, folder_guid); } else { dialog.destroy(); } } + + private Gtk.TreeIter? find_parent_iter(Guid parent_id) + { + Gtk.TreeIter? found = null; + _tree_store.foreach((model, path, iter) => { + Value val; + model.get_value(iter, Column.GUID, out val); + if (val.holds(typeof(Guid))) { + Guid guid = (Guid)val; + if (guid == parent_id) { + found = iter; + return true; + } + } + return false; + }); + return found; + } - private void add_folder_to_tree(Gtk.TreeIter? parent_iter, string folder_name) + private Guid get_parent_guid(Gtk.TreeIter? parent_iter) { - Guid folder_guid = Guid.new_guid(); - Gtk.TreeIter iter; + if (parent_iter == null) + return GUID_NONE_FOLDER; + + Value val; + _tree_store.get_value(parent_iter, Column.GUID, out val); + return (Guid)val; + } + private void add_folder_to_tree(bool save_to_file,Gtk.TreeIter? parent_iter, string name, Guid guid) + { + if (save_to_file) { + Guid parent_guid = get_parent_guid(parent_iter); + _level.add_folder(guid, name, parent_guid); + } - bool parent_valid = parent_iter != null && _tree_store.iter_is_valid(parent_iter); - - _tree_store.insert_with_values(out iter, parent_valid ? parent_iter : null, -1, + Gtk.TreeIter iter; + _tree_store.insert_with_values(out iter, parent_iter, -1, Column.TYPE, ItemType.FOLDER, - Column.GUID, folder_guid, - Column.NAME, folder_name + Column.GUID, guid, + Column.NAME, name ); - if (parent_valid) { - Gtk.TreePath parent_path = _tree_store.get_path(parent_iter); - _tree_view.expand_row(parent_path, false); + if (parent_iter != null) { + Gtk.TreePath path = _tree_store.get_path(parent_iter); + _tree_view.expand_row(path, false); } } - private void on_drag_data_get(Gtk.Widget widget, Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time) { Gtk.TreeModel model; GLib.List paths = _tree_selection.get_selected_rows(out model); @@ -464,22 +489,18 @@ public class LevelTreeView : Gtk.Box } } private void on_drag_data_received(Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { - // 1. Stop IMMÉDIATEMENT le handler par défaut Signal.stop_emission_by_name(_tree_view, "drag-data-received"); - // 2. Traitement des données string[] data = ((string)selection_data.get_data()).split(";"); if (data.length != 2) return; string source_set = data[0]; string[] guids = data[1].split(","); - // 3. Détection de la cible Gtk.TreePath path; Gtk.TreeViewDropPosition pos; if (!_tree_view.get_dest_row_at_pos(x, y, out path, out pos)) return; - // 4. Détermination du set cible Gtk.TreeIter target_iter; _tree_sort.get_iter(out target_iter, path); @@ -499,10 +520,8 @@ public class LevelTreeView : Gtk.Box target_set = (name == "Units") ? "units" : "sounds"; } - // 5. Validation finale if (target_set == null || target_set == source_set) return; - // 6. Mise à jour de la base foreach (string guid_str in guids) { if (guid_str == "") continue; Guid guid = Guid.parse(guid_str); @@ -510,7 +529,6 @@ public class LevelTreeView : Gtk.Box _db.add_to_set(_level._id, target_set, guid); } - // 7. Finalisation du drag Gtk.drag_finish(context, true, false, time); } @@ -608,11 +626,95 @@ public class LevelTreeView : Gtk.Box return ItemType.UNIT; } + private void add_folders_recursively(string parent_guid, HashTable folder_map) { + foreach (var folder in _level._folders) { + if (folder.parent_id.to_string() == parent_guid) { + Gtk.TreeIter iter; + Gtk.TreeIter? parent_iter = folder_map[parent_guid]; + _tree_store.append(out iter, parent_iter); + + _tree_store.set( + iter, + Column.TYPE, (int)ItemType.FOLDER, + Column.GUID, folder.id, + Column.NAME, folder.name, + -1 + ); + + folder_map[folder.id.to_string()] = iter; + add_folders_recursively(folder.id.to_string(), folder_map); + } + } + } + + public void rebuild_tree() { + _tree_store.clear(); + + var folder_map = new HashTable(str_hash, str_equal); + + Gtk.TreeIter units_iter, sounds_iter; + _tree_store.append(out units_iter, null); + _tree_store.set(units_iter, Column.TYPE, ItemType.FOLDER, Column.GUID, GUID_UNIT_FOLDER, Column.NAME, "Units", -1); + folder_map[GUID_UNIT_FOLDER.to_string()] = units_iter; + + _tree_store.append(out sounds_iter, null); + _tree_store.set(sounds_iter, Column.TYPE, ItemType.FOLDER, Column.GUID, GUID_SOUND_FOLDER, Column.NAME, "Sounds", -1); + folder_map[GUID_SOUND_FOLDER.to_string()] = sounds_iter; + + // Add all folders hierarchically + add_folders_recursively(GUID_UNIT_FOLDER.to_string(), folder_map); + add_folders_recursively(GUID_SOUND_FOLDER.to_string(), folder_map); + + sync_existing_units_and_sounds(); + } + + private void sync_existing_units_and_sounds() + { + sync_items("units", GUID_UNIT_FOLDER, ItemType.UNIT); + sync_items("sounds", GUID_SOUND_FOLDER, ItemType.SOUND); + } + + private void sync_items(string property_name, Guid default_folder, ItemType item_type) + { + var items = _db.get_property_set(_level._id, property_name, new HashSet()); + foreach (Guid guid in items) + { + string item_name = _level.object_editor_name(guid); + + var parent_id = _db.get_property_guid(guid, "parent_folder"); + + if (parent_id == GUID_ZERO) + { + parent_id = default_folder; + } + + Gtk.TreeIter? parent_iter = find_parent_iter(parent_id); + + if (parent_iter != null) + { + Gtk.TreeIter iter; + _tree_store.append(out iter, parent_iter); + _tree_store.set( + iter, + Column.TYPE, item_type, + Column.GUID, guid, + Column.NAME, item_name, + -1 + ); + } + else + { + print("ERROR: Parent iter for " + item_type.to_string() + " GUID " + guid.to_string() + " not found!"); + } + } + } + private void on_database_key_changed(Guid id, string key) { if (id != _level._id || (key != "units" && key != "sounds")) return; + _tree_selection.changed.disconnect(on_tree_selection_changed); - + string[] required_sections = {"Units", "Sounds"}; Gtk.TreeIter[] section_iters = new Gtk.TreeIter[2]; @@ -645,11 +747,10 @@ public class LevelTreeView : Gtk.Box ); } } - // Synchronize only modified keys int target_index = (key == "units") ? 0 : 1; Gtk.TreeIter target_iter = section_iters[target_index]; - + var current_guids = _db.get_property_set(id, key, new HashSet()); var existing_guids = new HashTable(Guid.hash_func, Guid.equal_func); @@ -659,9 +760,9 @@ public class LevelTreeView : Gtk.Box do { Value type_val; _tree_store.get_value(child, Column.TYPE, out type_val); - + if ((ItemType)type_val.get_int() == ItemType.FOLDER) continue; - + Value guid_val; _tree_store.get_value(child, Column.GUID, out guid_val); if (guid_val.holds(typeof(Guid))) { @@ -669,7 +770,7 @@ public class LevelTreeView : Gtk.Box } } while (_tree_store.iter_next(ref child)); } - + existing_guids.foreach((guid, iter) => { if (!current_guids.contains(guid)) { _tree_store.remove(ref iter); @@ -692,7 +793,7 @@ public class LevelTreeView : Gtk.Box } _tree_view.expand_all(); _tree_selection.changed.connect(on_tree_selection_changed); - } + } private void on_filter_entry_text_changed() { diff --git a/tools/resource/global_guids.vala b/tools/resource/global_guids.vala new file mode 100644 index 0000000000..76b99c41be --- /dev/null +++ b/tools/resource/global_guids.vala @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2012-2025 Daniele Bartolini et al. + * SPDX-License-Identifier: GPL-3.0-or-later + */ +namespace Crown +{ + public const Guid GUID_NONE_FOLDER = { 0x0000000000000000u, 0x0000000000000000u }; + public const Guid GUID_UNIT_FOLDER = { 0x0000000000000000u, 0x0000000000000001u }; + public const Guid GUID_SOUND_FOLDER = { 0x0000000000000000u, 0x0000000000000002u }; +} /* namespace Crown */ From d481051a1462fe94cd402edec7d884577f82f3bb Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:24:43 +0000 Subject: [PATCH 07/25] tools: fix many bugs but some bugs during drag and dropping remains --- tools/level_editor/level.vala | 4 +- tools/level_editor/level_tree_view.vala | 311 +++++++++++++++++------- tools/resource/types.vala | 2 + 3 files changed, 225 insertions(+), 92 deletions(-) diff --git a/tools/level_editor/level.vala b/tools/level_editor/level.vala index d15e3e673b..ee18a8a821 100644 --- a/tools/level_editor/level.vala +++ b/tools/level_editor/level.vala @@ -165,9 +165,9 @@ public class Level _db.add_to_set(_id, "folders", folder.id); } } - public void add_folder(Guid id, string name, Guid parent_id = GUID_ZERO) + public void add_folder(Guid id, string name,string type, Guid parent_id = GUID_ZERO) { - _db.create(id, OBJECT_TYPE_UNIT); + _db.create(id, type); _db.set_property_string(id, "editor.name", name); _db.set_property_guid(id, "parent_folder", parent_id); _db.add_to_set(_id, "folders", id); diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index a866aa1fd3..78ef51e105 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -11,8 +11,9 @@ namespace Crown public class LevelTreeView : Gtk.Box { private const Gtk.TargetEntry[] TARGET_ENTRIES = { - { "GTK_TREE_MODEL_ROW", Gtk.TargetFlags.SAME_APP, 0 } - }; + { CROWN_DND_TARGET, Gtk.TargetFlags.SAME_APP, 0 } + }; + private const string CROWN_DND_TARGET = "application/x-crown-set"; public enum ItemType { @@ -93,9 +94,9 @@ public class LevelTreeView : Gtk.Box _filter_entry.search_changed.connect(on_filter_entry_text_changed); _tree_store = new Gtk.TreeStore(Column.COUNT - , typeof(int) // Column.TYPE - , typeof(Guid) // Column.GUID - , typeof(string) // Column.NAME + , typeof(ItemType)// Column.TYPE + , typeof(Guid) // Column.GUID + , typeof(string) // Column.NAME ); _tree_filter = new Gtk.TreeModelFilter(_tree_store, null); @@ -107,7 +108,7 @@ public class LevelTreeView : Gtk.Box model.get_value(iter, Column.TYPE, out type); model.get_value(iter, Column.NAME, out name); - if ((int)type == ItemType.FOLDER) + if (type == ItemType.FOLDER) return true; string name_str = (string)name; @@ -160,24 +161,24 @@ public class LevelTreeView : Gtk.Box column.pack_start(cell_text, true); column.set_cell_data_func(cell_pixbuf, (cell_layout, cell, model, iter) => { Value type; - model.get_value(iter, LevelTreeView.Column.TYPE, out type); + model.get_value(iter, Column.TYPE, out type); - if ((int)type == LevelTreeView.ItemType.FOLDER) + if (type == ItemType.FOLDER) cell.set_property("icon-name", "browser-folder-symbolic"); - else if ((int)type == LevelTreeView.ItemType.UNIT) + else if (type == ItemType.UNIT) cell.set_property("icon-name", "level-object-unit"); - else if ((int)type == LevelTreeView.ItemType.SOUND) + else if (type == ItemType.SOUND) cell.set_property("icon-name", "level-object-sound"); - else if ((int)type == LevelTreeView.ItemType.LIGHT) + else if (type == ItemType.LIGHT) cell.set_property("icon-name", "level-object-light"); - else if ((int)type == LevelTreeView.ItemType.CAMERA) + else if (type == ItemType.CAMERA) cell.set_property("icon-name", "level-object-camera"); else cell.set_property("icon-name", "level-object-unknown"); }); column.set_cell_data_func(cell_text, (cell_layout, cell, model, iter) => { Value name; - model.get_value(iter, LevelTreeView.Column.NAME, out name); + model.get_value(iter, Column.NAME, out name); cell.set_property("text", (string)name); }); @@ -203,6 +204,8 @@ public class LevelTreeView : Gtk.Box _tree_view.enable_model_drag_dest(TARGET_ENTRIES, Gdk.DragAction.MOVE); _tree_view.drag_data_get.connect(on_drag_data_get); _tree_view.drag_data_received.connect(on_drag_data_received); + _tree_view.drag_drop.connect(on_drag_drop); + _tree_view.drag_motion.connect(on_drag_motion); _tree_selection = _tree_view.get_selection(); _tree_selection.set_mode(Gtk.SelectionMode.MULTIPLE); @@ -237,6 +240,7 @@ public class LevelTreeView : Gtk.Box _level.level_loaded.connect(() => { rebuild_tree(); }); + rebuild_tree(); } private bool on_button_pressed(Gdk.EventButton ev) @@ -258,7 +262,7 @@ public class LevelTreeView : Gtk.Box Value type; model.get_value(iter, Column.TYPE, out type); - if ((int)type == ItemType.FOLDER) { + if (type == ItemType.FOLDER) { Gtk.TreeIter parent_iter = Gtk.TreeIter(); Gtk.TreeStore base_store = null; bool valid_conversion = true; @@ -289,20 +293,42 @@ public class LevelTreeView : Gtk.Box } else { valid_conversion = false; } - if (valid_conversion && base_store.iter_is_valid(parent_iter)) { var menu_item = new Gtk.MenuItem.with_label("Create Subfolder"); - menu_item.activate.connect(() => { - if (base_store.iter_is_valid(parent_iter)) { - create_new_folder(parent_iter); + menu_item.activate.connect(() => { + Guid parent_guid = get_parent_guid(parent_iter); + print("Retrieved parent GUID: %s\n", parent_guid.to_string()); + Value? parent_type_val = null; + if (parent_guid == GUID_UNIT_FOLDER) { + parent_type_val = "unit"; + } else if (parent_guid == GUID_SOUND_FOLDER) { + parent_type_val = "sound"; + } else { + parent_type_val = _db.get_property(parent_guid, "_type"); + } + + if (parent_type_val.holds(typeof(string))) { + string parent_type_str = (string)parent_type_val; + + switch (parent_type_str) { + case "unit": + create_new_folder(parent_iter, OBJECT_TYPE_FOLDER_UNIT); + break; + case "sound": + create_new_folder(parent_iter, OBJECT_TYPE_FOLDER_SOUND); + break; + default: + print("Unmanaged or invalid type: %s\n", parent_type_str); + break; + } } else { - create_new_folder(); + print("Error: Parent type is not a string. Type found: %s\n", parent_type_val.type().name()); } }); menu.add(menu_item); } else { - print("Failed to convert path to base template\n"); - } + print("Error: Invalid conversion or iterator\n"); + } } }); @@ -338,7 +364,7 @@ public class LevelTreeView : Gtk.Box _tree_selection.selected_foreach((model, path, iter) => { Value type; model.get_value(iter, Column.TYPE, out type); - if ((int)type == ItemType.FOLDER) + if (type == ItemType.FOLDER) return; Value name; @@ -380,7 +406,7 @@ public class LevelTreeView : Gtk.Box return Gdk.EVENT_PROPAGATE; } - private void create_new_folder(Gtk.TreeIter? parent_iter = null) + private void create_new_folder(Gtk.TreeIter? parent_iter = null,string type) { Gtk.Dialog dialog = new Gtk.Dialog.with_buttons( "New Folder Name", @@ -402,7 +428,7 @@ public class LevelTreeView : Gtk.Box } dialog.destroy(); Guid folder_guid = Guid.new_guid(); - add_folder_to_tree(true,parent_iter, folder_name, folder_guid); + add_folder_to_tree(true,parent_iter, folder_name,type, folder_guid); } else { dialog.destroy(); } @@ -435,11 +461,11 @@ public class LevelTreeView : Gtk.Box _tree_store.get_value(parent_iter, Column.GUID, out val); return (Guid)val; } - private void add_folder_to_tree(bool save_to_file,Gtk.TreeIter? parent_iter, string name, Guid guid) + private void add_folder_to_tree(bool save_to_file,Gtk.TreeIter? parent_iter, string name,string type, Guid guid) { if (save_to_file) { Guid parent_guid = get_parent_guid(parent_iter); - _level.add_folder(guid, name, parent_guid); + _level.add_folder(guid, name,type, parent_guid); } Gtk.TreeIter iter; @@ -454,18 +480,50 @@ public class LevelTreeView : Gtk.Box _tree_view.expand_row(path, false); } } - private void on_drag_data_get(Gtk.Widget widget, Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time) { + + private bool on_drag_drop(Gtk.Widget widget, Gdk.DragContext context, int x, int y, uint time) { + _tree_selection.changed.disconnect(on_tree_selection_changed); + Gtk.drag_get_data( + widget, // will receive 'drag-data-received' signal + context, + Gdk.Atom.intern(CROWN_DND_TARGET, false), + time + ); + Signal.stop_emission_by_name(_tree_view, "drag-drop"); + _tree_selection.changed.connect(on_tree_selection_changed); + return true; + } + + private bool on_drag_motion(Gtk.Widget widget, Gdk.DragContext context, int x, int y, uint time) { + _tree_selection.changed.disconnect(on_tree_selection_changed); + Gtk.TreePath path; + Gtk.TreeViewDropPosition pos; + if (_tree_view.get_dest_row_at_pos(x, y, out path, out pos)) { + _tree_view.set_drag_dest_row(path, pos); + Gdk.drag_status(context, Gdk.DragAction.MOVE, time); + _tree_selection.changed.connect(on_tree_selection_changed); + return true; + } + _tree_selection.changed.connect(on_tree_selection_changed); + return false; + } + + private void on_drag_data_get(Gtk.Widget widget, Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time) { + _tree_selection.changed.disconnect(on_tree_selection_changed); Gtk.TreeModel model; GLib.List paths = _tree_selection.get_selected_rows(out model); string source_set = null; StringBuilder data_builder = new StringBuilder(); - + foreach (Gtk.TreePath path in paths) { Gtk.TreeIter iter; model.get_iter(out iter, path); - + + // Get parent information Gtk.TreeIter parent_iter; - if (model.iter_parent(out parent_iter, iter)) { + bool has_parent = model.iter_parent(out parent_iter, iter); + + if (has_parent) { Value parent_name_val; model.get_value(parent_iter, Column.NAME, out parent_name_val); string parent_name = (string)parent_name_val; @@ -475,63 +533,139 @@ public class LevelTreeView : Gtk.Box } else if (parent_name == "Sounds") { source_set = "sounds"; } - - Value guid_val; - model.get_value(iter, Column.GUID, out guid_val); - Guid guid = (Guid)guid_val; - data_builder.append(guid.to_string() + ","); + } else { + print("Item has no parent (root level)\n"); } - } + Value guid_val; + model.get_value(iter, Column.GUID, out guid_val); + Guid guid = (Guid)guid_val; + data_builder.append(guid.to_string() + ","); + } + + // REMOVE TRAILING COMMA + if (data_builder.len > 0) { + data_builder.truncate(data_builder.len - 1); + } + if (source_set != null) { - string data_str = source_set + ";" + data_builder.str; - selection_data.set(selection_data.get_target(), 8, data_str.data); + string data_str = source_set + ";" + data_builder.str; + var target = Gdk.Atom.intern(CROWN_DND_TARGET, false); + selection_data.set(target, 8, data_str.data); } + _tree_selection.changed.connect(on_tree_selection_changed); } - private void on_drag_data_received(Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { + + private void on_drag_data_received(Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { + _tree_selection.changed.disconnect(on_tree_selection_changed); Signal.stop_emission_by_name(_tree_view, "drag-data-received"); - string[] data = ((string)selection_data.get_data()).split(";"); - if (data.length != 2) return; - + var expected_target = Gdk.Atom.intern(CROWN_DND_TARGET, false); + + string raw_data = (string)selection_data.get_data(); + string[] data = raw_data.split(";"); + string source_set = data[0]; string[] guids = data[1].split(","); Gtk.TreePath path; Gtk.TreeViewDropPosition pos; - if (!_tree_view.get_dest_row_at_pos(x, y, out path, out pos)) return; - + if (!_tree_view.get_dest_row_at_pos(x, y, out path, out pos)) { + _tree_selection.changed.connect(on_tree_selection_changed); + return; + } + Gtk.TreeIter target_iter; _tree_sort.get_iter(out target_iter, path); - - string target_set = null; - Gtk.TreeIter parent_iter; - if (_tree_sort.iter_parent(out parent_iter, target_iter)) { - Value parent_name_val; - _tree_sort.get_value(parent_iter, Column.NAME, out parent_name_val); - string parent_name = (string)parent_name_val; - target_set = (parent_name == "Units") ? "units" : "sounds"; - } else { - Value name_val; - _tree_sort.get_value(target_iter, Column.NAME, out name_val); - string name = (string)name_val; - - target_set = (name == "Units") ? "units" : "sounds"; + string target_parent_guid = ""; + Value target_guid_val; + Value target_type_val; + + _tree_sort.get_value(target_iter, Column.TYPE, out target_type_val); + if ((ItemType)target_type_val == ItemType.FOLDER) { + _tree_sort.get_value(target_iter, Column.GUID, out target_guid_val); + target_parent_guid = ((Guid)target_guid_val).to_string(); + } + else { + Gtk.TreeIter parent_iter; + if (_tree_sort.iter_parent(out parent_iter, target_iter)) { + _tree_sort.get_value(parent_iter, Column.GUID, out target_guid_val); + target_parent_guid = ((Guid)target_guid_val).to_string(); + } + else { + Value name_val; + _tree_sort.get_value(target_iter, Column.NAME, out name_val); + target_parent_guid = ((string)name_val == "Units") + ? GUID_UNIT_FOLDER.to_string() + : GUID_SOUND_FOLDER.to_string(); + } } - if (target_set == null || target_set == source_set) return; - + string? moving_type = null; foreach (string guid_str in guids) { - if (guid_str == "") continue; + if (guid_str.strip().length == 0) continue; + Guid guid = Guid.parse(guid_str); - _db.remove_from_set(_level._id, source_set, guid); - _db.add_to_set(_level._id, target_set, guid); - } + Value? parent_guid_val = _db.get_property(guid, "parent_folder"); + Guid parent_guid; + + + if (parent_guid_val == null) { + if (_db.has_property(guid, "_type")) { + string item_type = (string)_db.get_property(guid, "_type"); + if (item_type == "unit") { + parent_guid = GUID_UNIT_FOLDER; + } else if (item_type == "sound") { + parent_guid = GUID_SOUND_FOLDER; + } else { + print("Error: Unable to determine the root parent of the element %s\n", guid.to_string()); + continue; + } + } else { + print("Error: Unable to retrieve element type %s\n", guid.to_string()); + continue; + } + } else { + parent_guid = (Guid)parent_guid_val; + } + + Value? parent_type_val = null; + if (parent_guid == GUID_UNIT_FOLDER) { + parent_type_val = "unit"; + } else if (parent_guid == GUID_SOUND_FOLDER) { + parent_type_val = "sound"; + } else { + parent_type_val = _db.get_property(parent_guid, "_type"); + } + + string parent_type = (string)parent_type_val; + if (moving_type == null) { + moving_type = parent_type; + } else if (moving_type != parent_type) { + print("Movement prohibited: mix between %s et %s\n", moving_type, parent_type); + return; + } + + if (target_parent_guid == null || target_parent_guid.strip().length == 0) { + print("Error: Invalid target GUID (NULL or empty)\n"); + return; + } + + Guid target_guid; + if (!Guid.try_parse(target_parent_guid, out target_guid)) { + print("Error: Invalid target GUID for parent %s\n", target_parent_guid); + return; + } + + _db.set_property_guid(guid, "parent_folder", target_guid); + } + Gtk.drag_finish(context, true, false, time); + _tree_selection.changed.connect(on_tree_selection_changed); } - + private void on_tree_selection_changed() { _level.selection_changed.disconnect(on_level_selection_changed); @@ -540,7 +674,7 @@ public class LevelTreeView : Gtk.Box _tree_selection.selected_foreach((model, path, iter) => { Value type; model.get_value(iter, Column.TYPE, out type); - if ((int)type == ItemType.FOLDER) + if (type == ItemType.FOLDER) return; Value id; @@ -562,7 +696,7 @@ public class LevelTreeView : Gtk.Box _tree_sort.foreach ((model, path, iter) => { Value type; model.get_value(iter, Column.TYPE, out type); - if ((int)type == ItemType.FOLDER) + if (type == ItemType.FOLDER) return false; Value id; @@ -590,7 +724,7 @@ public class LevelTreeView : Gtk.Box _tree_sort.foreach ((model, path, iter) => { Value type; model.get_value(iter, Column.TYPE, out type); - if ((int)type == ItemType.FOLDER) + if (type == ItemType.FOLDER) return false; Value guid; @@ -635,7 +769,7 @@ public class LevelTreeView : Gtk.Box _tree_store.set( iter, - Column.TYPE, (int)ItemType.FOLDER, + Column.TYPE, ItemType.FOLDER, Column.GUID, folder.id, Column.NAME, folder.name, -1 @@ -651,12 +785,12 @@ public class LevelTreeView : Gtk.Box _tree_store.clear(); var folder_map = new HashTable(str_hash, str_equal); - Gtk.TreeIter units_iter, sounds_iter; + _tree_store.append(out units_iter, null); _tree_store.set(units_iter, Column.TYPE, ItemType.FOLDER, Column.GUID, GUID_UNIT_FOLDER, Column.NAME, "Units", -1); folder_map[GUID_UNIT_FOLDER.to_string()] = units_iter; - + _tree_store.append(out sounds_iter, null); _tree_store.set(sounds_iter, Column.TYPE, ItemType.FOLDER, Column.GUID, GUID_SOUND_FOLDER, Column.NAME, "Sounds", -1); folder_map[GUID_SOUND_FOLDER.to_string()] = sounds_iter; @@ -673,21 +807,28 @@ public class LevelTreeView : Gtk.Box sync_items("units", GUID_UNIT_FOLDER, ItemType.UNIT); sync_items("sounds", GUID_SOUND_FOLDER, ItemType.SOUND); } - + private void sync_items(string property_name, Guid default_folder, ItemType item_type) { var items = _db.get_property_set(_level._id, property_name, new HashSet()); foreach (Guid guid in items) { string item_name = _level.object_editor_name(guid); - - var parent_id = _db.get_property_guid(guid, "parent_folder"); - + + Value? parent_value = _db.get_property(guid, "parent_folder"); + + Guid parent_id = GUID_ZERO; + + if (parent_value != null) + { + parent_id = (Guid)parent_value; + } + if (parent_id == GUID_ZERO) { parent_id = default_folder; } - + Gtk.TreeIter? parent_iter = find_parent_iter(parent_id); if (parent_iter != null) @@ -707,7 +848,7 @@ public class LevelTreeView : Gtk.Box print("ERROR: Parent iter for " + item_type.to_string() + " GUID " + guid.to_string() + " not found!"); } } - } + } private void on_database_key_changed(Guid id, string key) { if (id != _level._id || (key != "units" && key != "sounds")) return; @@ -734,18 +875,6 @@ public class LevelTreeView : Gtk.Box } } while (_tree_store.iter_next(ref iter)); } - - // Create folder if not exist - if (!exists) { - _tree_store.append(out section_iters[i], null); - _tree_store.set( - section_iters[i], - Column.TYPE, ItemType.FOLDER, - Column.GUID, GUID_ZERO, - Column.NAME, required_sections[i], - -1 - ); - } } // Synchronize only modified keys int target_index = (key == "units") ? 0 : 1; @@ -761,7 +890,7 @@ public class LevelTreeView : Gtk.Box Value type_val; _tree_store.get_value(child, Column.TYPE, out type_val); - if ((ItemType)type_val.get_int() == ItemType.FOLDER) continue; + if (type_val == ItemType.FOLDER) continue; Value guid_val; _tree_store.get_value(child, Column.GUID, out guid_val); @@ -780,6 +909,7 @@ public class LevelTreeView : Gtk.Box foreach (Guid guid in current_guids) { if (!existing_guids.contains(guid)) { Gtk.TreeIter new_iter; + ItemType type = (key == "units") ? item_type(Unit(_level._db, guid)) : ItemType.SOUND; _tree_store.append(out new_iter, target_iter); _tree_store.set( @@ -791,6 +921,7 @@ public class LevelTreeView : Gtk.Box ); } } + _tree_view.expand_all(); _tree_selection.changed.connect(on_tree_selection_changed); } diff --git a/tools/resource/types.vala b/tools/resource/types.vala index 6c92c6fbe2..1b12ad1e62 100644 --- a/tools/resource/types.vala +++ b/tools/resource/types.vala @@ -29,4 +29,6 @@ const string OBJECT_TYPE_SPRITE_ANIMATION = "sprite_animation"; const string OBJECT_TYPE_FONT = "font"; const string OBJECT_TYPE_MESH = "mesh"; +const string OBJECT_TYPE_FOLDER_UNIT = "unit_folder"; +const string OBJECT_TYPE_FOLDER_SOUND = "sound_folder"; } /* namespace Crown */ From 16efae3e95a84388768181412750089e874f3c22 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:36:34 +0000 Subject: [PATCH 08/25] scripts: fix some nil error from level_editor.lua , step to reproduce here ... Step to reproduce : 1 : open a .level 2 : drag and drop a folder/unit/sound somewhere 3 : open a new .level (click on save or not the old level) 4 : get the error 14:24:26.245301 editor: lua: ...crown\samples\core\editors\level_editor\level_editor.lua:288: attempt to index a nil value stack traceback: ...crown\samples\core\editors\level_editor\level_editor.lua:288: in function <...crown\samples\core\editors\level_editor\level_editor.lua:285> --- samples/core/editors/level_editor/level_editor.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/samples/core/editors/level_editor/level_editor.lua b/samples/core/editors/level_editor/level_editor.lua index 82afc3b6c8..b88e98b0a1 100644 --- a/samples/core/editors/level_editor/level_editor.lua +++ b/samples/core/editors/level_editor/level_editor.lua @@ -244,7 +244,9 @@ end function Selection:clear() for k, v in pairs(self._ids) do - LevelEditor._objects[v]:on_selected(false) + if LevelEditor._objects[v] ~= nil then + LevelEditor._objects[v]:on_selected(false) + end end self._ids = {} end @@ -285,7 +287,9 @@ end function Selection:set(ids) self:clear() for k, v in pairs(ids) do - LevelEditor._objects[v]:on_selected(true) + if LevelEditor._objects[v] ~= nil then + LevelEditor._objects[v]:on_selected(true) + end end self._ids = ids end From d4c50da4462c5d0fdc79f082d8af5d172998c740 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:47:21 +0000 Subject: [PATCH 09/25] tools: some bugfixes on on_database_key_changed --- tools/level_editor/level_tree_view.vala | 145 ++++++++++++++++-------- 1 file changed, 99 insertions(+), 46 deletions(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index 78ef51e105..a3a1bb6a51 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -659,11 +659,11 @@ public class LevelTreeView : Gtk.Box return; } - _db.set_property_guid(guid, "parent_folder", target_guid); + _db.set_property(guid, "parent_folder", target_guid); } - Gtk.drag_finish(context, true, false, time); _tree_selection.changed.connect(on_tree_selection_changed); + Gtk.drag_finish(context, true, false, time); } private void on_tree_selection_changed() @@ -853,45 +853,53 @@ public class LevelTreeView : Gtk.Box private void on_database_key_changed(Guid id, string key) { if (id != _level._id || (key != "units" && key != "sounds")) return; - _tree_selection.changed.disconnect(on_tree_selection_changed); - - string[] required_sections = {"Units", "Sounds"}; - Gtk.TreeIter[] section_iters = new Gtk.TreeIter[2]; - for (int i = 0; i < required_sections.length; i++) { - bool exists = false; - Gtk.TreeIter iter; - - // Search folder - if (_tree_store.get_iter_first(out iter)) { - do { - Value v; - _tree_store.get_value(iter, Column.NAME, out v); - if ((string)v == required_sections[i]) { - section_iters[i] = iter; - exists = true; + // Define target folder and item type based on the key + Guid target_folder = (key == "units") ? GUID_UNIT_FOLDER : GUID_SOUND_FOLDER; + ItemType item_type = (key == "units") ? ItemType.UNIT : ItemType.SOUND; + + // Track existing GUIDs within the folder + var existing_guids = new HashTable(Guid.hash_func, Guid.equal_func); + Gtk.TreeIter target_iter; + bool folder_exists = false; + + // Check if the target folder already exists + if (_tree_store.get_iter_first(out target_iter)) { + do { + Value guid_val; + _tree_store.get_value(target_iter, Column.GUID, out guid_val); + + // Ensure the value holds a Guid + if (guid_val.holds(typeof(Guid))) { + Guid guid = (Guid)guid_val; + + // Check if the extracted Guid matches the target folder + if (guid == target_folder) { + folder_exists = true; break; } - } while (_tree_store.iter_next(ref iter)); - } + } + } while (_tree_store.iter_next(ref target_iter)); } - // Synchronize only modified keys - int target_index = (key == "units") ? 0 : 1; - Gtk.TreeIter target_iter = section_iters[target_index]; - var current_guids = _db.get_property_set(id, key, new HashSet()); - var existing_guids = new HashTable(Guid.hash_func, Guid.equal_func); - // Update elements + // If the folder doesn't exist, create it + if (!folder_exists) { + _tree_store.append(out target_iter, null); + _tree_store.set( + target_iter, + Column.TYPE, ItemType.FOLDER, + Column.GUID, target_folder, + Column.NAME, (key == "units") ? "Units" : "Sounds", + -1 + ); + } + + // Populate existing GUIDs in the folder Gtk.TreeIter child; if (_tree_store.iter_children(out child, target_iter)) { do { - Value type_val; - _tree_store.get_value(child, Column.TYPE, out type_val); - - if (type_val == ItemType.FOLDER) continue; - Value guid_val; _tree_store.get_value(child, Column.GUID, out guid_val); if (guid_val.holds(typeof(Guid))) { @@ -900,32 +908,77 @@ public class LevelTreeView : Gtk.Box } while (_tree_store.iter_next(ref child)); } + // Retrieve updated GUIDs from the database + var current_guids = _db.get_property_set(id, key, new HashSet()); + + // Remove outdated items but avoid removing subfolders existing_guids.foreach((guid, iter) => { - if (!current_guids.contains(guid)) { - _tree_store.remove(ref iter); + // Only remove items that are not folders + Value item_type_val; + _tree_store.get_value(iter, Column.TYPE, out item_type_val); + if (item_type_val.holds(typeof(ItemType)) && (ItemType)item_type_val != ItemType.FOLDER) { + if (!current_guids.contains(guid)) { + _tree_store.remove(ref iter); + } } }); + // Synchronize parent folders and add missing items foreach (Guid guid in current_guids) { + // Check if the GUID is already present in the folder if (!existing_guids.contains(guid)) { - Gtk.TreeIter new_iter; - - ItemType type = (key == "units") ? item_type(Unit(_level._db, guid)) : ItemType.SOUND; - _tree_store.append(out new_iter, target_iter); - _tree_store.set( - new_iter, - Column.TYPE, type, - Column.GUID, guid, - Column.NAME, _level.object_editor_name(guid), - -1 - ); + string item_name = _level.object_editor_name(guid); + + Value? parent_value = _db.get_property(guid, "parent_folder"); + Guid parent_id = (parent_value != null) ? (Guid)parent_value : target_folder; + + // Check if we have the correct parent iter for the item + Gtk.TreeIter? parent_iter = find_parent_iter(parent_id); + + if (parent_iter != null) { + // Ensure we don't duplicate this item under the same parent + bool is_duplicate = false; + + // Check if the GUID is already under the parent folder + Gtk.TreeIter sibling_iter; + if (_tree_store.iter_children(out sibling_iter, parent_iter)) { + do { + Value sibling_guid_val; + _tree_store.get_value(sibling_iter, Column.GUID, out sibling_guid_val); + + Guid sibling_guid; + if (sibling_guid_val.holds(typeof(Guid))) { + sibling_guid = (Guid)sibling_guid_val; // Cast the Value directly to Guid + if (sibling_guid == guid) { + is_duplicate = true; + break; // If found, don't add it again + } + } + + } while (_tree_store.iter_next(ref sibling_iter)); + } + // Add the item if it is not a duplicate + if (!is_duplicate) { + Gtk.TreeIter iter; + _tree_store.append(out iter, parent_iter); + _tree_store.set( + iter, + Column.TYPE, item_type, + Column.GUID, guid, + Column.NAME, item_name, + -1 + ); + } + } else { + print("ERROR: Parent iter for GUID %s not found in the tree!\n", guid.to_string()); + } } } _tree_view.expand_all(); _tree_selection.changed.connect(on_tree_selection_changed); - } - + } + private void on_filter_entry_text_changed() { _tree_selection.changed.disconnect(on_tree_selection_changed); From 2005391317b38f465c64cf5c549c5fb80bf3ec15 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Tue, 25 Mar 2025 06:27:21 +0000 Subject: [PATCH 10/25] tools: many bug and crashes fix to level tree drag and drop implementation --- tools/level_editor/level_tree_view.vala | 249 +++++++++++++++--------- 1 file changed, 156 insertions(+), 93 deletions(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index a3a1bb6a51..5110d756d7 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -277,7 +277,7 @@ public class LevelTreeView : Gtk.Box if (inner_model is Gtk.TreeStore) { base_store = (Gtk.TreeStore)inner_model; } else { - print("The internal model of the filter is not a TreeStore.\n"); + print("The internal model of the filter is not a TreeStore."); return; } @@ -297,12 +297,12 @@ public class LevelTreeView : Gtk.Box var menu_item = new Gtk.MenuItem.with_label("Create Subfolder"); menu_item.activate.connect(() => { Guid parent_guid = get_parent_guid(parent_iter); - print("Retrieved parent GUID: %s\n", parent_guid.to_string()); + print("Retrieved parent GUID: %s", parent_guid.to_string()); Value? parent_type_val = null; if (parent_guid == GUID_UNIT_FOLDER) { - parent_type_val = "unit"; + parent_type_val = "unit_folder"; } else if (parent_guid == GUID_SOUND_FOLDER) { - parent_type_val = "sound"; + parent_type_val = "sound_folder"; } else { parent_type_val = _db.get_property(parent_guid, "_type"); } @@ -311,23 +311,23 @@ public class LevelTreeView : Gtk.Box string parent_type_str = (string)parent_type_val; switch (parent_type_str) { - case "unit": + case "unit_folder": create_new_folder(parent_iter, OBJECT_TYPE_FOLDER_UNIT); break; - case "sound": + case "sound_folder": create_new_folder(parent_iter, OBJECT_TYPE_FOLDER_SOUND); break; default: - print("Unmanaged or invalid type: %s\n", parent_type_str); + print("Unmanaged or invalid type: %s", parent_type_str); break; } } else { - print("Error: Parent type is not a string. Type found: %s\n", parent_type_val.type().name()); + print("Error: Parent type is not a string. Type found: %s", parent_type_val.type().name()); } }); menu.add(menu_item); } else { - print("Error: Invalid conversion or iterator\n"); + print("Error: Invalid conversion or iterator"); } } }); @@ -508,70 +508,96 @@ public class LevelTreeView : Gtk.Box return false; } - private void on_drag_data_get(Gtk.Widget widget, Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time) { + private void on_drag_data_get(Gtk.Widget widget, Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time) { _tree_selection.changed.disconnect(on_tree_selection_changed); Gtk.TreeModel model; GLib.List paths = _tree_selection.get_selected_rows(out model); - string source_set = null; StringBuilder data_builder = new StringBuilder(); - + string source_set = null; + foreach (Gtk.TreePath path in paths) { Gtk.TreeIter iter; model.get_iter(out iter, path); + // Get GUID of the selected item + Value guid_val; + model.get_value(iter, Column.GUID, out guid_val); + Guid guid = (Guid) guid_val; - // Get parent information - Gtk.TreeIter parent_iter; - bool has_parent = model.iter_parent(out parent_iter, iter); - - if (has_parent) { - Value parent_name_val; - model.get_value(parent_iter, Column.NAME, out parent_name_val); - string parent_name = (string)parent_name_val; - - if (parent_name == "Units") { - source_set = "units"; - } else if (parent_name == "Sounds") { - source_set = "sounds"; + if (guid == GUID_ZERO) { + print("Warning: GUID is GUID_ZERO, skipping...\n"); + continue; + } + Value? item_type = null; + if (guid == GUID_UNIT_FOLDER) { + item_type = "unit_folder"; + } else if (guid == GUID_SOUND_FOLDER) { + item_type = "sound_folder"; + } else { + item_type = _db.get_property(guid, "_type"); + } + + if (item_type != null) { + string type_str = (string)item_type; + switch (type_str) { + case "unit": + source_set = "units"; + break; + case "sound": + source_set = "sounds"; + break; + default: + continue; } } else { - print("Item has no parent (root level)\n"); + print("Error: Item type is not a string or is null for GUID: %s\n", guid.to_string()); + continue; } - - Value guid_val; - model.get_value(iter, Column.GUID, out guid_val); - Guid guid = (Guid)guid_val; data_builder.append(guid.to_string() + ","); } - - // REMOVE TRAILING COMMA + + // REMOVE TRAILING COMMA if (data_builder.len > 0) { data_builder.truncate(data_builder.len - 1); } - - if (source_set != null) { - string data_str = source_set + ";" + data_builder.str; - var target = Gdk.Atom.intern(CROWN_DND_TARGET, false); + + if (data_builder.len > 0) { + string data_str = @"$source_set;$(data_builder.str)"; + var target = Gdk.Atom.intern(CROWN_DND_TARGET, false); selection_data.set(target, 8, data_str.data); + } else { + print("Invalid data: no GUIDs found."); + Gtk.drag_finish(context, false, false, time); + _tree_selection.changed.connect(on_tree_selection_changed); + return; } + _tree_selection.changed.connect(on_tree_selection_changed); } + private void on_drag_data_received(Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { _tree_selection.changed.disconnect(on_tree_selection_changed); Signal.stop_emission_by_name(_tree_view, "drag-data-received"); var expected_target = Gdk.Atom.intern(CROWN_DND_TARGET, false); + if (selection_data.get_target() != expected_target) { + Gtk.drag_finish(context, false, false, time); + return; + } string raw_data = (string)selection_data.get_data(); - string[] data = raw_data.split(";"); - - string source_set = data[0]; + + raw_data = raw_data.strip(); + + string[] data = raw_data.split(";", 2); + + string source_set = data[0].strip(); string[] guids = data[1].split(","); - + Gtk.TreePath path; Gtk.TreeViewDropPosition pos; if (!_tree_view.get_dest_row_at_pos(x, y, out path, out pos)) { - _tree_selection.changed.connect(on_tree_selection_changed); + Gtk.drag_finish(context, false, false, time); return; } @@ -581,90 +607,127 @@ public class LevelTreeView : Gtk.Box string target_parent_guid = ""; Value target_guid_val; Value target_type_val; - + _tree_sort.get_value(target_iter, Column.TYPE, out target_type_val); - if ((ItemType)target_type_val == ItemType.FOLDER) { + if (target_type_val == ItemType.FOLDER) { _tree_sort.get_value(target_iter, Column.GUID, out target_guid_val); target_parent_guid = ((Guid)target_guid_val).to_string(); - } - else { - Gtk.TreeIter parent_iter; - if (_tree_sort.iter_parent(out parent_iter, target_iter)) { - _tree_sort.get_value(parent_iter, Column.GUID, out target_guid_val); - target_parent_guid = ((Guid)target_guid_val).to_string(); - } - else { - Value name_val; - _tree_sort.get_value(target_iter, Column.NAME, out name_val); - target_parent_guid = ((string)name_val == "Units") - ? GUID_UNIT_FOLDER.to_string() - : GUID_SOUND_FOLDER.to_string(); - } + } else { + return; } string? moving_type = null; foreach (string guid_str in guids) { - if (guid_str.strip().length == 0) continue; - + if (guid_str.strip().length == 0) continue; Guid guid = Guid.parse(guid_str); Value? parent_guid_val = _db.get_property(guid, "parent_folder"); Guid parent_guid; - if (parent_guid_val == null) { - if (_db.has_property(guid, "_type")) { - string item_type = (string)_db.get_property(guid, "_type"); - if (item_type == "unit") { - parent_guid = GUID_UNIT_FOLDER; - } else if (item_type == "sound") { - parent_guid = GUID_SOUND_FOLDER; - } else { - print("Error: Unable to determine the root parent of the element %s\n", guid.to_string()); - continue; - } + string item_type = (string)_db.get_property(guid, "_type"); + if (item_type == "unit") { + parent_guid = GUID_UNIT_FOLDER; + } else if (item_type == "sound") { + parent_guid = GUID_SOUND_FOLDER; } else { - print("Error: Unable to retrieve element type %s\n", guid.to_string()); - continue; + print("Error: Unable to determine the root parent of element %s", guid.to_string()); + continue; } } else { parent_guid = (Guid)parent_guid_val; } - + Value? parent_type_val = null; if (parent_guid == GUID_UNIT_FOLDER) { - parent_type_val = "unit"; + parent_type_val = "unit"; } else if (parent_guid == GUID_SOUND_FOLDER) { - parent_type_val = "sound"; + parent_type_val = "sound"; } else { - parent_type_val = _db.get_property(parent_guid, "_type"); + parent_type_val = _db.get_property(parent_guid, "_type"); } - + string parent_type = (string)parent_type_val; - if (moving_type == null) { - moving_type = parent_type; - } else if (moving_type != parent_type) { - print("Movement prohibited: mix between %s et %s\n", moving_type, parent_type); - return; - } - + moving_type = parent_type; + if (target_parent_guid == null || target_parent_guid.strip().length == 0) { - print("Error: Invalid target GUID (NULL or empty)\n"); + print("Error: Invalid target GUID (NULL or empty)"); + _tree_selection.changed.connect(on_tree_selection_changed); return; } - + Guid target_guid; if (!Guid.try_parse(target_parent_guid, out target_guid)) { - print("Error: Invalid target GUID for parent %s\n", target_parent_guid); + print("Error: Invalid target GUID for parent %s", target_parent_guid); + _tree_selection.changed.connect(on_tree_selection_changed); return; } - - _db.set_property(guid, "parent_folder", target_guid); + + Value? target_type_val_db = null; + string target_type; + + if (target_guid == GUID_UNIT_FOLDER) { + target_type = "unit_folder"; + } else if (target_guid == GUID_SOUND_FOLDER) { + target_type = "sound_folder"; + } else { + target_type_val_db = _db.get_property(target_guid, "_type"); + + if (target_type_val_db != null) { + target_type = (string)target_type_val_db; + } else { + print("Error: Unable to determine target type for GUID %s\n", target_guid.to_string()); + _tree_selection.changed.connect(on_tree_selection_changed); + return; + } + } + + string current_item_type = (string)_db.get_property(guid, "_type"); + if (current_item_type == "unit_folder" || current_item_type == "sound_folder") { + continue; + } + + if (!((current_item_type == "unit" && target_type == "unit_folder") || + (current_item_type == "sound" && target_type == "sound_folder"))) { + print("Error: Cannot move a %s into %s", current_item_type, target_type); + _tree_selection.changed.connect(on_tree_selection_changed); + return; + } + + if (target_guid != GUID_UNIT_FOLDER && target_guid != GUID_SOUND_FOLDER) { + _db.set_property(guid, "parent_folder", target_guid); + } + TreeIter? parent_iter = find_parent_iter(target_guid); + if (parent_iter != null) { + Gtk.TreeIter iter; + if (target_type == "unit_folder") { + _tree_store.append(out iter, parent_iter); + _tree_store.set( + iter, + Column.TYPE, ItemType.UNIT, + Column.GUID, guid, + Column.NAME, _level.object_editor_name(guid), + -1 + ); + } else if (target_type == "sound_folder") { + _tree_store.append(out iter, parent_iter); + _tree_store.set( + iter, + Column.TYPE, ItemType.SOUND, + Column.GUID, guid, + Column.NAME, _level.object_editor_name(guid), + -1 + ); + } else { + print("target_type folder : " + target_type + " not managed"); + } + } + + _tree_view.expand_all(); } - - _tree_selection.changed.connect(on_tree_selection_changed); + _tree_selection.changed.connect(on_tree_selection_changed); Gtk.drag_finish(context, true, false, time); - } + } private void on_tree_selection_changed() { @@ -970,7 +1033,7 @@ public class LevelTreeView : Gtk.Box ); } } else { - print("ERROR: Parent iter for GUID %s not found in the tree!\n", guid.to_string()); + print("ERROR: Parent iter for GUID %s not found in the tree!", guid.to_string()); } } } From a0cb1a7ebb32a70df5c515d358ca056a283d334c Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 07:48:29 +0000 Subject: [PATCH 11/25] tools: (LevelTreeView) move drag and drop , folder orga , item synching into a dedicated file + (regression) fixed if i drag and drop an item into a root folder, he don't make has need to be saved (*) + remove unecessary disconnect and connect calls for on_tree_selection_changed --- .../level_editor/level_tree_organization.vala | 518 +++++++++++++++++ tools/level_editor/level_tree_view.vala | 529 +----------------- 2 files changed, 543 insertions(+), 504 deletions(-) create mode 100644 tools/level_editor/level_tree_organization.vala diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala new file mode 100644 index 0000000000..c6945d7b22 --- /dev/null +++ b/tools/level_editor/level_tree_organization.vala @@ -0,0 +1,518 @@ +/* +* Copyright (c) 2012-2025 Daniele Bartolini et al. +* SPDX-License-Identifier: GPL-3.0-or-later +*/ +using Gee; +using Gtk; + +namespace Crown +{ + // Manage drag and drop , folder organization and items synching for the LevelTreeView + public class LevelTreeOrganization + { + public static bool on_drag_drop_internal(LevelTreeView view_i, string d_target, Gtk.Widget widget, Gdk.DragContext context, int x, int y, uint time) { + Gtk.drag_get_data( + widget, // will receive 'drag-data-received' signal + context, + Gdk.Atom.intern(d_target, false), + time + ); + Signal.stop_emission_by_name(view_i.tree_view, "drag-drop"); + return true; + } + + public static bool on_drag_motion_internal(LevelTreeView view_i, Gtk.Widget widget, Gdk.DragContext context, int x, int y, uint time) { + Gtk.TreePath path; + Gtk.TreeViewDropPosition pos; + if (view_i.tree_view.get_dest_row_at_pos(x, y, out path, out pos)) { + view_i.tree_view.set_drag_dest_row(path, pos); + Gdk.drag_status(context, Gdk.DragAction.MOVE, time); + return true; + } + return false; + } + + public static void on_drag_data_get_internal(LevelTreeView view_i, string d_target, Gtk.Widget widget, Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time) { + Gtk.TreeModel model; + GLib.List paths = view_i.tree_selection.get_selected_rows(out model); + StringBuilder data_builder = new StringBuilder(); + string source_set = null; + + foreach (Gtk.TreePath path in paths) { + Gtk.TreeIter iter; + model.get_iter(out iter, path); + // Get GUID of the selected item + Value guid_val; + model.get_value(iter, LevelTreeView.Column.GUID, out guid_val); + Guid guid = (Guid) guid_val; + + if (guid == GUID_ZERO) { + print("Warning: GUID is GUID_ZERO, skipping...\n"); + continue; + } + Value? item_type = null; + if (guid == GUID_UNIT_FOLDER) { + item_type = "unit_folder"; + } else if (guid == GUID_SOUND_FOLDER) { + item_type = "sound_folder"; + } else { + item_type = view_i.db.get_property(guid, "_type"); + } + + if (item_type != null) { + string type_str = (string)item_type; + switch (type_str) { + case "unit": + source_set = "units"; + break; + case "sound": + source_set = "sounds"; + break; + default: + continue; + } + } else { + print("Error: Item type is not a string or is null for GUID: %s\n", guid.to_string()); + continue; + } + data_builder.append(guid.to_string() + ","); + } + + // REMOVE TRAILING COMMA + if (data_builder.len > 0) { + data_builder.truncate(data_builder.len - 1); + } + + if (data_builder.len > 0) { + string data_str = @"$source_set;$(data_builder.str)"; + var target = Gdk.Atom.intern(d_target, false); + selection_data.set(target, 8, data_str.data); + } else { + print("Invalid data: no GUIDs found."); + Gtk.drag_finish(context, false, false, time); + return; + } + } + + public static void on_drag_data_received_internal(LevelTreeView view_i, string d_target, Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { + view_i.tree_selection.changed.disconnect(view_i.on_tree_selection_changed); + Signal.stop_emission_by_name(view_i.tree_view, "drag-data-received"); + + var expected_target = Gdk.Atom.intern(d_target, false); + if (selection_data.get_target() != expected_target) { + Gtk.drag_finish(context, false, false, time); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + return; + } + + string raw_data = (string)selection_data.get_data(); + + raw_data = raw_data.strip(); + + string[] data = raw_data.split(";", 2); + + string source_set = data[0].strip(); + string[] guids = data[1].split(","); + + Gtk.TreePath path; + Gtk.TreeViewDropPosition pos; + if (!view_i.tree_view.get_dest_row_at_pos(x, y, out path, out pos)) { + Gtk.drag_finish(context, false, false, time); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + return; + } + + Gtk.TreeIter target_iter; + view_i.tree_sort.get_iter(out target_iter, path); + + string target_parent_guid = ""; + Value target_guid_val; + Value target_type_val; + + view_i.tree_sort.get_value(target_iter, LevelTreeView.Column.TYPE, out target_type_val); + if (target_type_val == LevelTreeView.ItemType.FOLDER) { + view_i.tree_sort.get_value(target_iter, LevelTreeView.Column.GUID, out target_guid_val); + target_parent_guid = ((Guid)target_guid_val).to_string(); + } else { + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + return; + } + + string? moving_type = null; + foreach (string guid_str in guids) { + if (guid_str.strip().length == 0) continue; + Guid guid = Guid.parse(guid_str); + + Value? parent_guid_val = view_i.db.get_property(guid, "parent_folder"); + Guid parent_guid; + + if (parent_guid_val == null) { + string item_type = (string)view_i.db.get_property(guid, "_type"); + if (item_type == "unit") { + parent_guid = GUID_UNIT_FOLDER; + } else if (item_type == "sound") { + parent_guid = GUID_SOUND_FOLDER; + } else { + print("Error: Unable to determine the root parent of element %s", guid.to_string()); + continue; + } + } else { + parent_guid = (Guid)parent_guid_val; + } + + Value? parent_type_val = null; + if (parent_guid == GUID_UNIT_FOLDER) { + parent_type_val = "unit"; + } else if (parent_guid == GUID_SOUND_FOLDER) { + parent_type_val = "sound"; + } else { + parent_type_val = view_i.db.get_property(parent_guid, "_type"); + } + + string parent_type = (string)parent_type_val; + moving_type = parent_type; + + if (target_parent_guid == null || target_parent_guid.strip().length == 0) { + print("Error: Invalid target GUID (NULL or empty)"); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + return; + } + + Guid target_guid; + if (!Guid.try_parse(target_parent_guid, out target_guid)) { + print("Error: Invalid target GUID for parent %s", target_parent_guid); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + return; + } + + Value? target_type_val_db = null; + string target_type; + + if (target_guid == GUID_UNIT_FOLDER) { + target_type = "unit_folder"; + } else if (target_guid == GUID_SOUND_FOLDER) { + target_type = "sound_folder"; + } else { + target_type_val_db = view_i.db.get_property(target_guid, "_type"); + + if (target_type_val_db != null) { + target_type = (string)target_type_val_db; + } else { + print("Error: Unable to determine target type for GUID %s\n", target_guid.to_string()); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + return; + } + } + + string current_item_type = (string)view_i.db.get_property(guid, "_type"); + if (current_item_type == "unit_folder" || current_item_type == "sound_folder") { + continue; + } + + if (!((current_item_type == "unit" && target_type == "unit_folder") || + (current_item_type == "sound" && target_type == "sound_folder"))) { + print("Error: Cannot move a %s into %s", current_item_type, target_type); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + return; + } + + view_i.db.set_property(guid, "parent_folder", target_guid); + + TreeIter? parent_iter = find_parent_iter(view_i, target_guid); + if (parent_iter != null) { + Gtk.TreeIter iter; + if (target_type == "unit_folder") { + view_i.tree_store.append(out iter, parent_iter); + view_i.tree_store.set( + iter, + LevelTreeView.Column.TYPE, LevelTreeView.ItemType.UNIT, + LevelTreeView.Column.GUID, guid, + LevelTreeView.Column.NAME, view_i.level.object_editor_name(guid), + -1 + ); + } else if (target_type == "sound_folder") { + view_i.tree_store.append(out iter, parent_iter); + view_i.tree_store.set( + iter, + LevelTreeView.Column.TYPE, LevelTreeView.ItemType.SOUND, + LevelTreeView.Column.GUID, guid, + LevelTreeView.Column.NAME, view_i.level.object_editor_name(guid), + -1 + ); + } else { + print("target_type folder : " + target_type + " not managed"); + } + } + + view_i.tree_view.expand_all(); + } + Gtk.drag_finish(context, true, false, time); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + } + + + public static void on_database_key_changed_internal(LevelTreeView view_i, Guid id, string key) { + if (id != view_i.level._id || (key != "units" && key != "sounds")) return; + + view_i.tree_selection.changed.disconnect(view_i.on_tree_selection_changed); + + // Define target folder and item type based on the key + Guid target_folder = (key == "units") ? GUID_UNIT_FOLDER : GUID_SOUND_FOLDER; + LevelTreeView.ItemType item_type = (key == "units") ? LevelTreeView.ItemType.UNIT : LevelTreeView.ItemType.SOUND; + + // Track existing GUIDs within the folder + var existing_guids = new HashTable(Guid.hash_func, Guid.equal_func); + Gtk.TreeIter target_iter; + bool folder_exists = false; + + // Check if the target folder already exists + if (view_i.tree_store.get_iter_first(out target_iter)) { + do { + Value guid_val; + view_i.tree_store.get_value(target_iter, LevelTreeView.Column.GUID, out guid_val); + + // Ensure the value holds a Guid + if (guid_val.holds(typeof(Guid))) { + Guid guid = (Guid)guid_val; + + // Check if the extracted Guid matches the target folder + if (guid == target_folder) { + folder_exists = true; + break; + } + } + } while (view_i.tree_store.iter_next(ref target_iter)); + } + + + // If the folder doesn't exist, create it + if (!folder_exists) { + view_i.tree_store.append(out target_iter, null); + view_i.tree_store.set( + target_iter, + LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, + LevelTreeView.Column.GUID, target_folder, + LevelTreeView.Column.NAME, (key == "units") ? "Units" : "Sounds", + -1 + ); + } + + // Populate existing GUIDs in the folder + Gtk.TreeIter child; + if (view_i.tree_store.iter_children(out child, target_iter)) { + do { + Value guid_val; + view_i.tree_store.get_value(child, LevelTreeView.Column.GUID, out guid_val); + if (guid_val.holds(typeof(Guid))) { + existing_guids[(Guid)guid_val] = child; + } + } while (view_i.tree_store.iter_next(ref child)); + } + + // Retrieve updated GUIDs from the database + var current_guids = view_i.db.get_property_set(id, key, new HashSet()); + + // Remove outdated items but avoid removing subfolders + existing_guids.foreach((guid, iter) => { + // Only remove items that are not folders + Value item_type_val; + view_i.tree_store.get_value(iter, LevelTreeView.Column.TYPE, out item_type_val); + if (item_type_val.holds(typeof(LevelTreeView.ItemType)) && (LevelTreeView.ItemType)item_type_val != LevelTreeView.ItemType.FOLDER) { + if (!current_guids.contains(guid)) { + view_i.tree_store.remove(ref iter); + } + } + }); + + // Synchronize parent folders and add missing items + foreach (Guid guid in current_guids) { + // Check if the GUID is already present in the folder + if (!existing_guids.contains(guid)) { + string item_name = view_i.level.object_editor_name(guid); + + Value? parent_value = view_i.db.get_property(guid, "parent_folder"); + Guid parent_id = (parent_value != null) ? (Guid)parent_value : target_folder; + + // Check if we have the correct parent iter for the item + Gtk.TreeIter? parent_iter = find_parent_iter(view_i, parent_id); + + if (parent_iter != null) { + // Ensure we don't duplicate this item under the same parent + bool is_duplicate = false; + + // Check if the GUID is already under the parent folder + Gtk.TreeIter sibling_iter; + if (view_i.tree_store.iter_children(out sibling_iter, parent_iter)) { + do { + Value sibling_guid_val; + view_i.tree_store.get_value(sibling_iter, LevelTreeView.Column.GUID, out sibling_guid_val); + + Guid sibling_guid; + if (sibling_guid_val.holds(typeof(Guid))) { + sibling_guid = (Guid)sibling_guid_val; // Cast the Value directly to Guid + if (sibling_guid == guid) { + is_duplicate = true; + break; // If found, don't add it again + } + } + + } while (view_i.tree_store.iter_next(ref sibling_iter)); + } + // Add the item if it is not a duplicate + if (!is_duplicate) { + Gtk.TreeIter iter; + view_i.tree_store.append(out iter, parent_iter); + view_i.tree_store.set( + iter, + LevelTreeView.Column.TYPE, item_type, + LevelTreeView.Column.GUID, guid, + LevelTreeView.Column.NAME, item_name, + -1 + ); + } + } else { + print("ERROR: Parent iter for GUID %s not found in the tree!", guid.to_string()); + } + } + } + + view_i.tree_view.expand_all(); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + } + + + public static void rebuild_tree(LevelTreeView view_i) { + view_i.tree_store.clear(); + + var folder_map = new HashTable(str_hash, str_equal); + Gtk.TreeIter units_iter, sounds_iter; + + view_i.tree_store.append(out units_iter, null); + view_i.tree_store.set(units_iter, LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, LevelTreeView.Column.GUID, GUID_UNIT_FOLDER, LevelTreeView.Column.NAME, "Units", -1); + folder_map[GUID_UNIT_FOLDER.to_string()] = units_iter; + + view_i.tree_store.append(out sounds_iter, null); + view_i.tree_store.set(sounds_iter, LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, LevelTreeView.Column.GUID, GUID_SOUND_FOLDER, LevelTreeView.Column.NAME, "Sounds", -1); + folder_map[GUID_SOUND_FOLDER.to_string()] = sounds_iter; + + // Add all folders hierarchically + add_folders_recursively(view_i, GUID_UNIT_FOLDER.to_string(), folder_map); + add_folders_recursively(view_i, GUID_SOUND_FOLDER.to_string(), folder_map); + + sync_existing_units_and_sounds(view_i); + } + + public static void sync_existing_units_and_sounds(LevelTreeView view_i) + { + sync_items(view_i, "units", GUID_UNIT_FOLDER, LevelTreeView.ItemType.UNIT); + sync_items(view_i, "sounds", GUID_SOUND_FOLDER, LevelTreeView.ItemType.SOUND); + } + + public static void sync_items(LevelTreeView view_i, string property_name, Guid default_folder, LevelTreeView.ItemType item_type) + { + var items = view_i.db.get_property_set(view_i.level._id, property_name, new HashSet()); + foreach (Guid guid in items) + { + string item_name = view_i.level.object_editor_name(guid); + + Value? parent_value = view_i.db.get_property(guid, "parent_folder"); + + Guid parent_id = GUID_ZERO; + + if (parent_value != null) + { + parent_id = (Guid)parent_value; + } + + if (parent_id == GUID_ZERO) + { + parent_id = default_folder; + } + + Gtk.TreeIter? parent_iter = LevelTreeOrganization.find_parent_iter(view_i, parent_id); + + if (parent_iter != null) + { + Gtk.TreeIter iter; + view_i.tree_store.append(out iter, parent_iter); + view_i.tree_store.set( + iter, + LevelTreeView.Column.TYPE, item_type, + LevelTreeView.Column.GUID, guid, + LevelTreeView.Column.NAME, item_name, + -1 + ); + } + else + { + print("ERROR: Parent iter for " + item_type.to_string() + " GUID " + guid.to_string() + " not found!"); + } + } + } + private static void add_folders_recursively(LevelTreeView view_i, string parent_guid, HashTable folder_map) { + foreach (var folder in view_i.level._folders) { + if (folder.parent_id.to_string() == parent_guid) { + Gtk.TreeIter iter; + Gtk.TreeIter? parent_iter = folder_map[parent_guid]; + view_i.tree_store.append(out iter, parent_iter); + + view_i.tree_store.set( + iter, + LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, + LevelTreeView.Column.GUID, folder.id, + LevelTreeView.Column.NAME, folder.name, + -1 + ); + + folder_map[folder.id.to_string()] = iter; + add_folders_recursively(view_i, folder.id.to_string(), folder_map); + } + } + } + public static void add_folder_to_tree(LevelTreeView view_i, bool save_to_file, Gtk.TreeIter? parent_iter, string name,string type, Guid guid) + { + if (save_to_file) { + Guid parent_guid = get_parent_guid(view_i, parent_iter); + view_i.level.add_folder(guid, name,type, parent_guid); + } + + Gtk.TreeIter iter; + view_i.tree_store.insert_with_values(out iter, parent_iter, -1, + LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, + LevelTreeView.Column.GUID, guid, + LevelTreeView.Column.NAME, name + ); + + if (parent_iter != null) { + Gtk.TreePath path = view_i.tree_store.get_path(parent_iter); + view_i.tree_view.expand_row(path, false); + } + } + public static Guid get_parent_guid(LevelTreeView view_i, Gtk.TreeIter? parent_iter) + { + if (parent_iter == null) + return GUID_NONE_FOLDER; + + Value val; + view_i.tree_store.get_value(parent_iter, LevelTreeView.Column.GUID, out val); + return (Guid)val; + } + public static Gtk.TreeIter? find_parent_iter(LevelTreeView view_i, Guid parent_id) + { + Gtk.TreeIter? found = null; + view_i.tree_store.foreach((model, path, iter) => { + Value val; + model.get_value(iter, LevelTreeView.Column.GUID, out val); + if (val.holds(typeof(Guid))) { + Guid guid = (Guid)val; + if (guid == parent_id) { + found = iter; + return true; + } + } + return false; + }); + return found; + } + } +} \ No newline at end of file diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index 5110d756d7..b461d2c38a 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -76,6 +76,21 @@ public class LevelTreeView : Gtk.Box private Gtk.Popover _sort_items_popover; private Gtk.MenuButton _sort_items; + // Getters + public Level level { get { return _level; } } + public Database db { get { return _db; } } + public EntrySearch filter_entry { get { return _filter_entry; } } + public Gtk.TreeStore tree_store { get { return _tree_store; } } + public Gtk.TreeModelFilter tree_filter { get { return _tree_filter; } } + public Gtk.TreeModelSort tree_sort { get { return _tree_sort; } } + public Gtk.TreeView tree_view { get { return _tree_view; } } + public Gtk.TreeSelection tree_selection { get { return _tree_selection; } } + public Gtk.ScrolledWindow scrolled_window { get { return _scrolled_window; } } + public SortMode sort_mode { get { return _sort_mode; } } + public Gtk.Box sort_items_box { get { return _sort_items_box; } } + public Gtk.Popover sort_items_popover { get { return _sort_items_popover; } } + public Gtk.MenuButton sort_items { get { return _sort_items; } } + public LevelTreeView(Database db, Level level) { Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0); @@ -238,9 +253,9 @@ public class LevelTreeView : Gtk.Box this.pack_start(_scrolled_window, true, true, 0); _level.level_loaded.connect(() => { - rebuild_tree(); + LevelTreeOrganization.rebuild_tree(this); }); - rebuild_tree(); + LevelTreeOrganization.rebuild_tree(this); } private bool on_button_pressed(Gdk.EventButton ev) @@ -296,7 +311,7 @@ public class LevelTreeView : Gtk.Box if (valid_conversion && base_store.iter_is_valid(parent_iter)) { var menu_item = new Gtk.MenuItem.with_label("Create Subfolder"); menu_item.activate.connect(() => { - Guid parent_guid = get_parent_guid(parent_iter); + Guid parent_guid = LevelTreeOrganization.get_parent_guid(this, parent_iter); print("Retrieved parent GUID: %s", parent_guid.to_string()); Value? parent_type_val = null; if (parent_guid == GUID_UNIT_FOLDER) { @@ -428,308 +443,29 @@ public class LevelTreeView : Gtk.Box } dialog.destroy(); Guid folder_guid = Guid.new_guid(); - add_folder_to_tree(true,parent_iter, folder_name,type, folder_guid); + LevelTreeOrganization.add_folder_to_tree(this, true, parent_iter, folder_name,type, folder_guid); } else { dialog.destroy(); } } - - private Gtk.TreeIter? find_parent_iter(Guid parent_id) - { - Gtk.TreeIter? found = null; - _tree_store.foreach((model, path, iter) => { - Value val; - model.get_value(iter, Column.GUID, out val); - if (val.holds(typeof(Guid))) { - Guid guid = (Guid)val; - if (guid == parent_id) { - found = iter; - return true; - } - } - return false; - }); - return found; - } - - private Guid get_parent_guid(Gtk.TreeIter? parent_iter) - { - if (parent_iter == null) - return GUID_NONE_FOLDER; - - Value val; - _tree_store.get_value(parent_iter, Column.GUID, out val); - return (Guid)val; - } - private void add_folder_to_tree(bool save_to_file,Gtk.TreeIter? parent_iter, string name,string type, Guid guid) - { - if (save_to_file) { - Guid parent_guid = get_parent_guid(parent_iter); - _level.add_folder(guid, name,type, parent_guid); - } - Gtk.TreeIter iter; - _tree_store.insert_with_values(out iter, parent_iter, -1, - Column.TYPE, ItemType.FOLDER, - Column.GUID, guid, - Column.NAME, name - ); - - if (parent_iter != null) { - Gtk.TreePath path = _tree_store.get_path(parent_iter); - _tree_view.expand_row(path, false); - } - } - private bool on_drag_drop(Gtk.Widget widget, Gdk.DragContext context, int x, int y, uint time) { - _tree_selection.changed.disconnect(on_tree_selection_changed); - Gtk.drag_get_data( - widget, // will receive 'drag-data-received' signal - context, - Gdk.Atom.intern(CROWN_DND_TARGET, false), - time - ); - Signal.stop_emission_by_name(_tree_view, "drag-drop"); - _tree_selection.changed.connect(on_tree_selection_changed); - return true; + return LevelTreeOrganization.on_drag_drop_internal(this, CROWN_DND_TARGET, widget, context, x, y, time); } private bool on_drag_motion(Gtk.Widget widget, Gdk.DragContext context, int x, int y, uint time) { - _tree_selection.changed.disconnect(on_tree_selection_changed); - Gtk.TreePath path; - Gtk.TreeViewDropPosition pos; - if (_tree_view.get_dest_row_at_pos(x, y, out path, out pos)) { - _tree_view.set_drag_dest_row(path, pos); - Gdk.drag_status(context, Gdk.DragAction.MOVE, time); - _tree_selection.changed.connect(on_tree_selection_changed); - return true; - } - _tree_selection.changed.connect(on_tree_selection_changed); - return false; + return LevelTreeOrganization.on_drag_motion_internal(this, widget, context, x, y, time); } private void on_drag_data_get(Gtk.Widget widget, Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time) { - _tree_selection.changed.disconnect(on_tree_selection_changed); - Gtk.TreeModel model; - GLib.List paths = _tree_selection.get_selected_rows(out model); - StringBuilder data_builder = new StringBuilder(); - string source_set = null; - - foreach (Gtk.TreePath path in paths) { - Gtk.TreeIter iter; - model.get_iter(out iter, path); - // Get GUID of the selected item - Value guid_val; - model.get_value(iter, Column.GUID, out guid_val); - Guid guid = (Guid) guid_val; - - if (guid == GUID_ZERO) { - print("Warning: GUID is GUID_ZERO, skipping...\n"); - continue; - } - Value? item_type = null; - if (guid == GUID_UNIT_FOLDER) { - item_type = "unit_folder"; - } else if (guid == GUID_SOUND_FOLDER) { - item_type = "sound_folder"; - } else { - item_type = _db.get_property(guid, "_type"); - } - - if (item_type != null) { - string type_str = (string)item_type; - switch (type_str) { - case "unit": - source_set = "units"; - break; - case "sound": - source_set = "sounds"; - break; - default: - continue; - } - } else { - print("Error: Item type is not a string or is null for GUID: %s\n", guid.to_string()); - continue; - } - data_builder.append(guid.to_string() + ","); - } - - // REMOVE TRAILING COMMA - if (data_builder.len > 0) { - data_builder.truncate(data_builder.len - 1); - } - - if (data_builder.len > 0) { - string data_str = @"$source_set;$(data_builder.str)"; - var target = Gdk.Atom.intern(CROWN_DND_TARGET, false); - selection_data.set(target, 8, data_str.data); - } else { - print("Invalid data: no GUIDs found."); - Gtk.drag_finish(context, false, false, time); - _tree_selection.changed.connect(on_tree_selection_changed); - return; - } - - _tree_selection.changed.connect(on_tree_selection_changed); + LevelTreeOrganization.on_drag_data_get_internal(this, CROWN_DND_TARGET,widget, context, selection_data, info, time); } - private void on_drag_data_received(Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { - _tree_selection.changed.disconnect(on_tree_selection_changed); - Signal.stop_emission_by_name(_tree_view, "drag-data-received"); - - var expected_target = Gdk.Atom.intern(CROWN_DND_TARGET, false); - if (selection_data.get_target() != expected_target) { - Gtk.drag_finish(context, false, false, time); - return; - } - - string raw_data = (string)selection_data.get_data(); - - raw_data = raw_data.strip(); - - string[] data = raw_data.split(";", 2); - - string source_set = data[0].strip(); - string[] guids = data[1].split(","); - - Gtk.TreePath path; - Gtk.TreeViewDropPosition pos; - if (!_tree_view.get_dest_row_at_pos(x, y, out path, out pos)) { - Gtk.drag_finish(context, false, false, time); - return; - } - - Gtk.TreeIter target_iter; - _tree_sort.get_iter(out target_iter, path); - - string target_parent_guid = ""; - Value target_guid_val; - Value target_type_val; - - _tree_sort.get_value(target_iter, Column.TYPE, out target_type_val); - if (target_type_val == ItemType.FOLDER) { - _tree_sort.get_value(target_iter, Column.GUID, out target_guid_val); - target_parent_guid = ((Guid)target_guid_val).to_string(); - } else { - return; - } - - string? moving_type = null; - foreach (string guid_str in guids) { - if (guid_str.strip().length == 0) continue; - Guid guid = Guid.parse(guid_str); - - Value? parent_guid_val = _db.get_property(guid, "parent_folder"); - Guid parent_guid; - - if (parent_guid_val == null) { - string item_type = (string)_db.get_property(guid, "_type"); - if (item_type == "unit") { - parent_guid = GUID_UNIT_FOLDER; - } else if (item_type == "sound") { - parent_guid = GUID_SOUND_FOLDER; - } else { - print("Error: Unable to determine the root parent of element %s", guid.to_string()); - continue; - } - } else { - parent_guid = (Guid)parent_guid_val; - } - - Value? parent_type_val = null; - if (parent_guid == GUID_UNIT_FOLDER) { - parent_type_val = "unit"; - } else if (parent_guid == GUID_SOUND_FOLDER) { - parent_type_val = "sound"; - } else { - parent_type_val = _db.get_property(parent_guid, "_type"); - } - - string parent_type = (string)parent_type_val; - moving_type = parent_type; - - if (target_parent_guid == null || target_parent_guid.strip().length == 0) { - print("Error: Invalid target GUID (NULL or empty)"); - _tree_selection.changed.connect(on_tree_selection_changed); - return; - } - - Guid target_guid; - if (!Guid.try_parse(target_parent_guid, out target_guid)) { - print("Error: Invalid target GUID for parent %s", target_parent_guid); - _tree_selection.changed.connect(on_tree_selection_changed); - return; - } - - Value? target_type_val_db = null; - string target_type; - - if (target_guid == GUID_UNIT_FOLDER) { - target_type = "unit_folder"; - } else if (target_guid == GUID_SOUND_FOLDER) { - target_type = "sound_folder"; - } else { - target_type_val_db = _db.get_property(target_guid, "_type"); - - if (target_type_val_db != null) { - target_type = (string)target_type_val_db; - } else { - print("Error: Unable to determine target type for GUID %s\n", target_guid.to_string()); - _tree_selection.changed.connect(on_tree_selection_changed); - return; - } - } - - string current_item_type = (string)_db.get_property(guid, "_type"); - if (current_item_type == "unit_folder" || current_item_type == "sound_folder") { - continue; - } - - if (!((current_item_type == "unit" && target_type == "unit_folder") || - (current_item_type == "sound" && target_type == "sound_folder"))) { - print("Error: Cannot move a %s into %s", current_item_type, target_type); - _tree_selection.changed.connect(on_tree_selection_changed); - return; - } - - if (target_guid != GUID_UNIT_FOLDER && target_guid != GUID_SOUND_FOLDER) { - _db.set_property(guid, "parent_folder", target_guid); - } - TreeIter? parent_iter = find_parent_iter(target_guid); - if (parent_iter != null) { - Gtk.TreeIter iter; - if (target_type == "unit_folder") { - _tree_store.append(out iter, parent_iter); - _tree_store.set( - iter, - Column.TYPE, ItemType.UNIT, - Column.GUID, guid, - Column.NAME, _level.object_editor_name(guid), - -1 - ); - } else if (target_type == "sound_folder") { - _tree_store.append(out iter, parent_iter); - _tree_store.set( - iter, - Column.TYPE, ItemType.SOUND, - Column.GUID, guid, - Column.NAME, _level.object_editor_name(guid), - -1 - ); - } else { - print("target_type folder : " + target_type + " not managed"); - } - } - - _tree_view.expand_all(); - } - _tree_selection.changed.connect(on_tree_selection_changed); - Gtk.drag_finish(context, true, false, time); + LevelTreeOrganization.on_drag_data_received_internal(this, CROWN_DND_TARGET, widget, context, x, y, selection_data, info, time); } - private void on_tree_selection_changed() + public void on_tree_selection_changed() { _level.selection_changed.disconnect(on_level_selection_changed); @@ -823,223 +559,8 @@ public class LevelTreeView : Gtk.Box return ItemType.UNIT; } - private void add_folders_recursively(string parent_guid, HashTable folder_map) { - foreach (var folder in _level._folders) { - if (folder.parent_id.to_string() == parent_guid) { - Gtk.TreeIter iter; - Gtk.TreeIter? parent_iter = folder_map[parent_guid]; - _tree_store.append(out iter, parent_iter); - - _tree_store.set( - iter, - Column.TYPE, ItemType.FOLDER, - Column.GUID, folder.id, - Column.NAME, folder.name, - -1 - ); - - folder_map[folder.id.to_string()] = iter; - add_folders_recursively(folder.id.to_string(), folder_map); - } - } - } - - public void rebuild_tree() { - _tree_store.clear(); - - var folder_map = new HashTable(str_hash, str_equal); - Gtk.TreeIter units_iter, sounds_iter; - - _tree_store.append(out units_iter, null); - _tree_store.set(units_iter, Column.TYPE, ItemType.FOLDER, Column.GUID, GUID_UNIT_FOLDER, Column.NAME, "Units", -1); - folder_map[GUID_UNIT_FOLDER.to_string()] = units_iter; - - _tree_store.append(out sounds_iter, null); - _tree_store.set(sounds_iter, Column.TYPE, ItemType.FOLDER, Column.GUID, GUID_SOUND_FOLDER, Column.NAME, "Sounds", -1); - folder_map[GUID_SOUND_FOLDER.to_string()] = sounds_iter; - - // Add all folders hierarchically - add_folders_recursively(GUID_UNIT_FOLDER.to_string(), folder_map); - add_folders_recursively(GUID_SOUND_FOLDER.to_string(), folder_map); - - sync_existing_units_and_sounds(); - } - - private void sync_existing_units_and_sounds() - { - sync_items("units", GUID_UNIT_FOLDER, ItemType.UNIT); - sync_items("sounds", GUID_SOUND_FOLDER, ItemType.SOUND); - } - - private void sync_items(string property_name, Guid default_folder, ItemType item_type) - { - var items = _db.get_property_set(_level._id, property_name, new HashSet()); - foreach (Guid guid in items) - { - string item_name = _level.object_editor_name(guid); - - Value? parent_value = _db.get_property(guid, "parent_folder"); - - Guid parent_id = GUID_ZERO; - - if (parent_value != null) - { - parent_id = (Guid)parent_value; - } - - if (parent_id == GUID_ZERO) - { - parent_id = default_folder; - } - - Gtk.TreeIter? parent_iter = find_parent_iter(parent_id); - - if (parent_iter != null) - { - Gtk.TreeIter iter; - _tree_store.append(out iter, parent_iter); - _tree_store.set( - iter, - Column.TYPE, item_type, - Column.GUID, guid, - Column.NAME, item_name, - -1 - ); - } - else - { - print("ERROR: Parent iter for " + item_type.to_string() + " GUID " + guid.to_string() + " not found!"); - } - } - } - private void on_database_key_changed(Guid id, string key) { - if (id != _level._id || (key != "units" && key != "sounds")) return; - - _tree_selection.changed.disconnect(on_tree_selection_changed); - - // Define target folder and item type based on the key - Guid target_folder = (key == "units") ? GUID_UNIT_FOLDER : GUID_SOUND_FOLDER; - ItemType item_type = (key == "units") ? ItemType.UNIT : ItemType.SOUND; - - // Track existing GUIDs within the folder - var existing_guids = new HashTable(Guid.hash_func, Guid.equal_func); - Gtk.TreeIter target_iter; - bool folder_exists = false; - - // Check if the target folder already exists - if (_tree_store.get_iter_first(out target_iter)) { - do { - Value guid_val; - _tree_store.get_value(target_iter, Column.GUID, out guid_val); - - // Ensure the value holds a Guid - if (guid_val.holds(typeof(Guid))) { - Guid guid = (Guid)guid_val; - - // Check if the extracted Guid matches the target folder - if (guid == target_folder) { - folder_exists = true; - break; - } - } - } while (_tree_store.iter_next(ref target_iter)); - } - - - // If the folder doesn't exist, create it - if (!folder_exists) { - _tree_store.append(out target_iter, null); - _tree_store.set( - target_iter, - Column.TYPE, ItemType.FOLDER, - Column.GUID, target_folder, - Column.NAME, (key == "units") ? "Units" : "Sounds", - -1 - ); - } - - // Populate existing GUIDs in the folder - Gtk.TreeIter child; - if (_tree_store.iter_children(out child, target_iter)) { - do { - Value guid_val; - _tree_store.get_value(child, Column.GUID, out guid_val); - if (guid_val.holds(typeof(Guid))) { - existing_guids[(Guid)guid_val] = child; - } - } while (_tree_store.iter_next(ref child)); - } - - // Retrieve updated GUIDs from the database - var current_guids = _db.get_property_set(id, key, new HashSet()); - - // Remove outdated items but avoid removing subfolders - existing_guids.foreach((guid, iter) => { - // Only remove items that are not folders - Value item_type_val; - _tree_store.get_value(iter, Column.TYPE, out item_type_val); - if (item_type_val.holds(typeof(ItemType)) && (ItemType)item_type_val != ItemType.FOLDER) { - if (!current_guids.contains(guid)) { - _tree_store.remove(ref iter); - } - } - }); - - // Synchronize parent folders and add missing items - foreach (Guid guid in current_guids) { - // Check if the GUID is already present in the folder - if (!existing_guids.contains(guid)) { - string item_name = _level.object_editor_name(guid); - - Value? parent_value = _db.get_property(guid, "parent_folder"); - Guid parent_id = (parent_value != null) ? (Guid)parent_value : target_folder; - - // Check if we have the correct parent iter for the item - Gtk.TreeIter? parent_iter = find_parent_iter(parent_id); - - if (parent_iter != null) { - // Ensure we don't duplicate this item under the same parent - bool is_duplicate = false; - - // Check if the GUID is already under the parent folder - Gtk.TreeIter sibling_iter; - if (_tree_store.iter_children(out sibling_iter, parent_iter)) { - do { - Value sibling_guid_val; - _tree_store.get_value(sibling_iter, Column.GUID, out sibling_guid_val); - - Guid sibling_guid; - if (sibling_guid_val.holds(typeof(Guid))) { - sibling_guid = (Guid)sibling_guid_val; // Cast the Value directly to Guid - if (sibling_guid == guid) { - is_duplicate = true; - break; // If found, don't add it again - } - } - - } while (_tree_store.iter_next(ref sibling_iter)); - } - // Add the item if it is not a duplicate - if (!is_duplicate) { - Gtk.TreeIter iter; - _tree_store.append(out iter, parent_iter); - _tree_store.set( - iter, - Column.TYPE, item_type, - Column.GUID, guid, - Column.NAME, item_name, - -1 - ); - } - } else { - print("ERROR: Parent iter for GUID %s not found in the tree!", guid.to_string()); - } - } - } - - _tree_view.expand_all(); - _tree_selection.changed.connect(on_tree_selection_changed); + LevelTreeOrganization.on_database_key_changed_internal(this, id, key); } private void on_filter_entry_text_changed() From f747c50d2e37994e0a6cc44e7dd7523f15a43574 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 12:37:35 +0000 Subject: [PATCH 12/25] tools: save root folder into .level + simplify a bit the code + do not trigger set_property if parent_guid != target_guid --- tools/level_editor/level.vala | 70 ++++++++--- .../level_editor/level_tree_organization.vala | 109 +++++++++--------- tools/resource/global_guids.vala | 35 ++++-- 3 files changed, 134 insertions(+), 80 deletions(-) diff --git a/tools/level_editor/level.vala b/tools/level_editor/level.vala index ee18a8a821..87527ee361 100644 --- a/tools/level_editor/level.vala +++ b/tools/level_editor/level.vala @@ -104,7 +104,6 @@ public class Level if (ret != 0) return ret; - load_folders(); camera_decode(); GLib.Application.get_default().activate_action("camera-view", new GLib.Variant.int32(_camera_view_type)); @@ -123,28 +122,60 @@ public class Level // FIXME: hack to keep the LevelTreeView working. _db.key_changed(_id, "units"); + load_folders(); + level_loaded(); return 0; } - public void load_folders() - { - _folders.clear(); - - HashSet folder_ids = _db.get_property_set(_id, "folders", new HashSet()); - - foreach (Guid folder_id in folder_ids) { - if (!_db.has_object(folder_id)) - continue; - - _folders.add(new Folder( - folder_id, - _db.get_property_string(folder_id, "editor.name"), - _db.get_property_guid(folder_id, "parent_folder") - )); - } - } + public void load_folders() { + _folders.clear(); + + HashSet folder_ids = _db.get_property_set(_id, "folders", new HashSet()); + bool needToSave = false; + + foreach (var root_info in get_root_folder_info()) { + needToSave |= ensure_root_folder(folder_ids, root_info.guid, root_info.name, root_info.object_type); + } + if (_path != null && needToSave) { + _db.save(_path, _id); + } + + foreach (Guid folder_id in folder_ids) { + _folders.add(load_or_create_folder(folder_id)); + } + } + + private bool ensure_root_folder(HashSet folder_ids, Guid folder_id, string name, string type) { + if (!folder_ids.contains(folder_id)) { + if (_path != null) { + add_folder(folder_id, name, type, GUID_ZERO); + } + folder_ids.add(folder_id); + return true; + } + return false; + } + + private Folder load_or_create_folder(Guid folder_id) { + if (_db.has_object(folder_id)) { + return new Folder( + folder_id, + _db.get_property_string(folder_id, "editor.name"), + _db.get_property_guid(folder_id, "parent_folder") + ); + } else { + foreach (var root_info in get_root_folder_info()) { + if (folder_id == root_info.guid) { + return new Folder(folder_id, root_info.name, GUID_ZERO); + } + } + print("ERROR: Unknown Folder ID - %s", folder_id.to_string()); + return null; + } + } + public void save(string name) { string path = Path.build_filename(_project.source_dir(), name + ".level"); @@ -165,7 +196,7 @@ public class Level _db.add_to_set(_id, "folders", folder.id); } } - public void add_folder(Guid id, string name,string type, Guid parent_id = GUID_ZERO) + public void add_folder(Guid id, string name, string type, Guid parent_id = GUID_ZERO) { _db.create(id, type); _db.set_property_string(id, "editor.name", name); @@ -173,6 +204,7 @@ public class Level _db.add_to_set(_id, "folders", id); _folders.add(new Folder(id, name, parent_id)); } + public void spawn_empty_unit() { Guid id = Guid.new_guid(); diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index c6945d7b22..01cb6adaa5 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -50,14 +50,7 @@ namespace Crown print("Warning: GUID is GUID_ZERO, skipping...\n"); continue; } - Value? item_type = null; - if (guid == GUID_UNIT_FOLDER) { - item_type = "unit_folder"; - } else if (guid == GUID_SOUND_FOLDER) { - item_type = "sound_folder"; - } else { - item_type = view_i.db.get_property(guid, "_type"); - } + Value? item_type = view_i.db.get_property(guid, "_type"); if (item_type != null) { string type_str = (string)item_type; @@ -160,14 +153,7 @@ namespace Crown parent_guid = (Guid)parent_guid_val; } - Value? parent_type_val = null; - if (parent_guid == GUID_UNIT_FOLDER) { - parent_type_val = "unit"; - } else if (parent_guid == GUID_SOUND_FOLDER) { - parent_type_val = "sound"; - } else { - parent_type_val = view_i.db.get_property(parent_guid, "_type"); - } + Value? parent_type_val = view_i.db.get_property(parent_guid, "_type"); string parent_type = (string)parent_type_val; moving_type = parent_type; @@ -215,31 +201,31 @@ namespace Crown view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); return; } - - view_i.db.set_property(guid, "parent_folder", target_guid); - + if (parent_guid != target_guid) { + view_i.db.set_property(guid, "parent_folder", target_guid); + } + TreeIter? parent_iter = find_parent_iter(view_i, target_guid); if (parent_iter != null) { Gtk.TreeIter iter; - if (target_type == "unit_folder") { - view_i.tree_store.append(out iter, parent_iter); - view_i.tree_store.set( - iter, - LevelTreeView.Column.TYPE, LevelTreeView.ItemType.UNIT, - LevelTreeView.Column.GUID, guid, - LevelTreeView.Column.NAME, view_i.level.object_editor_name(guid), - -1 - ); - } else if (target_type == "sound_folder") { - view_i.tree_store.append(out iter, parent_iter); - view_i.tree_store.set( - iter, - LevelTreeView.Column.TYPE, LevelTreeView.ItemType.SOUND, - LevelTreeView.Column.GUID, guid, - LevelTreeView.Column.NAME, view_i.level.object_editor_name(guid), - -1 - ); - } else { + bool type_managed = false; + + foreach (var root_info in get_root_folder_info()) { + if (root_info.object_type == target_type) { + view_i.tree_store.append(out iter, parent_iter); + view_i.tree_store.set( + iter, + LevelTreeView.Column.TYPE, root_info.contains_item_type, + LevelTreeView.Column.GUID, guid, + LevelTreeView.Column.NAME, view_i.level.object_editor_name(guid), + -1 + ); + type_managed = true; + break; + } + } + + if (!type_managed) { print("target_type folder : " + target_type + " not managed"); } } @@ -380,28 +366,45 @@ namespace Crown view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); } - public static void rebuild_tree(LevelTreeView view_i) { view_i.tree_store.clear(); - var folder_map = new HashTable(str_hash, str_equal); - Gtk.TreeIter units_iter, sounds_iter; - - view_i.tree_store.append(out units_iter, null); - view_i.tree_store.set(units_iter, LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, LevelTreeView.Column.GUID, GUID_UNIT_FOLDER, LevelTreeView.Column.NAME, "Units", -1); - folder_map[GUID_UNIT_FOLDER.to_string()] = units_iter; - - view_i.tree_store.append(out sounds_iter, null); - view_i.tree_store.set(sounds_iter, LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, LevelTreeView.Column.GUID, GUID_SOUND_FOLDER, LevelTreeView.Column.NAME, "Sounds", -1); - folder_map[GUID_SOUND_FOLDER.to_string()] = sounds_iter; - // Add all folders hierarchically - add_folders_recursively(view_i, GUID_UNIT_FOLDER.to_string(), folder_map); - add_folders_recursively(view_i, GUID_SOUND_FOLDER.to_string(), folder_map); + foreach (var root_info in get_root_folder_info()) { + Gtk.TreeIter iter; + view_i.tree_store.append(out iter, null); + view_i.tree_store.set( + iter, + LevelTreeView.Column.TYPE, root_info.item_type, + LevelTreeView.Column.GUID, root_info.guid, + LevelTreeView.Column.NAME, root_info.name, + -1 + ); + folder_map[root_info.guid.to_string()] = iter; + } + + foreach (var folder in view_i.level._folders) { + if (folder.id in get_root_folder_guids()) { + continue; + } + + Gtk.TreeIter iter; + Gtk.TreeIter? parent_iter = folder_map[folder.parent_id.to_string()]; + + view_i.tree_store.append(out iter, parent_iter); + view_i.tree_store.set( + iter, + LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, + LevelTreeView.Column.GUID, folder.id, + LevelTreeView.Column.NAME, folder.name, + -1 + ); + + folder_map[folder.id.to_string()] = iter; + } sync_existing_units_and_sounds(view_i); } - public static void sync_existing_units_and_sounds(LevelTreeView view_i) { sync_items(view_i, "units", GUID_UNIT_FOLDER, LevelTreeView.ItemType.UNIT); diff --git a/tools/resource/global_guids.vala b/tools/resource/global_guids.vala index 76b99c41be..b9de46e13d 100644 --- a/tools/resource/global_guids.vala +++ b/tools/resource/global_guids.vala @@ -1,10 +1,29 @@ /* - * Copyright (c) 2012-2025 Daniele Bartolini et al. - * SPDX-License-Identifier: GPL-3.0-or-later - */ -namespace Crown -{ - public const Guid GUID_NONE_FOLDER = { 0x0000000000000000u, 0x0000000000000000u }; - public const Guid GUID_UNIT_FOLDER = { 0x0000000000000000u, 0x0000000000000001u }; - public const Guid GUID_SOUND_FOLDER = { 0x0000000000000000u, 0x0000000000000002u }; +* Copyright (c) 2012-2025 Daniele Bartolini et al. +* SPDX-License-Identifier: GPL-3.0-or-later +*/ +namespace Crown { + static Guid GUID_NONE_FOLDER = { 0x0000000000000000u, 0x0000000000000000u }; + static Guid GUID_UNIT_FOLDER = { 0x0000000000000000u, 0x0000000000000001u }; + static Guid GUID_SOUND_FOLDER = { 0x0000000000000000u, 0x0000000000000002u }; + + struct RootFolderInfo { + public Guid guid; + public string name; + public string object_type; + public LevelTreeView.ItemType item_type; + public LevelTreeView.ItemType contains_item_type; + public string contains_item_type_str; + } + + static RootFolderInfo[] get_root_folder_info() { + return new RootFolderInfo[] { + { GUID_UNIT_FOLDER, "Units", OBJECT_TYPE_FOLDER_UNIT, LevelTreeView.ItemType.FOLDER, LevelTreeView.ItemType.UNIT, "unit"}, + { GUID_SOUND_FOLDER, "Sounds", OBJECT_TYPE_FOLDER_SOUND, LevelTreeView.ItemType.FOLDER, LevelTreeView.ItemType.SOUND, "sound"} + }; + } + + static Guid[] get_root_folder_guids() { + return new Guid[] { GUID_UNIT_FOLDER, GUID_SOUND_FOLDER }; + } } /* namespace Crown */ From dd6fc4768bf4dad0070124c41beac9ffbe255cfe Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 12:46:30 +0000 Subject: [PATCH 13/25] tools: do not use hardcoded values in sync_existing_units_and_sounds + optimize sync_items method --- .../level_editor/level_tree_organization.vala | 57 +++++++++---------- tools/resource/global_guids.vala | 5 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index 01cb6adaa5..1b95aae923 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -407,51 +407,50 @@ namespace Crown } public static void sync_existing_units_and_sounds(LevelTreeView view_i) { - sync_items(view_i, "units", GUID_UNIT_FOLDER, LevelTreeView.ItemType.UNIT); - sync_items(view_i, "sounds", GUID_SOUND_FOLDER, LevelTreeView.ItemType.SOUND); + foreach (var folder in get_root_folder_info()) + { + sync_items(view_i, folder.contains_source_set_str, folder.guid, folder.contains_item_type); + } } - + public static void sync_items(LevelTreeView view_i, string property_name, Guid default_folder, LevelTreeView.ItemType item_type) { var items = view_i.db.get_property_set(view_i.level._id, property_name, new HashSet()); - foreach (Guid guid in items) - { - string item_name = view_i.level.object_editor_name(guid); - - Value? parent_value = view_i.db.get_property(guid, "parent_folder"); - - Guid parent_id = GUID_ZERO; - - if (parent_value != null) - { - parent_id = (Guid)parent_value; - } - - if (parent_id == GUID_ZERO) - { - parent_id = default_folder; + if (items.size == 0) return; + + // Cache parent GUID -> TreeIter mappings + var parent_iter_cache = new Gee.HashMap(Guid.hash_func, Guid.equal_func); + + foreach (Guid guid in items) { + // Get parent GUID (with default fallback) + var parent_value = view_i.db.get_property(guid, "parent_folder"); + Guid parent_id = (parent_value != null) ? (Guid)parent_value : default_folder; + if (parent_id == GUID_ZERO) parent_id = default_folder; + + // Cache lookup/creation + Gtk.TreeIter? parent_iter = null; + if (!parent_iter_cache.has_key(parent_id)) { + parent_iter = LevelTreeOrganization.find_parent_iter(view_i, parent_id); + parent_iter_cache[parent_id] = parent_iter; + } else { + parent_iter = parent_iter_cache[parent_id]; } - - Gtk.TreeIter? parent_iter = LevelTreeOrganization.find_parent_iter(view_i, parent_id); - - if (parent_iter != null) - { + + if (parent_iter != null) { Gtk.TreeIter iter; view_i.tree_store.append(out iter, parent_iter); view_i.tree_store.set( iter, LevelTreeView.Column.TYPE, item_type, LevelTreeView.Column.GUID, guid, - LevelTreeView.Column.NAME, item_name, + LevelTreeView.Column.NAME, view_i.level.object_editor_name(guid), -1 ); - } - else - { + } else { print("ERROR: Parent iter for " + item_type.to_string() + " GUID " + guid.to_string() + " not found!"); } } - } + } private static void add_folders_recursively(LevelTreeView view_i, string parent_guid, HashTable folder_map) { foreach (var folder in view_i.level._folders) { if (folder.parent_id.to_string() == parent_guid) { diff --git a/tools/resource/global_guids.vala b/tools/resource/global_guids.vala index b9de46e13d..c7145f6ff7 100644 --- a/tools/resource/global_guids.vala +++ b/tools/resource/global_guids.vala @@ -14,12 +14,13 @@ namespace Crown { public LevelTreeView.ItemType item_type; public LevelTreeView.ItemType contains_item_type; public string contains_item_type_str; + public string contains_source_set_str; } static RootFolderInfo[] get_root_folder_info() { return new RootFolderInfo[] { - { GUID_UNIT_FOLDER, "Units", OBJECT_TYPE_FOLDER_UNIT, LevelTreeView.ItemType.FOLDER, LevelTreeView.ItemType.UNIT, "unit"}, - { GUID_SOUND_FOLDER, "Sounds", OBJECT_TYPE_FOLDER_SOUND, LevelTreeView.ItemType.FOLDER, LevelTreeView.ItemType.SOUND, "sound"} + { GUID_UNIT_FOLDER, "Units", OBJECT_TYPE_FOLDER_UNIT, LevelTreeView.ItemType.FOLDER, LevelTreeView.ItemType.UNIT, "unit", "units"}, + { GUID_SOUND_FOLDER, "Sounds", OBJECT_TYPE_FOLDER_SOUND, LevelTreeView.ItemType.FOLDER, LevelTreeView.ItemType.SOUND, "sound", "sounds"} }; } From c362d9be691108ed84fda2ada4d03b3acbc8c7f0 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 12:51:22 +0000 Subject: [PATCH 14/25] tools: avoid hardcoded types usage in on_drag_data_get_internal --- tools/level_editor/level_tree_organization.vala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index 1b95aae923..2eaae17a9b 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -54,15 +54,16 @@ namespace Crown if (item_type != null) { string type_str = (string)item_type; - switch (type_str) { - case "unit": - source_set = "units"; - break; - case "sound": - source_set = "sounds"; + source_set = null; + foreach (var root_info in get_root_folder_info()) { + if (type_str == root_info.contains_item_type_str) { + source_set = root_info.contains_source_set_str; break; - default: - continue; + } + } + + if (source_set == null) { + continue; } } else { print("Error: Item type is not a string or is null for GUID: %s\n", guid.to_string()); From 8dc080cb9c50794182f3b7ff0d4bf68cde13c87f Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 13:06:35 +0000 Subject: [PATCH 15/25] tools: avoid some hardcoded values in on_drag_data_received_internal --- .../level_editor/level_tree_organization.vala | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index 2eaae17a9b..0bf7e18417 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -100,11 +100,9 @@ namespace Crown } string raw_data = (string)selection_data.get_data(); - raw_data = raw_data.strip(); string[] data = raw_data.split(";", 2); - string source_set = data[0].strip(); string[] guids = data[1].split(","); @@ -118,11 +116,9 @@ namespace Crown Gtk.TreeIter target_iter; view_i.tree_sort.get_iter(out target_iter, path); - string target_parent_guid = ""; Value target_guid_val; Value target_type_val; - view_i.tree_sort.get_value(target_iter, LevelTreeView.Column.TYPE, out target_type_val); if (target_type_val == LevelTreeView.ItemType.FOLDER) { view_i.tree_sort.get_value(target_iter, LevelTreeView.Column.GUID, out target_guid_val); @@ -134,7 +130,7 @@ namespace Crown string? moving_type = null; foreach (string guid_str in guids) { - if (guid_str.strip().length == 0) continue; + if (guid_str.strip().length == 0) continue; Guid guid = Guid.parse(guid_str); Value? parent_guid_val = view_i.db.get_property(guid, "parent_folder"); @@ -142,11 +138,15 @@ namespace Crown if (parent_guid_val == null) { string item_type = (string)view_i.db.get_property(guid, "_type"); - if (item_type == "unit") { - parent_guid = GUID_UNIT_FOLDER; - } else if (item_type == "sound") { - parent_guid = GUID_SOUND_FOLDER; - } else { + parent_guid = GUID_ZERO; + foreach (var root_info in get_root_folder_info()) { + if (item_type == root_info.contains_item_type_str) { + parent_guid = root_info.guid; + break; + } + } + + if (parent_guid == GUID_ZERO) { print("Error: Unable to determine the root parent of element %s", guid.to_string()); continue; } @@ -155,7 +155,6 @@ namespace Crown } Value? parent_type_val = view_i.db.get_property(parent_guid, "_type"); - string parent_type = (string)parent_type_val; moving_type = parent_type; @@ -171,46 +170,48 @@ namespace Crown view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); return; } - - Value? target_type_val_db = null; - string target_type; - - if (target_guid == GUID_UNIT_FOLDER) { - target_type = "unit_folder"; - } else if (target_guid == GUID_SOUND_FOLDER) { - target_type = "sound_folder"; - } else { - target_type_val_db = view_i.db.get_property(target_guid, "_type"); - + string target_type = null; + foreach (var root_info in get_root_folder_info()) { + if (Guid.equal_func(target_guid, root_info.guid)) { + target_type = root_info.object_type; + break; + } + } + if (target_type == null || target_type.strip().length == 0) { + Value? target_type_val_db = view_i.db.get_property(target_guid, "_type"); if (target_type_val_db != null) { target_type = (string)target_type_val_db; } else { - print("Error: Unable to determine target type for GUID %s\n", target_guid.to_string()); + print("Error: Unable to determine target type for GUID %s", target_guid.to_string()); view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); return; } - } - + } string current_item_type = (string)view_i.db.get_property(guid, "_type"); if (current_item_type == "unit_folder" || current_item_type == "sound_folder") { continue; } - - if (!((current_item_type == "unit" && target_type == "unit_folder") || - (current_item_type == "sound" && target_type == "sound_folder"))) { - print("Error: Cannot move a %s into %s", current_item_type, target_type); + + bool valid_move = false; + foreach (var root_info in get_root_folder_info()) { + if (target_type == root_info.object_type && current_item_type == root_info.contains_item_type_str) { + valid_move = true; + break; + } + } + + if (!valid_move) { + print("Error: Cannot move %s into %s", current_item_type, target_type); view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); return; } if (parent_guid != target_guid) { view_i.db.set_property(guid, "parent_folder", target_guid); } - TreeIter? parent_iter = find_parent_iter(view_i, target_guid); if (parent_iter != null) { Gtk.TreeIter iter; bool type_managed = false; - foreach (var root_info in get_root_folder_info()) { if (root_info.object_type == target_type) { view_i.tree_store.append(out iter, parent_iter); @@ -225,19 +226,16 @@ namespace Crown break; } } - if (!type_managed) { print("target_type folder : " + target_type + " not managed"); } } - view_i.tree_view.expand_all(); } Gtk.drag_finish(context, true, false, time); view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); } - public static void on_database_key_changed_internal(LevelTreeView view_i, Guid id, string key) { if (id != view_i.level._id || (key != "units" && key != "sounds")) return; From 401257df31c5f8d644c09c41696400d6b2d7d8c0 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 13:57:24 +0000 Subject: [PATCH 16/25] tools: dedupplicate appending to tree store code --- .../level_editor/level_tree_organization.vala | 82 ++++--------------- 1 file changed, 16 insertions(+), 66 deletions(-) diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index 0bf7e18417..de50d4c04b 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -214,13 +214,7 @@ namespace Crown bool type_managed = false; foreach (var root_info in get_root_folder_info()) { if (root_info.object_type == target_type) { - view_i.tree_store.append(out iter, parent_iter); - view_i.tree_store.set( - iter, - LevelTreeView.Column.TYPE, root_info.contains_item_type, - LevelTreeView.Column.GUID, guid, - LevelTreeView.Column.NAME, view_i.level.object_editor_name(guid), - -1 + append_to_tree_store(view_i, parent_iter, root_info.contains_item_type, guid, view_i.level.object_editor_name(guid) ); type_managed = true; break; @@ -272,14 +266,7 @@ namespace Crown // If the folder doesn't exist, create it if (!folder_exists) { - view_i.tree_store.append(out target_iter, null); - view_i.tree_store.set( - target_iter, - LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, - LevelTreeView.Column.GUID, target_folder, - LevelTreeView.Column.NAME, (key == "units") ? "Units" : "Sounds", - -1 - ); + append_to_tree_store(view_i, null, LevelTreeView.ItemType.FOLDER, target_folder, (key == "units") ? "Units" : "Sounds"); } // Populate existing GUIDs in the folder @@ -345,15 +332,7 @@ namespace Crown } // Add the item if it is not a duplicate if (!is_duplicate) { - Gtk.TreeIter iter; - view_i.tree_store.append(out iter, parent_iter); - view_i.tree_store.set( - iter, - LevelTreeView.Column.TYPE, item_type, - LevelTreeView.Column.GUID, guid, - LevelTreeView.Column.NAME, item_name, - -1 - ); + append_to_tree_store(view_i, parent_iter, item_type, guid, item_name); } } else { print("ERROR: Parent iter for GUID %s not found in the tree!", guid.to_string()); @@ -370,15 +349,7 @@ namespace Crown var folder_map = new HashTable(str_hash, str_equal); foreach (var root_info in get_root_folder_info()) { - Gtk.TreeIter iter; - view_i.tree_store.append(out iter, null); - view_i.tree_store.set( - iter, - LevelTreeView.Column.TYPE, root_info.item_type, - LevelTreeView.Column.GUID, root_info.guid, - LevelTreeView.Column.NAME, root_info.name, - -1 - ); + Gtk.TreeIter iter = append_to_tree_store(view_i, null, root_info.item_type, root_info.guid, root_info.name); folder_map[root_info.guid.to_string()] = iter; } @@ -386,19 +357,8 @@ namespace Crown if (folder.id in get_root_folder_guids()) { continue; } - - Gtk.TreeIter iter; Gtk.TreeIter? parent_iter = folder_map[folder.parent_id.to_string()]; - - view_i.tree_store.append(out iter, parent_iter); - view_i.tree_store.set( - iter, - LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, - LevelTreeView.Column.GUID, folder.id, - LevelTreeView.Column.NAME, folder.name, - -1 - ); - + Gtk.TreeIter iter = append_to_tree_store(view_i, parent_iter, LevelTreeView.ItemType.FOLDER, folder.id, folder.name); folder_map[folder.id.to_string()] = iter; } @@ -436,40 +396,30 @@ namespace Crown } if (parent_iter != null) { - Gtk.TreeIter iter; - view_i.tree_store.append(out iter, parent_iter); - view_i.tree_store.set( - iter, - LevelTreeView.Column.TYPE, item_type, - LevelTreeView.Column.GUID, guid, - LevelTreeView.Column.NAME, view_i.level.object_editor_name(guid), - -1 - ); + append_to_tree_store(view_i, parent_iter, item_type, guid, view_i.level.object_editor_name(guid)); } else { print("ERROR: Parent iter for " + item_type.to_string() + " GUID " + guid.to_string() + " not found!"); } } } + private static Gtk.TreeIter append_to_tree_store(LevelTreeView view_i, Gtk.TreeIter? parent_iter, LevelTreeView.ItemType type, Guid guid, string name) { + Gtk.TreeIter iter; + view_i.tree_store.append(out iter, parent_iter); + view_i.tree_store.set(iter, LevelTreeView.Column.TYPE, type, LevelTreeView.Column.GUID, guid, LevelTreeView.Column.NAME, name, -1); + return iter; + } + private static void add_folders_recursively(LevelTreeView view_i, string parent_guid, HashTable folder_map) { foreach (var folder in view_i.level._folders) { if (folder.parent_id.to_string() == parent_guid) { - Gtk.TreeIter iter; Gtk.TreeIter? parent_iter = folder_map[parent_guid]; - view_i.tree_store.append(out iter, parent_iter); - - view_i.tree_store.set( - iter, - LevelTreeView.Column.TYPE, LevelTreeView.ItemType.FOLDER, - LevelTreeView.Column.GUID, folder.id, - LevelTreeView.Column.NAME, folder.name, - -1 - ); - + Gtk.TreeIter iter = append_to_tree_store(view_i, parent_iter, LevelTreeView.ItemType.FOLDER, folder.id, folder.name); folder_map[folder.id.to_string()] = iter; add_folders_recursively(view_i, folder.id.to_string(), folder_map); } } - } + } + public static void add_folder_to_tree(LevelTreeView view_i, bool save_to_file, Gtk.TreeIter? parent_iter, string name,string type, Guid guid) { if (save_to_file) { From 148e38cff0dc9dc9947b34ae095af102e0fa3f66 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 14:30:37 +0000 Subject: [PATCH 17/25] tools: fix removing an item from subfolder do not refresh view --- .../level_editor/level_tree_organization.vala | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index de50d4c04b..5b86321417 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -232,7 +232,6 @@ namespace Crown public static void on_database_key_changed_internal(LevelTreeView view_i, Guid id, string key) { if (id != view_i.level._id || (key != "units" && key != "sounds")) return; - view_i.tree_selection.changed.disconnect(view_i.on_tree_selection_changed); // Define target folder and item type based on the key @@ -286,7 +285,6 @@ namespace Crown // Remove outdated items but avoid removing subfolders existing_guids.foreach((guid, iter) => { - // Only remove items that are not folders Value item_type_val; view_i.tree_store.get_value(iter, LevelTreeView.Column.TYPE, out item_type_val); if (item_type_val.holds(typeof(LevelTreeView.ItemType)) && (LevelTreeView.ItemType)item_type_val != LevelTreeView.ItemType.FOLDER) { @@ -294,6 +292,7 @@ namespace Crown view_i.tree_store.remove(ref iter); } } + remove_outdated_items_in_subfolders(view_i, iter, current_guids); }); // Synchronize parent folders and add missing items @@ -343,7 +342,34 @@ namespace Crown view_i.tree_view.expand_all(); view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); } - + private static void remove_outdated_items_in_subfolders(LevelTreeView view_i, Gtk.TreeIter iter, HashSet current_guids) { + // Iterate over all children (subfolders and items) under the current iter + Gtk.TreeIter child_iter; + if (view_i.tree_store.iter_children(out child_iter, iter)) { + do { + // Get the GUID and item type of the current child + Value guid_val; + view_i.tree_store.get_value(child_iter, LevelTreeView.Column.GUID, out guid_val); + Value item_type_val; + view_i.tree_store.get_value(child_iter, LevelTreeView.Column.TYPE, out item_type_val); + + if (guid_val.holds(typeof(Guid)) && item_type_val.holds(typeof(LevelTreeView.ItemType))) { + Guid guid = (Guid)guid_val; + LevelTreeView.ItemType item_type = (LevelTreeView.ItemType)item_type_val; + + // If this item is not a folder, check if it's outdated and remove it + if (item_type != LevelTreeView.ItemType.FOLDER) { + if (!current_guids.contains(guid)) { + view_i.tree_store.remove(ref child_iter); + } + } else { + // If it's a folder, recursively check for outdated items inside it + remove_outdated_items_in_subfolders(view_i, child_iter, current_guids); + } + } + } while (view_i.tree_store.iter_next(ref child_iter)); + } + } public static void rebuild_tree(LevelTreeView view_i) { view_i.tree_store.clear(); var folder_map = new HashTable(str_hash, str_equal); @@ -448,7 +474,7 @@ namespace Crown view_i.tree_store.get_value(parent_iter, LevelTreeView.Column.GUID, out val); return (Guid)val; } - public static Gtk.TreeIter? find_parent_iter(LevelTreeView view_i, Guid parent_id) + private static Gtk.TreeIter? find_parent_iter(LevelTreeView view_i, Guid parent_id) { Gtk.TreeIter? found = null; view_i.tree_store.foreach((model, path, iter) => { From 3317afc5e39901108d935818019e24f65526fa85 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:37:58 +0000 Subject: [PATCH 18/25] tools: fix some crashes caused by missing properties in database + missing raw data for folders --- tools/level_editor/level.vala | 6 +- .../level_editor/level_tree_organization.vala | 82 ++++++++++--------- tools/resource/global_guids.vala | 5 +- 3 files changed, 53 insertions(+), 40 deletions(-) diff --git a/tools/level_editor/level.vala b/tools/level_editor/level.vala index 87527ee361..519245ad8d 100644 --- a/tools/level_editor/level.vala +++ b/tools/level_editor/level.vala @@ -168,9 +168,13 @@ public class Level } else { foreach (var root_info in get_root_folder_info()) { if (folder_id == root_info.guid) { + _db.create(folder_id,root_info.object_type); + _db.set_property(folder_id, "editor.name", root_info.name); + _db.set_property(folder_id, "parent_folder", GUID_ZERO); + _db.set_property(folder_id, "_type", root_info.object_type); return new Folder(folder_id, root_info.name, GUID_ZERO); } - } + } print("ERROR: Unknown Folder ID - %s", folder_id.to_string()); return null; } diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index 5b86321417..ed702544b4 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -37,75 +37,83 @@ namespace Crown GLib.List paths = view_i.tree_selection.get_selected_rows(out model); StringBuilder data_builder = new StringBuilder(); string source_set = null; - + foreach (Gtk.TreePath path in paths) { Gtk.TreeIter iter; model.get_iter(out iter, path); - // Get GUID of the selected item + + // Retrieve the GUID Value guid_val; model.get_value(iter, LevelTreeView.Column.GUID, out guid_val); - Guid guid = (Guid) guid_val; - - if (guid == GUID_ZERO) { - print("Warning: GUID is GUID_ZERO, skipping...\n"); - continue; - } + Guid guid = (Guid)guid_val; + Value? item_type = view_i.db.get_property(guid, "_type"); - - if (item_type != null) { - string type_str = (string)item_type; - source_set = null; - foreach (var root_info in get_root_folder_info()) { - if (type_str == root_info.contains_item_type_str) { - source_set = root_info.contains_source_set_str; - break; - } - } - - if (source_set == null) { - continue; + + string type_str = (string)item_type; + + if (type_str.has_suffix("_folder")) { + print("Folder drag ignored: %s\n", type_str); + Gtk.drag_cancel(context); + return; + } + + // Find the corresponding source_set + bool type_found = false; + foreach (var root_info in get_root_folder_info()) { + if (type_str == root_info.contains_item_type_str) { + source_set = root_info.contains_source_set_str; + type_found = true; + break; } - } else { - print("Error: Item type is not a string or is null for GUID: %s\n", guid.to_string()); + } + + if (!type_found) { + print("Unhandled type: %s\n", type_str); continue; } - data_builder.append(guid.to_string() + ","); - } - // REMOVE TRAILING COMMA - if (data_builder.len > 0) { - data_builder.truncate(data_builder.len - 1); + data_builder.append(guid.to_string() + ","); } if (data_builder.len > 0) { + data_builder.truncate(data_builder.len - 1); // Remove trailing comma string data_str = @"$source_set;$(data_builder.str)"; var target = Gdk.Atom.intern(d_target, false); selection_data.set(target, 8, data_str.data); } else { - print("Invalid data: no GUIDs found."); + print("No draggable items - cancelling drag operation."); Gtk.drag_finish(context, false, false, time); - return; } - } + } - public static void on_drag_data_received_internal(LevelTreeView view_i, string d_target, Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { + // TODO SUPPORT FOLDER DRAG AND DROPPING LATER + public static void on_drag_data_received_internal(LevelTreeView view_i, string d_target, Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { view_i.tree_selection.changed.disconnect(view_i.on_tree_selection_changed); Signal.stop_emission_by_name(view_i.tree_view, "drag-data-received"); - + var expected_target = Gdk.Atom.intern(d_target, false); if (selection_data.get_target() != expected_target) { Gtk.drag_finish(context, false, false, time); view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); return; } - + string raw_data = (string)selection_data.get_data(); + if (raw_data == null || raw_data.strip() == null) { + print("ERROR: Received empty or null drag data (if you tried to drag and dropped a folder its normal that you had this error ,for now its not supported).\n"); + Gtk.drag_finish(context, false, false, time); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + return; + } + raw_data = raw_data.strip(); - + string[] data = raw_data.split(";", 2); + string source_set = data[0].strip(); - string[] guids = data[1].split(","); + string[] guids = data[1].split(","); + Gtk.TreePath path; Gtk.TreeViewDropPosition pos; if (!view_i.tree_view.get_dest_row_at_pos(x, y, out path, out pos)) { @@ -113,7 +121,6 @@ namespace Crown view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); return; } - Gtk.TreeIter target_iter; view_i.tree_sort.get_iter(out target_iter, path); string target_parent_guid = ""; @@ -284,6 +291,7 @@ namespace Crown var current_guids = view_i.db.get_property_set(id, key, new HashSet()); // Remove outdated items but avoid removing subfolders + // TODO SUPPORT FOLDER REMOVING LATER existing_guids.foreach((guid, iter) => { Value item_type_val; view_i.tree_store.get_value(iter, LevelTreeView.Column.TYPE, out item_type_val); diff --git a/tools/resource/global_guids.vala b/tools/resource/global_guids.vala index c7145f6ff7..12143d63f6 100644 --- a/tools/resource/global_guids.vala +++ b/tools/resource/global_guids.vala @@ -12,6 +12,7 @@ namespace Crown { public string name; public string object_type; public LevelTreeView.ItemType item_type; + public string item_type_str; public LevelTreeView.ItemType contains_item_type; public string contains_item_type_str; public string contains_source_set_str; @@ -19,8 +20,8 @@ namespace Crown { static RootFolderInfo[] get_root_folder_info() { return new RootFolderInfo[] { - { GUID_UNIT_FOLDER, "Units", OBJECT_TYPE_FOLDER_UNIT, LevelTreeView.ItemType.FOLDER, LevelTreeView.ItemType.UNIT, "unit", "units"}, - { GUID_SOUND_FOLDER, "Sounds", OBJECT_TYPE_FOLDER_SOUND, LevelTreeView.ItemType.FOLDER, LevelTreeView.ItemType.SOUND, "sound", "sounds"} + { GUID_UNIT_FOLDER, "Units", OBJECT_TYPE_FOLDER_UNIT, LevelTreeView.ItemType.FOLDER, "unit_folder", LevelTreeView.ItemType.UNIT, "unit", "units"}, + { GUID_SOUND_FOLDER, "Sounds", OBJECT_TYPE_FOLDER_SOUND, LevelTreeView.ItemType.FOLDER, "sound_folder", LevelTreeView.ItemType.SOUND, "sound", "sounds"} }; } From 52b576b33b49afe5c636ca570fe7bb07e24c5c65 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:45:19 +0000 Subject: [PATCH 19/25] tools: avoid some hardcoded checks while creating a folder --- tools/level_editor/level_tree_view.vala | 51 ++++++++++++------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index b461d2c38a..245df1100f 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -310,40 +310,39 @@ public class LevelTreeView : Gtk.Box } if (valid_conversion && base_store.iter_is_valid(parent_iter)) { var menu_item = new Gtk.MenuItem.with_label("Create Subfolder"); - menu_item.activate.connect(() => { + menu_item.activate.connect(() => { Guid parent_guid = LevelTreeOrganization.get_parent_guid(this, parent_iter); print("Retrieved parent GUID: %s", parent_guid.to_string()); - Value? parent_type_val = null; - if (parent_guid == GUID_UNIT_FOLDER) { - parent_type_val = "unit_folder"; - } else if (parent_guid == GUID_SOUND_FOLDER) { - parent_type_val = "sound_folder"; - } else { - parent_type_val = _db.get_property(parent_guid, "_type"); + + string? parent_type_str = null; + foreach (var root_info in get_root_folder_info()) { + if (parent_guid == root_info.guid) { + parent_type_str = root_info.item_type_str; + break; + } } - - if (parent_type_val.holds(typeof(string))) { - string parent_type_str = (string)parent_type_val; - - switch (parent_type_str) { - case "unit_folder": - create_new_folder(parent_iter, OBJECT_TYPE_FOLDER_UNIT); - break; - case "sound_folder": - create_new_folder(parent_iter, OBJECT_TYPE_FOLDER_SOUND); - break; - default: - print("Unmanaged or invalid type: %s", parent_type_str); - break; + if (parent_type_str == null) { + Value parent_type_val = _db.get_property(parent_guid, "_type"); + parent_type_str = parent_type_val.holds(typeof(string)) ? (string)parent_type_val : null; + } + if (parent_type_str != null) { + foreach (var root_info in get_root_folder_info()) { + if (parent_type_str == root_info.item_type_str) { + create_new_folder(parent_iter, root_info.object_type); + return; + } } - } else { - print("Error: Parent type is not a string. Type found: %s", parent_type_val.type().name()); + print("Unmanaged or invalid type: %s", parent_type_str); + } + else { + print("Error: Parent type is not a string. Type found: %s", parent_guid.to_string()); } }); menu.add(menu_item); - } else { + } + else { print("Error: Invalid conversion or iterator"); - } + } } }); From 407774ac266b97cbb7d222fd184decffa9d1ea1d Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:48:50 +0000 Subject: [PATCH 20/25] tools: add a todo in level.vala --- tools/level_editor/level.vala | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/level_editor/level.vala b/tools/level_editor/level.vala index 519245ad8d..6de6cf2419 100644 --- a/tools/level_editor/level.vala +++ b/tools/level_editor/level.vala @@ -168,6 +168,7 @@ public class Level } else { foreach (var root_info in get_root_folder_info()) { if (folder_id == root_info.guid) { + // TODO AVOID THIS TO PREVENT "NEED TO SAVE TRIGGER" , but for now its needed to fix crash at Value? item_type = view_i.db.get_property(guid, "_type"); from on_drag_data_get_internal _db.create(folder_id,root_info.object_type); _db.set_property(folder_id, "editor.name", root_info.name); _db.set_property(folder_id, "parent_folder", GUID_ZERO); From 01a31dcc3503e78d26f35bcaa57a70d8d3634357 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 16:20:31 +0000 Subject: [PATCH 21/25] tools: remove unnecessary checks + inline --- .../level_editor/level_tree_organization.vala | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index ed702544b4..3fca7a884a 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -11,12 +11,7 @@ namespace Crown public class LevelTreeOrganization { public static bool on_drag_drop_internal(LevelTreeView view_i, string d_target, Gtk.Widget widget, Gdk.DragContext context, int x, int y, uint time) { - Gtk.drag_get_data( - widget, // will receive 'drag-data-received' signal - context, - Gdk.Atom.intern(d_target, false), - time - ); + Gtk.drag_get_data(widget, context, Gdk.Atom.intern(d_target, false), time); Signal.stop_emission_by_name(view_i.tree_view, "drag-drop"); return true; } @@ -164,13 +159,7 @@ namespace Crown Value? parent_type_val = view_i.db.get_property(parent_guid, "_type"); string parent_type = (string)parent_type_val; moving_type = parent_type; - - if (target_parent_guid == null || target_parent_guid.strip().length == 0) { - print("Error: Invalid target GUID (NULL or empty)"); - view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); - return; - } - + Guid target_guid; if (!Guid.try_parse(target_parent_guid, out target_guid)) { print("Error: Invalid target GUID for parent %s", target_parent_guid); @@ -195,10 +184,7 @@ namespace Crown } } string current_item_type = (string)view_i.db.get_property(guid, "_type"); - if (current_item_type == "unit_folder" || current_item_type == "sound_folder") { - continue; - } - + bool valid_move = false; foreach (var root_info in get_root_folder_info()) { if (target_type == root_info.object_type && current_item_type == root_info.contains_item_type_str) { @@ -268,8 +254,7 @@ namespace Crown } } while (view_i.tree_store.iter_next(ref target_iter)); } - - + // If the folder doesn't exist, create it if (!folder_exists) { append_to_tree_store(view_i, null, LevelTreeView.ItemType.FOLDER, target_folder, (key == "units") ? "Units" : "Sounds"); From 30f0b3c11221820089fc6e10943714199cba6b44 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 16:28:35 +0000 Subject: [PATCH 22/25] tools: move level tree items synching into a dedicated file --- .../level_editor/level_tree_organization.vala | 214 +---------------- tools/level_editor/level_tree_synching.vala | 219 ++++++++++++++++++ tools/level_editor/level_tree_view.vala | 6 +- 3 files changed, 226 insertions(+), 213 deletions(-) create mode 100644 tools/level_editor/level_tree_synching.vala diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index 3fca7a884a..351e6dfcf2 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -7,7 +7,7 @@ using Gtk; namespace Crown { - // Manage drag and drop , folder organization and items synching for the LevelTreeView + // Manage drag and drop and folder organization for the LevelTreeView public class LevelTreeOrganization { public static bool on_drag_drop_internal(LevelTreeView view_i, string d_target, Gtk.Widget widget, Gdk.DragContext context, int x, int y, uint time) { @@ -207,8 +207,7 @@ namespace Crown bool type_managed = false; foreach (var root_info in get_root_folder_info()) { if (root_info.object_type == target_type) { - append_to_tree_store(view_i, parent_iter, root_info.contains_item_type, guid, view_i.level.object_editor_name(guid) - ); + LevelTreeSynching.append_to_tree_store(view_i, parent_iter, root_info.contains_item_type, guid, view_i.level.object_editor_name(guid)); type_managed = true; break; } @@ -222,217 +221,12 @@ namespace Crown Gtk.drag_finish(context, true, false, time); view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); } - - public static void on_database_key_changed_internal(LevelTreeView view_i, Guid id, string key) { - if (id != view_i.level._id || (key != "units" && key != "sounds")) return; - view_i.tree_selection.changed.disconnect(view_i.on_tree_selection_changed); - - // Define target folder and item type based on the key - Guid target_folder = (key == "units") ? GUID_UNIT_FOLDER : GUID_SOUND_FOLDER; - LevelTreeView.ItemType item_type = (key == "units") ? LevelTreeView.ItemType.UNIT : LevelTreeView.ItemType.SOUND; - - // Track existing GUIDs within the folder - var existing_guids = new HashTable(Guid.hash_func, Guid.equal_func); - Gtk.TreeIter target_iter; - bool folder_exists = false; - - // Check if the target folder already exists - if (view_i.tree_store.get_iter_first(out target_iter)) { - do { - Value guid_val; - view_i.tree_store.get_value(target_iter, LevelTreeView.Column.GUID, out guid_val); - - // Ensure the value holds a Guid - if (guid_val.holds(typeof(Guid))) { - Guid guid = (Guid)guid_val; - - // Check if the extracted Guid matches the target folder - if (guid == target_folder) { - folder_exists = true; - break; - } - } - } while (view_i.tree_store.iter_next(ref target_iter)); - } - - // If the folder doesn't exist, create it - if (!folder_exists) { - append_to_tree_store(view_i, null, LevelTreeView.ItemType.FOLDER, target_folder, (key == "units") ? "Units" : "Sounds"); - } - - // Populate existing GUIDs in the folder - Gtk.TreeIter child; - if (view_i.tree_store.iter_children(out child, target_iter)) { - do { - Value guid_val; - view_i.tree_store.get_value(child, LevelTreeView.Column.GUID, out guid_val); - if (guid_val.holds(typeof(Guid))) { - existing_guids[(Guid)guid_val] = child; - } - } while (view_i.tree_store.iter_next(ref child)); - } - - // Retrieve updated GUIDs from the database - var current_guids = view_i.db.get_property_set(id, key, new HashSet()); - - // Remove outdated items but avoid removing subfolders - // TODO SUPPORT FOLDER REMOVING LATER - existing_guids.foreach((guid, iter) => { - Value item_type_val; - view_i.tree_store.get_value(iter, LevelTreeView.Column.TYPE, out item_type_val); - if (item_type_val.holds(typeof(LevelTreeView.ItemType)) && (LevelTreeView.ItemType)item_type_val != LevelTreeView.ItemType.FOLDER) { - if (!current_guids.contains(guid)) { - view_i.tree_store.remove(ref iter); - } - } - remove_outdated_items_in_subfolders(view_i, iter, current_guids); - }); - - // Synchronize parent folders and add missing items - foreach (Guid guid in current_guids) { - // Check if the GUID is already present in the folder - if (!existing_guids.contains(guid)) { - string item_name = view_i.level.object_editor_name(guid); - - Value? parent_value = view_i.db.get_property(guid, "parent_folder"); - Guid parent_id = (parent_value != null) ? (Guid)parent_value : target_folder; - - // Check if we have the correct parent iter for the item - Gtk.TreeIter? parent_iter = find_parent_iter(view_i, parent_id); - - if (parent_iter != null) { - // Ensure we don't duplicate this item under the same parent - bool is_duplicate = false; - - // Check if the GUID is already under the parent folder - Gtk.TreeIter sibling_iter; - if (view_i.tree_store.iter_children(out sibling_iter, parent_iter)) { - do { - Value sibling_guid_val; - view_i.tree_store.get_value(sibling_iter, LevelTreeView.Column.GUID, out sibling_guid_val); - - Guid sibling_guid; - if (sibling_guid_val.holds(typeof(Guid))) { - sibling_guid = (Guid)sibling_guid_val; // Cast the Value directly to Guid - if (sibling_guid == guid) { - is_duplicate = true; - break; // If found, don't add it again - } - } - - } while (view_i.tree_store.iter_next(ref sibling_iter)); - } - // Add the item if it is not a duplicate - if (!is_duplicate) { - append_to_tree_store(view_i, parent_iter, item_type, guid, item_name); - } - } else { - print("ERROR: Parent iter for GUID %s not found in the tree!", guid.to_string()); - } - } - } - - view_i.tree_view.expand_all(); - view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); - } - private static void remove_outdated_items_in_subfolders(LevelTreeView view_i, Gtk.TreeIter iter, HashSet current_guids) { - // Iterate over all children (subfolders and items) under the current iter - Gtk.TreeIter child_iter; - if (view_i.tree_store.iter_children(out child_iter, iter)) { - do { - // Get the GUID and item type of the current child - Value guid_val; - view_i.tree_store.get_value(child_iter, LevelTreeView.Column.GUID, out guid_val); - Value item_type_val; - view_i.tree_store.get_value(child_iter, LevelTreeView.Column.TYPE, out item_type_val); - - if (guid_val.holds(typeof(Guid)) && item_type_val.holds(typeof(LevelTreeView.ItemType))) { - Guid guid = (Guid)guid_val; - LevelTreeView.ItemType item_type = (LevelTreeView.ItemType)item_type_val; - - // If this item is not a folder, check if it's outdated and remove it - if (item_type != LevelTreeView.ItemType.FOLDER) { - if (!current_guids.contains(guid)) { - view_i.tree_store.remove(ref child_iter); - } - } else { - // If it's a folder, recursively check for outdated items inside it - remove_outdated_items_in_subfolders(view_i, child_iter, current_guids); - } - } - } while (view_i.tree_store.iter_next(ref child_iter)); - } - } - public static void rebuild_tree(LevelTreeView view_i) { - view_i.tree_store.clear(); - var folder_map = new HashTable(str_hash, str_equal); - - foreach (var root_info in get_root_folder_info()) { - Gtk.TreeIter iter = append_to_tree_store(view_i, null, root_info.item_type, root_info.guid, root_info.name); - folder_map[root_info.guid.to_string()] = iter; - } - - foreach (var folder in view_i.level._folders) { - if (folder.id in get_root_folder_guids()) { - continue; - } - Gtk.TreeIter? parent_iter = folder_map[folder.parent_id.to_string()]; - Gtk.TreeIter iter = append_to_tree_store(view_i, parent_iter, LevelTreeView.ItemType.FOLDER, folder.id, folder.name); - folder_map[folder.id.to_string()] = iter; - } - - sync_existing_units_and_sounds(view_i); - } - public static void sync_existing_units_and_sounds(LevelTreeView view_i) - { - foreach (var folder in get_root_folder_info()) - { - sync_items(view_i, folder.contains_source_set_str, folder.guid, folder.contains_item_type); - } - } - - public static void sync_items(LevelTreeView view_i, string property_name, Guid default_folder, LevelTreeView.ItemType item_type) - { - var items = view_i.db.get_property_set(view_i.level._id, property_name, new HashSet()); - if (items.size == 0) return; - - // Cache parent GUID -> TreeIter mappings - var parent_iter_cache = new Gee.HashMap(Guid.hash_func, Guid.equal_func); - - foreach (Guid guid in items) { - // Get parent GUID (with default fallback) - var parent_value = view_i.db.get_property(guid, "parent_folder"); - Guid parent_id = (parent_value != null) ? (Guid)parent_value : default_folder; - if (parent_id == GUID_ZERO) parent_id = default_folder; - - // Cache lookup/creation - Gtk.TreeIter? parent_iter = null; - if (!parent_iter_cache.has_key(parent_id)) { - parent_iter = LevelTreeOrganization.find_parent_iter(view_i, parent_id); - parent_iter_cache[parent_id] = parent_iter; - } else { - parent_iter = parent_iter_cache[parent_id]; - } - - if (parent_iter != null) { - append_to_tree_store(view_i, parent_iter, item_type, guid, view_i.level.object_editor_name(guid)); - } else { - print("ERROR: Parent iter for " + item_type.to_string() + " GUID " + guid.to_string() + " not found!"); - } - } - } - private static Gtk.TreeIter append_to_tree_store(LevelTreeView view_i, Gtk.TreeIter? parent_iter, LevelTreeView.ItemType type, Guid guid, string name) { - Gtk.TreeIter iter; - view_i.tree_store.append(out iter, parent_iter); - view_i.tree_store.set(iter, LevelTreeView.Column.TYPE, type, LevelTreeView.Column.GUID, guid, LevelTreeView.Column.NAME, name, -1); - return iter; - } private static void add_folders_recursively(LevelTreeView view_i, string parent_guid, HashTable folder_map) { foreach (var folder in view_i.level._folders) { if (folder.parent_id.to_string() == parent_guid) { Gtk.TreeIter? parent_iter = folder_map[parent_guid]; - Gtk.TreeIter iter = append_to_tree_store(view_i, parent_iter, LevelTreeView.ItemType.FOLDER, folder.id, folder.name); + Gtk.TreeIter iter = LevelTreeSynching.append_to_tree_store(view_i, parent_iter, LevelTreeView.ItemType.FOLDER, folder.id, folder.name); folder_map[folder.id.to_string()] = iter; add_folders_recursively(view_i, folder.id.to_string(), folder_map); } @@ -467,7 +261,7 @@ namespace Crown view_i.tree_store.get_value(parent_iter, LevelTreeView.Column.GUID, out val); return (Guid)val; } - private static Gtk.TreeIter? find_parent_iter(LevelTreeView view_i, Guid parent_id) + public static Gtk.TreeIter? find_parent_iter(LevelTreeView view_i, Guid parent_id) { Gtk.TreeIter? found = null; view_i.tree_store.foreach((model, path, iter) => { diff --git a/tools/level_editor/level_tree_synching.vala b/tools/level_editor/level_tree_synching.vala new file mode 100644 index 0000000000..0a302a7f94 --- /dev/null +++ b/tools/level_editor/level_tree_synching.vala @@ -0,0 +1,219 @@ +/* +* Copyright (c) 2012-2025 Daniele Bartolini et al. +* SPDX-License-Identifier: GPL-3.0-or-later +*/ +using Gee; +using Gtk; + +namespace Crown +{ + // Manage synching for the LevelTreeView + public class LevelTreeSynching + { + public static void on_database_key_changed_internal(LevelTreeView view_i, Guid id, string key) { + if (id != view_i.level._id || (key != "units" && key != "sounds")) return; + view_i.tree_selection.changed.disconnect(view_i.on_tree_selection_changed); + + // Define target folder and item type based on the key + Guid target_folder = (key == "units") ? GUID_UNIT_FOLDER : GUID_SOUND_FOLDER; + LevelTreeView.ItemType item_type = (key == "units") ? LevelTreeView.ItemType.UNIT : LevelTreeView.ItemType.SOUND; + + // Track existing GUIDs within the folder + var existing_guids = new HashTable(Guid.hash_func, Guid.equal_func); + Gtk.TreeIter target_iter; + bool folder_exists = false; + + // Check if the target folder already exists + if (view_i.tree_store.get_iter_first(out target_iter)) { + do { + Value guid_val; + view_i.tree_store.get_value(target_iter, LevelTreeView.Column.GUID, out guid_val); + + // Ensure the value holds a Guid + if (guid_val.holds(typeof(Guid))) { + Guid guid = (Guid)guid_val; + + // Check if the extracted Guid matches the target folder + if (guid == target_folder) { + folder_exists = true; + break; + } + } + } while (view_i.tree_store.iter_next(ref target_iter)); + } + + // If the folder doesn't exist, create it + if (!folder_exists) { + append_to_tree_store(view_i, null, LevelTreeView.ItemType.FOLDER, target_folder, (key == "units") ? "Units" : "Sounds"); + } + + // Populate existing GUIDs in the folder + Gtk.TreeIter child; + if (view_i.tree_store.iter_children(out child, target_iter)) { + do { + Value guid_val; + view_i.tree_store.get_value(child, LevelTreeView.Column.GUID, out guid_val); + if (guid_val.holds(typeof(Guid))) { + existing_guids[(Guid)guid_val] = child; + } + } while (view_i.tree_store.iter_next(ref child)); + } + + // Retrieve updated GUIDs from the database + var current_guids = view_i.db.get_property_set(id, key, new HashSet()); + + // Remove outdated items but avoid removing subfolders + // TODO SUPPORT FOLDER REMOVING LATER + existing_guids.foreach((guid, iter) => { + Value item_type_val; + view_i.tree_store.get_value(iter, LevelTreeView.Column.TYPE, out item_type_val); + if (item_type_val.holds(typeof(LevelTreeView.ItemType)) && (LevelTreeView.ItemType)item_type_val != LevelTreeView.ItemType.FOLDER) { + if (!current_guids.contains(guid)) { + view_i.tree_store.remove(ref iter); + } + } + remove_outdated_items_in_subfolders(view_i, iter, current_guids); + }); + + // Synchronize parent folders and add missing items + foreach (Guid guid in current_guids) { + // Check if the GUID is already present in the folder + if (!existing_guids.contains(guid)) { + string item_name = view_i.level.object_editor_name(guid); + + Value? parent_value = view_i.db.get_property(guid, "parent_folder"); + Guid parent_id = (parent_value != null) ? (Guid)parent_value : target_folder; + + // Check if we have the correct parent iter for the item + Gtk.TreeIter? parent_iter = LevelTreeOrganization.find_parent_iter(view_i, parent_id); + + if (parent_iter != null) { + // Ensure we don't duplicate this item under the same parent + bool is_duplicate = false; + + // Check if the GUID is already under the parent folder + Gtk.TreeIter sibling_iter; + if (view_i.tree_store.iter_children(out sibling_iter, parent_iter)) { + do { + Value sibling_guid_val; + view_i.tree_store.get_value(sibling_iter, LevelTreeView.Column.GUID, out sibling_guid_val); + + Guid sibling_guid; + if (sibling_guid_val.holds(typeof(Guid))) { + sibling_guid = (Guid)sibling_guid_val; // Cast the Value directly to Guid + if (sibling_guid == guid) { + is_duplicate = true; + break; // If found, don't add it again + } + } + + } while (view_i.tree_store.iter_next(ref sibling_iter)); + } + // Add the item if it is not a duplicate + if (!is_duplicate) { + append_to_tree_store(view_i, parent_iter, item_type, guid, item_name); + } + } else { + print("ERROR: Parent iter for GUID %s not found in the tree!", guid.to_string()); + } + } + } + + view_i.tree_view.expand_all(); + view_i.tree_selection.changed.connect(view_i.on_tree_selection_changed); + } + private static void remove_outdated_items_in_subfolders(LevelTreeView view_i, Gtk.TreeIter iter, HashSet current_guids) { + // Iterate over all children (subfolders and items) under the current iter + Gtk.TreeIter child_iter; + if (view_i.tree_store.iter_children(out child_iter, iter)) { + do { + // Get the GUID and item type of the current child + Value guid_val; + view_i.tree_store.get_value(child_iter, LevelTreeView.Column.GUID, out guid_val); + Value item_type_val; + view_i.tree_store.get_value(child_iter, LevelTreeView.Column.TYPE, out item_type_val); + + if (guid_val.holds(typeof(Guid)) && item_type_val.holds(typeof(LevelTreeView.ItemType))) { + Guid guid = (Guid)guid_val; + LevelTreeView.ItemType item_type = (LevelTreeView.ItemType)item_type_val; + + // If this item is not a folder, check if it's outdated and remove it + if (item_type != LevelTreeView.ItemType.FOLDER) { + if (!current_guids.contains(guid)) { + view_i.tree_store.remove(ref child_iter); + } + } else { + // If it's a folder, recursively check for outdated items inside it + remove_outdated_items_in_subfolders(view_i, child_iter, current_guids); + } + } + } while (view_i.tree_store.iter_next(ref child_iter)); + } + } + public static void rebuild_tree(LevelTreeView view_i) { + view_i.tree_store.clear(); + var folder_map = new HashTable(str_hash, str_equal); + + foreach (var root_info in get_root_folder_info()) { + Gtk.TreeIter iter = append_to_tree_store(view_i, null, root_info.item_type, root_info.guid, root_info.name); + folder_map[root_info.guid.to_string()] = iter; + } + + foreach (var folder in view_i.level._folders) { + if (folder.id in get_root_folder_guids()) { + continue; + } + Gtk.TreeIter? parent_iter = folder_map[folder.parent_id.to_string()]; + Gtk.TreeIter iter = append_to_tree_store(view_i, parent_iter, LevelTreeView.ItemType.FOLDER, folder.id, folder.name); + folder_map[folder.id.to_string()] = iter; + } + + sync_existing_units_and_sounds(view_i); + } + public static void sync_existing_units_and_sounds(LevelTreeView view_i) + { + foreach (var folder in get_root_folder_info()) + { + sync_items(view_i, folder.contains_source_set_str, folder.guid, folder.contains_item_type); + } + } + + public static void sync_items(LevelTreeView view_i, string property_name, Guid default_folder, LevelTreeView.ItemType item_type) + { + var items = view_i.db.get_property_set(view_i.level._id, property_name, new HashSet()); + if (items.size == 0) return; + + // Cache parent GUID -> TreeIter mappings + var parent_iter_cache = new Gee.HashMap(Guid.hash_func, Guid.equal_func); + + foreach (Guid guid in items) { + // Get parent GUID (with default fallback) + var parent_value = view_i.db.get_property(guid, "parent_folder"); + Guid parent_id = (parent_value != null) ? (Guid)parent_value : default_folder; + if (parent_id == GUID_ZERO) parent_id = default_folder; + + // Cache lookup/creation + Gtk.TreeIter? parent_iter = null; + if (!parent_iter_cache.has_key(parent_id)) { + parent_iter = LevelTreeOrganization.find_parent_iter(view_i, parent_id); + parent_iter_cache[parent_id] = parent_iter; + } else { + parent_iter = parent_iter_cache[parent_id]; + } + + if (parent_iter != null) { + append_to_tree_store(view_i, parent_iter, item_type, guid, view_i.level.object_editor_name(guid)); + } else { + print("ERROR: Parent iter for " + item_type.to_string() + " GUID " + guid.to_string() + " not found!"); + } + } + } + + public static Gtk.TreeIter append_to_tree_store(LevelTreeView view_i, Gtk.TreeIter? parent_iter, LevelTreeView.ItemType type, Guid guid, string name) { + Gtk.TreeIter iter; + view_i.tree_store.append(out iter, parent_iter); + view_i.tree_store.set(iter, LevelTreeView.Column.TYPE, type, LevelTreeView.Column.GUID, guid, LevelTreeView.Column.NAME, name, -1); + return iter; + } + } +} \ No newline at end of file diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index 245df1100f..10d07baa7f 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -253,9 +253,9 @@ public class LevelTreeView : Gtk.Box this.pack_start(_scrolled_window, true, true, 0); _level.level_loaded.connect(() => { - LevelTreeOrganization.rebuild_tree(this); + LevelTreeSynching.rebuild_tree(this); }); - LevelTreeOrganization.rebuild_tree(this); + LevelTreeSynching.rebuild_tree(this); } private bool on_button_pressed(Gdk.EventButton ev) @@ -559,7 +559,7 @@ public class LevelTreeView : Gtk.Box } private void on_database_key_changed(Guid id, string key) { - LevelTreeOrganization.on_database_key_changed_internal(this, id, key); + LevelTreeSynching.on_database_key_changed_internal(this, id, key); } private void on_filter_entry_text_changed() From 8787baa0932c0e5f7dded2242c2882f956e551c6 Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 18:16:33 +0000 Subject: [PATCH 23/25] tools: support folder renaming --- tools/level_editor/level_tree_view.vala | 189 ++++++++++++++++++------ 1 file changed, 141 insertions(+), 48 deletions(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index 10d07baa7f..2c0912b685 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -312,7 +312,6 @@ public class LevelTreeView : Gtk.Box var menu_item = new Gtk.MenuItem.with_label("Create Subfolder"); menu_item.activate.connect(() => { Guid parent_guid = LevelTreeOrganization.get_parent_guid(this, parent_iter); - print("Retrieved parent GUID: %s", parent_guid.to_string()); string? parent_type_str = null; foreach (var root_info in get_root_folder_info()) { @@ -348,56 +347,85 @@ public class LevelTreeView : Gtk.Box mi = new Gtk.MenuItem.with_label("Rename..."); mi.activate.connect(() => { - Gtk.Dialog dg = new Gtk.Dialog.with_buttons("New Name" - , (Gtk.Window)this.get_toplevel() - , DialogFlags.MODAL - , "Cancel" - , ResponseType.CANCEL - , "Ok" - , ResponseType.OK - , null - ); - - EntryText sb = new EntryText(); - _tree_selection.selected_foreach((model, path, iter) => { - Value name; - model.get_value(iter, Column.NAME, out name); - sb.value = (string)name; - return; - }); - sb.activate.connect(() => { dg.response(ResponseType.OK); }); - dg.get_content_area().add(sb); - dg.skip_taskbar_hint = true; - dg.show_all(); - - if (dg.run() == (int)ResponseType.OK) { - string cur_name = ""; - string new_name = ""; - Guid object_id = GUID_ZERO; - - _tree_selection.selected_foreach((model, path, iter) => { - Value type; - model.get_value(iter, Column.TYPE, out type); - if (type == ItemType.FOLDER) - return; - - Value name; - model.get_value(iter, Column.NAME, out name); - cur_name = (string)name; - + GLib.List selectedPaths = null; + Gtk.TreeModel model = null; + selectedPaths = _tree_selection.get_selected_rows(out model); + + if (selectedPaths != null) { + foreach (Gtk.TreePath selectedPath in selectedPaths) { + Gtk.TreeIter iter; + + if (model.get_iter(out iter, selectedPath)) { + Value type; + model.get_value(iter, Column.TYPE, out type); + + if ((ItemType)type == ItemType.FOLDER) { Value guid; model.get_value(iter, Column.GUID, out guid); - object_id = (Guid)guid; - - new_name = sb.text.strip(); - }); - - if (new_name != "" && new_name != cur_name) - _level.object_set_editor_name(object_id, new_name); + Guid object_id = (Guid)guid; + + bool can_rename = true; + foreach (var root_folder in Crown.get_root_folder_info()) { + if (root_folder.guid == object_id) { + can_rename = false; + break; + } + } + + if (can_rename) { + rename_folder(model, iter); + } else { + Gtk.MessageDialog dialog = new Gtk.MessageDialog( + (Gtk.Window)this.get_toplevel(), + Gtk.DialogFlags.MODAL, + Gtk.MessageType.WARNING, + Gtk.ButtonsType.OK, + "You cannot rename this folder, it's a predefined root folder." + ); + dialog.run(); + dialog.destroy(); + } + } else { + Gtk.Dialog dg = new Gtk.Dialog.with_buttons("New Name" + , (Gtk.Window)this.get_toplevel() + , DialogFlags.MODAL + , "Cancel" + , ResponseType.CANCEL + , "Ok" + , ResponseType.OK + , null + ); + + EntryText sb = new EntryText(); + + Value name; + model.get_value(iter, Column.NAME, out name); + sb.value = (string)name; + + sb.activate.connect(() => { dg.response(ResponseType.OK); }); + dg.get_content_area().add(sb); + dg.skip_taskbar_hint = true; + dg.show_all(); + + if (dg.run() == (int)ResponseType.OK) { + string cur_name = (string)name; + string new_name = sb.text.strip(); + Guid object_id = GUID_ZERO; + + Value guid; + model.get_value(iter, Column.GUID, out guid); + object_id = (Guid)guid; + + if (new_name != "" && new_name != cur_name) + _level.object_set_editor_name(object_id, new_name); + } + dg.destroy(); + } + } } - - dg.destroy(); - }); + } + }); + if (_tree_selection.count_selected_rows() == 1) menu.add(mi); @@ -420,6 +448,71 @@ public class LevelTreeView : Gtk.Box return Gdk.EVENT_PROPAGATE; } + private void rename_folder(Gtk.TreeModel model, Gtk.TreeIter iter) + { + Gtk.TreeStore base_store = null; + Gtk.TreeIter base_iter = iter; + bool conversion_valide = true; + + if (model is Gtk.TreeModelSort sort_model) + { + if (sort_model.model is Gtk.TreeModelFilter filter_model) + { + base_store = filter_model.get_model() as Gtk.TreeStore; + sort_model.convert_iter_to_child_iter(out Gtk.TreeIter filter_iter, iter); + filter_model.convert_iter_to_child_iter(out base_iter, filter_iter); + } + else + { + conversion_valide = false; + } + } + else if (model is Gtk.TreeStore) + { + base_store = (Gtk.TreeStore)model; + } + else + { + conversion_valide = false; + } + base_store.get_value(base_iter, Column.NAME, out Value name_value); + string current_name = (string)name_value; + var dialog = new Gtk.Dialog.with_buttons( + "New Name", + (Gtk.Window)get_toplevel(), + Gtk.DialogFlags.MODAL, + "Cancel", Gtk.ResponseType.CANCEL, + "Ok", Gtk.ResponseType.OK, + null + ); + var entry = new Gtk.Entry { text = current_name }; + dialog.get_content_area().add(entry); + dialog.show_all(); + + if (dialog.run() == Gtk.ResponseType.OK) + { + string new_name = entry.text.strip(); + + if (!string.IsNullOrEmpty(new_name) && new_name != current_name) + { + base_store.set_value(base_iter, Column.NAME, new_name); + + base_store.get_value(base_iter, Column.GUID, out Value guid_value); + Guid folder_guid = (Guid)guid_value; + + var folder = _level._folders.FirstOrDefault(f => f.id == folder_guid); + if (folder != null) + { + folder.name = new_name; + } + + _level._db.set_property_string(folder_guid, "editor.name", new_name); + } + } + + dialog.destroy(); + } + private void create_new_folder(Gtk.TreeIter? parent_iter = null,string type) { Gtk.Dialog dialog = new Gtk.Dialog.with_buttons( From 94146e1401dd0b7775d4642f1e475dbabbcf661c Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 18:20:52 +0000 Subject: [PATCH 24/25] tools : revert rename_folder refactor code --- tools/level_editor/level_tree_view.vala | 45 +++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/tools/level_editor/level_tree_view.vala b/tools/level_editor/level_tree_view.vala index 2c0912b685..307b6bad8c 100644 --- a/tools/level_editor/level_tree_view.vala +++ b/tools/level_editor/level_tree_view.vala @@ -454,12 +454,16 @@ public class LevelTreeView : Gtk.Box Gtk.TreeIter base_iter = iter; bool conversion_valide = true; - if (model is Gtk.TreeModelSort sort_model) + if (model is Gtk.TreeModelSort) { - if (sort_model.model is Gtk.TreeModelFilter filter_model) + Gtk.TreeModelSort sort_model = (Gtk.TreeModelSort)model; + Gtk.TreeModelFilter filter_model = sort_model.model as Gtk.TreeModelFilter; + + if (filter_model != null) { base_store = filter_model.get_model() as Gtk.TreeStore; - sort_model.convert_iter_to_child_iter(out Gtk.TreeIter filter_iter, iter); + Gtk.TreeIter filter_iter; + sort_model.convert_iter_to_child_iter(out filter_iter, iter); filter_model.convert_iter_to_child_iter(out base_iter, filter_iter); } else @@ -469,15 +473,18 @@ public class LevelTreeView : Gtk.Box } else if (model is Gtk.TreeStore) { - base_store = (Gtk.TreeStore)model; + base_store = model as Gtk.TreeStore; } else { conversion_valide = false; } - base_store.get_value(base_iter, Column.NAME, out Value name_value); + + Value name_value; + base_store.get_value(base_iter, Column.NAME, out name_value); string current_name = (string)name_value; - var dialog = new Gtk.Dialog.with_buttons( + + Gtk.Dialog dialog = new Gtk.Dialog.with_buttons( "New Name", (Gtk.Window)get_toplevel(), Gtk.DialogFlags.MODAL, @@ -485,31 +492,35 @@ public class LevelTreeView : Gtk.Box "Ok", Gtk.ResponseType.OK, null ); - var entry = new Gtk.Entry { text = current_name }; + + Gtk.Entry entry = new Gtk.Entry(); + entry.text = current_name; dialog.get_content_area().add(entry); dialog.show_all(); if (dialog.run() == Gtk.ResponseType.OK) { string new_name = entry.text.strip(); - - if (!string.IsNullOrEmpty(new_name) && new_name != current_name) + + if (new_name != "" && new_name != current_name) { base_store.set_value(base_iter, Column.NAME, new_name); - - base_store.get_value(base_iter, Column.GUID, out Value guid_value); + + Value guid_value; + base_store.get_value(base_iter, Column.GUID, out guid_value); Guid folder_guid = (Guid)guid_value; - - var folder = _level._folders.FirstOrDefault(f => f.id == folder_guid); - if (folder != null) + + foreach (var folder in _level._folders) { - folder.name = new_name; + if (folder.id == folder_guid) + { + folder.name = new_name; + break; + } } - _level._db.set_property_string(folder_guid, "editor.name", new_name); } } - dialog.destroy(); } From 7f3dbc3c4352e4b8057adb01d9be3fe5c32f2cde Mon Sep 17 00:00:00 2001 From: quentin452 <42176772+quentin452@users.noreply.github.com> Date: Wed, 26 Mar 2025 20:24:14 +0000 Subject: [PATCH 25/25] add todos --- tools/level_editor/level_tree_organization.vala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/level_editor/level_tree_organization.vala b/tools/level_editor/level_tree_organization.vala index 351e6dfcf2..33448ee64d 100644 --- a/tools/level_editor/level_tree_organization.vala +++ b/tools/level_editor/level_tree_organization.vala @@ -1,3 +1,11 @@ +/*TODOS +fix ctrl + z and probably ctrl + y causing crash if i move an object into a subfolder and click on ctrl + z +support can drag and drop multiple elements at once easily + +made can dupplicate folder (like unit) +made can remove folder (like unit) +support undo/redo feature +support folder drag and drop /* * Copyright (c) 2012-2025 Daniele Bartolini et al. * SPDX-License-Identifier: GPL-3.0-or-later