Merge branch 'coretk' into coretk-config

This commit is contained in:
Huy Pham 2019-11-07 07:57:48 -08:00
commit 98cfb301bb
6 changed files with 183 additions and 186 deletions

View file

@ -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(
"<Configure>", lambda event: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.bind(
"<Configure>", 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

View file

@ -28,6 +28,7 @@ class ServicesSelectDialog(Dialog):
for group in sorted(self.app.core.services): for group in sorted(self.app.core.services):
self.groups.listbox.insert(tk.END, group) self.groups.listbox.insert(tk.END, group)
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change) self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
self.groups.listbox.selection_set(0)
self.services = CheckboxList( self.services = CheckboxList(
frame, text="Services", clicked=self.service_clicked frame, text="Services", clicked=self.service_clicked
@ -46,6 +47,9 @@ class ServicesSelectDialog(Dialog):
button = tk.Button(frame, text="Cancel", command=self.destroy) button = tk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew") button.grid(row=0, column=1, sticky="ew")
# trigger group change
self.groups.listbox.event_generate("<<ListboxSelect>>")
def handle_group_change(self, event): def handle_group_change(self, event):
selection = self.groups.listbox.curselection() selection = self.groups.listbox.curselection()
if selection: if selection:
@ -53,7 +57,8 @@ class ServicesSelectDialog(Dialog):
group = self.groups.listbox.get(index) group = self.groups.listbox.get(index)
self.services.clear() self.services.clear()
for service in sorted(self.app.core.services[group], key=lambda x: x.name): 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): def service_clicked(self, name, var):
if var.get() and name not in self.current_services: if var.get() and name not in self.current_services:

View file

@ -7,9 +7,9 @@ import tkinter as tk
import webbrowser import webbrowser
from tkinter import ttk from tkinter import ttk
from coretk import configutils
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
from coretk.images import ImageEnum, Images from coretk.images import ImageEnum, Images
from coretk.widgets import ConfigFrame
PAD_X = 2 PAD_X = 2
PAD_Y = 2 PAD_Y = 2
@ -38,10 +38,10 @@ class EmaneConfiguration(Dialog):
self.emane_options() self.emane_options()
self.draw_apply_and_cancel() self.draw_apply_and_cancel()
self.values = None self.emane_config_frame = None
self.options = app.core.emane_config self.options = app.core.emane_config
self.model_options = None self.model_options = None
self.model_values = None self.model_config_frame = None
def create_text_variable(self, val): def create_text_variable(self, val):
""" """
@ -75,7 +75,7 @@ class EmaneConfiguration(Dialog):
f.grid(row=0, column=0, sticky="nsew") f.grid(row=0, column=0, sticky="nsew")
def save_emane_option(self): def save_emane_option(self):
configutils.parse_config(self.options, self.values) self.emane_config_frame.parse_config()
self.emane_dialog.destroy() self.emane_dialog.destroy()
def draw_emane_options(self): def draw_emane_options(self):
@ -83,21 +83,27 @@ class EmaneConfiguration(Dialog):
self.emane_dialog = Dialog( self.emane_dialog = Dialog(
self, self.app, "emane configuration", modal=False 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: if self.options is None:
session_id = self.app.core.session_id session_id = self.app.core.session_id
response = self.app.core.client.get_emane_config(session_id) response = self.app.core.client.get_emane_config(session_id)
logging.info("emane config: %s", response) logging.info("emane config: %s", response)
self.options = response.config self.options = response.config
self.values = configutils.create_config(
self.emane_dialog, self.options, PAD_X, PAD_Y self.emane_dialog.columnconfigure(0, weight=1)
) self.emane_dialog.rowconfigure(0, weight=1)
b1.grid(row=1, column=0) self.emane_config_frame = ConfigFrame(self.emane_dialog, config=self.options)
b2.grid(row=1, column=1) 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() self.emane_dialog.show()
def save_emane_model_options(self): def save_emane_model_options(self):
@ -110,8 +116,7 @@ class EmaneConfiguration(Dialog):
model_name = self.emane_models[self.emane_model_combobox.current()] model_name = self.emane_models[self.emane_model_combobox.current()]
# parse configuration # parse configuration
configutils.parse_config(self.model_options, self.model_values) config = self.model_config_frame.parse_config()
config = {x: self.model_options[x].value for x in self.model_options}
# add string emane_ infront for grpc call # add string emane_ infront for grpc call
response = self.app.core.client.set_emane_model_config( response = self.app.core.client.set_emane_model_config(
@ -145,17 +150,10 @@ class EmaneConfiguration(Dialog):
# create the dialog and the necessry widget # create the dialog and the necessry widget
if not self.emane_model_dialog or not self.emane_model_dialog.winfo_exists(): if not self.emane_model_dialog or not self.emane_model_dialog.winfo_exists():
self.emane_model_dialog = Dialog( 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 # query for configurations
session_id = self.app.core.session_id session_id = self.app.core.session_id
@ -166,12 +164,20 @@ class EmaneConfiguration(Dialog):
logging.info("emane model config %s", response) logging.info("emane model config %s", response)
self.model_options = response.config self.model_options = response.config
self.model_values = configutils.create_config( self.model_config_frame = ConfigFrame(
self.emane_model_dialog, self.model_options, PAD_X, PAD_Y 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") frame = tk.Frame(self.emane_model_dialog)
b2.grid(row=1, column=1, sticky="nsew") 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() self.emane_model_dialog.show()
def draw_option_buttons(self, parent): def draw_option_buttons(self, parent):

View file

@ -1,8 +1,8 @@
import logging import logging
import tkinter as tk import tkinter as tk
from coretk import configutils
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
from coretk.widgets import ConfigFrame
PAD_X = 2 PAD_X = 2
PAD_Y = 2 PAD_Y = 2
@ -11,25 +11,33 @@ PAD_Y = 2
class SessionOptionsDialog(Dialog): class SessionOptionsDialog(Dialog):
def __init__(self, master, app): def __init__(self, master, app):
super().__init__(master, app, "Session Options", modal=True) super().__init__(master, app, "Session Options", modal=True)
self.options = None self.config_frame = 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.draw() self.draw()
def draw(self): def draw(self):
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
session_id = self.app.core.session_id session_id = self.app.core.session_id
response = self.app.core.client.get_session_options(session_id) response = self.app.core.client.get_session_options(session_id)
logging.info("session options: %s", response) logging.info("session options: %s", response)
self.options = response.config
self.values = configutils.create_config(self, self.options, PAD_X, PAD_Y) self.config_frame = ConfigFrame(self, config=response.config)
self.save_button.grid(row=1, pady=PAD_Y, padx=PAD_X, sticky="ew") self.config_frame.draw_config()
self.cancel_button.grid(row=1, column=1, pady=PAD_Y, padx=PAD_X, sticky="ew") 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): def save(self):
configutils.parse_config(self.options, self.values) config = self.config_frame.parse_config()
session_id = self.app.core.session_id 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) response = self.app.core.client.set_session_options(session_id, config)
logging.info("saved session config: %s", response) logging.info("saved session config: %s", response)
self.destroy() self.destroy()

View file

@ -1,42 +1,131 @@
import logging
import tkinter as tk import tkinter as tk
from functools import partial from functools import partial
from tkinter import ttk
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): 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) super().__init__(master, cnf, **kw)
self.rowconfigure(0, weight=1) self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1) self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.canvas = tk.Canvas(self, highlightthickness=0) 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.columnconfigure(0, weight=1)
self.canvas.rowconfigure(0, weight=1) self.canvas.rowconfigure(0, weight=1)
self.scrollbar = tk.Scrollbar( self.scrollbar = tk.Scrollbar(
self, orient="vertical", command=self.canvas.yview 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 = _cls(self.canvas)
self.frame.columnconfigure(0, weight=1)
self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame) self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame)
self.canvas.update_idletasks() self.canvas.update_idletasks()
self.canvas.configure( self.canvas.configure(
scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set
) )
self.frame.bind( self.frame.bind("<Configure>", self._configure_frame)
"<Configure>", self.canvas.bind("<Configure>", self._configure_canvas)
lambda event: self.canvas.configure(scrollregion=self.canvas.bbox("all")),
) def _configure_frame(self, event):
self.canvas.bind( req_width = self.frame.winfo_reqwidth()
"<Configure>", if req_width != self.canvas.winfo_reqwidth():
lambda event: self.canvas.itemconfig(self.frame_id, width=event.width), self.canvas.configure(width=req_width)
) self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def _configure_canvas(self, event):
self.canvas.itemconfig(self.frame_id, width=event.width)
def clear(self): def clear(self):
for widget in self.frame.winfo_children(): for widget in self.frame.winfo_children():
widget.destroy() widget.destroy()
class ConfigFrame(FrameScroll):
def __init__(self, master=None, cnf={}, config=None, **kw):
super().__init__(master, cnf, ttk.Notebook, **kw)
self.config = config
self.values = {}
def draw_config(self):
padx = 2
pady = 2
group_mapping = {}
for key in self.config:
option = self.config[key]
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:
logging.error("unhandled config option type: %s", option.type)
self.values[option.name] = value
def parse_config(self):
for key in self.config:
option = self.config[key]
value = self.values[key]
config_value = value.get()
if option.type == core_pb2.ConfigOptionType.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): class ListboxScroll(tk.LabelFrame):
def __init__(self, master=None, cnf={}, **kw): def __init__(self, master=None, cnf={}, **kw):
super().__init__(master, cnf, **kw) super().__init__(master, cnf, **kw)
@ -51,43 +140,14 @@ class ListboxScroll(tk.LabelFrame):
self.scrollbar.config(command=self.listbox.yview) self.scrollbar.config(command=self.listbox.yview)
class CheckboxList(tk.LabelFrame): class CheckboxList(FrameScroll):
def __init__(self, master=None, cnf={}, clicked=None, **kw): def __init__(self, master=None, cnf={}, clicked=None, **kw):
super().__init__(master, cnf, **kw) super().__init__(master, cnf, **kw)
self.clicked = clicked 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.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(
"<Configure>",
lambda event: self.canvas.configure(scrollregion=self.canvas.bbox("all")),
)
self.canvas.bind(
"<Configure>",
lambda event: self.canvas.itemconfig(self.frame_id, width=event.width),
)
def clear(self): def add(self, name, checked):
for widget in self.frame.winfo_children(): var = tk.BooleanVar(value=checked)
widget.destroy()
def add(self, name):
var = tk.BooleanVar()
func = partial(self.clicked, name, var) func = partial(self.clicked, name, var)
checkbox = tk.Checkbutton(self.frame, text=name, variable=var, command=func) checkbox = tk.Checkbutton(self.frame, text=name, variable=var, command=func)
checkbox.grid(sticky="w") checkbox.grid(sticky="w")

View file

@ -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 { message ServiceValidationMode {
enum Enum { enum Enum {
BLOCKING = 0; BLOCKING = 0;