From 99876375647701c5508c8c6d1a26ba01e288da44 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 6 Nov 2019 14:36:36 -0800 Subject: [PATCH 1/3] updated and used common frame scroll class where needed, created common config widget to use where all configurations get drawn --- coretk/coretk/dialogs/customnodes.py | 7 +- coretk/coretk/dialogs/emaneconfig.py | 1 + coretk/coretk/dialogs/sessionoptions.py | 30 +++--- coretk/coretk/widgets.py | 126 ++++++++++++++++-------- 4 files changed, 109 insertions(+), 55 deletions(-) diff --git a/coretk/coretk/dialogs/customnodes.py b/coretk/coretk/dialogs/customnodes.py index 841b2dcf..38d59c83 100644 --- a/coretk/coretk/dialogs/customnodes.py +++ b/coretk/coretk/dialogs/customnodes.py @@ -28,6 +28,7 @@ class ServicesSelectDialog(Dialog): for group in sorted(self.app.core.services): self.groups.listbox.insert(tk.END, group) self.groups.listbox.bind("<>", self.handle_group_change) + self.groups.listbox.selection_set(0) self.services = CheckboxList( frame, text="Services", clicked=self.service_clicked @@ -46,6 +47,9 @@ class ServicesSelectDialog(Dialog): button = tk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") + # trigger group change + self.groups.listbox.event_generate("<>") + def handle_group_change(self, event): selection = self.groups.listbox.curselection() if selection: @@ -53,7 +57,8 @@ class ServicesSelectDialog(Dialog): group = self.groups.listbox.get(index) self.services.clear() for service in sorted(self.app.core.services[group], key=lambda x: x.name): - self.services.add(service.name) + checked = service.name in self.current_services + self.services.add(service.name, checked) def service_clicked(self, name, var): if var.get() and name not in self.current_services: diff --git a/coretk/coretk/dialogs/emaneconfig.py b/coretk/coretk/dialogs/emaneconfig.py index d6436deb..b3bca2fd 100644 --- a/coretk/coretk/dialogs/emaneconfig.py +++ b/coretk/coretk/dialogs/emaneconfig.py @@ -93,6 +93,7 @@ class EmaneConfiguration(Dialog): response = self.app.core.client.get_emane_config(session_id) logging.info("emane config: %s", response) self.options = response.config + self.values = configutils.create_config( self.emane_dialog, self.options, PAD_X, PAD_Y ) diff --git a/coretk/coretk/dialogs/sessionoptions.py b/coretk/coretk/dialogs/sessionoptions.py index e887c2a1..8702d581 100644 --- a/coretk/coretk/dialogs/sessionoptions.py +++ b/coretk/coretk/dialogs/sessionoptions.py @@ -1,8 +1,8 @@ import logging import tkinter as tk -from coretk import configutils from coretk.dialogs.dialog import Dialog +from coretk.widgets import ConfigFrame PAD_X = 2 PAD_Y = 2 @@ -11,25 +11,33 @@ PAD_Y = 2 class SessionOptionsDialog(Dialog): def __init__(self, master, app): super().__init__(master, app, "Session Options", modal=True) - self.options = None - self.values = None - self.save_button = tk.Button(self, text="Save", command=self.save) - self.cancel_button = tk.Button(self, text="Cancel", command=self.destroy) + self.config_frame = None self.draw() def draw(self): + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + session_id = self.app.core.session_id response = self.app.core.client.get_session_options(session_id) logging.info("session options: %s", response) - self.options = response.config - self.values = configutils.create_config(self, self.options, PAD_X, PAD_Y) - self.save_button.grid(row=1, pady=PAD_Y, padx=PAD_X, sticky="ew") - self.cancel_button.grid(row=1, column=1, pady=PAD_Y, padx=PAD_X, sticky="ew") + + self.config_frame = ConfigFrame(self, config=response.config) + self.config_frame.draw_config() + self.config_frame.grid(sticky="nsew") + + frame = tk.Frame(self) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + button = tk.Button(frame, text="Save", command=self.save) + button.grid(row=0, column=0, pady=PAD_Y, padx=PAD_X, sticky="ew") + button = tk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, pady=PAD_Y, padx=PAD_X, sticky="ew") def save(self): - configutils.parse_config(self.options, self.values) + config = self.config_frame.parse_config() session_id = self.app.core.session_id - config = {x: self.options[x].value for x in self.options} response = self.app.core.client.set_session_options(session_id, config) logging.info("saved session config: %s", response) self.destroy() diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index 606f7662..cb377dcc 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -1,5 +1,9 @@ +import logging import tkinter as tk from functools import partial +from tkinter import ttk + +from coretk.configutils import ConfigType class FrameScroll(tk.LabelFrame): @@ -7,29 +11,39 @@ class FrameScroll(tk.LabelFrame): super().__init__(master, cnf, **kw) self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) - self.columnconfigure(1, weight=1) self.canvas = tk.Canvas(self, highlightthickness=0) - self.canvas.grid(row=0, columnspan=2, sticky="nsew", padx=2, pady=2) + self.canvas.grid(row=0, sticky="nsew", padx=2, pady=2) self.canvas.columnconfigure(0, weight=1) self.canvas.rowconfigure(0, weight=1) self.scrollbar = tk.Scrollbar( self, orient="vertical", command=self.canvas.yview ) - self.scrollbar.grid(row=0, column=2, sticky="ns") + self.scrollbar.grid(row=0, column=1, sticky="ns") self.frame = tk.Frame(self.canvas, padx=2, pady=2) - self.frame.columnconfigure(0, weight=1) self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame) self.canvas.update_idletasks() self.canvas.configure( scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set ) - self.frame.bind( - "", - lambda event: self.canvas.configure(scrollregion=self.canvas.bbox("all")), - ) - self.canvas.bind( - "", - lambda event: self.canvas.itemconfig(self.frame_id, width=event.width), + self.frame.bind("", self._configure_frame) + self.canvas.bind("", self._configure_canvas) + + def _configure_frame(self, event): + req_width = self.frame.winfo_reqwidth() + req_height = self.frame.winfo_reqheight() + if req_width != self.canvas.winfo_reqwidth(): + self.canvas.configure(width=req_width) + if req_height != self.canvas.winfo_reqheight(): + self.canvas.configure(height=req_height) + self.canvas.configure(scrollregion=self.canvas.bbox("all")) + + def _configure_canvas(self, event): + self.canvas.itemconfig(self.frame_id, width=event.width) + + def update_canvas(self): + self.canvas.update_idletasks() + self.canvas.configure( + scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set ) def clear(self): @@ -37,6 +51,61 @@ class FrameScroll(tk.LabelFrame): widget.destroy() +class ConfigFrame(FrameScroll): + def __init__(self, master=None, cnf={}, config=None, **kw): + super().__init__(master, cnf, **kw) + self.frame.columnconfigure(1, weight=1) + if not config: + config = {} + self.config = config + self.values = {} + + def draw_config(self): + padx = 2 + pady = 2 + for index, key in enumerate(sorted(self.config)): + option = self.config[key] + label = tk.Label(self.frame, text=option.label) + label.grid(row=index, pady=pady, padx=padx, sticky="w") + value = tk.StringVar() + config_type = ConfigType(option.type) + if config_type == ConfigType.BOOL: + select = tuple(option.select) + combobox = ttk.Combobox(self.frame, textvariable=value, values=select) + combobox.grid(row=index, column=1, sticky="ew", pady=pady) + if option.value == "1": + value.set("On") + else: + value.set("Off") + elif config_type == ConfigType.STRING: + value.set(option.value) + entry = tk.Entry(self.frame, textvariable=value) + entry.grid(row=index, column=1, sticky="ew", pady=pady) + elif config_type == ConfigType.EMANECONFIG: + value.set(option.value) + entry = tk.Entry(self.frame, textvariable=value) + entry.grid(row=index, column=1, sticky="ew", pady=pady) + else: + logging.error("unhandled config option type: %s", config_type) + self.values[key] = value + + def parse_config(self): + for key in self.config: + option = self.config[key] + value = self.values[key] + config_type = ConfigType(option.type) + config_value = value.get() + if config_type == ConfigType.BOOL: + if config_value == "On": + option.value = "1" + else: + option.value = "0" + else: + option.value = config_value + + return {x: self.config[x].value for x in self.config} + + class ListboxScroll(tk.LabelFrame): def __init__(self, master=None, cnf={}, **kw): super().__init__(master, cnf, **kw) @@ -51,43 +120,14 @@ class ListboxScroll(tk.LabelFrame): self.scrollbar.config(command=self.listbox.yview) -class CheckboxList(tk.LabelFrame): +class CheckboxList(FrameScroll): def __init__(self, master=None, cnf={}, clicked=None, **kw): super().__init__(master, cnf, **kw) self.clicked = clicked - self.rowconfigure(0, weight=1) - self.columnconfigure(0, weight=1) - self.columnconfigure(1, weight=1) - self.canvas = tk.Canvas(self, highlightthickness=0) - self.canvas.grid(row=0, columnspan=2, sticky="nsew", padx=2, pady=2) - self.canvas.columnconfigure(0, weight=1) - self.canvas.rowconfigure(0, weight=1) - self.scrollbar = tk.Scrollbar( - self, orient="vertical", command=self.canvas.yview - ) - self.scrollbar.grid(row=0, column=2, sticky="ns") - self.frame = tk.Frame(self.canvas, padx=2, pady=2) self.frame.columnconfigure(0, weight=1) - self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame) - self.canvas.update_idletasks() - self.canvas.configure( - scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set - ) - self.frame.bind( - "", - lambda event: self.canvas.configure(scrollregion=self.canvas.bbox("all")), - ) - self.canvas.bind( - "", - lambda event: self.canvas.itemconfig(self.frame_id, width=event.width), - ) - def clear(self): - for widget in self.frame.winfo_children(): - widget.destroy() - - def add(self, name): - var = tk.BooleanVar() + def add(self, name, checked): + var = tk.BooleanVar(value=checked) func = partial(self.clicked, name, var) checkbox = tk.Checkbutton(self.frame, text=name, variable=var, command=func) checkbox.grid(sticky="w") From 0147bb9988a27e2c750cf6df9222ed5c83070fdc Mon Sep 17 00:00:00 2001 From: bharnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 6 Nov 2019 20:49:09 -0800 Subject: [PATCH 2/3] updated config generation to use config frame where possible --- coretk/coretk/configutils.py | 99 ---------------------------- coretk/coretk/dialogs/emaneconfig.py | 63 ++++++++++-------- coretk/coretk/widgets.py | 43 ++++++++---- 3 files changed, 65 insertions(+), 140 deletions(-) delete mode 100644 coretk/coretk/configutils.py diff --git a/coretk/coretk/configutils.py b/coretk/coretk/configutils.py deleted file mode 100644 index 267feb73..00000000 --- a/coretk/coretk/configutils.py +++ /dev/null @@ -1,99 +0,0 @@ -import enum -import logging -import tkinter as tk -from tkinter import ttk - - -class ConfigType(enum.Enum): - STRING = 10 - BOOL = 11 - EMANECONFIG = 7 - - -def create_config(master, config, padx=2, pady=2): - """ - Creates a scrollable canvas with an embedded window for displaying configuration - options. Will use grid layout to consume row 0 and columns 0-2. - - :param master: master to add scrollable canvas to - :param dict config: config option mapping keys to config options - :param int padx: x padding for widgets - :param int pady: y padding for widgets - :return: widget value mapping - """ - master.rowconfigure(0, weight=1) - master.columnconfigure(0, weight=1) - master.columnconfigure(1, weight=1) - - canvas = tk.Canvas(master, highlightthickness=0) - canvas.grid(row=0, columnspan=2, sticky="nsew", padx=padx, pady=pady) - canvas.columnconfigure(0, weight=1) - canvas.rowconfigure(0, weight=1) - - scroll_y = tk.Scrollbar(master, orient="vertical", command=canvas.yview) - scroll_y.grid(row=0, column=2, sticky="ns") - - frame = tk.Frame(canvas, padx=padx, pady=pady) - frame.columnconfigure(0, weight=1) - frame.columnconfigure(1, weight=3) - - values = {} - for index, key in enumerate(sorted(config)): - option = config[key] - label = tk.Label(frame, text=option.label) - label.grid(row=index, pady=pady, padx=padx, sticky="ew") - value = tk.StringVar() - config_type = ConfigType(option.type) - if config_type == ConfigType.BOOL: - select = tuple(option.select) - combobox = ttk.Combobox(frame, textvariable=value, values=select) - combobox.grid(row=index, column=1, sticky="ew", pady=pady) - if option.value == "1": - value.set("On") - else: - value.set("Off") - elif config_type == ConfigType.STRING: - value.set(option.value) - entry = tk.Entry(frame, textvariable=value) - entry.grid(row=index, column=1, sticky="ew", pady=pady) - elif config_type == ConfigType.EMANECONFIG: - value.set(option.value) - entry = tk.Entry(frame, textvariable=value, bg="white") - entry.grid(row=index, column=1, sticky="ew", pady=pady) - else: - logging.error("unhandled config option type: %s", config_type) - values[key] = value - - frame_id = canvas.create_window(0, 0, anchor="nw", window=frame) - canvas.update_idletasks() - canvas.configure(scrollregion=canvas.bbox("all"), yscrollcommand=scroll_y.set) - - frame.bind( - "", lambda event: canvas.configure(scrollregion=canvas.bbox("all")) - ) - canvas.bind( - "", lambda event: canvas.itemconfig(frame_id, width=event.width) - ) - return values - - -def parse_config(options, values): - """ - Given a set of configurations, parse out values and transform them when needed. - - :param dict options: option key mapping to configuration options - :param dict values: option key mapping to widget values - :return: nothing - """ - for key in options: - option = options[key] - value = values[key] - config_type = ConfigType(option.type) - config_value = value.get() - if config_type == ConfigType.BOOL: - if config_value == "On": - option.value = "1" - else: - option.value = "0" - else: - option.value = config_value diff --git a/coretk/coretk/dialogs/emaneconfig.py b/coretk/coretk/dialogs/emaneconfig.py index b3bca2fd..d90a970c 100644 --- a/coretk/coretk/dialogs/emaneconfig.py +++ b/coretk/coretk/dialogs/emaneconfig.py @@ -7,9 +7,9 @@ import tkinter as tk import webbrowser from tkinter import ttk -from coretk import configutils from coretk.dialogs.dialog import Dialog from coretk.images import ImageEnum, Images +from coretk.widgets import ConfigFrame PAD_X = 2 PAD_Y = 2 @@ -38,10 +38,10 @@ class EmaneConfiguration(Dialog): self.emane_options() self.draw_apply_and_cancel() - self.values = None + self.emane_config_frame = None self.options = app.core.emane_config self.model_options = None - self.model_values = None + self.model_config_frame = None def create_text_variable(self, val): """ @@ -75,7 +75,7 @@ class EmaneConfiguration(Dialog): f.grid(row=0, column=0, sticky="nsew") def save_emane_option(self): - configutils.parse_config(self.options, self.values) + self.emane_config_frame.parse_config() self.emane_dialog.destroy() def draw_emane_options(self): @@ -83,10 +83,6 @@ class EmaneConfiguration(Dialog): self.emane_dialog = Dialog( self, self.app, "emane configuration", modal=False ) - b1 = tk.Button(self.emane_dialog, text="Appy", command=self.save_emane_option) - b2 = tk.Button( - self.emane_dialog, text="Cancel", command=self.emane_dialog.destroy - ) if self.options is None: session_id = self.app.core.session_id @@ -94,11 +90,20 @@ class EmaneConfiguration(Dialog): logging.info("emane config: %s", response) self.options = response.config - self.values = configutils.create_config( - self.emane_dialog, self.options, PAD_X, PAD_Y - ) - b1.grid(row=1, column=0) - b2.grid(row=1, column=1) + self.emane_dialog.columnconfigure(0, weight=1) + self.emane_dialog.rowconfigure(0, weight=1) + self.emane_config_frame = ConfigFrame(self.emane_dialog, config=self.options) + self.emane_config_frame.draw_config() + self.emane_config_frame.grid(sticky="nsew") + + frame = tk.Frame(self.emane_dialog) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + b1 = tk.Button(frame, text="Appy", command=self.save_emane_option) + b1.grid(row=0, column=0, sticky="ew") + b2 = tk.Button(frame, text="Cancel", command=self.emane_dialog.destroy) + b2.grid(row=0, column=1, sticky="ew") self.emane_dialog.show() def save_emane_model_options(self): @@ -111,8 +116,7 @@ class EmaneConfiguration(Dialog): model_name = self.emane_models[self.emane_model_combobox.current()] # parse configuration - configutils.parse_config(self.model_options, self.model_values) - config = {x: self.model_options[x].value for x in self.model_options} + config = self.model_config_frame.parse_config() # add string emane_ infront for grpc call response = self.app.core.client.set_emane_model_config( @@ -141,17 +145,10 @@ class EmaneConfiguration(Dialog): # create the dialog and the necessry widget if not self.emane_model_dialog or not self.emane_model_dialog.winfo_exists(): self.emane_model_dialog = Dialog( - self, self.app, model_name + " configuration", modal=False + self, self.app, f"{model_name} configuration", modal=False ) - - b1 = tk.Button( - self.emane_model_dialog, text="Apply", command=self.save_emane_model_options - ) - b2 = tk.Button( - self.emane_model_dialog, - text="Cancel", - command=self.emane_model_dialog.destroy, - ) + self.emane_model_dialog.columnconfigure(0, weight=1) + self.emane_model_dialog.rowconfigure(0, weight=1) # query for configurations session_id = self.app.core.session_id @@ -162,12 +159,20 @@ class EmaneConfiguration(Dialog): logging.info("emane model config %s", response) self.model_options = response.config - self.model_values = configutils.create_config( - self.emane_model_dialog, self.model_options, PAD_X, PAD_Y + self.model_config_frame = ConfigFrame( + self.emane_model_dialog, config=self.model_options ) + self.model_config_frame.grid(sticky="nsew") + self.model_config_frame.draw_config() - b1.grid(row=1, column=0, sticky="nsew") - b2.grid(row=1, column=1, sticky="nsew") + frame = tk.Frame(self.emane_model_dialog) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + b1 = tk.Button(frame, text="Apply", command=self.save_emane_model_options) + b1.grid(row=0, column=0, sticky="ew") + b2 = tk.Button(frame, text="Cancel", command=self.emane_model_dialog.destroy) + b2.grid(row=0, column=1, sticky="ew") self.emane_model_dialog.show() def draw_option_buttons(self, parent): diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index cb377dcc..9625bc67 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -3,7 +3,18 @@ import tkinter as tk from functools import partial from tkinter import ttk -from coretk.configutils import ConfigType +from core.api.grpc import core_pb2 + +INT_TYPES = { + core_pb2.ConfigOptionType.UINT8, + core_pb2.ConfigOptionType.UINT16, + core_pb2.ConfigOptionType.UINT32, + core_pb2.ConfigOptionType.UINT64, + core_pb2.ConfigOptionType.INT8, + core_pb2.ConfigOptionType.INT16, + core_pb2.ConfigOptionType.INT32, + core_pb2.ConfigOptionType.INT64, +} class FrameScroll(tk.LabelFrame): @@ -30,11 +41,8 @@ class FrameScroll(tk.LabelFrame): def _configure_frame(self, event): req_width = self.frame.winfo_reqwidth() - req_height = self.frame.winfo_reqheight() if req_width != self.canvas.winfo_reqwidth(): self.canvas.configure(width=req_width) - if req_height != self.canvas.winfo_reqheight(): - self.canvas.configure(height=req_height) self.canvas.configure(scrollregion=self.canvas.bbox("all")) def _configure_canvas(self, event): @@ -68,34 +76,45 @@ class ConfigFrame(FrameScroll): label = tk.Label(self.frame, text=option.label) label.grid(row=index, pady=pady, padx=padx, sticky="w") value = tk.StringVar() - config_type = ConfigType(option.type) - if config_type == ConfigType.BOOL: + if option.type == core_pb2.ConfigOptionType.BOOL: select = tuple(option.select) - combobox = ttk.Combobox(self.frame, textvariable=value, values=select) + combobox = ttk.Combobox( + self.frame, textvariable=value, values=select, state="readonly" + ) combobox.grid(row=index, column=1, sticky="ew", pady=pady) if option.value == "1": value.set("On") else: value.set("Off") - elif config_type == ConfigType.STRING: + elif option.select: + value.set(option.value) + select = tuple(option.select) + combobox = ttk.Combobox( + self.frame, textvariable=value, values=select, state="readonly" + ) + combobox.grid(row=index, column=1, sticky="ew", pady=pady) + elif option.type == core_pb2.ConfigOptionType.STRING: value.set(option.value) entry = tk.Entry(self.frame, textvariable=value) entry.grid(row=index, column=1, sticky="ew", pady=pady) - elif config_type == ConfigType.EMANECONFIG: + elif option.type in INT_TYPES: + value.set(option.value) + entry = tk.Entry(self.frame, textvariable=value) + entry.grid(row=index, column=1, sticky="ew", pady=pady) + elif option.type == core_pb2.ConfigOptionType.FLOAT: value.set(option.value) entry = tk.Entry(self.frame, textvariable=value) entry.grid(row=index, column=1, sticky="ew", pady=pady) else: - logging.error("unhandled config option type: %s", config_type) + logging.error("unhandled config option type: %s", option.type) self.values[key] = value def parse_config(self): for key in self.config: option = self.config[key] value = self.values[key] - config_type = ConfigType(option.type) config_value = value.get() - if config_type == ConfigType.BOOL: + if option.type == core_pb2.ConfigOptionType.BOOL: if config_value == "On": option.value = "1" else: From 5d6d22c6eb14e857495dd5cc4214365b9f727fab Mon Sep 17 00:00:00 2001 From: bharnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 6 Nov 2019 22:41:05 -0800 Subject: [PATCH 3/3] updated config frame widget to draw tabs for each config group --- coretk/coretk/widgets.py | 95 ++++++++++++++------------- daemon/proto/core/api/grpc/core.proto | 17 +++++ 2 files changed, 65 insertions(+), 47 deletions(-) diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index 9625bc67..236dbc24 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -18,7 +18,7 @@ INT_TYPES = { class FrameScroll(tk.LabelFrame): - def __init__(self, master=None, cnf={}, **kw): + def __init__(self, master=None, cnf={}, _cls=tk.Frame, **kw): super().__init__(master, cnf, **kw) self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) @@ -30,7 +30,7 @@ class FrameScroll(tk.LabelFrame): self, orient="vertical", command=self.canvas.yview ) self.scrollbar.grid(row=0, column=1, sticky="ns") - self.frame = tk.Frame(self.canvas, padx=2, pady=2) + self.frame = _cls(self.canvas) self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame) self.canvas.update_idletasks() self.canvas.configure( @@ -48,12 +48,6 @@ class FrameScroll(tk.LabelFrame): def _configure_canvas(self, event): self.canvas.itemconfig(self.frame_id, width=event.width) - def update_canvas(self): - self.canvas.update_idletasks() - self.canvas.configure( - scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set - ) - def clear(self): for widget in self.frame.winfo_children(): widget.destroy() @@ -61,53 +55,60 @@ class FrameScroll(tk.LabelFrame): class ConfigFrame(FrameScroll): def __init__(self, master=None, cnf={}, config=None, **kw): - super().__init__(master, cnf, **kw) - self.frame.columnconfigure(1, weight=1) - if not config: - config = {} + super().__init__(master, cnf, ttk.Notebook, **kw) self.config = config self.values = {} def draw_config(self): padx = 2 pady = 2 - for index, key in enumerate(sorted(self.config)): + group_mapping = {} + for key in self.config: option = self.config[key] - label = tk.Label(self.frame, text=option.label) - label.grid(row=index, pady=pady, padx=padx, sticky="w") - value = tk.StringVar() - if option.type == core_pb2.ConfigOptionType.BOOL: - select = tuple(option.select) - combobox = ttk.Combobox( - self.frame, textvariable=value, values=select, state="readonly" - ) - combobox.grid(row=index, column=1, sticky="ew", pady=pady) - if option.value == "1": - value.set("On") + group = group_mapping.setdefault(option.group, []) + group.append(option) + + for group_name in sorted(group_mapping): + group = group_mapping[group_name] + frame = tk.Frame(self.frame) + frame.columnconfigure(1, weight=1) + self.frame.add(frame, text=group_name) + for index, option in enumerate(sorted(group, key=lambda x: x.name)): + label = tk.Label(frame, text=option.label) + label.grid(row=index, pady=pady, padx=padx, sticky="w") + value = tk.StringVar() + if option.type == core_pb2.ConfigOptionType.BOOL: + select = tuple(option.select) + combobox = ttk.Combobox( + frame, textvariable=value, values=select, state="readonly" + ) + combobox.grid(row=index, column=1, sticky="ew", pady=pady) + if option.value == "1": + value.set("On") + else: + value.set("Off") + elif option.select: + value.set(option.value) + select = tuple(option.select) + combobox = ttk.Combobox( + frame, textvariable=value, values=select, state="readonly" + ) + combobox.grid(row=index, column=1, sticky="ew", pady=pady) + elif option.type == core_pb2.ConfigOptionType.STRING: + value.set(option.value) + entry = tk.Entry(frame, textvariable=value) + entry.grid(row=index, column=1, sticky="ew", pady=pady) + elif option.type in INT_TYPES: + value.set(option.value) + entry = tk.Entry(frame, textvariable=value) + entry.grid(row=index, column=1, sticky="ew", pady=pady) + elif option.type == core_pb2.ConfigOptionType.FLOAT: + value.set(option.value) + entry = tk.Entry(frame, textvariable=value) + entry.grid(row=index, column=1, sticky="ew", pady=pady) else: - value.set("Off") - elif option.select: - value.set(option.value) - select = tuple(option.select) - combobox = ttk.Combobox( - self.frame, textvariable=value, values=select, state="readonly" - ) - combobox.grid(row=index, column=1, sticky="ew", pady=pady) - elif option.type == core_pb2.ConfigOptionType.STRING: - value.set(option.value) - entry = tk.Entry(self.frame, textvariable=value) - entry.grid(row=index, column=1, sticky="ew", pady=pady) - elif option.type in INT_TYPES: - value.set(option.value) - entry = tk.Entry(self.frame, textvariable=value) - entry.grid(row=index, column=1, sticky="ew", pady=pady) - elif option.type == core_pb2.ConfigOptionType.FLOAT: - value.set(option.value) - entry = tk.Entry(self.frame, textvariable=value) - entry.grid(row=index, column=1, sticky="ew", pady=pady) - else: - logging.error("unhandled config option type: %s", option.type) - self.values[key] = value + logging.error("unhandled config option type: %s", option.type) + self.values[option.name] = value def parse_config(self): for key in self.config: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 19a1ff97..a53a11bb 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -768,6 +768,23 @@ message NodeType { } } +message ConfigOptionType { + enum Enum { + NONE = 0; + UINT8 = 1; + UINT16 = 2; + UINT32 = 3; + UINT64 = 4; + INT8 = 5; + INT16 = 6; + INT32 = 7; + INT64 = 8; + FLOAT = 9; + STRING = 10; + BOOL = 11; + } +} + message ServiceValidationMode { enum Enum { BLOCKING = 0;