From bba34318b05e4827a3f9227740306e33dc576422 Mon Sep 17 00:00:00 2001 From: Pavel Tisnovsky Date: Tue, 17 Dec 2024 14:03:48 +0100 Subject: [PATCH 1/7] Script to start the editor --- config-editor.sh | 2 ++ 1 file changed, 2 insertions(+) create mode 100755 config-editor.sh diff --git a/config-editor.sh b/config-editor.sh new file mode 100755 index 0000000..bbead31 --- /dev/null +++ b/config-editor.sh @@ -0,0 +1,2 @@ +export PYTHONDONTWRITEBYTECODE=1 +pdm run src/config_editor.py From 0d492bc7a44074a51b84174fd74acbc77d331d35 Mon Sep 17 00:00:00 2001 From: Pavel Tisnovsky Date: Tue, 17 Dec 2024 14:03:54 +0100 Subject: [PATCH 2/7] Stub for tests --- tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 From e0c20a5028c5ac238fecef5fdd90294ac2db5794 Mon Sep 17 00:00:00 2001 From: Pavel Tisnovsky Date: Tue, 17 Dec 2024 14:06:07 +0100 Subject: [PATCH 3/7] First version of editor --- src/config_editor.py | 45 ++++ src/gui/__init__.py | 1 + src/gui/dialogs/__init__.py | 1 + src/gui/dialogs/about_dialog.py | 10 + src/gui/dialogs/auth_dialog.py | 39 ++++ src/gui/dialogs/cache_dialog.py | 66 ++++++ src/gui/dialogs/check_configuration.py | 39 ++++ src/gui/dialogs/data_collection_dialog.py | 39 ++++ src/gui/dialogs/default_model_dialog.py | 39 ++++ src/gui/dialogs/default_provider_dialog.py | 39 ++++ src/gui/dialogs/edit_dialog.py | 254 +++++++++++++++++++++ src/gui/dialogs/help_dialog.py | 56 +++++ src/gui/dialogs/llm_dialog.py | 39 ++++ src/gui/dialogs/logging_dialog.py | 95 ++++++++ src/gui/dialogs/security_profile_dialog.py | 41 ++++ src/gui/dialogs/tls_dialog.py | 39 ++++ src/gui/icons.py | 43 ++++ src/gui/main_window.py | 104 +++++++++ src/gui/menubar.py | 110 +++++++++ src/gui/status_bar.py | 23 ++ src/gui/toolbar.py | 109 +++++++++ src/gui/tooltip.py | 73 ++++++ src/icons/__init__.py | 1 + src/icons/added.py | 18 ++ src/icons/application_exit.py | 21 ++ src/icons/checkbox.py | 28 +++ src/icons/configure.py | 24 ++ src/icons/copyright | 66 ++++++ src/icons/edit.py | 25 ++ src/icons/file_new.py | 25 ++ src/icons/file_open.py | 16 ++ src/icons/file_save.py | 28 +++ src/icons/file_save_as.py | 29 +++ src/icons/help_about.py | 22 ++ src/icons/help_faq.py | 26 +++ src/icons/list.py | 19 ++ src/icons/removed.py | 19 ++ src/icons/server.py | 21 ++ tests/__init__.py | 1 + 39 files changed, 1693 insertions(+) create mode 100644 src/gui/__init__.py create mode 100644 src/gui/dialogs/__init__.py create mode 100644 src/gui/dialogs/about_dialog.py create mode 100644 src/gui/dialogs/auth_dialog.py create mode 100644 src/gui/dialogs/cache_dialog.py create mode 100644 src/gui/dialogs/check_configuration.py create mode 100644 src/gui/dialogs/data_collection_dialog.py create mode 100644 src/gui/dialogs/default_model_dialog.py create mode 100644 src/gui/dialogs/default_provider_dialog.py create mode 100644 src/gui/dialogs/edit_dialog.py create mode 100644 src/gui/dialogs/help_dialog.py create mode 100644 src/gui/dialogs/llm_dialog.py create mode 100644 src/gui/dialogs/logging_dialog.py create mode 100644 src/gui/dialogs/security_profile_dialog.py create mode 100644 src/gui/dialogs/tls_dialog.py create mode 100644 src/gui/icons.py create mode 100644 src/gui/main_window.py create mode 100644 src/gui/menubar.py create mode 100644 src/gui/status_bar.py create mode 100644 src/gui/toolbar.py create mode 100644 src/gui/tooltip.py create mode 100644 src/icons/__init__.py create mode 100644 src/icons/added.py create mode 100644 src/icons/application_exit.py create mode 100644 src/icons/checkbox.py create mode 100644 src/icons/configure.py create mode 100644 src/icons/copyright create mode 100644 src/icons/edit.py create mode 100644 src/icons/file_new.py create mode 100644 src/icons/file_open.py create mode 100644 src/icons/file_save.py create mode 100644 src/icons/file_save_as.py create mode 100644 src/icons/help_about.py create mode 100644 src/icons/help_faq.py create mode 100644 src/icons/list.py create mode 100644 src/icons/removed.py create mode 100644 src/icons/server.py diff --git a/src/config_editor.py b/src/config_editor.py index f6e03b2..2c867c2 100644 --- a/src/config_editor.py +++ b/src/config_editor.py @@ -1 +1,46 @@ """Configuration editor for Road Core service.""" + +import yaml + +from gui.main_window import MainWindow + +DEFAULT_CONFIGURATION_FILE = "olsconfig.yaml" + + +class ConfigEditor: + """Class representing instances of configuration editor.""" + + def __init__(self): + """Initialize configuration editor.""" + self.configuration = None + + def new_configuration(self): + """Create new configuration to be edited.""" + self.configuration = None + + def load_configuration(self, filename): + """Load configuration from YAML file.""" + with open(filename) as fin: + self.configuration = yaml.safe_load(fin) + self.filename = filename + + def save_configuration_as(self, filename): + """Store configuration into YAML file.""" + with open(filename, "w") as fout: + yaml.dump(self.configuration, fout) + + def save_configuration(self): + """Store configuration into YAML file.""" + with open(self.filename, "w") as fout: + yaml.dump(self.configuration, fout) + + def check_configuration(self): + """Check if configuration is correct one.""" + pass + + +config_editor = ConfigEditor() +config_editor.load_configuration(DEFAULT_CONFIGURATION_FILE) + +main_window = MainWindow(config_editor) +main_window.show() diff --git a/src/gui/__init__.py b/src/gui/__init__.py new file mode 100644 index 0000000..085f64e --- /dev/null +++ b/src/gui/__init__.py @@ -0,0 +1 @@ +"""Graphical user interface for the Road Core config editor.""" diff --git a/src/gui/dialogs/__init__.py b/src/gui/dialogs/__init__.py new file mode 100644 index 0000000..d3a7bd8 --- /dev/null +++ b/src/gui/dialogs/__init__.py @@ -0,0 +1 @@ +"""Implementation of all GUI dialog boxes.""" diff --git a/src/gui/dialogs/about_dialog.py b/src/gui/dialogs/about_dialog.py new file mode 100644 index 0000000..d804a45 --- /dev/null +++ b/src/gui/dialogs/about_dialog.py @@ -0,0 +1,10 @@ +"""Implementation of simple 'About' dialog.""" + +from tkinter import messagebox + + +def about(): + """Show 'about' dialog.""" + messagebox.showinfo( + "Config editor", "Configuration editor for the Road Core service" + ) diff --git a/src/gui/dialogs/auth_dialog.py b/src/gui/dialogs/auth_dialog.py new file mode 100644 index 0000000..5a8254f --- /dev/null +++ b/src/gui/dialogs/auth_dialog.py @@ -0,0 +1,39 @@ +"""Auth dialog.""" + +import tkinter + + +class AuthDialog(tkinter.Toplevel): + """Dialog for editing authentication configuration.""" + + def __init__(self, parent, icons): + """Initialize authentication configuration dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("Authentication configuration") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/dialogs/cache_dialog.py b/src/gui/dialogs/cache_dialog.py new file mode 100644 index 0000000..065566c --- /dev/null +++ b/src/gui/dialogs/cache_dialog.py @@ -0,0 +1,66 @@ +"""Conversation cache dialog.""" + +import tkinter +import tkinter.ttk as ttk + + +class ConversationCacheDialog(tkinter.Toplevel): + """Dialog for editing conversation settings.""" + + def __init__(self, parent, icons): + """Initialize dialog for editing conversation settings.""" + tkinter.Toplevel.__init__(self, parent) + self.title("Conversation cache") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + # UI groups + self.group = tkinter.LabelFrame(self, text="Conversation cache", padx=5, pady=8) + + label1 = tkinter.Label(self.group, text="Type") + + label1.grid(row=1, column=1, sticky="W", padx=5, pady=5) + + cache_types = ("In-memory", "PostgreSQL", "Redis") + + cache_type = tkinter.StringVar(self.group, cache_types[0], "cache_type") + print(cache_type) + + cb1 = ttk.Combobox( + self.group, + values=cache_types, + # textvariable=app_log_levels, + state="readonly", + ) + cb1.current(0) + cb1.grid(row=1, column=2, sticky="W", padx=5, pady=5) + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + + # UI groups placement + self.group.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) + + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/dialogs/check_configuration.py b/src/gui/dialogs/check_configuration.py new file mode 100644 index 0000000..fc5ce3b --- /dev/null +++ b/src/gui/dialogs/check_configuration.py @@ -0,0 +1,39 @@ +"""Check configuration dialog.""" + +import tkinter + + +class CheckConfigurationDialog(tkinter.Toplevel): + """Dialog for checking the configuration.""" + + def __init__(self, parent, icons): + """Initialize dialog for checking the configuration.""" + tkinter.Toplevel.__init__(self, parent) + self.title("Check configuration") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/dialogs/data_collection_dialog.py b/src/gui/dialogs/data_collection_dialog.py new file mode 100644 index 0000000..2266264 --- /dev/null +++ b/src/gui/dialogs/data_collection_dialog.py @@ -0,0 +1,39 @@ +"""Data collection dialog.""" + +import tkinter + + +class DataCollectionDialog(tkinter.Toplevel): + """Data collection dialog.""" + + def __init__(self, parent, icons): + """Initialize data collection dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("Data collection settings") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/dialogs/default_model_dialog.py b/src/gui/dialogs/default_model_dialog.py new file mode 100644 index 0000000..19cd51c --- /dev/null +++ b/src/gui/dialogs/default_model_dialog.py @@ -0,0 +1,39 @@ +"""Default model selection dialog.""" + +import tkinter + + +class DefaultModelSelection(tkinter.Toplevel): + """Default model selection dialog.""" + + def __init__(self, parent, icons): + """Initialize default model selection dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("Default model selection") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/dialogs/default_provider_dialog.py b/src/gui/dialogs/default_provider_dialog.py new file mode 100644 index 0000000..789a914 --- /dev/null +++ b/src/gui/dialogs/default_provider_dialog.py @@ -0,0 +1,39 @@ +"""Default provider selection dialog.""" + +import tkinter + + +class DefaultProviderSelection(tkinter.Toplevel): + """Default provider selection dialog.""" + + def __init__(self, parent, icons): + """Initialize default provider selection dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("Default provider selection") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/dialogs/edit_dialog.py b/src/gui/dialogs/edit_dialog.py new file mode 100644 index 0000000..398e0fa --- /dev/null +++ b/src/gui/dialogs/edit_dialog.py @@ -0,0 +1,254 @@ +"""Implementation of configuration editor.""" + +import tkinter +import tkinter.ttk as ttk + +from gui.dialogs.auth_dialog import AuthDialog +from gui.dialogs.cache_dialog import ConversationCacheDialog +from gui.dialogs.data_collection_dialog import DataCollectionDialog +from gui.dialogs.default_model_dialog import DefaultModelSelection +from gui.dialogs.default_provider_dialog import DefaultProviderSelection +from gui.dialogs.llm_dialog import LLMDialog +from gui.dialogs.logging_dialog import LoggingDialog +from gui.dialogs.security_profile_dialog import TLSSecurityProfileDialog +from gui.dialogs.tls_dialog import TLSConfigurationDialog + +BUTTON_WIDTH = 100 + + +class EditDialog(tkinter.Toplevel): + """Implementation of configuration editor.""" + + def __init__(self, parent, icons): + """Initialize the dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("Configuration editor") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # UI groups + self.group1 = tkinter.LabelFrame(self, text="LLM section", padx=5, pady=8) + self.group2 = tkinter.LabelFrame(self, text="Service settings", padx=5, pady=8) + self.group3 = tkinter.LabelFrame(self, text="Devel settings", padx=5, pady=8) + + # LLM settings + button_new_llm = tkinter.Button( + self.group1, + text="Add LLM", + width=BUTTON_WIDTH, + compound="left", + image=self.icons.added_icon, + command=self.new_llm, + ) + button_new_llm.grid(row=1, column=1, sticky="WE", padx=5, pady=5) + + # service settings + label1 = tkinter.Label(self.group2, text="Authentication") + label1.grid(row=1, column=1, sticky="W", padx=5, pady=5) + btn_auth = tkinter.Button( + self.group2, + text="Configure", + width=BUTTON_WIDTH, + compound="left", + image=self.icons.configure_icon, + command=self.auth_config, + ) + btn_auth.grid(row=1, column=2, sticky="W", padx=5, pady=5) + + label2 = tkinter.Label(self.group2, text="Conversation cache") + label2.grid(row=2, column=1, sticky="W", padx=5, pady=5) + btn_cache = tkinter.Button( + self.group2, + text="Configure", + width=BUTTON_WIDTH, + compound="left", + image=self.icons.configure_icon, + command=self.cache_config, + ) + btn_cache.grid(row=2, column=2, sticky="W", padx=5, pady=5) + + label3 = tkinter.Label(self.group2, text="Logging") + label3.grid(row=3, column=1, sticky="W", padx=5, pady=5) + btn_logging = tkinter.Button( + self.group2, + text="Configure", + width=BUTTON_WIDTH, + compound="left", + image=self.icons.configure_icon, + command=self.logging_config, + ) + btn_logging.grid(row=3, column=2, sticky="W", padx=5, pady=5) + + label4 = tkinter.Label(self.group2, text="Default provider") + label4.grid(row=4, column=1, sticky="W", padx=5, pady=5) + btn_provider = tkinter.Button( + self.group2, + text="Select", + width=BUTTON_WIDTH, + compound="left", + image=self.icons.list_icon, + command=self.default_provider_selection, + ) + btn_provider.grid(row=4, column=2, sticky="W", padx=5, pady=5) + + label5 = tkinter.Label(self.group2, text="Default model") + label5.grid(row=5, column=1, sticky="W", padx=5, pady=5) + btn_model = tkinter.Button( + self.group2, + text="Select", + width=BUTTON_WIDTH, + compound="left", + image=self.icons.list_icon, + command=self.default_model_selection, + ) + btn_model.grid(row=5, column=2, sticky="W", padx=5, pady=5) + + label6 = tkinter.Label(self.group2, text="TLS security profile") + label6.grid(row=6, column=1, sticky="W", padx=5, pady=5) + btn_sec_profile = tkinter.Button( + self.group2, + text="Configure", + width=BUTTON_WIDTH, + compound="left", + image=self.icons.configure_icon, + command=self.tls_security_profile, + ) + btn_sec_profile.grid(row=6, column=2, sticky="W", padx=5, pady=5) + + label7 = tkinter.Label(self.group2, text="TLS configuration") + label7.grid(row=7, column=1, sticky="W", padx=5, pady=5) + btn_tls = tkinter.Button( + self.group2, + text="Configure", + width=BUTTON_WIDTH, + compound="left", + image=self.icons.configure_icon, + command=self.tls_config, + ) + btn_tls.grid(row=7, column=2, sticky="W", padx=5, pady=5) + + label8 = tkinter.Label(self.group2, text="User data collection") + label8.grid(row=8, column=1, sticky="W", padx=5, pady=5) + btn_data_collection = tkinter.Button( + self.group2, + text="Configure", + width=BUTTON_WIDTH, + compound="left", + image=self.icons.configure_icon, + command=self.data_collection_config, + ) + btn_data_collection.grid(row=8, column=2, sticky="W", padx=5, pady=5) + + label9 = tkinter.Label(self.group2, text="Query validation method") + label9.grid(row=9, column=1, sticky="W", padx=5, pady=5) + query_validation_methods = ("LLM", "Keyword") + query_validation_method = query_validation_methods[0] + + cb = ttk.Combobox( + self.group2, + values=query_validation_methods, + textvariable=query_validation_method, + state="readonly", + ) + cb.current(0) + cb.grid(row=9, column=2, sticky="W", padx=5, pady=5) + + # devel settings + cb1 = tkinter.Checkbutton( + self.group3, text="Authentication" + ) # , variable=var1, onvalue=1, offvalue=0) + cb1.grid(row=1, column=1, sticky="W", padx=5, pady=5) + cb2 = tkinter.Checkbutton( + self.group3, text="TLS enabled" + ) # , variable=var1, onvalue=1, offvalue=0) + cb2.grid(row=2, column=1, sticky="W", padx=5, pady=5) + cb3 = tkinter.Checkbutton( + self.group3, text="Dev UI enabled" + ) # , variable=var1, onvalue=1, offvalue=0) + cb3.grid(row=3, column=1, sticky="W", padx=5, pady=5) + + # UI groups placement + self.group1.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) + self.group2.grid(row=1, column=2, sticky="NSWE", padx=5, pady=5) + self.group3.grid(row=1, column=3, sticky="NSWE", padx=5, pady=5) + + # rest + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + + cancel_button = tkinter.Button( + self, + text="Cancel", + command=self.cancel, + compound="left", + image=self.icons.exit_icon, + width=200, + ) + cancel_button.grid(row=2, column=2, sticky="W", padx=10, pady=10) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + # how the buttons should behave + self.bind("", lambda _: self.ok()) + self.bind("", lambda _: self.destroy()) + + # set the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() + + def cancel(self) -> None: + """Handle Cancel button press.""" + self.destroy() + + def new_llm(self) -> None: + """Show sialog to add new LLM into configuration.""" + LLMDialog(self, self.icons) + + def auth_config(self) -> None: + """Show authentication dialog.""" + AuthDialog(self, self.icons) + + def tls_config(self) -> None: + """Show TLS configuration dialog.""" + TLSConfigurationDialog(self, self.icons) + + def tls_security_profile(self) -> None: + """Show TLS security profile dialog.""" + TLSSecurityProfileDialog(self, self.icons) + + def cache_config(self) -> None: + """Show cache configuration dialog.""" + ConversationCacheDialog(self, self.icons) + + def logging_config(self) -> None: + """Show logging configuration dialog.""" + LoggingDialog(self, self.icons) + + def data_collection_config(self) -> None: + """Show data collection configuration dialog.""" + DataCollectionDialog(self, self.icons) + + def default_model_selection(self) -> None: + """Show default model selection dialog.""" + DefaultModelSelection(self, self.icons) + + def default_provider_selection(self) -> None: + """Show default provider selection dialog.""" + DefaultProviderSelection(self, self.icons) diff --git a/src/gui/dialogs/help_dialog.py b/src/gui/dialogs/help_dialog.py new file mode 100644 index 0000000..7e375a7 --- /dev/null +++ b/src/gui/dialogs/help_dialog.py @@ -0,0 +1,56 @@ +"""Help dialog implementation.""" + +import tkinter + + +class HelpDialog(tkinter.Toplevel): + """Help dialog implementation.""" + + def __init__(self, parent): + """Perform initialization of help dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("Nápověda") + self.transient(parent) + + self.grab_set() + + f = tkinter.LabelFrame(self, text="x") + + scrollbar = tkinter.Scrollbar(f) + text = tkinter.Text(f, height=5, width=60) + + scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y) + text.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=1) + scrollbar.config(command=text.yview) + text.config(yscrollcommand=scrollbar.set) + + text.tag_configure("h1", font=("Arial", 20, "bold")) + text.tag_configure("h2", font=("Arial", 16, "bold")) + + text.insert(tkinter.END, "Help\n", "h1") + text.insert(tkinter.END, "Config editor\n", "h2") + + help_message = """""" + text.insert(tkinter.END, help_message) + + text.config(state=tkinter.DISABLED) + f.grid(row=0, column=0, sticky="NWSE") + + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + + # rest + ok_button = tkinter.Button(self, text="OK", command=self.ok) + ok_button.grid(row=1, column=0, sticky="NWSE") + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + def ok(self): + """Ok button handler.""" + self.destroy() + + +def show_help(): + """Display help dialog.""" + HelpDialog(None) diff --git a/src/gui/dialogs/llm_dialog.py b/src/gui/dialogs/llm_dialog.py new file mode 100644 index 0000000..18a090e --- /dev/null +++ b/src/gui/dialogs/llm_dialog.py @@ -0,0 +1,39 @@ +"""New LLM dialog.""" + +import tkinter + + +class LLMDialog(tkinter.Toplevel): + """New LLM dialog.""" + + def __init__(self, parent, icons): + """Initialize new LLM dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("New LLM") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/dialogs/logging_dialog.py b/src/gui/dialogs/logging_dialog.py new file mode 100644 index 0000000..c617eb3 --- /dev/null +++ b/src/gui/dialogs/logging_dialog.py @@ -0,0 +1,95 @@ +"""Logging dialog.""" + +import tkinter +import tkinter.ttk as ttk + + +class LoggingDialog(tkinter.Toplevel): + """Logging dialog.""" + + def __init__(self, parent, icons): + """Initialize logging dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("Logging settings") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + # UI groups + self.group = tkinter.LabelFrame(self, text="Logging levels", padx=5, pady=8) + + label1 = tkinter.Label(self.group, text="Application") + label2 = tkinter.Label(self.group, text="Libraries") + label3 = tkinter.Label(self.group, text="Uvicorn") + + label1.grid(row=1, column=1, sticky="W", padx=5, pady=5) + label2.grid(row=2, column=1, sticky="W", padx=5, pady=5) + label3.grid(row=3, column=1, sticky="W", padx=5, pady=5) + + debug_levels = ("Not set", "Debug", "Info", "Warning", "Error", "Critical") + + app_log_levels = tkinter.StringVar( + self.group, debug_levels[0], "app_log_levels" + ) + print(app_log_levels) + cb1 = ttk.Combobox( + self.group, + values=debug_levels, + # textvariable=app_log_levels, + state="readonly", + ) + cb1.current(0) + cb1.grid(row=1, column=2, sticky="W", padx=5, pady=5) + + lib_log_levels = tkinter.StringVar( + self.group, debug_levels[0], "lib_log_levels" + ) + print(lib_log_levels) + cb2 = ttk.Combobox( + self.group, + values=debug_levels, + # textvariable=lib_log_levels, + state="readonly", + ) + cb2.current(0) + cb2.grid(row=2, column=2, sticky="W", padx=5, pady=5) + + uvicorn_log_levels = tkinter.StringVar( + self.group, debug_levels[0], "uvicorn_log_levels" + ) + print(uvicorn_log_levels) + cb3 = ttk.Combobox( + self.group, + values=debug_levels, + # textvariable=uvicorn_log_levels, + state="readonly", + ) + cb3.current(0) + cb3.grid(row=3, column=2, sticky="W", padx=5, pady=5) + + # UI groups placement + self.group.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/dialogs/security_profile_dialog.py b/src/gui/dialogs/security_profile_dialog.py new file mode 100644 index 0000000..2481f0b --- /dev/null +++ b/src/gui/dialogs/security_profile_dialog.py @@ -0,0 +1,41 @@ +"""Security profile dialog.""" + +import tkinter + + +class TLSSecurityProfileDialog(tkinter.Toplevel): + """Security profile dialog.""" + + def __init__(self, parent, icons): + """Initialize security profile dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("TLS security profile") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + # app, lib, uvicorn + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/dialogs/tls_dialog.py b/src/gui/dialogs/tls_dialog.py new file mode 100644 index 0000000..b0e9265 --- /dev/null +++ b/src/gui/dialogs/tls_dialog.py @@ -0,0 +1,39 @@ +"""TLS configuration dialog.""" + +import tkinter + + +class TLSConfigurationDialog(tkinter.Toplevel): + """TLS configuration dialog.""" + + def __init__(self, parent, icons): + """Initialize TLS configuration dialog.""" + tkinter.Toplevel.__init__(self, parent) + self.title("TLS configuration") + self.icons = icons + self.parent = parent + + # don't display the dialog in list of opened windows + self.transient(parent) + + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + # get the focus + ok_button.focus_set() + + def ok(self) -> None: + """Handle Ok button press.""" + self.destroy() diff --git a/src/gui/icons.py b/src/gui/icons.py new file mode 100644 index 0000000..d2a96e9 --- /dev/null +++ b/src/gui/icons.py @@ -0,0 +1,43 @@ +"""All icons used on the GUI.""" + +import tkinter + +import icons.added +import icons.application_exit +import icons.checkbox +import icons.configure +import icons.edit +import icons.file_new +import icons.file_open +import icons.file_save +import icons.file_save_as +import icons.help_about +import icons.help_faq +import icons.list +import icons.removed +import icons.server + + +class Icons: + """All icons used on the GUI.""" + + def __init__(self): + """Initialize all icons and convert them to PhotoImage.""" + self.exit_icon = tkinter.PhotoImage(data=icons.application_exit.icon) + self.help_faq_icon = tkinter.PhotoImage(data=icons.help_faq.icon) + self.help_about_icon = tkinter.PhotoImage(data=icons.help_about.icon) + self.file_new_icon = tkinter.PhotoImage(data=icons.file_new.icon) + + self.file_open_icon = tkinter.PhotoImage(data=icons.file_open.icon) + self.file_save_icon = tkinter.PhotoImage(data=icons.file_save.icon) + self.file_save_as_icon = tkinter.PhotoImage(data=icons.file_save_as.icon) + + self.edit_icon = tkinter.PhotoImage(data=icons.edit.icon) + self.checkbox_icon = tkinter.PhotoImage(data=icons.checkbox.icon) + + self.list_icon = tkinter.PhotoImage(data=icons.list.icon) + self.configure_icon = tkinter.PhotoImage(data=icons.configure.icon) + self.server_icon = tkinter.PhotoImage(data=icons.server.icon) + + self.added_icon = tkinter.PhotoImage(data=icons.added.icon) + self.removed_icon = tkinter.PhotoImage(data=icons.removed.icon) diff --git a/src/gui/main_window.py b/src/gui/main_window.py new file mode 100644 index 0000000..da2ca0b --- /dev/null +++ b/src/gui/main_window.py @@ -0,0 +1,104 @@ +"""Main window shown on screen.""" + +import tkinter +from tkinter import filedialog, messagebox + +from gui.dialogs.edit_dialog import EditDialog +from gui.icons import Icons +from gui.menubar import Menubar +from gui.status_bar import StatusBar +from gui.toolbar import Toolbar + + +class MainWindow: + """Main window shown on screen.""" + + def __init__(self, config_editor): + """Initialize main window.""" + self.config_editor = config_editor + self.root = tkinter.Tk() + self.root.title("Road Core config editor") + + self.icons = Icons() + + self.toolbar = Toolbar(self.root, self) + self.statusbar = StatusBar(self.root) + + self.configure_grid() + + self.toolbar.grid(column=1, row=1, columnspan=2, sticky="WE") + self.statusbar.grid(column=1, row=3, columnspan=2, sticky="WE") + + self.menubar = Menubar(self.root, self) + self.root.config(menu=self.menubar) + self.root.geometry("480x320") + # EditDialog(self.root, self.icons) + + def show(self): + """Display the main window on screen.""" + self.root.mainloop() + + def quit(self): + """Display message box whether to quit the application.""" + answer = messagebox.askyesno( + "Do you want to quit the program?", "Do you want to quit the program?" + ) + if answer: + self.root.quit() + + def configure_grid(self): + """Configure grid on canvas.""" + tkinter.Grid.rowconfigure(self.root, 2, weight=1) + tkinter.Grid.columnconfigure(self.root, 2, weight=1) + + def new_configuration(self): + """Initialize new configuration.""" + answer = messagebox.askyesno( + "Clear current configuration?", "Clear current configuration?" + ) + if answer: + self.config_editor.new_configuration() + + def load_configuration(self): + """Load configuration from YAML file.""" + filetypes = [("YAML files", "*.yaml"), ("YAML files", "*.yaml")] + dialog = filedialog.Open(self.root, filetypes=filetypes) + filename = dialog.show() + if filename is not None and filename != "": + try: + self.config_editor.load_configuration(filename) + messagebox.showinfo("Configuration loaded", "Configuration loaded") + except Exception as e: + messagebox.showerror("Configuration loading failed", f"Failure {e}") + print(e) + + def save_configuration(self): + """Save configuration into YAML file.""" + try: + self.config_editor.save_configuration() + messagebox.showinfo("Configuration saved", "Configuration saved") + except Exception as e: + messagebox.showerror("Configuration saving failed", f"Failure {e}") + print(e) + + def save_as_configuration(self): + """Save configuration into specified YAML file.""" + filetypes = [("YAML files", "*.yaml"), ("YAML files", "*.yaml")] + dialog = filedialog.SaveAs(self.root, filetypes=filetypes) + filename = dialog.show() + if filename is not None and filename != "": + try: + self.config_editor.save_configuration_as(filename) + messagebox.showinfo("Configuration saved", "Configuration saved") + except Exception as e: + messagebox.showerror("Configuration saving failed", f"Failure {e}") + print(e) + + def edit_configuration(self): + """Edit configuration using the specialized dialog.""" + EditDialog(self.root, self.icons) + + def check_configuration(self): + """Check configuration.""" + result = self.config_editor.check_configuration() + print(result) diff --git a/src/gui/menubar.py b/src/gui/menubar.py new file mode 100644 index 0000000..ae68d34 --- /dev/null +++ b/src/gui/menubar.py @@ -0,0 +1,110 @@ +"""Menu bar displayed on the main window.""" + +import tkinter + +from gui.dialogs.about_dialog import about +from gui.dialogs.help_dialog import show_help + + +class Menubar(tkinter.Menu): + """Menu bar displayed on the main window.""" + + def __init__(self, parent, main_window): + """Initialize the menu bar.""" + super().__init__(tearoff=0) + + self.parent = parent + self.main_window = main_window + + self.file_menu = tkinter.Menu(self, tearoff=0) + self.file_menu.add_command( + label="New configuration", + image=main_window.icons.file_new_icon, + compound="left", + underline=0, + accelerator="Ctrl+N", + command=self.main_window.new_configuration, + ) + self.file_menu.add_separator() + self.file_menu.add_command( + label="Load configuration", + image=main_window.icons.file_open_icon, + compound="left", + underline=0, + accelerator="Ctrl+L", + command=self.main_window.load_configuration, + ) + self.file_menu.add_command( + label="Save configuration", + image=main_window.icons.file_save_icon, + compound="left", + underline=0, + accelerator="Ctrl+S", + command=self.main_window.save_configuration, + ) + self.file_menu.add_command( + label="Save configuration as", + image=main_window.icons.file_save_as_icon, + compound="left", + underline=0, + command=self.main_window.save_as_configuration, + ) + self.file_menu.add_separator() + self.file_menu.add_command( + label="Quit", + image=main_window.icons.exit_icon, + compound="left", + underline=0, + accelerator="Ctrl+Q", + command=main_window.quit, + ) + + self.configuration_menu = tkinter.Menu(self, tearoff=0) + self.configuration_menu.add_command( + label="Edit", + image=main_window.icons.edit_icon, + compound="left", + underline=0, + accelerator="F4", + command=main_window.edit_configuration, + ) + self.configuration_menu.add_command( + label="Check", + image=main_window.icons.checkbox_icon, + compound="left", + underline=0, + accelerator="F5", + command=main_window.check_configuration, + ) + + self.help_menu = tkinter.Menu(self, tearoff=0) + self.help_menu.add_command( + label="Help", + image=main_window.icons.help_faq_icon, + compound="left", + underline=0, + accelerator="F1", + command=show_help, + ) + self.help_menu.add_separator() + self.help_menu.add_command( + label="About", + image=main_window.icons.help_about_icon, + accelerator="F11", + compound="left", + underline=0, + command=about, + ) + + self.add_cascade(label="File", menu=self.file_menu, underline=0) + self.add_cascade( + label="Configuration", menu=self.configuration_menu, underline=0 + ) + self.add_cascade(label="Help", menu=self.help_menu, underline=0) + + self.parent.bind("", lambda _: show_help()) + self.parent.bind("", lambda _: about()) + self.parent.bind( + "", + lambda _: self.main_window.new_configuration, + ) diff --git a/src/gui/status_bar.py b/src/gui/status_bar.py new file mode 100644 index 0000000..0b5698f --- /dev/null +++ b/src/gui/status_bar.py @@ -0,0 +1,23 @@ +"""Status bar displayed in the main window.""" + +import tkinter + + +class StatusBar(tkinter.Frame): + """Status bar displayed in the main window.""" + + def __init__(self, master: tkinter.Tk) -> None: + """Initialize the class.""" + tkinter.Frame.__init__(self, master) + self.label = tkinter.Label(self, bd=1, relief=tkinter.SUNKEN, anchor=tkinter.W) + self.label.pack(fill=tkinter.X) + + def set(self, string_format, *args): + """Set status bar messages.""" + self.label.config(text=string_format % args) + self.label.update_idletasks() + + def clear(self) -> None: + """Clear status bar content.""" + self.label.config(text="") + self.label.update_idletasks() diff --git a/src/gui/toolbar.py b/src/gui/toolbar.py new file mode 100644 index 0000000..0f4e71a --- /dev/null +++ b/src/gui/toolbar.py @@ -0,0 +1,109 @@ +"""Toolbar displayed on the main window.""" + +import tkinter + +from gui.tooltip import Tooltip + + +class Toolbar(tkinter.LabelFrame): + """Toolbar displayed on the main window.""" + + def __init__(self, parent: tkinter.Tk, main_window) -> None: + """Initialize the toolbar.""" + super().__init__(parent, text="Tools", padx=5, pady=5) + + self.parent = parent + self.main_window = main_window + + self.button_new_config = tkinter.Button( + self, + text="New configuration", + image=main_window.icons.file_new_icon, + command=main_window.new_configuration, + ) + + Tooltip(self.button_new_config, "New configuration") + + self.button_file_open = tkinter.Button( + self, + text="Load configuration", + image=main_window.icons.file_open_icon, + command=main_window.load_configuration, + ) + + Tooltip(self.button_file_open, "Load configuration from file") + + self.button_file_save = tkinter.Button( + self, + text="Save configuration", + image=main_window.icons.file_save_icon, + command=self.main_window.save_configuration, + ) + + Tooltip(self.button_file_save, "Save configuration") + + self.button_file_save_as = tkinter.Button( + self, + text="Save configuration into different file", + image=main_window.icons.file_save_as_icon, + command=self.main_window.save_as_configuration, + ) + + Tooltip(self.button_file_save_as, "Save configuration into different file") + + self.button_edit_configuration = tkinter.Button( + self, + text="Edit configuration", + image=main_window.icons.edit_icon, + command=main_window.edit_configuration, + ) + + Tooltip(self.button_edit_configuration, "Edit configuration") + + self.button_check_configuration = tkinter.Button( + self, + text="Check configuration", + image=main_window.icons.checkbox_icon, + command=main_window.check_configuration, + ) + + Tooltip(self.button_check_configuration, "Check configuration") + + self.button_quit = tkinter.Button( + self, + text="Quit", + image=main_window.icons.exit_icon, + command=main_window.quit, + ) + + Tooltip(self.button_quit, "Quit") + + spacer1 = tkinter.Label(self, text=" ") + spacer2 = tkinter.Label(self, text=" ") + spacer3 = tkinter.Label(self, text=" ") + + self.button_new_config.grid(column=1, row=1) + + spacer1.grid(column=2, row=1) + + self.button_file_open.grid(column=3, row=1) + self.button_file_save.grid(column=4, row=1) + self.button_file_save_as.grid(column=5, row=1) + + spacer2.grid(column=6, row=1) + + self.button_edit_configuration.grid(column=7, row=1) + self.button_check_configuration.grid(column=8, row=1) + + spacer3.grid(column=9, row=1) + self.button_quit.grid(column=10, row=1) + + @staticmethod + def disable_button(button): + """Disable specified button on toolbar.""" + button["state"] = "disabled" + + @staticmethod + def enable_button(button: tkinter.Button) -> None: + """Enable specified button on toolbar.""" + button["state"] = "normal" diff --git a/src/gui/tooltip.py b/src/gui/tooltip.py new file mode 100644 index 0000000..bca40db --- /dev/null +++ b/src/gui/tooltip.py @@ -0,0 +1,73 @@ +"""Create a tooltip for a given widget.""" + +# taken from: +# https://stackoverflow.com/questions/3221956/how-do-i-display-tooltips-in-tkinter#36221216 + +import tkinter +from typing import Optional + + +class Tooltip: + """Create a tooltip for a given widget.""" + + def __init__(self, widget: tkinter.Button, text: str = "widget info") -> None: + """Initialize the widget.""" + self.waittime = 500 # miliseconds + self.wraplength = 180 # pixels + self.widget = widget + self.text = text + self.widget.bind("", self.enter) + self.widget.bind("", self.leave) + self.widget.bind("", self.leave) + self.id: Optional[str] = None + self.tw = None + + def enter(self, event: tkinter.Event | None = None) -> None: + """Handle the event: cursor pointer moves to the tooltip area.""" + self.schedule() + + def leave(self, event: tkinter.Event | None = None) -> None: + """Handle the event: cursor pointer leaves the tooltip area.""" + self.unschedule() + self.hidetip() + + def schedule(self) -> None: + """Schedule time for displaying tooltip.""" + self.unschedule() + self.id = self.widget.after(self.waittime, self.showtip) + + def unschedule(self) -> None: + """Unschedule time for displaying tooltip.""" + current_id = self.id + self.id = None + if current_id: + self.widget.after_cancel(current_id) + + def showtip(self, event=None): + """Show the tooltip on screen.""" + x = y = 0 + x, y, cx, cy = self.widget.bbox("insert") + x += self.widget.winfo_rootx() + 25 + y += self.widget.winfo_rooty() + 20 + # creates a toplevel window + self.tw = tkinter.Toplevel(self.widget) + # Leaves only the label and removes the app window + self.tw.wm_overrideredirect(True) + self.tw.wm_geometry("+%d+%d" % (x, y)) + label = tkinter.Label( + self.tw, + text=self.text, + justify="left", + background="#ffffff", + relief="solid", + borderwidth=1, + wraplength=self.wraplength, + ) + label.pack(ipadx=1) + + def hidetip(self) -> None: + """Hide the tooltip.""" + tw = self.tw + self.tw = None + if tw: + tw.destroy() diff --git a/src/icons/__init__.py b/src/icons/__init__.py new file mode 100644 index 0000000..a46af9b --- /dev/null +++ b/src/icons/__init__.py @@ -0,0 +1 @@ +"""Set of icons used on GUI. Icons are encoded by base64 so they can be read directly by Tkinter.""" diff --git a/src/icons/added.py b/src/icons/added.py new file mode 100644 index 0000000..8d733cb --- /dev/null +++ b/src/icons/added.py @@ -0,0 +1,18 @@ +"""Icon for the following action: add/added operation.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAMAAADzapwJAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAN +1wAADdcBQiibeAAAAAd0SU1FB9kMBhQDEUPMLQcAAADJUExURQAAACUlAAAAAA4sAg4tAxEtBxEt +Bv////f5+Pv0//z1//33///4///7///9//////31//72//74///5///7///9///+/////+3t7e3u +7e7t7u7u7e7u7u7v7v///////0htAE53AFF6AFqDABtDAClOAClPACpOACtRADFYADNZADhhADpp +AD5nAEl0AEl7AEp3AEt3AEx5AE98AFaEAFeGAFmJAFmKAFqJAFuMAF6QAGCYAGKVAGWXAGmcAGyg +AHSqAHSwAHqxALw/+HEAAAAkdFJOUwAACzc3R0iorKysrKysrKytra2tra2trbKysrKyssLD/v7+ +/qM1hPYAAAABYktHRAcWYYjrAAAAyElEQVR42o2R5w6CMBRG2bigKqOiQB1UxL0Vt77/Q1kqCkRM +POmPm5O29+a7DMMyX7DR4XhRSiHyXOS5AnIzoAJHXgjIjrAcAByLlkggWnRtCvA88KpckWgp1tD3 +YayltMb4L00bNSAewAZtHmsHQIIcjGTTMEz1fRt4Psb9YbsznS+Xi7Ga1sGk21tvw3A3U7KfrPay +TlCyLfXNSaOVlRlQP1zrOXNr51stT18e1Y9Ooqoc78VPVEmwdrnUbCXB5q8hf2k/VvwE0OgmkNbJ +VWUAAAAASUVORK5CYII= +""" diff --git a/src/icons/application_exit.py b/src/icons/application_exit.py new file mode 100644 index 0000000..33b3d59 --- /dev/null +++ b/src/icons/application_exit.py @@ -0,0 +1,21 @@ +"""Icon for the following action: exit from application.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +R0lGODlhFgAWAPZJAAAAAHgCAYcHBYgZGZUJA5cRCpsYEpkkFpkrJKQLBKgYBaQQELQCArEYGKQo +Fq4yFrUlBrAoE7g5F6g0J6Y2NqlMO6lJRrVLRLpSSLZaWbxpacg1B9U7BMwpKMw5ONQnJ9w4OMlH +FttDBtFOFudJBPNPAPdTAMFbTsZmV8J3d+FiYut9fauCebGHfqyLhK6cnLaMhL+Si7ynp8mIiMaW +j86Skt+KitGZmcSsq92trd6yrd+ysuuIh+6oqOS7reW3svKqqurDtM7OztjY2OTMyujT0/DExOjo +6Pj4+P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5 +BAEAAEkALAAAAAAWABYAAAf+gEmCg4SFg0hIgz0rIB+Oj5AdjypANoI8Rjs5mzs7Pz2boZw9HoId +N6ipqquoOQ2CDDOyLy81sreztTM3C7CyOEdHMriyMsE4MzW9SQwpKURIR0gyzs7G0UQpM8sMGt5F +wUc43sbB2RozBoIJGe0aRMFFOMDmGu0pBevt+/BHQ/9DiOzTkIKAoAUUKFS4gAEDEYABUZyoMIFA +AYNJFhAQQEBBBAkuILaAoCDBxgICDm7sCIEFRCFCWJAkQMBAyiQGaCZQ0CLYEB8+YA4ZqaDmzZwd +Y4QLQoKEj39HYkBIcOAoTRpLSZQwUSLIkGA0FDgIIOgATR/RgojQypVEEGlMP8SSRXLgogIfQTZw +WNuUA4cgcQk4SInEQl0FECDoFbFWBIcNG0gqmDDXQgUHDyRICBFihOcRnDU/mICAbJIBAVKrXs16 +taHXsAUFAgA7 +""" diff --git a/src/icons/checkbox.py b/src/icons/checkbox.py new file mode 100644 index 0000000..cafbe64 --- /dev/null +++ b/src/icons/checkbox.py @@ -0,0 +1,28 @@ +"""Icon for the following action: classic checkbox.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAEQElEQVR42uWTa0ybVRjHz4bNwgdE +JkIKBEbFEfaKupgYYiTBOZbMyMK4iOiwLfRCkbY4ylpKKRQobGNCuykOxk0YFEPm5CKQttSNbFOJ +bp80U8MXSQxybUuBCkn/9tStAtkSv/jJX/JPznnPeX7nvMlzyP+TkLyQMnY+W0myCIs8isHBwRaT +yYS+vr7Hpr+/3xe6b2BgAGnqNBwxHYFwQgiv+Ap5jwT4hRcvXiQdHW2B7e0dWFpagsvlgtPphMPh +gN1ux/LyMhYXFzE/P0/j+7ayvILCTwvBaeFgZGEEo0ujnnBjOEgG+WSfcN/fcgCkpaX1OUOzERt/ +bmDmtxk47A56yA7h3NwcnA6nL/JWGeIuxaH35x50/tKJzl870T/T70nuTAZ5nbzhFxuaLzE1+jpk +GTPB7+bDeN1IBf7bLiws+OarzlUoOhRIaE7Ax3eNMHzfhKbpJhimDci/ng+WlLVIjpIY8pAiVXFS +gvAQUtpew9lbteBUc3B59DJWHatU7pO6Vl0o7y7Hi42JaLitQ81UJWpuaKGzaSG+JgQREwdJ3ZPk +l8ZkRwYEnQqaeMVwGFW2Myi3lUBtlSFefRAdY+1Yc61hbW0N2l4tXqpNRNWN01BNyqE0y6EYk6Lk +yyIEigPBvJP4PqGkPxDvPbb3qejT0Y4Ph3ge6TAPsnEeSsw8KCZ4iC85iF7rVegH9HhBxUBpE/jW +5N41yVAe1F/JESp/GhkFb0Ol0DDf3jXv6sUT+7VhxaEQXUuDcDgDheMZkFgyIB/PRKwgFoek8SiZ +zIbEmgHxRDr4Q2mosyiwXxyCkTsj4OYJIBIVM8A88QOAiIWy51/NTUaMPBKi4VTwx46iwHoMgpup +EHvnMttbqPiOD/UdLmTmk1AM5yBM9AxMVhPcG27k5nJRUCBhAGwX/0GEwg8YPk+MVksrYkRscIcP +I330WWSZvbFycNISjeQvWEj6PAAneqPA5keg13IV9hU7NjbWweUKQR3U5cdunyVFRTLGeyIoZwfP +4QA3AjmWKLz59ZNIuxWMtNvBOH4zCKfGOIjIZaPH0kNbj3aMV7wBgaAI1EFdfmZnbUQmK2PEYike +UmOqRey7bAjuRSP7xxBk/hQM0TdxCE8Pw2eTPdh0b9LX6XuFbrcbEokM1EFdfmy2LlJWVsFIpaXY +TmWvFpzMcMh+P4CS+/EIOx6K7sluUDY3N7G+vk6fPx17pQpQB3X56e6uJmq1jiktVcKPBz7OdCnx +ckGUt0cj0Wa+8s+yx0OF9La+cWmpCtRBXX66uj4i1dV1CRUVWmxtbWE3Iz+MYOr+FB4HfTy0ljqo +axv3SF2dPlKvPzet1dZBo9HtSFVlLbTe0PGjQmtoLXVQ1w50unpWQ0Mjo9efz6mvP8+vr7/wII27 +cmFH6F5aQ2upY4c0JSWFUIzGjj3e33lCqdSwNBrVvwrdS2tord/1X/IXDe9/ODUL3ycAAAAASUVO +RK5CYII= +""" diff --git a/src/icons/configure.py b/src/icons/configure.py new file mode 100644 index 0000000..5105c30 --- /dev/null +++ b/src/icons/configure.py @@ -0,0 +1,24 @@ +"""Icon for the following action: configuration.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAADkElEQVR42qVUa0yTVxguUKo/ZlzI +DAWCurkoSCyKY1xEbM1YMVGpUSJi8DZvhGxAv0qlaluE2oIzOqUKlFqrFCJFjRGBwpK5omAW2OSS +EItQ4zTiALGKBC/ts7f884/S+iRPcs533vN873nP8x6WF/CRSJhlFkuTobXV2nfrzz8GbtRft1ww +6net4idyWF7CR6GQ73o69PSt0/keDocD9od2dP7dgavX6lzMgdwzFMP2WHX1D4IFjU0NE8Mjw3gz ++Qbjr8eh0aid0dHf9fv7+5+kkBiin8fCKSnrxG3trRh6NoTx8Vd4TcKJ/FUXaCloKlMv4bd9R4a5 +ynQJVAqMjT2H46UDaWmbpe4SEb3GF8Ul6oeHDufj38ePMDz8H0ZHR3Gzob5l3vx5H1yaWJIze9qq +KxMToq5cNbuUBQo0N1swQnV+RiXpu9+HkuOa33PEv8ytrjFFNbdY9KpjRf3TdsMR+WF5+9070FVW +YH9mJpg8KZ48eUyuGAQJ4mC+dKKzo8PV29uD5GThX9MVnllrvtx1yVSFNWvXo0ynwyJeJBqbGmG3 +D6L1thVFqkJ093ShUl/5ztfXN3da2SqVigyb7T7u3fsHvOXR0On1WJMiwo7dP2Fg4AF6ertBx4fZ +XOua89WcetrD/aSoVlua3tnZMUFdhhcvxiCVybAhNRWZWVkoryifusi7VCKGEb/lcDjXaE/YR13C +i1ziZzQaFFXVJqdQKERMbAysVisGBwdAbexMT9/yUsDn2/l8vpXNZrs7bhsx8KOi+/bvmWWqMdXl +y/IRtTwKSUlJ2ERZ2vptToZh2ikzGYUJiQuJAUTOJ73MSMTfGi8aulJE6xE6NxSRSyMhEAjQ0HDT +lZ2TXUch33jatj4H8pjks2WlIzGx3yOQG4iQ0BCELw5HcYkGao361pSoh/AXMzkH1cWqd7ylPASH +hCAoOAjcIC7Stqa5vWujmGiPWpfLDZxRpFLqKirLkJ37MxJWrkBYeNhUGeLi43DeoB8JCAgQefxq +HT9RLFcelTviV8RJM7Zv3Xhae+pRbFwsiGSnc5MRERFus89geQqDUd+TkBDfQsMviT6/ntBk7d23 +B6dLf3MKBPxS+jab5Q2qa6raNqVu7KbhIiJbpSksKCvXukSidVdoHszyFnTcjMu11e9zxdm36bkz +ndGemqQf3aClr1mfCd+iY4U7FQVH7kjyxG0/CpNk7kw/9/H+H4EzvsmAjdpSAAAAAElFTkSuQmCC +""" diff --git a/src/icons/copyright b/src/icons/copyright new file mode 100644 index 0000000..71bd74e --- /dev/null +++ b/src/icons/copyright @@ -0,0 +1,66 @@ +This package was debianized by Armin Berres on +Wed, 24 Jun 2009 19:47:47 +0200 + +It was downloaded from ftp://ftp.kde.org + + +Upstream authors and copyright holders: + + +The Oxygen Icon Theme + Copyright (C) 2007 Nuno Pinheiro + Copyright (C) 2007 David Vignoni + Copyright (C) 2007 David Miller + Copyright (C) 2007 Johann Ollivier Lapeyre + Copyright (C) 2007 Kenneth Wimer + Copyright (C) 2007 Riccardo Iaconelli + +and others + +License: + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . + +Clarification: + + The GNU Lesser General Public License or LGPL is written for + software libraries in the first place. We expressly want the LGPL to + be valid for this artwork library too. + + KDE Oxygen theme icons is a special kind of software library, it is an + artwork library, it's elements can be used in a Graphical User Interface, or + GUI. + + Source code, for this library means: + - where they exist, SVG; + - otherwise, if applicable, the multi-layered formats xcf or psd, or + otherwise png. + + The LGPL in some sections obliges you to make the files carry + notices. With images this is in some cases impossible or hardly useful. + + With this library a notice is placed at a prominent place in the directory + containing the elements. You may follow this practice. + + The exception in section 5 of the GNU Lesser General Public License covers + the use of elements of this art library in a GUI. + + kde-artists [at] kde.org + +On Debian systems, the complete text of the GNU Lesser General Public +License version 3 can be found in /usr/share/common-licenses/LGPL-3 + + +The Debian packaging is (C) 2007-2011 by Debian Qt/KDE Maintainers and +is licensed under the GPL, see `/usr/share/common-licenses/GPL'. diff --git a/src/icons/edit.py b/src/icons/edit.py new file mode 100644 index 0000000..5eeb989 --- /dev/null +++ b/src/icons/edit.py @@ -0,0 +1,25 @@ +"""Icon for the following action: edit drawing.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAADq0lEQVR42q3UXUxTZxwGcKlbGwGD +S5RUcDtAS6NEECjTuDnNiEl1alRsELnQBGaNH8lEuHDRzWXLrmbWEOesi3HU8SHGSRAJJn5rRFQC +TGxNr6CKtYX2tD39hpZn73tYj6VJG2P2Js9Vk1//7/P23zn/xzFUbVMNqFSBIZWq83FNzZLYzzJJ +1r5PDlWI1PbOj13Pt66e7svLwx8MY/45MzMrCm/Ge5yQ9zXYu8W49ftirFwmx4X0dFyWSKAViRqj +8Jbp6WlYbTbYxscxYbfD7nDAwbJgnU44XS64aNxuuDmOj9NhwcT9L/CsbQlKV8ghlUqx+8uUcMcq +UaROJDoWC89CWYI6CUpB938g5/HAQ8JxLth61TB3MVi3Wg6GYVCuFIVfNM+dajkh1hNvjVAFgSk6 +e0oCclHQ64WXxOfzYWKgDuy9Yqi/KoBMJkPZsnkYPC+KXPtFfIdYu0hyYjsWUH7KWJBgPr8ffpIJ +YyM8fZ/j0J4y5Ofno0C+AL06Me6ckpgk4jnfEOtTkpRYOBalVxYmpGAgEIBr9Ap8/Rug/W49jyry +pOg5+SH+aWPw0fyUVuJsI/mAgrFwPCqAwWAQnPURvEO7cPG3CigUCshlDFp/kMB0aRGO1teAGI0k +aRSLhxOiPtYE3/A+3L+0G0VFhQSV4dSRVIx2ZODI/s1oaGigcKWgxcNcHBoKheDnLPA8r8NQjwbK +0mK+ghNfL4Dlajrq965CdXU1BgcHKbwpIeyZ6XRmUoIG/W54jN9i5OFhrFtTyqMHKjNh607F8X0K +lJeXo6urC1NTUwnhTRT2zjwUf/1QMAC38UfYBo5i4/oyHt25MZtMmoqTh6UoLCyEVqtFOBymSQ77 +YipwGX+Ca7geVVuVPKpay2D07zQ0fZ+BrKwsaDQaWhuPTk5OJoWFad1kXV/1HoCmqoBHPyvLhaF1 +Pnp+TUdaqhglJSXo7+/H2NgYH4vFkhQWpn0zMgTT02YcrNmAFctl6DuXAUPbQiiL81FbWwuj0Qi6 +qZFIhKw9i/b2i066GInhYJCHX/T9hSfdDbipV6H79HKEHi0F+2YYBqOBgrNQ/QW9MycnZyfdtoQw +reGleQQtpzW421qB283bYXr8J/yclYfMZvMsVHdW58zOzq6MoknhpvPnoCwpwoMeHfweB0UoRh+J +9vkW1Z1hyV+lOoomhWkNt25ch90+LvyM4mH++vomlkwqoMnh5If/AqvVSh6qnc3Nzd3xFk1+PiFR +v2NWvgv6LwqZ8W5/rgThAAAAAElFTkSuQmCC +""" diff --git a/src/icons/file_new.py b/src/icons/file_new.py new file mode 100644 index 0000000..d16b382 --- /dev/null +++ b/src/icons/file_new.py @@ -0,0 +1,25 @@ +"""Icon for the following action: display image.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz +AAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANeSURB +VDiNtZXNax1VGMZ/55y5c5P7MTfJTZukhKRSWyEifoCgWOrCP8CNuFDcKEYoKBYEQf8Es1IXUVHc +dqOrLAShNEWFfqBtaGulVnITY5O0TXOTuZP5OOd1MffepCUUUfPAyzmcGX7zzDNz3qNEhL2Q3hMq +4M39+stniEwqpXa9QUS2ywlOBBF373r7mojgnEMJnzN39Wex1sr/JWutnJr9XjwR0FpzevYUW1tb +D3w9pVS3oDNuq+j7HD16jMxmeDbLANjc3KS5fGNXoDYGrTVKqfaYz1GKDloAqY8BkKUZXmZzcJIm +GNe6BxhGKatrIcP7+qhVS2htQLnuQ+537MTl4CzDy9qO0yTBk22w1oZCWWNUkeWVWyRxhbGRflbv +RgwPBt04NsKYSslHKUhdB5xug5M0pSRRnqX2MFrQxqPS73Og3svC8iaz53/n8FidonFsRik3/lyn +6HuI62FfrYfY7XSctsFJgi8ttDZo49Daoo2gtXB9aY0jY3UWl5tMjNfQ2mELsBHGPHawjLYR6foK +N6UEgLV2RxRpQq+O0F4hB5sC2kBsMypF4YeLDYzSFFVMlrS4vbTKb3NXeXbkIJZ8p1lzeMfH60SR +pJRKW2jtUNqhtcN4UC5o+oYc/Sbm/emfeH70Ua4tbvD1d38w9dYTaBXm8ZkCPX4xB+eObddx2YtR +2jH0+CNUx8dRQHRrgeVL5xgtxbz6XIXJj2aJEsfJD5+m4rXaUJ9CZYAe6elmrLejSCnTZP9YldpD +o2ivD1WoU9o/zuhTT1Krxbz0TEAYhrzxwiDD5YSKiaj6lqBaRRfKZNayuLhIFLWkGwVA4GdUjkyA +qqJUb/7jmz4K/QeoBgqb5b3j9voWQdGB8aF3kPe+SUnsTQ49fI1Wq0UrjGwXbJ3j2FQEUyd33X0d +xYU6314ucuZjy/BQwMhwjWZrhXdPvENjvsH8fAOBi13w4MAAr7z84gOhHRljCIIaQTWgXC5z7vxZ +Go2FHCpiz5z+8TUvTVOcc0y+efwfQe/XlSuX2dho0phvYJ3LLpy98PbMzMySl6XJV198Of06tPvp +Lj12t3LtXZbPJb27dufSp59MfxCG4XWgqQAPmPhXdnP1Aw5YA/4C7oiIqP965imlfCATabe2zvpe +HaZ/A5auKIj+99jfAAAAAElFTkSuQmCC +""" diff --git a/src/icons/file_open.py b/src/icons/file_open.py new file mode 100644 index 0000000..14237b5 --- /dev/null +++ b/src/icons/file_open.py @@ -0,0 +1,16 @@ +"""Icon for the following action: open the file.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +R0lGODlhFgAWAPU6AAAAABMTEwIbOAohPgAAegMiRxUzVR88YSI/ZShHbS1OdTRUejVYgztll0Ne +g0Rrm1Brj1t0lUZvok53qUd6tVF5qFJ+tWJ7nFyDr1eDulyT0WKYzm6c03edynWc0X6l2Jubm46a +qJWeqZehr5qksqCfn6WlpqWxv7i5uoSp2ZGxzpS00Zm84IPg/7nBy6jK6K3R7av//7P//8rJytfX +19/g4ejo6O3u8fDv7/n5+f///wAAAAAAAAAAAAAAAAAAACH5BAEAADoALAAAAAAWABYAAAboQJ0u +ACgaj8KkUgjIOZ9OGwiwXDZxTizONpuZqFVdJpF9bmeg3DdscUC13LTNxLDYM0KJG5rb0tJbJhUf +HxMbOg0Lb302NCZZNiUNHA94C4p8ODg0KHwmDwwaOgoKfJCcJiYlICgoCC06CWSZmjY1NLg0MzkF +MTIJBlA3LiQkI8fHIiEhJwI6BAYFyMcrL9bX2DAwKh3a2SwrKeLj5CkXFeXp6uIR6Ovv4ufkH+L0 +8CntKYT0+/36+yk84HNnr1zBdBEaZKDAsKHDhw4XYLh3kNyFCR4yatyokcOHjh4gGBhJsqTJkwaC +AAA7 +""" diff --git a/src/icons/file_save.py b/src/icons/file_save.py new file mode 100644 index 0000000..8dfa462 --- /dev/null +++ b/src/icons/file_save.py @@ -0,0 +1,28 @@ +"""Icon for the following action: save file.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +R0lGODlhFgAWAPfPABkXFxwZGhwaGh8dHR8dHiAdHiAeHiIfICIgICMiIiQgIyUiIyUjIyYkJSgm +JiknKCooKCooKSspKSsqKiwqKy4qKy8rLC4sLS8uLjAuLjEuLzEvLzIuLzEvMDMvMDIwMTIxMTMw +MDQxMjQyMjQyMzczNDU0NDY1NTc0NDc1NTc2Njg0NTg1Njg3Nzk2Nzo3ODk4ODo4OTs4OTs6Ojs6 +Ozw4OTw7Ozw7PD07PD47PD48PT49PT89Pj8+PkA8PUE+P0A/QEE/QEM/QEJBQURBQkRDQ0VDREhG +R0lGR01JSk1KS01LTE9MTVFPUFNRUlRQUVZTVFdUVVdVVlhWV1lXWFpXWFpYWVtZWlxaW11bXF5c +XV9dXmBeX2FfYGJgYWNhYmRiY2VjZGdlZnZ2dnh4eHp6enx8fH19fYCAgImHiIqJiouLi4yKi5CQ +kJSUlJiYmJmZmZycnJ2dnZ6enqCgoKGhoaWlpaalpqampqmpqaqqqq6sra6urq6xtbKysrOzs7Cz +trOztrO0t7O1t7W1tba2tre3t7O1uLS2uLW2uba3ube4uru7u7LG2cDAwMTExMXFxcnJydXV1dfX +18za5s3a5s7b58/b5s/c59Dc6NHd6NLe6dPe6dTg6dbg6tfh69fi69ji7Nnj7Nrk7dvk7dvl7Nzm +7t7m7t7n7uDo7+Hq8OPr8ePr8uTr8eTr8ubs8uft8+ju9Onv9Orv9Orw9evw9ezx9ezx9u3x9u7y +9vH1+vL1+vP2+vP2+/T2+/T3+/X3+/X4+vb4+vf5+/b4/Pf5/Pj6/Pj6/fn6/Pn7/fr7/Pv8/fz9 +/f39/v7+/v///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAANAALAAAAAAWABYA +AAj+AKEJHEiwoEGBR3D06BOoYaBBhxBJnBioBw4uAnvsUaNql0ePvICJLJYsmSw2aYYItGHjCype +H0MCK0aypCsxYIAInGHiS6pkjYIKHfpMVZgvPXZq4aLqmdOnz5aVLBYs1RctNgSayKIFVbChYBvd +QqUlywytVqyUusX2lixacGXFggVLFBYqZ6GZoEJFlKuwQ1F1qlIFhkASUKBwIiUKlajHkEV96qQp +ihMTAkE8eWJp0yZOnkNnumTp0iUnlwV2aMK6tevXrUkIvMCktu3bt5XY3jA7SZIlS3wDB+7bt5Lj +SjgIpICkeaRJ0KNDl4SDQ4QjRy4IjECkOyIjP5CLPBo/3tGUHxy6QxDYwId7QjVK9BjfyFChP0+E +ZPAhxIHABDXY8AIhK5CwAiOG+LGHHngk4UMGNVTBgEAHdFACCnuQ4AAHe+RxRx1ywEEEChCgkAQC +AilQQAUc1HEHHXPQEccbbrTBRg0VMMABBwYMVEAAAZSBxhlmlEHGGEiOMQAAAAggQI8HRXlQQAA7 +""" diff --git a/src/icons/file_save_as.py b/src/icons/file_save_as.py new file mode 100644 index 0000000..cb93041 --- /dev/null +++ b/src/icons/file_save_as.py @@ -0,0 +1,29 @@ +"""Icon for the following action: save as.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz +AAADdgAAA3YBfdWCzAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAROSURB +VHjatZRtbBRFGMd/e6+l0ioNqPSNlkpbBGwKqa1irUBMS4IviJEPWpMq2IparJZi8QUlESEgL5HU +QEJQowkxQUggmhYMVbFGUNAEhKQtDbRcS0t61x5327vZu3XnNtneWfngB/+X52Y2M/Ob/z6zzzh0 +Xef/kANgaWVVTyAQyLHZbax9cz1OoyV+Q5sCKChK7B8g1jdbhZDQ2LltC9FIlLy8vL4Dn3+WFQMH +1WBOQ0MDXd3dlDxYzu3JSZhYJMYCWXATaITZjgRUui5eIBKJ0NrWmmk5jkaj7Ni5g4L8AoSmGQYV +C2zBTSBRoeJZ10ho6AYZzc2kFM9HaFE6Ozsl2GDpWGCZZ80Ayg0k1Om00XqmiwnSdXJ9a9Gz3Uzq +6OFoRQUPHz6CY36pXBsLXY+S4LiwoDC2gWIzwHaFZWWz/snkYnsjeWkenjohKAsKHhKCH1evpuL0 +H9KtDJnnRMcSLvMVMQbUsMb3Z3uIV7DnK57Ob+PZTRq/XxgmPNOAP56K7fJcRCQqoaZrXU9wLAdM +sNFXQxoL52RZ0P7O74z8f8O6FvjlzyDTUgNsq4XQbY8wWFpDJA6sR2/hWIvojIUFZzsHkBK+C1Sl +bWPPYcGxU4aj0BAtbznwK7kcOV/OE1W58sAk2OTo8WD5CsYAYDmenT2VMX8fMxz7aT/t5dPDOiPe +6xzY4CZ1ShqHLj3J/cX3cWd6ViwVUROc6FjmRQKlhIggu5HwKDOCu+js6qJ5r50R3xBb6mzkZrrY +17GEu3OmM+veoth56IAmjZmseMdmKqQ2Na5BIcrG1Ulc8//Eqi2T8fqCrKryUj7PRd3OFAZGfiZv +5gC/nmwlQSYr3rGVipjWLA+Qc89CVtRe4qaqsmTeAHXLXdR/Mpluj43C/EwUMNckyjI4wfEiA1Kx +uJqVtfu44hHMmtbL5loH7x1IouM8BjQbp9Npzp+oiQUiKy87rZ/6l5+hbv1+/uoRTEu+TkujjS9P +3sHpy1Np3vAClZWVlrPal2rx+0fNtZlZSEXiC0R+KlOSBtnzbjFNH3zBD2d1Ulyj7G0Mc/yMg11f +w9KlJZSUlDA8PGxdARnp6QwOOvB5vQghMFnaONiu6LzzopvtLcc4eiqNZJfG7ld89A45efVjlbIH +FlBdXS0Xy5DQWEQkTNeRP00I7Ha7NDkOXrxwDu681/j23Docdh8bn+snOdnNsqYASZMmU19fj6qq +CVAZISGsG03eiukZGVzt6x0HP7qoFKH28VFTOe3HD1K+YAor3k42Jk7H7XbHKnJ01MxlfKhjKqHQ +mPksBH5jjuV40xsrU+cWzdVHB88p6vB5cmYUceLaIupezyIswvKQ/hUq3QdvBhhTxxDhMMLlwuPx +jIM1HB++v/2gUvNYRv+htqt3XfH4bc/XzMflUq36N+BWxOuaAQqHQwAyVSbQ6QjE2hHvjb7S2Skl +DZvbfmvYzH/S1q1buZX+Bldqqa+aLBAwAAAAAElFTkSuQmCC +""" diff --git a/src/icons/help_about.py b/src/icons/help_about.py new file mode 100644 index 0000000..6bb9818 --- /dev/null +++ b/src/icons/help_about.py @@ -0,0 +1,22 @@ +"""Icon for the following action: help/about.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAC8klEQVR42rWTbUhTYRSA7+6mppZl +FiJFWEQfkBAEWUFB0K8o8k8UFgVSrf5pvyQEhYQ+7BOiIsiNbYxJw4QU60c/mu76MWc4TcyPnNvc +3PfcvZsuGqdzxjVaw1G6Ljy897zve5/37LxnDAD8F/5pc6+GKci4uFvBHDIq2RCNmRM3MCynkris +5stAI8UZERsUTMVQ+14h4u8AS0eZQHFGxJyaNbsn6oH3toBn6g5wapl51eIuJXOgT1coRIMfwPdN +DovzBujTredpflVio1qqtw/diEd8WgjZqmEh9A4cI9Vxo1qmX5GYMiJpf0tRLBrsBGHuAUTc92DB +/xpiQj9mXRSj9eUyT7l9uhi8eXO/bqMwM3gxLvhaQfC8SEi/h5QJFkNtKDeBc7Q2bnpTItAd0He/ +d0tSn3IqmcvSuV+wW6og6HgEgZk6rOt1KkGSmN7DzkYQvGpYDPeC3/YSRj+eiHAqqWupz3/9o6j5 +raazEHI+Bu90Ldg+l4NjqBzcY2cgMC2H8GwdCu8T9E5ziTX310qIBN7DQrgPHMM1YFRKQuRLzlgt +c5lbi3nrwCnwWeshYLsN3slr4Bk/RyLMvIZIvAftDSD49BDxt+NlymGwtYTnVKyY8TI1ppoZNSw/ +bjj4I2h/iJJGzK6CDkgQdr8C3qOFqZ6TcU4jRaEkpcbpu0LF6DlNdizoeAJzY+dhdvgoeCavwvxc +M3DqrJhRJUnbFWkh+UT3sbjPeguspp0QsN+FCePxOEpX1sf4sIi04xlT3qPN4QP2JrAOlGG3PIUe +bRZP87SOsH8lxkcifpCDrEOKPilYy+zIFZh3PQfnFzlQTPPieo64X5IqTpXmIoXIVmRX002m2vy2 +NBrx68DctiNKMc0jW8R9a5bk6cQyJB/ZjGxHynJlzBGDQuafGagEGjE+jPP7kFJkE5KXRpxU22yk +ACkWP95TW8Vc6GpmeRox3o1sEw9fi2T9Wet0FycTf2K+eEjhpdMJ2QYxzqMEUuor8hPqR7fmTMBl +hQAAAABJRU5ErkJggg== +""" diff --git a/src/icons/help_faq.py b/src/icons/help_faq.py new file mode 100644 index 0000000..f01adc9 --- /dev/null +++ b/src/icons/help_faq.py @@ -0,0 +1,26 @@ +"""Icon for the following action: help/FAQ.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAMAAADzapwJAAAB8lBMVEX///8AAACAgIAAAACqqqoA +AAAAAAAAAACAgIAAAAAAAAAAAAAzMzMAAAAAAAAAAAAAAAAAAAAAAABAQEAAAAAAAAAAAABra2sA +AAAAAAAAAAAMDAwAAAAAAAAAAAAAAAAAAABoaGiKiooODg6DioOKioOGjIBhZ2FGS0aJiYSIjYiG +ioaHi4NMUExiZmKEiISOjouMj4mJiYZ9gH17fniysrKyt7KIi4WLjYju7uz09PT39/T39/f///+J +iYbs7OmChYCIi4aDhYG5u7aKioaOkIp/gH2Ji4WPkIuMjYqVl5J8fnl8gHqRkY6Vl5Owsq2ysq+8 +wLzBwcDKzMqQkYy1trPk5OTr6+l2otCHrdWJioWMjomYt9eivNqivtqqxd6sxd68z+PA0OPf5Oju +7u3w8vDy8vDz8/P19fP39/X39/f4+Pf6+vr8/Pz9/f3////r6+qDhIC7vbm5uriKi4eOkIqPkYyO +kIqOkIuMjoqNj4qYmpaWl5O0trK2t7SVl5Oxsq69v7zBwb+QkY3Ky8mQko2ztLHj5OPq6ul2otCH +rdWIioWYt9ehvdqivtuqxN2rxN28z+K/0OLf5eju7uzu7+3v7+3w8fDx8vDy8vD09PP19fP19fT2 +9vX39/b4+Pf5+fn6+vr7+/v9/f3+/v7////+p1POAAAAiXRSTlMAAQIDAwQFBgYHCQoKCwwNDg8Q +EBESExMUFRYWFxgZGxwgIyQlJSgqLDg6PUJDRk1PUFJYWVlZXFxcXFxcXF1dXmdrdIODh4iIiZGU +lJWVlZWVlZWXl5iYmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZm5zAwdrd3d/i5OXw8ff3+Pj4+Pn5 ++/z+/q0RsC0AAAFcSURBVHjabc9VVwJRFIbhY4OIiIndndjYiN0d2IojojB2d+uxxVEMVOT8T/fM +WeiNz8V38a59sxFwjlcWUFlxzuhXfH5JKVWcF/OXlSVFKpWqXNOgpapTXREoKFUBTbeBpabbUv5y +g2FKR+kN1UikXsbU5uKcbmh4cEKnY7VIvW0l1PvJApyOjgt52fpipl6tWwwzNsIwDGRMoFxdXz6Y +zQTr9f16IGSO4x6523uOI3iGErIF3N08WywEG419RiDkN3DBD8EmylgH+cOBYPiFz62ZkG3gnB+C +tV29rIntyJGilU+7g20jJKmq3cT2VHgj9c4nAVC/92rCAkJzWyZn6yRIVLmK8fEZsZODpohgX5lP +Yn1ncxBCYpkiMn1p305O5xVyT3exh1ehNhkhN6l/WFRs4+7T15q/xAnxErIRdIk8MCQ8Y/3oMBqq +wFVYFw9PqcRvAJeh/6TVIocf2+agdQ0b2vsAAAAASUVORK5CYII= +""" diff --git a/src/icons/list.py b/src/icons/list.py new file mode 100644 index 0000000..2d18ef0 --- /dev/null +++ b/src/icons/list.py @@ -0,0 +1,19 @@ +"""Icon for the following action: show list of rooms.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAMAAADzapwJAAABI1BMVEX///////////////////// +//8AWQAAaAABYwADXgEDgAIDhQMHZwQJfAUQdQkScxASfgoTdgoZew0aiw0jmRMkiRMnjRQvmhkx +mRo0mRs5njk7ryE8qCBEqSRPtClVVVVYmlRarVp9xmiAs/+Ix4iKyACSxQKazwGbzAKczwKc2Qqd +zgOh1gWi1QWk2gal2Z2n2gio3Qus2gOs3Jmtzv+00v+7u7u8vLy85K+9vb2+vr6/v7/AwMDBwcHC +wsLDw8PExMTFxcXGxsbHx8fIyMjJycnKysrLy8vMzMzOzs7Pz8/Q0NDR0dHS0tLT09PY2NjZ2dna +2tro6Ojv9+7y8vL09PT0+PT19fX2+vb39/f4+/f4/Pf4/Pj5/Pn6/fn7/vr///9g2PZ8AAAABnRS +TlMANDnN1da9rO1UAAABFElEQVR4AXWPh07rQBBFJ+8l9N57ANN7SSEJ6Xbs2ISEgTB09v+/grtS +1jIgrnRk6Wh05KW/dtdpW3qtm8Bvem7Dses16A6LlU6nLWZuAgdUodvQqVTKEhEP2KACbfUWFqqV +stYIKKWSoAFqoASNgtEOqIJiL5LUM4VS8Rr6liXQ1ziv41sGBegWix/tgjx0+ApTKORz0AGLhwsb +VIBeFtpncVXYPV2+UioD/a2gZg5WPnJaeyzmf5+Vmj6Zf1UPMSKXxXS3hkYnjuYWVy/jRJHC8Pbu +/sXx2fnaJ5HDYv53o39g5HBqdmEzQWSz6G4RdJ8eB/cm79+6/4gir8jmVN/O2EshQz/2f2l8PU6/ +Fou/J1CgL/Y3aCbu0E1VAAAAAElFTkSuQmCC +""" diff --git a/src/icons/removed.py b/src/icons/removed.py new file mode 100644 index 0000000..2bb7369 --- /dev/null +++ b/src/icons/removed.py @@ -0,0 +1,19 @@ +"""Icon for the following action: remove/removed operation.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAMAAADzapwJAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAN +1wAADdcBQiibeAAAAAd0SU1FB9kMBhQQEFUkXAMAAAECUExURQAAAHwTE4UUFJAWFqYiIqwODv8A +AP///0UAAEQAAEcAAFoAAGUAAGQAAGQAAGUAAJI8PJM8PGkAAGoAAGoAAJM9PZI8PI87O5A6Oo86 +Oo87O+rf3+3n5+////D///H///L///P///T///X///b///f///j///n///r///v///Dy8vD09PHy +8vH09PL09O3t7e7u7vDs7PT29vP19fP29vb5+fX394xPT5tPT61PT08AAFAAAFEAAFcAAFkAAF8A +AGUAAGYAAGkAAGoAAGsAAHEAAHoAAHsAAIMAAIgAAIkAAIwAAJcAAJ4AAKgAALAAALkAAMAAAMkA +AM4AANMAANYAANZTrScAAAA6dFJOUwAAAAAAAAAACgwMDxMxMzNFRUZGR0dIVFRVVaGsra2tra2t +ra2tra2trbCwsLCwsrK1v8DAxMXf3988ExMrAAAAAWJLR0QHFmGI6wAAAL9JREFUeNqVkbsOwjAM +Re3EaSlCFTNi5f+/CgmGSoiHqtaNjSkiJCoLdzw5vncIQIBFEDC4sPaYMRnGaSLeHLwUrjt1kaDx +rJzRgLtbT0CiTStfl3s1aP3cXmLCfnutsaJ5JdYJDzLfwCL/YCX9YMyxT9jpL1sL7OCDixJclqSh +dJIy2/SeuYuKKqDDBmDGEarHPrhXVCxdDToSDOge3csVKzB9RTiazeedroryI9t75SkUn8asEcHZ +cZXh0QanJ9B2UzvXaeeHAAAAAElFTkSuQmCC +""" diff --git a/src/icons/server.py b/src/icons/server.py new file mode 100644 index 0000000..9c40b63 --- /dev/null +++ b/src/icons/server.py @@ -0,0 +1,21 @@ +"""Icon for the following action: show service info.""" + +# This icon was converted from the Oxygen Icon Theme +# Please see copyright for further informations + +icon = """ +iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAQAAABuvaSwAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAE +wgAABMIBvM+QGAAAAAd0SU1FB9kDCAwwCdRxwksAAAACYktHRAD/h4/MvwAAAoZJREFUGBkFwU1v +VGUYANDz3nvbmQ6CNFogINjS0mCAEIVoJO4ERVeGpTFhx9LEP+NvcAVRY2IkGqUEMBI1oI1YSqcN +VEbKFKdl2vm49/GcBADI5XIZKqVSCQAJQO3c9KUL0yeHkyN7G+MRz5/1W8XyX79/fvnmoh4AdtS/ +/uzOvc1qGMOoIiIiIqKMYQxjs/r13hefqgE+np2/3Y6n0YnN6MZ29KIf/ejFdnRjIzqxHu347eYH +0yS1H67PnA6FJAmhEkgySUJlIDc/9/7Z4uj4xKmuQAgAQCBJkmTijHpRH1lwEIFMkgBAqFSScN9I +KlYH7TS0WwMAAEDo6prPB7KDVcsf+voGyGQymUwmk2GgtKGlHaLYaVc1n/1rzoemjCpkKmQYqDx1 +xYShV6sxxdKwlp30QGXBpP2+V9ol89yas1jwyItmzWfDqjjlaWykM06bd8uSP+2xW+4/K35Ex0U9 +azarQVVU1Xh/pdZy3XlH5I7Z0FY56W2VpOVLE0qH+mNl9ks1rJ3QFBaVDrhrUQhN19TVrFs1MKVV +j1ScqG2Wz/IzXvfAbY/cN2Gv3JrHbqhs+MSmDf3BdlGs5i8Nl/Mn5pxzXDKjoy0z6w1hxIqv7Lft +UFmMZs1ia/SYJpr69rtrESy7pi7X8Y+eaQ9rURSdbnOhPvumGU/c8NCSPV5QWLfmuqFtF2zbsny3 +7Of6a4u7z+8b+8lr3jHugJ06el4247AZyVUNy62rlzp/56K99OhqNTU6NZImTfrZuoZkXdMRuRW3 +ytVvrlx8fFuZQLKjcXTqo3ffe+v4Un0iHVZY8SD2dZfvfPvdvctb920JEoAk17Arb4zvq7+i6j1s +Py67OraUAP8Dpeo+PSktWm8AAAAASUVORK5CYII= +""" diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..e0310a0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Unit tests.""" From 24346e0d8a7017b6acffefe518c8fdee87b20619 Mon Sep 17 00:00:00 2001 From: Pavel Tisnovsky Date: Tue, 17 Dec 2024 14:52:00 +0100 Subject: [PATCH 4/7] Pylinter settings --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 621f6ad..2177ff0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,3 +29,7 @@ target-version = "py311" lint.pydocstyle.convention = "google" line-length = 100 + +[tool.pylint."MESSAGES CONTROL"] +good-names = ["e"] +disable = ["W1203", "C0103", "C0301", "C0302", "C0415", "E0602", "E0611", "E1101", "R0902", "R0903", "R0913", "R0914", "W0102", "W0212", "W0511", "W0613", "W0621", "W0622", "W0707", "W0718", "W0719", "E0401", "R0801", "R0917"] From b7ea4ef589e84deb2697d8efb8d86f8cef0551ef Mon Sep 17 00:00:00 2001 From: Pavel Tisnovsky Date: Tue, 17 Dec 2024 15:25:05 +0100 Subject: [PATCH 5/7] Fixed issues found by Pylinter --- src/config_editor.py | 8 +-- src/gui/dialogs/cache_dialog.py | 2 +- src/gui/dialogs/edit_dialog.py | 102 ++++++++++++++++++------------ src/gui/dialogs/logging_dialog.py | 2 +- src/gui/tooltip.py | 4 +- 5 files changed, 68 insertions(+), 50 deletions(-) diff --git a/src/config_editor.py b/src/config_editor.py index 2c867c2..831cee1 100644 --- a/src/config_editor.py +++ b/src/config_editor.py @@ -13,6 +13,7 @@ class ConfigEditor: def __init__(self): """Initialize configuration editor.""" self.configuration = None + self.filename = None def new_configuration(self): """Create new configuration to be edited.""" @@ -20,23 +21,22 @@ def new_configuration(self): def load_configuration(self, filename): """Load configuration from YAML file.""" - with open(filename) as fin: + with open(filename, encoding="utf-8") as fin: self.configuration = yaml.safe_load(fin) self.filename = filename def save_configuration_as(self, filename): """Store configuration into YAML file.""" - with open(filename, "w") as fout: + with open(filename, "w", encoding="utf-8") as fout: yaml.dump(self.configuration, fout) def save_configuration(self): """Store configuration into YAML file.""" - with open(self.filename, "w") as fout: + with open(self.filename, "w", encoding="utf-8") as fout: yaml.dump(self.configuration, fout) def check_configuration(self): """Check if configuration is correct one.""" - pass config_editor = ConfigEditor() diff --git a/src/gui/dialogs/cache_dialog.py b/src/gui/dialogs/cache_dialog.py index 065566c..40d7ef4 100644 --- a/src/gui/dialogs/cache_dialog.py +++ b/src/gui/dialogs/cache_dialog.py @@ -1,7 +1,7 @@ """Conversation cache dialog.""" import tkinter -import tkinter.ttk as ttk +from tkinter import ttk class ConversationCacheDialog(tkinter.Toplevel): diff --git a/src/gui/dialogs/edit_dialog.py b/src/gui/dialogs/edit_dialog.py index 398e0fa..5e72884 100644 --- a/src/gui/dialogs/edit_dialog.py +++ b/src/gui/dialogs/edit_dialog.py @@ -1,7 +1,7 @@ """Implementation of configuration editor.""" import tkinter -import tkinter.ttk as ttk +from tkinter import ttk from gui.dialogs.auth_dialog import AuthDialog from gui.dialogs.cache_dialog import ConversationCacheDialog @@ -25,14 +25,65 @@ def __init__(self, parent, icons): self.title("Configuration editor") self.icons = icons self.parent = parent + self.group1 = None + self.group2 = None + self.group3 = None # don't display the dialog in list of opened windows self.transient(parent) + self.add_widgets() + self.set_dialog_properties() + def add_widgets(self): + """Add all widgets on the dialog.""" + self.add_llm_group() + self.add_service_settings_group() + self.add_devel_settings_group() + + # UI groups placement + self.group1.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) + self.group2.grid(row=1, column=2, sticky="NSWE", padx=5, pady=5) + self.group3.grid(row=1, column=3, sticky="NSWE", padx=5, pady=5) + + # rest + ok_button = tkinter.Button( + self, + text="OK", + command=self.ok, + compound="left", + image=self.icons.checkbox_icon, + width=200, + ) + ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) + + cancel_button = tkinter.Button( + self, + text="Cancel", + command=self.cancel, + compound="left", + image=self.icons.exit_icon, + width=200, + ) + cancel_button.grid(row=2, column=2, sticky="W", padx=10, pady=10) + # set the focus + ok_button.focus_set() + + def set_dialog_properties(self): + """Set edit dialog properties.""" + # close the dialog on 'x' click + self.protocol("WM_DELETE_WINDOW", self.destroy) + + # get the focus + self.grab_set() + + # how the buttons should behave + self.bind("", lambda _: self.ok()) + self.bind("", lambda _: self.destroy()) + + def add_llm_group(self): + """Add LLM group widgets onto the dialog.""" # UI groups self.group1 = tkinter.LabelFrame(self, text="LLM section", padx=5, pady=8) - self.group2 = tkinter.LabelFrame(self, text="Service settings", padx=5, pady=8) - self.group3 = tkinter.LabelFrame(self, text="Devel settings", padx=5, pady=8) # LLM settings button_new_llm = tkinter.Button( @@ -45,6 +96,9 @@ def __init__(self, parent, icons): ) button_new_llm.grid(row=1, column=1, sticky="WE", padx=5, pady=5) + def add_service_settings_group(self): + """Add service settings widgets onto the dialog.""" + self.group2 = tkinter.LabelFrame(self, text="Service settings", padx=5, pady=8) # service settings label1 = tkinter.Label(self.group2, text="Authentication") label1.grid(row=1, column=1, sticky="W", padx=5, pady=5) @@ -156,6 +210,9 @@ def __init__(self, parent, icons): cb.current(0) cb.grid(row=9, column=2, sticky="W", padx=5, pady=5) + def add_devel_settings_group(self): + """Add devel settings widgets onto the dialog.""" + self.group3 = tkinter.LabelFrame(self, text="Devel settings", padx=5, pady=8) # devel settings cb1 = tkinter.Checkbutton( self.group3, text="Authentication" @@ -170,45 +227,6 @@ def __init__(self, parent, icons): ) # , variable=var1, onvalue=1, offvalue=0) cb3.grid(row=3, column=1, sticky="W", padx=5, pady=5) - # UI groups placement - self.group1.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) - self.group2.grid(row=1, column=2, sticky="NSWE", padx=5, pady=5) - self.group3.grid(row=1, column=3, sticky="NSWE", padx=5, pady=5) - - # rest - ok_button = tkinter.Button( - self, - text="OK", - command=self.ok, - compound="left", - image=self.icons.checkbox_icon, - width=200, - ) - ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) - - cancel_button = tkinter.Button( - self, - text="Cancel", - command=self.cancel, - compound="left", - image=self.icons.exit_icon, - width=200, - ) - cancel_button.grid(row=2, column=2, sticky="W", padx=10, pady=10) - - # close the dialog on 'x' click - self.protocol("WM_DELETE_WINDOW", self.destroy) - - # get the focus - self.grab_set() - - # how the buttons should behave - self.bind("", lambda _: self.ok()) - self.bind("", lambda _: self.destroy()) - - # set the focus - ok_button.focus_set() - def ok(self) -> None: """Handle Ok button press.""" self.destroy() diff --git a/src/gui/dialogs/logging_dialog.py b/src/gui/dialogs/logging_dialog.py index c617eb3..6686796 100644 --- a/src/gui/dialogs/logging_dialog.py +++ b/src/gui/dialogs/logging_dialog.py @@ -1,7 +1,7 @@ """Logging dialog.""" import tkinter -import tkinter.ttk as ttk +from tkinter import ttk class LoggingDialog(tkinter.Toplevel): diff --git a/src/gui/tooltip.py b/src/gui/tooltip.py index bca40db..9d0fefe 100644 --- a/src/gui/tooltip.py +++ b/src/gui/tooltip.py @@ -46,14 +46,14 @@ def unschedule(self) -> None: def showtip(self, event=None): """Show the tooltip on screen.""" x = y = 0 - x, y, cx, cy = self.widget.bbox("insert") + x, y, _, _ = self.widget.bbox("insert") x += self.widget.winfo_rootx() + 25 y += self.widget.winfo_rooty() + 20 # creates a toplevel window self.tw = tkinter.Toplevel(self.widget) # Leaves only the label and removes the app window self.tw.wm_overrideredirect(True) - self.tw.wm_geometry("+%d+%d" % (x, y)) + self.tw.wm_geometry(f"+{x}+{y}") label = tkinter.Label( self.tw, text=self.text, From 34957dfe076cf09685e14a9dead2cf1a73d8eb53 Mon Sep 17 00:00:00 2001 From: Pavel Tisnovsky Date: Tue, 17 Dec 2024 17:14:29 +0100 Subject: [PATCH 6/7] Added type hints --- src/config_editor.py | 18 +++++++++------ src/gui/dialogs/about_dialog.py | 2 +- src/gui/dialogs/auth_dialog.py | 4 +++- src/gui/dialogs/cache_dialog.py | 16 ++++++++----- src/gui/dialogs/check_configuration.py | 4 +++- src/gui/dialogs/data_collection_dialog.py | 4 +++- src/gui/dialogs/default_model_dialog.py | 4 +++- src/gui/dialogs/default_provider_dialog.py | 4 +++- src/gui/dialogs/edit_dialog.py | 26 +++++++++++++--------- src/gui/dialogs/help_dialog.py | 7 +++--- src/gui/dialogs/llm_dialog.py | 4 +++- src/gui/dialogs/logging_dialog.py | 26 ++++++++++++---------- src/gui/dialogs/security_profile_dialog.py | 4 +++- src/gui/dialogs/tls_dialog.py | 4 +++- src/gui/icons.py | 2 +- src/gui/main_window.py | 25 +++++++++++---------- src/gui/menubar.py | 3 ++- src/gui/status_bar.py | 3 ++- src/gui/toolbar.py | 4 ++-- src/gui/tooltip.py | 6 ++--- 20 files changed, 103 insertions(+), 67 deletions(-) diff --git a/src/config_editor.py b/src/config_editor.py index 831cee1..5ad3f50 100644 --- a/src/config_editor.py +++ b/src/config_editor.py @@ -1,5 +1,7 @@ """Configuration editor for Road Core service.""" +from typing import Optional + import yaml from gui.main_window import MainWindow @@ -10,32 +12,34 @@ class ConfigEditor: """Class representing instances of configuration editor.""" - def __init__(self): + def __init__(self) -> None: """Initialize configuration editor.""" self.configuration = None - self.filename = None + self.filename: Optional[str] = None - def new_configuration(self): + def new_configuration(self) -> None: """Create new configuration to be edited.""" self.configuration = None - def load_configuration(self, filename): + def load_configuration(self, filename: str) -> None: """Load configuration from YAML file.""" with open(filename, encoding="utf-8") as fin: self.configuration = yaml.safe_load(fin) self.filename = filename - def save_configuration_as(self, filename): + def save_configuration_as(self, filename: str) -> None: """Store configuration into YAML file.""" with open(filename, "w", encoding="utf-8") as fout: yaml.dump(self.configuration, fout) - def save_configuration(self): + def save_configuration(self) -> None: """Store configuration into YAML file.""" + if self.filename is None: + return with open(self.filename, "w", encoding="utf-8") as fout: yaml.dump(self.configuration, fout) - def check_configuration(self): + def check_configuration(self) -> None: """Check if configuration is correct one.""" diff --git a/src/gui/dialogs/about_dialog.py b/src/gui/dialogs/about_dialog.py index d804a45..a47b3bb 100644 --- a/src/gui/dialogs/about_dialog.py +++ b/src/gui/dialogs/about_dialog.py @@ -3,7 +3,7 @@ from tkinter import messagebox -def about(): +def about() -> None: """Show 'about' dialog.""" messagebox.showinfo( "Config editor", "Configuration editor for the Road Core service" diff --git a/src/gui/dialogs/auth_dialog.py b/src/gui/dialogs/auth_dialog.py index 5a8254f..b038d95 100644 --- a/src/gui/dialogs/auth_dialog.py +++ b/src/gui/dialogs/auth_dialog.py @@ -2,11 +2,13 @@ import tkinter +from gui.icons import Icons + class AuthDialog(tkinter.Toplevel): """Dialog for editing authentication configuration.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize authentication configuration dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("Authentication configuration") diff --git a/src/gui/dialogs/cache_dialog.py b/src/gui/dialogs/cache_dialog.py index 40d7ef4..fc667c0 100644 --- a/src/gui/dialogs/cache_dialog.py +++ b/src/gui/dialogs/cache_dialog.py @@ -3,11 +3,13 @@ import tkinter from tkinter import ttk +from gui.icons import Icons + class ConversationCacheDialog(tkinter.Toplevel): """Dialog for editing conversation settings.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize dialog for editing conversation settings.""" tkinter.Toplevel.__init__(self, parent) self.title("Conversation cache") @@ -24,19 +26,21 @@ def __init__(self, parent, icons): self.grab_set() # UI groups - self.group = tkinter.LabelFrame(self, text="Conversation cache", padx=5, pady=8) + self.uigroup = tkinter.LabelFrame( + self, text="Conversation cache", padx=5, pady=8 + ) - label1 = tkinter.Label(self.group, text="Type") + label1 = tkinter.Label(self.uigroup, text="Type") label1.grid(row=1, column=1, sticky="W", padx=5, pady=5) cache_types = ("In-memory", "PostgreSQL", "Redis") - cache_type = tkinter.StringVar(self.group, cache_types[0], "cache_type") + cache_type = tkinter.StringVar(self.uigroup, cache_types[0], "cache_type") print(cache_type) cb1 = ttk.Combobox( - self.group, + self.uigroup, values=cache_types, # textvariable=app_log_levels, state="readonly", @@ -54,7 +58,7 @@ def __init__(self, parent, icons): ) # UI groups placement - self.group.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) + self.uigroup.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) ok_button.grid(row=2, column=1, sticky="W", padx=10, pady=10) diff --git a/src/gui/dialogs/check_configuration.py b/src/gui/dialogs/check_configuration.py index fc5ce3b..56fa1e5 100644 --- a/src/gui/dialogs/check_configuration.py +++ b/src/gui/dialogs/check_configuration.py @@ -2,11 +2,13 @@ import tkinter +from gui.icons import Icons + class CheckConfigurationDialog(tkinter.Toplevel): """Dialog for checking the configuration.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize dialog for checking the configuration.""" tkinter.Toplevel.__init__(self, parent) self.title("Check configuration") diff --git a/src/gui/dialogs/data_collection_dialog.py b/src/gui/dialogs/data_collection_dialog.py index 2266264..11db1bb 100644 --- a/src/gui/dialogs/data_collection_dialog.py +++ b/src/gui/dialogs/data_collection_dialog.py @@ -2,11 +2,13 @@ import tkinter +from gui.icons import Icons + class DataCollectionDialog(tkinter.Toplevel): """Data collection dialog.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize data collection dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("Data collection settings") diff --git a/src/gui/dialogs/default_model_dialog.py b/src/gui/dialogs/default_model_dialog.py index 19cd51c..f52baa0 100644 --- a/src/gui/dialogs/default_model_dialog.py +++ b/src/gui/dialogs/default_model_dialog.py @@ -2,11 +2,13 @@ import tkinter +from gui.icons import Icons + class DefaultModelSelection(tkinter.Toplevel): """Default model selection dialog.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize default model selection dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("Default model selection") diff --git a/src/gui/dialogs/default_provider_dialog.py b/src/gui/dialogs/default_provider_dialog.py index 789a914..01544f7 100644 --- a/src/gui/dialogs/default_provider_dialog.py +++ b/src/gui/dialogs/default_provider_dialog.py @@ -2,11 +2,13 @@ import tkinter +from gui.icons import Icons + class DefaultProviderSelection(tkinter.Toplevel): """Default provider selection dialog.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize default provider selection dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("Default provider selection") diff --git a/src/gui/dialogs/edit_dialog.py b/src/gui/dialogs/edit_dialog.py index 5e72884..6c67900 100644 --- a/src/gui/dialogs/edit_dialog.py +++ b/src/gui/dialogs/edit_dialog.py @@ -2,6 +2,7 @@ import tkinter from tkinter import ttk +from typing import Optional from gui.dialogs.auth_dialog import AuthDialog from gui.dialogs.cache_dialog import ConversationCacheDialog @@ -12,6 +13,7 @@ from gui.dialogs.logging_dialog import LoggingDialog from gui.dialogs.security_profile_dialog import TLSSecurityProfileDialog from gui.dialogs.tls_dialog import TLSConfigurationDialog +from gui.icons import Icons BUTTON_WIDTH = 100 @@ -19,28 +21,30 @@ class EditDialog(tkinter.Toplevel): """Implementation of configuration editor.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize the dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("Configuration editor") self.icons = icons self.parent = parent - self.group1 = None - self.group2 = None - self.group3 = None + self.group1: Optional[tkinter.LabelFrame] = None + self.group2: Optional[tkinter.LabelFrame] = None + self.group3: Optional[tkinter.LabelFrame] = None # don't display the dialog in list of opened windows self.transient(parent) self.add_widgets() self.set_dialog_properties() - def add_widgets(self): + def add_widgets(self) -> None: """Add all widgets on the dialog.""" self.add_llm_group() self.add_service_settings_group() self.add_devel_settings_group() # UI groups placement + if self.group1 is None or self.group2 is None or self.group3 is None: + return self.group1.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) self.group2.grid(row=1, column=2, sticky="NSWE", padx=5, pady=5) self.group3.grid(row=1, column=3, sticky="NSWE", padx=5, pady=5) @@ -68,7 +72,7 @@ def add_widgets(self): # set the focus ok_button.focus_set() - def set_dialog_properties(self): + def set_dialog_properties(self) -> None: """Set edit dialog properties.""" # close the dialog on 'x' click self.protocol("WM_DELETE_WINDOW", self.destroy) @@ -80,7 +84,7 @@ def set_dialog_properties(self): self.bind("", lambda _: self.ok()) self.bind("", lambda _: self.destroy()) - def add_llm_group(self): + def add_llm_group(self) -> None: """Add LLM group widgets onto the dialog.""" # UI groups self.group1 = tkinter.LabelFrame(self, text="LLM section", padx=5, pady=8) @@ -96,7 +100,7 @@ def add_llm_group(self): ) button_new_llm.grid(row=1, column=1, sticky="WE", padx=5, pady=5) - def add_service_settings_group(self): + def add_service_settings_group(self) -> None: """Add service settings widgets onto the dialog.""" self.group2 = tkinter.LabelFrame(self, text="Service settings", padx=5, pady=8) # service settings @@ -199,7 +203,9 @@ def add_service_settings_group(self): label9 = tkinter.Label(self.group2, text="Query validation method") label9.grid(row=9, column=1, sticky="W", padx=5, pady=5) query_validation_methods = ("LLM", "Keyword") - query_validation_method = query_validation_methods[0] + query_validation_method = tkinter.StringVar( + self.group2, query_validation_methods[0], "query_validation_method" + ) cb = ttk.Combobox( self.group2, @@ -210,7 +216,7 @@ def add_service_settings_group(self): cb.current(0) cb.grid(row=9, column=2, sticky="W", padx=5, pady=5) - def add_devel_settings_group(self): + def add_devel_settings_group(self) -> None: """Add devel settings widgets onto the dialog.""" self.group3 = tkinter.LabelFrame(self, text="Devel settings", padx=5, pady=8) # devel settings diff --git a/src/gui/dialogs/help_dialog.py b/src/gui/dialogs/help_dialog.py index 7e375a7..4bc7514 100644 --- a/src/gui/dialogs/help_dialog.py +++ b/src/gui/dialogs/help_dialog.py @@ -1,12 +1,13 @@ """Help dialog implementation.""" import tkinter +from typing import Optional class HelpDialog(tkinter.Toplevel): """Help dialog implementation.""" - def __init__(self, parent): + def __init__(self, parent: Optional[tkinter.Toplevel]) -> None: """Perform initialization of help dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("Nápověda") @@ -46,11 +47,11 @@ def __init__(self, parent): # close the dialog on 'x' click self.protocol("WM_DELETE_WINDOW", self.destroy) - def ok(self): + def ok(self) -> None: """Ok button handler.""" self.destroy() -def show_help(): +def show_help() -> None: """Display help dialog.""" HelpDialog(None) diff --git a/src/gui/dialogs/llm_dialog.py b/src/gui/dialogs/llm_dialog.py index 18a090e..42d2fec 100644 --- a/src/gui/dialogs/llm_dialog.py +++ b/src/gui/dialogs/llm_dialog.py @@ -2,11 +2,13 @@ import tkinter +from gui.icons import Icons + class LLMDialog(tkinter.Toplevel): """New LLM dialog.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize new LLM dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("New LLM") diff --git a/src/gui/dialogs/logging_dialog.py b/src/gui/dialogs/logging_dialog.py index 6686796..4164b3b 100644 --- a/src/gui/dialogs/logging_dialog.py +++ b/src/gui/dialogs/logging_dialog.py @@ -3,11 +3,13 @@ import tkinter from tkinter import ttk +from gui.icons import Icons + class LoggingDialog(tkinter.Toplevel): """Logging dialog.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize logging dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("Logging settings") @@ -24,11 +26,11 @@ def __init__(self, parent, icons): self.grab_set() # UI groups - self.group = tkinter.LabelFrame(self, text="Logging levels", padx=5, pady=8) + self.uigroup = tkinter.LabelFrame(self, text="Logging levels", padx=5, pady=8) - label1 = tkinter.Label(self.group, text="Application") - label2 = tkinter.Label(self.group, text="Libraries") - label3 = tkinter.Label(self.group, text="Uvicorn") + label1 = tkinter.Label(self.uigroup, text="Application") + label2 = tkinter.Label(self.uigroup, text="Libraries") + label3 = tkinter.Label(self.uigroup, text="Uvicorn") label1.grid(row=1, column=1, sticky="W", padx=5, pady=5) label2.grid(row=2, column=1, sticky="W", padx=5, pady=5) @@ -37,11 +39,11 @@ def __init__(self, parent, icons): debug_levels = ("Not set", "Debug", "Info", "Warning", "Error", "Critical") app_log_levels = tkinter.StringVar( - self.group, debug_levels[0], "app_log_levels" + self.uigroup, debug_levels[0], "app_log_levels" ) print(app_log_levels) cb1 = ttk.Combobox( - self.group, + self.uigroup, values=debug_levels, # textvariable=app_log_levels, state="readonly", @@ -50,11 +52,11 @@ def __init__(self, parent, icons): cb1.grid(row=1, column=2, sticky="W", padx=5, pady=5) lib_log_levels = tkinter.StringVar( - self.group, debug_levels[0], "lib_log_levels" + self.uigroup, debug_levels[0], "lib_log_levels" ) print(lib_log_levels) cb2 = ttk.Combobox( - self.group, + self.uigroup, values=debug_levels, # textvariable=lib_log_levels, state="readonly", @@ -63,11 +65,11 @@ def __init__(self, parent, icons): cb2.grid(row=2, column=2, sticky="W", padx=5, pady=5) uvicorn_log_levels = tkinter.StringVar( - self.group, debug_levels[0], "uvicorn_log_levels" + self.uigroup, debug_levels[0], "uvicorn_log_levels" ) print(uvicorn_log_levels) cb3 = ttk.Combobox( - self.group, + self.uigroup, values=debug_levels, # textvariable=uvicorn_log_levels, state="readonly", @@ -76,7 +78,7 @@ def __init__(self, parent, icons): cb3.grid(row=3, column=2, sticky="W", padx=5, pady=5) # UI groups placement - self.group.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) + self.uigroup.grid(row=1, column=1, sticky="NSWE", padx=5, pady=5) ok_button = tkinter.Button( self, diff --git a/src/gui/dialogs/security_profile_dialog.py b/src/gui/dialogs/security_profile_dialog.py index 2481f0b..a52afca 100644 --- a/src/gui/dialogs/security_profile_dialog.py +++ b/src/gui/dialogs/security_profile_dialog.py @@ -2,11 +2,13 @@ import tkinter +from gui.icons import Icons + class TLSSecurityProfileDialog(tkinter.Toplevel): """Security profile dialog.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize security profile dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("TLS security profile") diff --git a/src/gui/dialogs/tls_dialog.py b/src/gui/dialogs/tls_dialog.py index b0e9265..df7f140 100644 --- a/src/gui/dialogs/tls_dialog.py +++ b/src/gui/dialogs/tls_dialog.py @@ -2,11 +2,13 @@ import tkinter +from gui.icons import Icons + class TLSConfigurationDialog(tkinter.Toplevel): """TLS configuration dialog.""" - def __init__(self, parent, icons): + def __init__(self, parent: tkinter.Toplevel, icons: Icons) -> None: """Initialize TLS configuration dialog.""" tkinter.Toplevel.__init__(self, parent) self.title("TLS configuration") diff --git a/src/gui/icons.py b/src/gui/icons.py index d2a96e9..48eed08 100644 --- a/src/gui/icons.py +++ b/src/gui/icons.py @@ -21,7 +21,7 @@ class Icons: """All icons used on the GUI.""" - def __init__(self): + def __init__(self) -> None: """Initialize all icons and convert them to PhotoImage.""" self.exit_icon = tkinter.PhotoImage(data=icons.application_exit.icon) self.help_faq_icon = tkinter.PhotoImage(data=icons.help_faq.icon) diff --git a/src/gui/main_window.py b/src/gui/main_window.py index da2ca0b..46df767 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -2,6 +2,7 @@ import tkinter from tkinter import filedialog, messagebox +from typing import Any from gui.dialogs.edit_dialog import EditDialog from gui.icons import Icons @@ -13,7 +14,7 @@ class MainWindow: """Main window shown on screen.""" - def __init__(self, config_editor): + def __init__(self, config_editor: Any) -> None: """Initialize main window.""" self.config_editor = config_editor self.root = tkinter.Tk() @@ -34,11 +35,11 @@ def __init__(self, config_editor): self.root.geometry("480x320") # EditDialog(self.root, self.icons) - def show(self): + def show(self) -> None: """Display the main window on screen.""" self.root.mainloop() - def quit(self): + def quit(self) -> None: """Display message box whether to quit the application.""" answer = messagebox.askyesno( "Do you want to quit the program?", "Do you want to quit the program?" @@ -46,12 +47,12 @@ def quit(self): if answer: self.root.quit() - def configure_grid(self): + def configure_grid(self) -> None: """Configure grid on canvas.""" tkinter.Grid.rowconfigure(self.root, 2, weight=1) tkinter.Grid.columnconfigure(self.root, 2, weight=1) - def new_configuration(self): + def new_configuration(self) -> None: """Initialize new configuration.""" answer = messagebox.askyesno( "Clear current configuration?", "Clear current configuration?" @@ -59,11 +60,11 @@ def new_configuration(self): if answer: self.config_editor.new_configuration() - def load_configuration(self): + def load_configuration(self) -> None: """Load configuration from YAML file.""" filetypes = [("YAML files", "*.yaml"), ("YAML files", "*.yaml")] dialog = filedialog.Open(self.root, filetypes=filetypes) - filename = dialog.show() + filename = dialog.show() # type: ignore [no-untyped-call] if filename is not None and filename != "": try: self.config_editor.load_configuration(filename) @@ -72,7 +73,7 @@ def load_configuration(self): messagebox.showerror("Configuration loading failed", f"Failure {e}") print(e) - def save_configuration(self): + def save_configuration(self) -> None: """Save configuration into YAML file.""" try: self.config_editor.save_configuration() @@ -81,11 +82,11 @@ def save_configuration(self): messagebox.showerror("Configuration saving failed", f"Failure {e}") print(e) - def save_as_configuration(self): + def save_as_configuration(self) -> None: """Save configuration into specified YAML file.""" filetypes = [("YAML files", "*.yaml"), ("YAML files", "*.yaml")] dialog = filedialog.SaveAs(self.root, filetypes=filetypes) - filename = dialog.show() + filename = dialog.show() # type: ignore [no-untyped-call] if filename is not None and filename != "": try: self.config_editor.save_configuration_as(filename) @@ -94,11 +95,11 @@ def save_as_configuration(self): messagebox.showerror("Configuration saving failed", f"Failure {e}") print(e) - def edit_configuration(self): + def edit_configuration(self) -> None: """Edit configuration using the specialized dialog.""" EditDialog(self.root, self.icons) - def check_configuration(self): + def check_configuration(self) -> None: """Check configuration.""" result = self.config_editor.check_configuration() print(result) diff --git a/src/gui/menubar.py b/src/gui/menubar.py index ae68d34..62ac0e2 100644 --- a/src/gui/menubar.py +++ b/src/gui/menubar.py @@ -1,6 +1,7 @@ """Menu bar displayed on the main window.""" import tkinter +from typing import Any from gui.dialogs.about_dialog import about from gui.dialogs.help_dialog import show_help @@ -9,7 +10,7 @@ class Menubar(tkinter.Menu): """Menu bar displayed on the main window.""" - def __init__(self, parent, main_window): + def __init__(self, parent: tkinter.Toplevel, main_window: Any) -> None: """Initialize the menu bar.""" super().__init__(tearoff=0) diff --git a/src/gui/status_bar.py b/src/gui/status_bar.py index 0b5698f..f939100 100644 --- a/src/gui/status_bar.py +++ b/src/gui/status_bar.py @@ -1,6 +1,7 @@ """Status bar displayed in the main window.""" import tkinter +from typing import Any class StatusBar(tkinter.Frame): @@ -12,7 +13,7 @@ def __init__(self, master: tkinter.Tk) -> None: self.label = tkinter.Label(self, bd=1, relief=tkinter.SUNKEN, anchor=tkinter.W) self.label.pack(fill=tkinter.X) - def set(self, string_format, *args): + def set(self, string_format: str, *args: Any) -> None: """Set status bar messages.""" self.label.config(text=string_format % args) self.label.update_idletasks() diff --git a/src/gui/toolbar.py b/src/gui/toolbar.py index 0f4e71a..eaa09e2 100644 --- a/src/gui/toolbar.py +++ b/src/gui/toolbar.py @@ -8,7 +8,7 @@ class Toolbar(tkinter.LabelFrame): """Toolbar displayed on the main window.""" - def __init__(self, parent: tkinter.Tk, main_window) -> None: + def __init__(self, parent: tkinter.Tk, main_window) -> None: # type: ignore [no-untyped-def] """Initialize the toolbar.""" super().__init__(parent, text="Tools", padx=5, pady=5) @@ -99,7 +99,7 @@ def __init__(self, parent: tkinter.Tk, main_window) -> None: self.button_quit.grid(column=10, row=1) @staticmethod - def disable_button(button): + def disable_button(button: tkinter.Button) -> None: """Disable specified button on toolbar.""" button["state"] = "disabled" diff --git a/src/gui/tooltip.py b/src/gui/tooltip.py index 9d0fefe..0409ae6 100644 --- a/src/gui/tooltip.py +++ b/src/gui/tooltip.py @@ -20,7 +20,7 @@ def __init__(self, widget: tkinter.Button, text: str = "widget info") -> None: self.widget.bind("", self.leave) self.widget.bind("", self.leave) self.id: Optional[str] = None - self.tw = None + self.tw: Optional[tkinter.Toplevel] = None def enter(self, event: tkinter.Event | None = None) -> None: """Handle the event: cursor pointer moves to the tooltip area.""" @@ -43,10 +43,10 @@ def unschedule(self) -> None: if current_id: self.widget.after_cancel(current_id) - def showtip(self, event=None): + def showtip(self, event: Optional[tkinter.Event] = None) -> None: """Show the tooltip on screen.""" x = y = 0 - x, y, _, _ = self.widget.bbox("insert") + x, y, _, _ = self.widget.bbox() # type: ignore [misc] x += self.widget.winfo_rootx() + 25 y += self.widget.winfo_rooty() + 20 # creates a toplevel window From cf1dd8ac8488be5f2b18f0e88e67c07f6c931212 Mon Sep 17 00:00:00 2001 From: Pavel Tisnovsky Date: Tue, 17 Dec 2024 17:19:17 +0100 Subject: [PATCH 7/7] Install mypy deps --- .github/workflows/mypy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 1009bbd..63ee2c9 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,6 +15,6 @@ jobs: - name: Python version run: python --version - name: Mypy install - run: pip install --user mypy pydantic + run: pip install --user mypy pydantic types-PyYAML - name: Type checks run: mypy --explicit-package-bases --disallow-untyped-calls --disallow-untyped-defs --disallow-incomplete-defs --ignore-missing-imports --disable-error-code attr-defined src/