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