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."""