import re import ctypes import sys import dearpygui.dearpygui as dpg from styles import GuiStyle, INDENT, SPACE, FONT_SIZE, ICON_PATH from config import load_config, dump_config ctypes.windll.shcore.SetProcessDpiAwareness(1) def get_absolute_y(item): y = dpg.get_item_pos(item)[1] parent = dpg.get_item_parent(item) while parent: if dpg.get_item_type(parent) == 'mvAppItemType::mvChildWindow': y += dpg.get_item_pos(parent)[1] parent = dpg.get_item_parent(parent) return y def get_item_level(item): level = 0 parent = dpg.get_item_parent(item) while parent: if dpg.get_item_type(parent) == 'mvAppItemType::mvTabBar': level += 1 parent = dpg.get_item_parent(parent) return level def config_iter(config, prefix=""): for path in config: namespaces = path.split('.') for i in range(len(namespaces)): yield prefix + '.' + '.'.join(namespaces[:i+1]) class ConfigEditor: def __init__(self): dpg.create_context() self.styles = GuiStyle() self.config = dict() self.config_file_path = str() def delete_config_window_and_clear_aliases(self, file_path): if dpg.does_item_exist(file_path): if dpg.get_item_type(file_path) == "mvAppItemType::mvChildWindow": uuid = dpg.get_item_parent(dpg.get_item_parent(file_path)) dpg.delete_item(uuid) else: dpg.delete_item(file_path) if dpg.does_alias_exist(file_path): dpg.remove_alias(file_path) for path in config_iter(self.config, prefix=self.config_file_path): if dpg.does_alias_exist(path): dpg.remove_alias(path) def update_config(self): for path in self.config: if dpg.does_item_exist(f"{self.config_file_path}.{path}"): self.config[path].value = dpg.get_value(f"{self.config_file_path}.{path}") def check_tabview(self, root): for uuid in dpg.get_item_children(root, 1): if dpg.get_item_type(uuid) == "mvAppItemType::mvTabBar": return True, uuid return False, None def manage_file(self, sender, sender_data, user_data): file_path = sender_data['file_path_name'] if not file_path: return if user_data == "open": self.delete_config_window_and_clear_aliases(self.config_file_path) self.config_file_path = file_path self.config = load_config(file_path) self.construct_config_ui() elif user_data == "save_as" or user_data == "save": self.update_config() dump_config(self.config, file_path) dpg.show_item("file_save_popup") def set_file_path(self, sender, sender_data, user_data): file_path = sender_data['file_path_name'] dpg.set_value(user_data, file_path) def open_file_dialog(self, sender, sender_data, user_data): dpg.show_item("file_dialog") dpg.set_item_user_data("file_dialog", user_data) def open_folder_dialog(self, sender, sender_data, user_data): dpg.show_item("folder_dialog") dpg.set_item_user_data("folder_dialog", user_data) def construct_config_ui(self): self.update_config() self.delete_config_window_and_clear_aliases(self.config_file_path) if dpg.get_value("use_tabs_ui"): self.construct_config_ui_tabs() else: self.construct_config_ui_collapsing_headers() def construct_main_window(self): dpg.bind_font(self.styles.montserrat_font) dpg.bind_theme(self.styles.main_theme) with dpg.window(label="config file editor", pos=(10, 10), width=dpg.get_viewport_client_width(), height=dpg.get_viewport_client_height(), tag="main", menubar=True): with dpg.menu_bar(tag="main_menu_bar"): with dpg.menu(label="file"): dpg.add_menu_item(label="open", callback=self.open_file_dialog, tag="open_btn", user_data="open") dpg.add_menu_item(label="save", callback=lambda: self.manage_file("", {'file_path_name': self.config_file_path}, "save")) dpg.add_menu_item(label="save as", callback=self.open_file_dialog, tag="save_as_btn", user_data="save_as") with dpg.menu(label="view"): dpg.add_menu_item(label="use default theme", check=True, callback=lambda _, v: dpg.bind_theme(None if v else self.styles.main_theme), default_value=False) dpg.add_menu_item(label="use default font", check=True, callback=lambda _, v: dpg.bind_font(None if v else self.styles.montserrat_font), default_value=False) dpg.add_menu_item(label="use tabs", check=True, callback=lambda: self.construct_config_ui(), default_value=False, tag="use_tabs_ui") dpg.add_menu_item(label="show theme editor", callback=dpg.show_style_editor) with dpg.menu(label="settings"): dpg.add_menu_item(label="show mouse coords", check=True, callback=lambda _, v: dpg.show_item("mouse") if v else dpg.hide_item("mouse"), default_value=False) dpg.add_text("", tag="mouse", show=False) with dpg.file_dialog(directory_selector=False, show=False, callback=self.manage_file, tag="file_dialog", width=600, height=400): dpg.add_file_extension(".txt", color=(0, 255, 0, 255)) dpg.add_file_extension(".*", color=(255, 255, 255, 255)) with dpg.file_dialog(directory_selector=True, show=False, callback=self.set_file_path, tag="folder_dialog", width=500, height=400): dpg.add_file_extension(".*", color=(255, 255, 255, 255)) with dpg.window(label="", autosize=True, no_resize=True, modal=True, tag="file_save_popup", show=False, pos=(40, 40)): dpg.add_text("file saved successfully:)") with dpg.item_handler_registry(tag="main_handler") as handler: dpg.add_item_resize_handler(callback=lambda: self.change_height(self.config_file_path)) dpg.bind_item_handler_registry("main", "main_handler") dpg.set_viewport_resize_callback(lambda: self.change_height(self.config_file_path)) uuid = dpg.add_handler_registry() def clb(s, a, u): dpg.set_value("mouse", str(a)) dpg.add_mouse_move_handler(parent=uuid, callback=clb) def construct_config_ui_collapsing_headers(self): dpg.add_collapsing_header(label=self.config_file_path, parent="main", tag=self.config_file_path, indent=0, default_open=True) for full_path in self.config: namespaces = full_path.split('.') parent = self.config_file_path for level in range(len(namespaces) - 1): path = self.config_file_path + '.' + '.'.join(namespaces[:level+1]) if not dpg.does_item_exist(path): dpg.add_collapsing_header(label=namespaces[level], parent=parent, tag=path, indent=INDENT) parent = path self.create_widget(full_path) def construct_config_ui_tabs(self): tab_bar_exists, tab_bar_uuid = self.check_tabview("main") if not tab_bar_exists: tab_bar_uuid = dpg.add_tab_bar(parent="main") tab_uuid = dpg.add_tab(label=self.config_file_path, parent=tab_bar_uuid) dpg.add_child_window(parent=tab_uuid, tag=self.config_file_path, border=True, autosize_y=False, autosize_x=True) dpg.set_item_user_data(self.config_file_path, 2 * SPACE) for full_path in self.config: namespaces = full_path.split('.') parent = self.config_file_path for level in range(len(namespaces) - 1): path = self.config_file_path + '.' + '.'.join(namespaces[:level+1]) tab_bar_exists, tab_bar_uuid = self.check_tabview(parent) if not tab_bar_exists: tab_bar_uuid = dpg.add_tab_bar(parent=parent, callback=lambda s, a: self.change_height(a)) dpg.set_item_user_data(parent, dpg.get_item_user_data(parent) + 4 * SPACE + FONT_SIZE) if not dpg.does_item_exist(path): tab_uuid = dpg.add_tab(label=namespaces[level], parent=tab_bar_uuid) dpg.add_child_window(parent=tab_uuid, tag=path, border=True, autosize_y=False, autosize_x=True) dpg.set_item_user_data(path, 2 * SPACE) parent = path self.create_widget(full_path) def change_height(self, item): if not dpg.does_item_exist(item): return if dpg.get_item_type(item) == "mvAppItemType::mvChildWindow": uuid = dpg.get_item_parent(item) new_height = dpg.get_viewport_client_height() - get_absolute_y(uuid) - SPACE * get_item_level(item) dpg.set_item_height(item, max(new_height, dpg.get_item_user_data(item))) for child in dpg.get_item_children(item, 1): self.change_height(child) def create_widget(self, full_path): tag = self.config_file_path + '.' + full_path parent = '.'.join([self.config_file_path] + full_path.split('.')[:-1]) value = self.config[full_path].value value_type = self.config[full_path].value_type key = self.config[full_path].key comment = self.config[full_path].comment if dpg.get_value("use_tabs_ui") and parent != self.config_file_path and full_path != key: dpg.set_item_user_data(parent, dpg.get_item_user_data(parent) + 2 * SPACE + FONT_SIZE + SPACE) indent = INDENT if dpg.get_value("use_tabs_ui"): indent = 0 with dpg.group(horizontal=True, parent=parent, indent=indent): text_uuid = dpg.add_text(key) if value_type == 'BOOLEAN': dpg.add_checkbox(default_value=value, tag=tag) elif value_type == 'DOUBLE': dpg.add_input_double(default_value=value, tag=tag, format="%.10f") elif value_type == 'INT': dpg.add_input_int(default_value=value, tag=tag) elif "dir" in key.lower(): dpg.add_input_text(default_value=value, tag=tag) dpg.add_button(label="...", callback=self.open_folder_dialog, user_data=tag) else: dpg.add_input_text(default_value=value, tag=tag) if comment: dpg.add_text(comment, parent=dpg.add_tooltip(parent=text_uuid)) def run(self): dpg.create_viewport( title='config file editor', small_icon=ICON_PATH, large_icon=ICON_PATH ) dpg.setup_dearpygui() dpg.show_viewport() self.construct_main_window() if self.config_file_path: self.construct_config_ui() dpg.set_primary_window("main", True) dpg.start_dearpygui() dpg.destroy_context() if __name__ == "__main__": app = ConfigEditor() if len(sys.argv) > 1: app.config_file_path = sys.argv[1] app.config = load_config(sys.argv[1]) app.run()