extends TouchVerticalContainer class_name Stage signal save # (database_entry: Dictionary) signal discard # () const OPTION_SETS_FILE_PATH: String = "user://option_sets.json" const OPTION_SETS_FILE_VERSION: int = 1 const OPTION_SETS_NOT_AVAILABLE: String = "--" const OPTION_SETS_TREE_STRUCTURE := { "place": null, "anesthesia": null, "first_assistant": null, "type": { "sub_type": { "sub_sub_type": null, "pathology": null, "intervention": null, } } } var staged_entry_hash: int var option_sets: Dictionary onready var title := get_node("controls/title") as Label onready var process_id := get_node("controls/process_id") as LineEdit onready var surgery_id := get_node("controls/surgery_id") as LineEdit onready var date := get_node("controls/date_picker") as DatePicker onready var place := get_node("controls/place") as OptionSet onready var anesthesia := get_node("controls/anesthesia") as OptionSet onready var first_assistant := get_node("controls/first_assistant") as OptionSet onready var type := get_node("controls/type") as OptionSet onready var sub_type := get_node("controls/sub_type") as OptionSet onready var sub_sub_type := get_node("controls/sub_sub_type") as OptionSet onready var pathology := get_node("controls/pathology") as OptionSet onready var intervention := get_node("controls/intervention") as OptionSet onready var is_urgency := get_node("controls/is_urgency") as Button onready var notes := get_node("controls/notes") as LineEdit onready var save_button := get_node("controls/buttons/save") as Button onready var discard_button := get_node("controls/buttons/discard") as Button onready var scrollbar := get_v_scrollbar() onready var popup := get_node("/root/main/popup") as ModalPopup onready var dialog := get_node("/root/main/dialog") as Dialog func _init(): exclude_controls = ["date_picker", "save", "discard"] load_option_sets() func _ready(): # Fix height of buttons container. (get_node("controls/buttons") as Control).rect_min_size.y = save_button.rect_size.y scrollbar.connect("visibility_changed", self, "adjust_layout") save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") for it in controls.get_children(): if it is LineEdit: it.connect("focus_entered", it, "set", ["caret_position", it.max_length]) it.connect("focus_exited", it, "deselect") # Map option sets buttons. var option_sets_map := { "place": place, "anesthesia": anesthesia, "first_assistant": first_assistant, "type": type, "sub_type": sub_type, "sub_sub_type": sub_sub_type, "pathology": pathology, "intervention": intervention } for key in option_sets_map: var button := option_sets_map[key].get_node("options") as Button # @DAM Maybe rename "options". Also requires rename on option_set. button.connect("pressed", self, "show_options", [key]) func adjust_layout(): var margin_size := scrollbar.rect_size.x self.margin_left = margin_size self.margin_top = margin_size self.margin_bottom = -margin_size self.margin_right = 0.0 if scrollbar.visible else -margin_size func show_options(field: String): var option_set_field := self[field] as OptionSet var options: Array match field: "sub_type": options = option_sets.get("type", {}).get(type.text, {}).get(field, {}).keys() "sub_sub_type", "pathology", "intervention": options = option_sets.get("type", {}).get(type.text, {}).get("sub_type", {}).get(sub_type.text, {}).get(field, {}).keys() _: options = option_sets.get(field, {}).keys() options.sort() option_set_field.show_options(options) func save_action(): self.visible = false var staged_entry := get_stage() gather_option_sets(staged_entry, option_sets) save_option_sets() emit_signal("save", staged_entry) func discard_action(): if get_stage().hash() != staged_entry_hash: dialog.setup("Changes made to this entry will be discarded.", "Discard", "No") dialog.connect("accepted", self, "discard_action_confirmed") popup.open_popup("Discard changes?", dialog) else: discard_action_confirmed() func discard_action_confirmed(): self.visible = false emit_signal("discard") func set_stage(entry: Dictionary, title: String): self.title.text = title staged_entry_hash = entry.hash() process_id.text = entry.process_id surgery_id.text = entry.surgery_id date.set_date(entry.date_year, entry.date_month, entry.date_day) place.text = entry.place anesthesia.text = entry.anesthesia first_assistant.text = entry.first_assistant type.text = entry.type sub_type.text = entry.sub_type sub_sub_type.text = entry.sub_sub_type pathology.text = entry.pathology intervention.text = entry.intervention is_urgency.pressed = entry.is_urgency notes.text = entry.notes self.scroll_vertical = 0 func get_stage() -> Dictionary: var entry := { "process_id": process_id.text, "surgery_id": surgery_id.text, "date_year": date.get_year(), "date_month": date.get_month(), "date_day": date.get_day(), "place": place.text, "anesthesia": anesthesia.text, "first_assistant": first_assistant.text, "type": type.text, "sub_type": sub_type.text, "sub_sub_type": sub_sub_type.text, "pathology": pathology.text, "intervention": intervention.text, "is_urgency": is_urgency.pressed, "notes": notes.text, } return entry 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: if blueprint.has(key) == false: keys_to_delete.append(key) for key in keys_to_delete: entry.erase(key) for key in blueprint: # Add missing keys. if typeof(entry.get(key)) != TYPE_DICTIONARY: entry[key] = {} # Process sub-keys if blueprint[key] != null: for sub_key in entry[key]: if typeof(entry[key][sub_key]) != TYPE_DICTIONARY: entry[key][sub_key] = {} sanitize_option_sets(entry[key][sub_key], blueprint[key]) 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] = {} var value := (entry[key] as String).strip_edges() if value == "" || value == OPTION_SETS_NOT_AVAILABLE: continue if target[key].has(value) == false: target[key][value] = null if blueprint[key] == null else {} if blueprint[key] != null: 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() file.close() var parse_result = JSON.parse(file_content) if parse_result.error != OK || typeof(parse_result.result) != TYPE_DICTIONARY: printerr("Failed to parse option sets file: '%s'.") return {} if parse_result.result["version"] != OPTION_SETS_FILE_VERSION: printerr("Invalid option sets file version '%s', expected '%s'." % OPTION_SETS_FILE_VERSION) return {} if typeof(parse_result.result["option_sets"]) != TYPE_DICTIONARY: printerr("Failed to load option sets file contents.") return {} return parse_result.result["option_sets"] 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(): option_sets = {}