"""Shared configuration, runtime state and widget handles. Every other module reaches this state as ``app_state.`` rather than importing the names directly. A plain ``from app_state import is_dirty`` would copy the value, so later rebinding would only ever be seen by the module that did it. """ import configparser import os VERSION = "0.0.0.028" PROPERTIES_FILE = "editor.properties" DEFAULT_GRID_SIZE = 10 DEFAULT_ADD_TIMESTAMP = True # Purely a display choice, so it lives in editor.properties. The SAX file has nowhere to # record it: meta stores a position and nothing about box size. DEFAULT_BOX_WIDTH = 100 MIN_ALLOWED_BOX_WIDTH = 60 MAX_ALLOWED_BOX_WIDTH = 400 # Widget handles, filled in by main once the UI exists window = None canvas = None tree = None # Maps to hold tracking states tree_element_map = {} # Treeview item ID -> ET.Element canvas_comp_map = {} # Canvas item tag -> ET.Element canvas_tag_by_element = {} # ET.Element -> Canvas unique tag string component_slot_offsets = {} # Canvas unique tag -> {slot name: y offset from box top} # File modification tracking state is_dirty = False current_file_path = None active_components_list = [] # Tracks what components are actively painted selected_canvas_tag = None # Tracks the currently clicked/highlighted canvas component tag selected_tree_item_id = None # Tracks the currently right-clicked Treeview item xml_root_element = None # Keep a persistent root reference to redraw tree layouts cleanly current_sheet_parent = None # Element whose children are painted; None means the app root # Drag-and-drop state variables drag_data = {"x": 0, "y": 0, "tag": None} # Global component slot registries compiled dynamically per file load final_components_registry = {} active_schema_kits = {} def load_preferences(): """Loads application configurations from editor.properties or initializes defaults.""" config = configparser.ConfigParser() grid_size = DEFAULT_GRID_SIZE add_timestamp = DEFAULT_ADD_TIMESTAMP box_width = DEFAULT_BOX_WIDTH sedona_home = "" if os.path.exists(PROPERTIES_FILE): try: config.read(PROPERTIES_FILE) grid_size = config.getint("Editor", "grid_size", fallback=DEFAULT_GRID_SIZE) add_timestamp = config.getboolean("Editor", "add_timestamp", fallback=DEFAULT_ADD_TIMESTAMP) box_width = config.getint("Editor", "box_width", fallback=DEFAULT_BOX_WIDTH) sedona_home = config.get("Editor", "sedona.home", fallback="").strip() except Exception: pass else: config["Editor"] = { "sedona.home": "", "grid_size": str(DEFAULT_GRID_SIZE), "add_timestamp": str(DEFAULT_ADD_TIMESTAMP), "box_width": str(DEFAULT_BOX_WIDTH) } try: with open(PROPERTIES_FILE, "w") as configfile: config.write(configfile) except Exception: pass box_width = max(MIN_ALLOWED_BOX_WIDTH, min(MAX_ALLOWED_BOX_WIDTH, box_width)) return sedona_home, grid_size, add_timestamp, box_width def save_preferences(): """Writes the current preference values back to editor.properties.""" config = configparser.ConfigParser() config["Editor"] = { "sedona.home": SEDONA_HOME, "grid_size": str(GRID_SIZE), "add_timestamp": str(ADD_TIMESTAMP), "box_width": str(BOX_WIDTH) } with open(PROPERTIES_FILE, "w") as configfile: config.write(configfile) def mark_dirty(): """Single funnel for 'the document changed', including the unsaved-state tint.""" global is_dirty if not is_dirty: is_dirty = True if canvas is not None: canvas.config(bg="#ffe6e6") def mark_clean(): """Clears the unsaved-state marker after a successful save or a fresh load.""" global is_dirty is_dirty = False if canvas is not None: canvas.config(bg="white") SEDONA_HOME, GRID_SIZE, ADD_TIMESTAMP, BOX_WIDTH = load_preferences()