diff options
| author | dam <dam@gudinoff> | 2022-04-06 22:52:14 +0000 |
|---|---|---|
| committer | dam <dam@gudinoff> | 2022-04-06 22:52:14 +0000 |
| commit | 1fe674aec4bd6ce3fc666c8545dadb4f0614067d (patch) | |
| tree | 949a5ff1380bf82edaf387a5b4e04d0a4bf9d46a | |
| parent | 0f1adc3bb1f41b5dd3490f176a5eee2c17007923 (diff) | |
| download | surgery-log-1fe674aec4bd6ce3fc666c8545dadb4f0614067d.tar.zst surgery-log-1fe674aec4bd6ce3fc666c8545dadb4f0614067d.zip | |
Improved file operations for database and option sets.
Added confirmation dialogs when importing database and option sets files.
Standardized the error print calls.
Fixed capitalization on menu entries.
| -rw-r--r-- | dialog/dialog.gd | 58 | ||||
| -rw-r--r-- | logic/database.gd | 145 | ||||
| -rw-r--r-- | logic/database_entry.gd | 6 | ||||
| -rw-r--r-- | logic/popup.gd | 12 | ||||
| -rw-r--r-- | logic/stage.gd | 85 | ||||
| -rw-r--r-- | menu/menu.gd | 63 | ||||
| -rw-r--r-- | touch_item_list/touch_item_list.gd | 2 |
7 files changed, 231 insertions, 140 deletions
diff --git a/dialog/dialog.gd b/dialog/dialog.gd index a22a3a6..89fee5c 100644 --- a/dialog/dialog.gd +++ b/dialog/dialog.gd @@ -8,37 +8,37 @@ signal rejected # () export var clear_signals_on_hide := true var message : Label -var accept : Button -var reject : Button +var accept_button : Button +var reject_button : Button func _init(): - self.anchor_right = 1.0 - self.anchor_bottom = 1.0 - self.rect_clip_content = true + self.anchor_right = 1.0 + self.anchor_bottom = 1.0 + self.rect_clip_content = true self.connect("hide", self, "_clear_signals") - reject = Button.new() - reject.anchor_top = 1.0 - reject.anchor_left = 0.0 - reject.anchor_right = 0.5 - reject.margin_right = -5.0 - reject.rect_min_size.y = 62.0 - reject.grow_vertical = Control.GROW_DIRECTION_BEGIN - reject.name = "reject" - reject.connect("pressed", self, "_signal_rejected") - add_child(reject) + reject_button = Button.new() + reject_button.anchor_top = 1.0 + reject_button.anchor_left = 0.0 + reject_button.anchor_right = 0.5 + reject_button.margin_right = -5.0 + reject_button.rect_min_size.y = 62.0 + reject_button.grow_vertical = Control.GROW_DIRECTION_BEGIN + reject_button.name = "reject" + reject_button.connect("pressed", self, "_signal_rejected") + add_child(reject_button) - accept = Button.new() - accept.anchor_top = 1.0 - accept.anchor_left = 0.5 - accept.anchor_right = 1.0 - accept.margin_left = 5.0 - accept.rect_min_size.y = 62.0 - accept.grow_vertical = Control.GROW_DIRECTION_BEGIN - accept.name = "accept" - accept.connect("pressed", self, "_signal_accepted") - add_child(accept) + accept_button = Button.new() + accept_button.anchor_top = 1.0 + accept_button.anchor_left = 0.5 + accept_button.anchor_right = 1.0 + accept_button.margin_left = 5.0 + accept_button.rect_min_size.y = 62.0 + accept_button.grow_vertical = Control.GROW_DIRECTION_BEGIN + accept_button.name = "accept" + accept_button.connect("pressed", self, "_signal_accepted") + add_child(accept_button) message = Label.new() message.autowrap = true @@ -71,9 +71,9 @@ func _signal_accepted(): func setup(message: String, accept_label: String = "Accept", reject_label: String = "Reject"): self.message.text = message - accept.visible = accept_label != "" - accept.text = accept_label - reject.visible = reject_label != "" - reject.text = reject_label + accept_button.visible = accept_label != "" + accept_button.text = accept_label + reject_button.visible = reject_label != "" + reject_button.text = reject_label diff --git a/logic/database.gd b/logic/database.gd index 262a18a..8acf1b6 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -8,7 +8,7 @@ var db: Array var selected_idx: int var staged_idx: int -onready var stage := get_node("/root/main/stage") as Stage +onready var stage := get_node("/root/main/stage") # as Stage @DAM Commented to avoid cyclic dependency. onready var delete_button := get_node("actions/delete") as Button onready var edit_button := get_node("actions/edit") as Button onready var add_button := get_node("actions/add") as Button @@ -18,7 +18,6 @@ onready var dialog := get_node("/root/main/dialog") as Dialog func _init(): selected_idx = -1 staged_idx = -1 - load_database() func _ready(): @@ -32,10 +31,7 @@ func _ready(): stage.connect("save", self, "save_stage") stage.connect("discard", self, "discard_stage") - for it in db: - self.add_item(get_entry_view(it)) - - clear_selection() + load_database() func get_entry_view(database_entry: Dictionary) -> String: @@ -62,7 +58,7 @@ func delete_action(): if selected_idx < 0: return - dialog.setup("The entry with process ID '%s' will be deleted from the database." % db[selected_idx].process_id, "Delete", "No") + dialog.setup("The entry #%d with process ID '%s' will be deleted from the database." % [selected_idx+1, db[selected_idx].process_id], "Delete", "No") dialog.connect("accepted", self, "delete_action_confirmed") popup.open_popup("Delete entry?", dialog) @@ -95,8 +91,8 @@ func add_action(): func save_stage(entry: Dictionary): if DatabaseEntry.is_valid_entry(entry) == false: - printerr("INVALID ENTRY RECEIVED") - return # @DAM Deal with this. + printerr("Invalid entry detected.") + return var next_selected_idx: int if staged_idx >= 0: @@ -125,7 +121,89 @@ func discard_stage(): grab_focus() +func save_database(file_path: String = DATABASE_FILE_PATH): + match file_path.get_extension(): + "json": + export_json(file_path, db) + + "csv": + export_csv(file_path, db) + + _: + printerr("Invalid database file extension: '%s'." % file_path.get_file()) + return + + +static func export_json(file_path: String, data: Array): + var database_file := { + "version": DATABASE_FILE_VERSION, + "database": data, + } + var indentation_char := "" if file_path == DATABASE_FILE_PATH else "\t" + var file_content := JSON.print(database_file, indentation_char) + + var file := File.new() + file.open(file_path, File.WRITE) + file.store_string(file_content) + file.close() + + +static func export_csv(file_path: String, data: Array): + var file := File.new() + file.open(file_path, File.WRITE) + var header := PoolStringArray(DatabaseEntry.ENTRY_PROTOTYPE.keys()) + file.store_csv_line(header) + for it in data: + file.store_csv_line(it.values()) + file.close() + + func load_database(file_path: String = DATABASE_FILE_PATH): + match file_path.get_extension(): + "json": + clear_database() + db = import_json(file_path) + if file_path != DATABASE_FILE_PATH: + sanitize_database(db) + + "csv": + clear_database() + db = import_csv(file_path) + + _: + printerr("Invalid database file extension: '%s'." % file_path.get_file()) + return + + for it in db: + # The JSON specification does not define integer or float types, but only a number type. + # Therefore, converting a Variant to JSON text will convert all numerical values to float types. + # Thus, we cast all integer values once we load them. + it["date_year"] = int(it["date_year"]) + it["date_month"] = int(it["date_month"]) + it["date_day"] = int(it["date_day"]) + + self.add_item(get_entry_view(it)) + + +static func sanitize_database(database: Array): + var blueprint := DatabaseEntry.ENTRY_PROTOTYPE + + for entry in database: + # Delete extra keys. + var keys_to_delete: Array + for key in entry: + if blueprint.has(key) == false: + keys_to_delete.append(key) + for key in keys_to_delete: + entry.erase(key) + + # Fix wrong value types. + for key in blueprint: + if entry.has(key) == false || typeof(entry[key]) != typeof(blueprint[key]): + entry[key] = blueprint[key] + + +static func import_json(file_path: String) -> Array: var file := File.new() file.open(file_path, File.READ_WRITE) var file_content = file.get_as_text() @@ -133,43 +211,21 @@ func load_database(file_path: String = DATABASE_FILE_PATH): var parse_result = JSON.parse(file_content) if parse_result.error != OK || typeof(parse_result.result) != TYPE_DICTIONARY: - push_error("Failed to parse database file: '%s'.") - return + printerr("Failed to parse database file: '%s'.") + return [] if parse_result.result["version"] != DATABASE_FILE_VERSION: - push_error("Invalid database file version '%s', expected '%s'." % DATABASE_FILE_VERSION) - return + printerr("Invalid database file version '%s', expected '%s'." % DATABASE_FILE_VERSION) + return [] if typeof(parse_result.result["database"]) != TYPE_ARRAY: - push_error("Failed to load database file contents.") - return - - db = parse_result.result["database"] - - # The JSON specification does not define integer or float types, but only a number type. - # Therefore, converting a Variant to JSON text will convert all numerical values to float types. - # Thus, we cast all integer values once we load them. - for it in db: - it.date_year = int(it.date_year) - it.date_month = int(it.date_month) - it.date_day = int(it.date_day) - - -func save_database(file_path: String = DATABASE_FILE_PATH): - var database_file := { - "version": DATABASE_FILE_VERSION, - "database": db, - } - var indentation_char := "" if file_path == DATABASE_FILE_PATH else "\t" - var file_content := JSON.print(database_file, indentation_char) + printerr("Failed to load database file contents.") + return [] - var file := File.new() - file.open(file_path, File.WRITE) - file.store_string(file_content) - file.close() + return parse_result.result["database"] -static func import_database(file_path: String) -> Array: +static func import_csv(file_path: String) -> Array: var result: Array var file := File.new() file.open(file_path, File.READ_WRITE) @@ -196,17 +252,6 @@ static func import_database(file_path: String) -> Array: return result -static func export_database(file_path: String, database: Array = db): - var file := File.new() - file.open(file_path, File.WRITE) - var header := PoolStringArray(DatabaseEntry.ENTRY_PROTOTYPE.keys()) - file.store_csv_line(header) - for it in database: - # @DAM This approach depends on the order the dictionary fields are created. - file.store_csv_line(it.values()) - file.close() - - func clear_database(): clear_selection() self.clear() diff --git a/logic/database_entry.gd b/logic/database_entry.gd index 496b752..8b0c51f 100644 --- a/logic/database_entry.gd +++ b/logic/database_entry.gd @@ -6,9 +6,9 @@ const DATE_FORMAT: String = "%04d-%02d-%02d" const ENTRY_PROTOTYPE: Dictionary = { "process_id": "", "surgery_id": "", - "date_year": 0, - "date_month": 0, - "date_day": 0, + "date_year": 1, + "date_month": 1, + "date_day": 1, "place": "", "anesthesia": "", "first_assistant": "", diff --git a/logic/popup.gd b/logic/popup.gd index d7c98f9..95fc2c3 100644 --- a/logic/popup.gd +++ b/logic/popup.gd @@ -5,12 +5,12 @@ signal dismissed # () export var clear_signals_on_hide := true -var control : Control -var control_parent : Node +var control : Control +var control_parent : Node -onready var title := get_node("title") as Label -onready var background := get_node("background") as Panel -onready var dismiss := get_node("dismiss") as Button +onready var title := get_node("title") as Label +onready var background := get_node("background") as Panel +onready var dismiss_button := get_node("dismiss") as Button func _init(): @@ -21,7 +21,7 @@ func _init(): func _ready(): - dismiss.connect("pressed", self, "dismiss") + dismiss_button.connect("pressed", self, "dismiss") func _clear_signals(): diff --git a/logic/stage.gd b/logic/stage.gd index e2248fa..764ee52 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -105,7 +105,7 @@ func show_options(field: String): func save_action(): self.visible = false var staged_entry := get_stage() - gather_option_sets(staged_entry) + gather_option_sets(staged_entry, option_sets) save_option_sets() emit_signal("save", staged_entry) @@ -164,7 +164,7 @@ func get_stage() -> Dictionary: return entry -func sanitize_option_sets(entry: Dictionary, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): +static func sanitize_option_sets(entry: Dictionary, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): # Delete extra keys. var keys_to_delete: Array for key in entry: @@ -185,7 +185,7 @@ func sanitize_option_sets(entry: Dictionary, blueprint: Dictionary = OPTION_SETS sanitize_option_sets(entry[key][sub_key], blueprint[key]) -func gather_option_sets(entry: Dictionary, target: Dictionary = option_sets, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): +static func gather_option_sets(entry: Dictionary, target: Dictionary, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): for key in blueprint: if target.get(key) == null: target[key] = {} @@ -201,7 +201,55 @@ func gather_option_sets(entry: Dictionary, target: Dictionary = option_sets, blu gather_option_sets(entry, target[key][value], blueprint[key]) +func save_option_sets(file_path: String = OPTION_SETS_FILE_PATH): + match file_path.get_extension(): + "json": + export_json(file_path, option_sets) + +# "csv": +# export_csv(file_path, option_sets) + + _: + printerr("Invalid option sets file extension: '%s'." % file_path.get_file()) + return + + +static func export_json(file_path: String, data: Dictionary): + var option_sets_file := { + "version": OPTION_SETS_FILE_VERSION, + "option_sets": data, + } + var indentation_char := "" if file_path == OPTION_SETS_FILE_PATH else "\t" + var file_content := JSON.print(option_sets_file, indentation_char) + + var file := File.new() + file.open(file_path, File.WRITE) + file.store_string(file_content) + file.close() + + +#static func export_csv(file_path: String, data: Dictionary): +# pass + + func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): + match file_path.get_extension(): + "json": + clear_option_sets() + option_sets = import_json(file_path) + if file_path != OPTION_SETS_FILE_PATH: + sanitize_option_sets(option_sets) + + "csv": + clear_option_sets() + option_sets = import_csv(file_path) + + _: + printerr("Invalid option sets file extension: '%s'." % file_path.get_file()) + return + + +static func import_json(file_path: String) -> Dictionary: var file := File.new() file.open(file_path, File.READ_WRITE) var file_content = file.get_as_text() @@ -209,32 +257,25 @@ func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): var parse_result = JSON.parse(file_content) if parse_result.error != OK || typeof(parse_result.result) != TYPE_DICTIONARY: - push_error("Failed to parse option sets file: '%s'.") - return + printerr("Failed to parse option sets file: '%s'.") + return {} if parse_result.result["version"] != OPTION_SETS_FILE_VERSION: - push_error("Invalid option sets file version '%s', expected '%s'." % OPTION_SETS_FILE_VERSION) - return + printerr("Invalid option sets file version '%s', expected '%s'." % OPTION_SETS_FILE_VERSION) + return {} if typeof(parse_result.result["option_sets"]) != TYPE_DICTIONARY: - push_error("Failed to load option sets file contents.") - return + printerr("Failed to load option sets file contents.") + return {} - option_sets = parse_result.result["option_sets"] + return parse_result.result["option_sets"] -func save_option_sets(file_path: String = OPTION_SETS_FILE_PATH): - var option_sets_file := { - "version": OPTION_SETS_FILE_VERSION, - "option_sets": option_sets, - } - var indentation_char := "" if file_path == OPTION_SETS_FILE_PATH else "\t" - var file_content := JSON.print(option_sets_file, indentation_char) - - var file := File.new() - file.open(file_path, File.WRITE) - file.store_string(file_content) - file.close() +static func import_csv(file_path: String) -> Dictionary: + var result := {} + for it in Database.import_csv(file_path): + gather_option_sets(it, result) + return result func clear_option_sets(): diff --git a/menu/menu.gd b/menu/menu.gd index 5f13949..735f86a 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -1,12 +1,13 @@ extends MenuButton const menu_items: Array = [ - { label = "IMPORT OPTION SETS", action = "import_option_sets_action" }, - { label = "EXPORT OPTION SETS", action = "export_option_sets_action" }, - { label = "CLEAR OPTION SETS", action = "clear_option_sets_action" }, - { label = "EXPORT DATA", action = "export_data_action" }, - { label = "CLEAR DATA", action = "clear_data_action" }, - { label = "ABOUT", action = "about_action" }, + { label = "Import Option Sets", action = "import_option_sets_action" }, + { label = "Export Option Sets", action = "export_option_sets_action" }, + { label = "Clear Option Sets", action = "clear_option_sets_action" }, + { label = "Import Database", action = "import_database_action" }, + { label = "Export Database", action = "export_database_action" }, + { label = "Clear Database", action = "clear_database_action" }, + { label = "About", action = "about_action" }, { label = "TEST_FAKE_DB", action = "test_fake_db_action" }, ] const license_font_b612: String = "res://licenses/font_b612.txt" @@ -31,39 +32,27 @@ func id_pressed(id: int): func import_option_sets_action(): + dialog.setup("All option sets from the dropdown menus will be replaced.", "Continue", "No") + dialog.connect("accepted", self, "import_option_sets_action_accepted") + popup.open_popup("Replace option sets?", dialog) + + +func import_option_sets_action_accepted(): file_picker.mode = FileDialog.MODE_OPEN_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters = ["*.json", "*.csv"] file_picker.current_file = "" - file_picker.connect("file_selected", self, "import_option_sets_action_confirmed") + file_picker.connect("file_selected", stage, "load_option_sets") file_picker.show_modal(true) file_picker.invalidate() -func import_option_sets_action_confirmed(file_path: String): - match file_path.get_extension(): - "json": - stage.load_option_sets(file_path) - stage.sanitize_option_sets(stage.option_sets) - stage.save_option_sets() - - "csv": - var database := Database.import_database(file_path) - for it in database: - stage.gather_option_sets(it) - stage.save_option_sets() - - _: - push_error("Invalid file extension selected to be parsed for option sets: '%s'." % file_path.get_file()) - return - - func export_option_sets_action(): file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters = ["*.json"] file_picker.current_file = "" - file_picker.connect("file_selected", stage, "save_option_sets", [], CONNECT_ONESHOT) + file_picker.connect("file_selected", stage, "save_option_sets") file_picker.show_modal(true) file_picker.invalidate() @@ -74,17 +63,33 @@ func clear_option_sets_action(): popup.open_popup("Clear option sets?", dialog) -func export_data_action(): +func import_database_action(): + dialog.setup("All entries from the database will be replaced.", "Continue", "No") + dialog.connect("accepted", self, "import_database_action_accepted") + popup.open_popup("Replace database?", dialog) + + +func import_database_action_accepted(): + file_picker.mode = FileDialog.MODE_OPEN_FILE + file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) + file_picker.filters = ["*.json", "*.csv"] + file_picker.current_file = "" + file_picker.connect("file_selected", database, "load_database") + file_picker.show_modal(true) + file_picker.invalidate() + + +func export_database_action(): file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters = ["*.csv"] file_picker.current_file = "" - file_picker.connect("file_selected", database, "save_database", [], CONNECT_ONESHOT) + file_picker.connect("file_selected", database, "save_database") file_picker.show_modal(true) file_picker.invalidate() -func clear_data_action(): +func clear_database_action(): dialog.setup("All entries from the database will be deleted.", "Delete all", "No") dialog.connect("accepted", database, "clear_database") popup.open_popup("Clear database?", dialog) diff --git a/touch_item_list/touch_item_list.gd b/touch_item_list/touch_item_list.gd index 67f88d7..6794138 100644 --- a/touch_item_list/touch_item_list.gd +++ b/touch_item_list/touch_item_list.gd @@ -3,7 +3,7 @@ class_name TouchItemList const POINTER_VELOCITY_DECAYING_FACTOR: float = PI const POINTER_VELOCITY_BOOST_FACTOR: float = 1.25 -const EXACT_SELECTION: bool = false +const EXACT_SELECTION: bool = true var is_pointer_dragging := false var pointer_drag_velocity := 0.0 |
