From 891e9aef9af1befefc2cdc1f8aaeb72b3b9614a7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 31 Oct 2019 14:06:50 -0700 Subject: [PATCH 1/4] initial add with a common dialog class and leveraging it for a session options dialog --- coretk/coretk/configutils.py | 52 +++++++++++++++++++++++++ coretk/coretk/coremenubar.py | 2 +- coretk/coretk/dialogs/__init__.py | 0 coretk/coretk/dialogs/dialog.py | 23 +++++++++++ coretk/coretk/dialogs/sessionoptions.py | 35 +++++++++++++++++ coretk/coretk/menuaction.py | 10 +++-- 6 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 coretk/coretk/configutils.py create mode 100644 coretk/coretk/dialogs/__init__.py create mode 100644 coretk/coretk/dialogs/dialog.py create mode 100644 coretk/coretk/dialogs/sessionoptions.py diff --git a/coretk/coretk/configutils.py b/coretk/coretk/configutils.py new file mode 100644 index 00000000..97230561 --- /dev/null +++ b/coretk/coretk/configutils.py @@ -0,0 +1,52 @@ +import enum +import logging +import tkinter as tk +from tkinter import ttk + + +class ConfigType(enum.Enum): + STRING = 10 + BOOL = 11 + + +def create_config(master, config, pad_x=2, pad_y=2): + master.columnconfigure(0, weight=1) + master.columnconfigure(1, weight=3) + values = {} + for index, key in enumerate(sorted(config)): + option = config[key] + label = tk.Label(master, text=option.label) + label.grid(row=index, pady=pad_y, padx=pad_x, sticky="ew") + value = tk.StringVar() + config_type = ConfigType(option.type) + if config_type == ConfigType.BOOL: + select = tuple(option.select) + combobox = ttk.Combobox(master, textvariable=value, values=select) + combobox.grid(row=index, column=1, sticky="ew", pady=pad_y) + if option.value == "1": + value.set("On") + else: + value.set("Off") + elif config_type == ConfigType.STRING: + entry = tk.Entry(master, textvariable=value) + entry.grid(row=index, column=1, sticky="ew", pady=pad_y) + else: + logging.error("unhandled config option type: %s", config_type) + values[key] = value + return values + + +def parse_config(options, values): + config = {} + 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": + config_value = "1" + else: + config_value = "0" + config[key] = config_value + return config diff --git a/coretk/coretk/coremenubar.py b/coretk/coretk/coremenubar.py index 1129e8b7..1a558684 100644 --- a/coretk/coretk/coremenubar.py +++ b/coretk/coretk/coremenubar.py @@ -620,7 +620,7 @@ class CoreMenubar(object): underline=0, ) session_menu.add_command( - label="Options...", command=action.session_options, underline=0 + label="Options...", command=self.menu_action.session_options, underline=0 ) self.menubar.add_cascade(label="Session", menu=session_menu, underline=0) diff --git a/coretk/coretk/dialogs/__init__.py b/coretk/coretk/dialogs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/coretk/coretk/dialogs/dialog.py b/coretk/coretk/dialogs/dialog.py new file mode 100644 index 00000000..9ce421b9 --- /dev/null +++ b/coretk/coretk/dialogs/dialog.py @@ -0,0 +1,23 @@ +import tkinter as tk + +from coretk.images import ImageEnum, Images + + +class Dialog(tk.Toplevel): + def __init__(self, master, title, modal=False): + super().__init__(master, padx=5, pady=5) + image = Images.get(ImageEnum.CORE.value) + self.tk.call("wm", "iconphoto", self._w, image) + self.title(title) + self.protocol("WM_DELETE_WINDOW", self.destroy) + self.withdraw() + self.modal = modal + + def show(self): + self.transient(self.master) + self.focus_force() + if self.modal: + self.grab_set() + self.update() + self.deiconify() + self.wait_window() diff --git a/coretk/coretk/dialogs/sessionoptions.py b/coretk/coretk/dialogs/sessionoptions.py new file mode 100644 index 00000000..3612dab5 --- /dev/null +++ b/coretk/coretk/dialogs/sessionoptions.py @@ -0,0 +1,35 @@ +import logging +import tkinter as tk + +from coretk import configutils +from coretk.dialogs.dialog import Dialog + +PAD_X = 2 +PAD_Y = 2 + + +class SessionOptionsDialog(Dialog): + def __init__(self, master): + super().__init__(master, "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.draw() + + def draw(self): + session_id = self.master.core_grpc.session_id + response = self.master.core_grpc.core.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) + row = len(response.config) + self.save_button.grid(row=row, pady=PAD_Y, padx=PAD_X, sticky="ew") + self.cancel_button.grid(row=row, column=1, pady=PAD_Y, padx=PAD_X, sticky="ew") + + def save(self): + config = configutils.parse_config(self.options, self.values) + session_id = self.master.core_grpc.session_id + response = self.master.core_grpc.core.set_session_options(session_id, config) + logging.info("saved session config: %s", response) + self.destroy() diff --git a/coretk/coretk/menuaction.py b/coretk/coretk/menuaction.py index 999801c2..22329516 100644 --- a/coretk/coretk/menuaction.py +++ b/coretk/coretk/menuaction.py @@ -7,6 +7,7 @@ import webbrowser from tkinter import filedialog, messagebox from core.api.grpc import core_pb2 +from coretk.dialogs.sessionoptions import SessionOptionsDialog from coretk.setwallpaper import CanvasWallpaper from coretk.sizeandscale import SizeAndScale @@ -314,10 +315,6 @@ def session_emulation_servers(): logging.debug("Click session emulation servers") -def session_options(): - logging.debug("Click session options") - - def help_about(): logging.debug("Click help About") @@ -433,3 +430,8 @@ class MenuAction: def help_core_documentation(self): webbrowser.open_new("http://coreemu.github.io/core/") + + def session_options(self): + logging.debug("Click session options") + dialog = SessionOptionsDialog(self.application) + dialog.show() From c65c846638041214cc8cb77652b59f909ee8f83e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 31 Oct 2019 15:18:49 -0700 Subject: [PATCH 2/4] updated configutils to generate a scrollable view for configurations --- coretk/coretk/configutils.py | 59 +++++++++++++++++++++---- coretk/coretk/dialogs/sessionoptions.py | 5 +-- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/coretk/coretk/configutils.py b/coretk/coretk/configutils.py index 97230561..fd5263f3 100644 --- a/coretk/coretk/configutils.py +++ b/coretk/coretk/configutils.py @@ -9,34 +9,77 @@ class ConfigType(enum.Enum): BOOL = 11 -def create_config(master, config, pad_x=2, pad_y=2): +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=3) + master.columnconfigure(1, weight=1) + + canvas = tk.Canvas(master) + 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(master, text=option.label) - label.grid(row=index, pady=pad_y, padx=pad_x, sticky="ew") + 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(master, textvariable=value, values=select) - combobox.grid(row=index, column=1, sticky="ew", pady=pad_y) + 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: - entry = tk.Entry(master, textvariable=value) - entry.grid(row=index, column=1, sticky="ew", pady=pad_y) + entry = tk.Entry(frame, textvariable=value) + 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: + """ config = {} for key in options: option = options[key] diff --git a/coretk/coretk/dialogs/sessionoptions.py b/coretk/coretk/dialogs/sessionoptions.py index 3612dab5..65faae81 100644 --- a/coretk/coretk/dialogs/sessionoptions.py +++ b/coretk/coretk/dialogs/sessionoptions.py @@ -23,9 +23,8 @@ class SessionOptionsDialog(Dialog): logging.info("session options: %s", response) self.options = response.config self.values = configutils.create_config(self, self.options, PAD_X, PAD_Y) - row = len(response.config) - self.save_button.grid(row=row, pady=PAD_Y, padx=PAD_X, sticky="ew") - self.cancel_button.grid(row=row, column=1, pady=PAD_Y, padx=PAD_X, sticky="ew") + 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") def save(self): config = configutils.parse_config(self.options, self.values) From f0c32304db4d3662b09dac4db70052c30e63d935 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 31 Oct 2019 15:54:38 -0700 Subject: [PATCH 3/4] removed canvas border for configuration options --- coretk/coretk/configutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coretk/coretk/configutils.py b/coretk/coretk/configutils.py index fd5263f3..7c87f9dd 100644 --- a/coretk/coretk/configutils.py +++ b/coretk/coretk/configutils.py @@ -24,7 +24,7 @@ def create_config(master, config, padx=2, pady=2): master.columnconfigure(0, weight=1) master.columnconfigure(1, weight=1) - canvas = tk.Canvas(master) + 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) From 01d3a3158a5e1d192d622eba70770960d6a7002f Mon Sep 17 00:00:00 2001 From: bharnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 31 Oct 2019 23:17:26 -0700 Subject: [PATCH 4/4] updated sessions dialog to use common dialog base, cleaned up code and made widgets expandable --- coretk/coretk/app.py | 2 +- coretk/coretk/coregrpc.py | 24 +--- coretk/coretk/coremenubar.py | 2 +- coretk/coretk/coretoolbar.py | 115 ++++++++------- coretk/coretk/dialogs/dialog.py | 11 +- coretk/coretk/dialogs/sessionoptions.py | 4 +- coretk/coretk/dialogs/sessions.py | 159 ++++++++++++++++++++ coretk/coretk/graph_helper.py | 2 +- coretk/coretk/images.py | 26 ++-- coretk/coretk/menuaction.py | 12 +- coretk/coretk/querysessiondrawing.py | 183 ------------------------ 11 files changed, 250 insertions(+), 290 deletions(-) create mode 100644 coretk/coretk/dialogs/sessions.py delete mode 100644 coretk/coretk/querysessiondrawing.py diff --git a/coretk/coretk/app.py b/coretk/coretk/app.py index 644f1eb2..f253fd01 100644 --- a/coretk/coretk/app.py +++ b/coretk/coretk/app.py @@ -40,7 +40,7 @@ class Application(tk.Frame): def setup_app(self): self.master.title("CORE") self.master.geometry("1000x800") - image = Images.get(ImageEnum.CORE.value) + image = Images.get(ImageEnum.CORE) self.master.tk.call("wm", "iconphoto", self.master._w, image) self.pack(fill=tk.BOTH, expand=True) diff --git a/coretk/coretk/coregrpc.py b/coretk/coretk/coregrpc.py index 9b51116a..13fe28b3 100644 --- a/coretk/coretk/coregrpc.py +++ b/coretk/coretk/coregrpc.py @@ -6,8 +6,8 @@ import os from collections import OrderedDict from core.api.grpc import client, core_pb2 +from coretk.dialogs.sessions import SessionsDialog from coretk.linkinfo import Throughput -from coretk.querysessiondrawing import SessionTable from coretk.wirelessconnection import WirelessConnection @@ -18,12 +18,9 @@ class CoreGrpc: """ self.core = client.CoreGrpcClient() self.session_id = sid - self.node_ids = [] - + self.app = app self.master = app.master - - # self.set_up() self.interface_helper = None self.throughput_draw = Throughput(app.canvas, self) self.wireless_draw = WirelessConnection(app.canvas, self) @@ -62,16 +59,6 @@ class CoreGrpc: self.core.events(self.session_id, self.log_event) # self.core.throughputs(self.log_throughput) - def query_existing_sessions(self, sessions): - """ - Query for existing sessions and prompt to join one - - :param repeated core_pb2.SessionSummary sessions: summaries of all the existing sessions - - :return: nothing - """ - SessionTable(self, self.master) - def delete_session(self, custom_sid=None): if custom_sid is None: sid = self.session_id @@ -100,18 +87,15 @@ class CoreGrpc: :return: existing sessions """ self.core.connect() - response = self.core.get_sessions() - # logging.info("coregrpc.py: all sessions: %s", response) # if there are no sessions, create a new session, else join a session sessions = response.sessions - if len(sessions) == 0: self.create_new_session() else: - - self.query_existing_sessions(sessions) + dialog = SessionsDialog(self.app, self.app) + dialog.show() def get_session_state(self): response = self.core.get_session(self.session_id) diff --git a/coretk/coretk/coremenubar.py b/coretk/coretk/coremenubar.py index 1a558684..aa00a927 100644 --- a/coretk/coretk/coremenubar.py +++ b/coretk/coretk/coremenubar.py @@ -594,7 +594,7 @@ class CoreMenubar(object): session_menu.add_command( label="Change sessions...", - command=action.session_change_sessions, + command=self.menu_action.session_change_sessions, underline=0, ) diff --git a/coretk/coretk/coretoolbar.py b/coretk/coretk/coretoolbar.py index d46a9791..83831a8a 100644 --- a/coretk/coretk/coretoolbar.py +++ b/coretk/coretk/coretoolbar.py @@ -200,55 +200,55 @@ class CoreToolbar(object): def pick_router(self, main_button): logging.debug("Pick router option") self.network_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.ROUTER.value)) + main_button.configure(image=Images.get(ImageEnum.ROUTER)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.ROUTER.value) + self.canvas.draw_node_image = Images.get(ImageEnum.ROUTER) self.canvas.draw_node_name = "router" def pick_host(self, main_button): logging.debug("Pick host option") self.network_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.HOST.value)) + main_button.configure(image=Images.get(ImageEnum.HOST)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.HOST.value) + self.canvas.draw_node_image = Images.get(ImageEnum.HOST) self.canvas.draw_node_name = "host" def pick_pc(self, main_button): logging.debug("Pick PC option") self.network_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.PC.value)) + main_button.configure(image=Images.get(ImageEnum.PC)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.PC.value) + self.canvas.draw_node_image = Images.get(ImageEnum.PC) self.canvas.draw_node_name = "PC" def pick_mdr(self, main_button): logging.debug("Pick MDR option") self.network_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.MDR.value)) + main_button.configure(image=Images.get(ImageEnum.MDR)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.MDR.value) + self.canvas.draw_node_image = Images.get(ImageEnum.MDR) self.canvas.draw_node_name = "mdr" def pick_prouter(self, main_button): logging.debug("Pick prouter option") self.network_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.PROUTER.value)) + main_button.configure(image=Images.get(ImageEnum.PROUTER)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.PROUTER.value) + self.canvas.draw_node_image = Images.get(ImageEnum.PROUTER) self.canvas.draw_node_name = "prouter" def pick_ovs(self, main_button): logging.debug("Pick OVS option") self.network_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.OVS.value)) + main_button.configure(image=Images.get(ImageEnum.OVS)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.OVS.value) + self.canvas.draw_node_image = Images.get(ImageEnum.OVS) self.canvas.draw_node_name = "OVS" # TODO what graph node is this def pick_editnode(self, main_button): self.network_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.EDITNODE.value)) + main_button.configure(image=Images.get(ImageEnum.EDITNODE)) logging.debug("Pick editnode option") def draw_network_layer_options(self, network_layer_button): @@ -262,13 +262,13 @@ class CoreToolbar(object): self.destroy_previous_frame() option_frame = tk.Frame(self.master, padx=1, pady=1) img_list = [ - Images.get(ImageEnum.ROUTER.value), - Images.get(ImageEnum.HOST.value), - Images.get(ImageEnum.PC.value), - Images.get(ImageEnum.MDR.value), - Images.get(ImageEnum.PROUTER.value), - Images.get(ImageEnum.OVS.value), - Images.get(ImageEnum.EDITNODE.value), + Images.get(ImageEnum.ROUTER), + Images.get(ImageEnum.HOST), + Images.get(ImageEnum.PC), + Images.get(ImageEnum.MDR), + Images.get(ImageEnum.PROUTER), + Images.get(ImageEnum.OVS), + Images.get(ImageEnum.EDITNODE), ] func_list = [ self.pick_router, @@ -312,7 +312,7 @@ class CoreToolbar(object): :return: nothing """ - router_image = Images.get(ImageEnum.ROUTER.value) + router_image = Images.get(ImageEnum.ROUTER) network_layer_button = tk.Radiobutton( self.edit_frame, indicatoron=False, @@ -329,41 +329,41 @@ class CoreToolbar(object): def pick_hub(self, main_button): logging.debug("Pick link-layer node HUB") self.link_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.HUB.value)) + main_button.configure(image=Images.get(ImageEnum.HUB)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.HUB.value) + self.canvas.draw_node_image = Images.get(ImageEnum.HUB) self.canvas.draw_node_name = "hub" def pick_switch(self, main_button): logging.debug("Pick link-layer node SWITCH") self.link_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.SWITCH.value)) + main_button.configure(image=Images.get(ImageEnum.SWITCH)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.SWITCH.value) + self.canvas.draw_node_image = Images.get(ImageEnum.SWITCH) self.canvas.draw_node_name = "switch" def pick_wlan(self, main_button): logging.debug("Pick link-layer node WLAN") self.link_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.WLAN.value)) + main_button.configure(image=Images.get(ImageEnum.WLAN)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.WLAN.value) + self.canvas.draw_node_image = Images.get(ImageEnum.WLAN) self.canvas.draw_node_name = "wlan" def pick_rj45(self, main_button): logging.debug("Pick link-layer node RJ45") self.link_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.RJ45.value)) + main_button.configure(image=Images.get(ImageEnum.RJ45)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.RJ45.value) + self.canvas.draw_node_image = Images.get(ImageEnum.RJ45) self.canvas.draw_node_name = "rj45" def pick_tunnel(self, main_button): logging.debug("Pick link-layer node TUNNEL") self.link_layer_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.TUNNEL.value)) + main_button.configure(image=Images.get(ImageEnum.TUNNEL)) self.canvas.mode = GraphMode.PICKNODE - self.canvas.draw_node_image = Images.get(ImageEnum.TUNNEL.value) + self.canvas.draw_node_image = Images.get(ImageEnum.TUNNEL) self.canvas.draw_node_name = "tunnel" def draw_link_layer_options(self, link_layer_button): @@ -377,11 +377,11 @@ class CoreToolbar(object): self.destroy_previous_frame() option_frame = tk.Frame(self.master, padx=1, pady=1) img_list = [ - Images.get(ImageEnum.HUB.value), - Images.get(ImageEnum.SWITCH.value), - Images.get(ImageEnum.WLAN.value), - Images.get(ImageEnum.RJ45.value), - Images.get(ImageEnum.TUNNEL.value), + Images.get(ImageEnum.HUB), + Images.get(ImageEnum.SWITCH), + Images.get(ImageEnum.WLAN), + Images.get(ImageEnum.RJ45), + Images.get(ImageEnum.TUNNEL), ] func_list = [ self.pick_hub, @@ -421,7 +421,7 @@ class CoreToolbar(object): :return: nothing """ - hub_image = Images.get(ImageEnum.HUB.value) + hub_image = Images.get(ImageEnum.HUB) link_layer_button = tk.Radiobutton( self.edit_frame, indicatoron=False, @@ -437,22 +437,22 @@ class CoreToolbar(object): def pick_marker(self, main_button): self.marker_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.MARKER.value)) + main_button.configure(image=Images.get(ImageEnum.MARKER)) logging.debug("Pick MARKER") def pick_oval(self, main_button): self.marker_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.OVAL.value)) + main_button.configure(image=Images.get(ImageEnum.OVAL)) logging.debug("Pick OVAL") def pick_rectangle(self, main_button): self.marker_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.RECTANGLE.value)) + main_button.configure(image=Images.get(ImageEnum.RECTANGLE)) logging.debug("Pick RECTANGLE") def pick_text(self, main_button): self.marker_option_menu.destroy() - main_button.configure(image=Images.get(ImageEnum.TEXT.value)) + main_button.configure(image=Images.get(ImageEnum.TEXT)) logging.debug("Pick TEXT") def draw_marker_options(self, main_button): @@ -466,10 +466,10 @@ class CoreToolbar(object): self.destroy_previous_frame() option_frame = tk.Frame(self.master, padx=1, pady=1) img_list = [ - Images.get(ImageEnum.MARKER.value), - Images.get(ImageEnum.OVAL.value), - Images.get(ImageEnum.RECTANGLE.value), - Images.get(ImageEnum.TEXT.value), + Images.get(ImageEnum.MARKER), + Images.get(ImageEnum.OVAL), + Images.get(ImageEnum.RECTANGLE), + Images.get(ImageEnum.TEXT), ] func_list = [ self.pick_marker, @@ -498,7 +498,7 @@ class CoreToolbar(object): :return: nothing """ - marker_image = Images.get(ImageEnum.MARKER.value) + marker_image = Images.get(ImageEnum.MARKER) marker_main_button = tk.Radiobutton( self.edit_frame, indicatoron=False, @@ -520,13 +520,13 @@ class CoreToolbar(object): """ self.create_regular_button( self.edit_frame, - Images.get(ImageEnum.START.value), + Images.get(ImageEnum.START), self.click_start_session_tool, "start the session", ) self.create_radio_button( self.edit_frame, - Images.get(ImageEnum.SELECT.value), + Images.get(ImageEnum.SELECT), self.click_selection_tool, self.radio_value, 1, @@ -534,7 +534,7 @@ class CoreToolbar(object): ) self.create_radio_button( self.edit_frame, - Images.get(ImageEnum.LINK.value), + Images.get(ImageEnum.LINK), self.click_link_tool, self.radio_value, 2, @@ -548,7 +548,7 @@ class CoreToolbar(object): def create_observe_button(self): menu_button = tk.Menubutton( self.edit_frame, - image=Images.get(ImageEnum.OBSERVE.value), + image=Images.get(ImageEnum.OBSERVE), width=self.width, height=self.height, direction=tk.RIGHT, @@ -605,13 +605,13 @@ class CoreToolbar(object): def create_runtime_toolbar(self): self.create_regular_button( self.edit_frame, - Images.get(ImageEnum.STOP.value), + Images.get(ImageEnum.STOP), self.click_stop_button, "stop the session", ) self.create_radio_button( self.edit_frame, - Images.get(ImageEnum.SELECT.value), + Images.get(ImageEnum.SELECT), self.click_selection_tool, self.exec_radio_value, 1, @@ -620,7 +620,7 @@ class CoreToolbar(object): self.create_observe_button() self.create_radio_button( self.edit_frame, - Images.get(ImageEnum.PLOT.value), + Images.get(ImageEnum.PLOT), self.click_plot_button, self.exec_radio_value, 2, @@ -628,7 +628,7 @@ class CoreToolbar(object): ) self.create_radio_button( self.edit_frame, - Images.get(ImageEnum.MARKER.value), + Images.get(ImageEnum.MARKER), self.click_marker_button, self.exec_radio_value, 3, @@ -636,16 +636,13 @@ class CoreToolbar(object): ) self.create_radio_button( self.edit_frame, - Images.get(ImageEnum.TWONODE.value), + Images.get(ImageEnum.TWONODE), self.click_two_node_button, self.exec_radio_value, 4, "run command from one node to another", ) self.create_regular_button( - self.edit_frame, - Images.get(ImageEnum.RUN.value), - self.click_run_button, - "run", + self.edit_frame, Images.get(ImageEnum.RUN), self.click_run_button, "run" ) self.exec_radio_value.set(1) diff --git a/coretk/coretk/dialogs/dialog.py b/coretk/coretk/dialogs/dialog.py index 9ce421b9..b218d8c7 100644 --- a/coretk/coretk/dialogs/dialog.py +++ b/coretk/coretk/dialogs/dialog.py @@ -4,14 +4,15 @@ from coretk.images import ImageEnum, Images class Dialog(tk.Toplevel): - def __init__(self, master, title, modal=False): + def __init__(self, master, app, title, modal=False): super().__init__(master, padx=5, pady=5) - image = Images.get(ImageEnum.CORE.value) - self.tk.call("wm", "iconphoto", self._w, image) + self.withdraw() + self.app = app + self.modal = modal self.title(title) self.protocol("WM_DELETE_WINDOW", self.destroy) - self.withdraw() - self.modal = modal + image = Images.get(ImageEnum.CORE) + self.tk.call("wm", "iconphoto", self._w, image) def show(self): self.transient(self.master) diff --git a/coretk/coretk/dialogs/sessionoptions.py b/coretk/coretk/dialogs/sessionoptions.py index 65faae81..a34c6658 100644 --- a/coretk/coretk/dialogs/sessionoptions.py +++ b/coretk/coretk/dialogs/sessionoptions.py @@ -9,8 +9,8 @@ PAD_Y = 2 class SessionOptionsDialog(Dialog): - def __init__(self, master): - super().__init__(master, "Session Options", modal=True) + 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) diff --git a/coretk/coretk/dialogs/sessions.py b/coretk/coretk/dialogs/sessions.py new file mode 100644 index 00000000..10165e2e --- /dev/null +++ b/coretk/coretk/dialogs/sessions.py @@ -0,0 +1,159 @@ +import logging +import tkinter as tk +from tkinter.ttk import Scrollbar, Treeview + +from core.api.grpc import core_pb2 +from coretk.dialogs.dialog import Dialog +from coretk.images import ImageEnum, Images + + +class SessionsDialog(Dialog): + def __init__(self, master, app): + """ + create session table instance + + :param coretk.coregrpc.CoreGrpc grpc: coregrpc + :param root.master master: + """ + super().__init__(master, app, "Sessions", modal=True) + self.selected = False + self.selected_id = None + self.tree = None + self.draw() + + def draw(self): + self.columnconfigure(0, weight=1) + self.draw_description() + self.draw_tree() + self.draw_buttons() + + def draw_description(self): + """ + write a short description + :return: nothing + """ + label = tk.Label( + self, + text="Below is a list of active CORE sessions. Double-click to \n" + "connect to an existing session. Usually, only sessions in \n" + "the RUNTIME state persist in the daemon, except for the \n" + "one you might be concurrently editting.", + ) + label.grid(row=0, sticky="ew", pady=5) + + def draw_tree(self): + self.tree = Treeview(self, columns=("id", "state", "nodes"), show="headings") + self.tree.grid(row=1, sticky="nsew") + self.tree.column("id", stretch=tk.YES) + self.tree.heading("id", text="ID") + self.tree.column("state", stretch=tk.YES) + self.tree.heading("state", text="State") + self.tree.column("nodes", stretch=tk.YES) + self.tree.heading("nodes", text="Node Count") + + response = self.app.core_grpc.core.get_sessions() + logging.info("sessions: %s", response) + for index, session in enumerate(response.sessions): + state_name = core_pb2.SessionState.Enum.Name(session.state) + self.tree.insert( + "", + tk.END, + text=str(session.id), + values=(session.id, state_name, session.nodes), + ) + self.tree.bind("", self.on_selected) + self.tree.bind("<>", self.click_select) + + yscrollbar = Scrollbar(self, orient="vertical", command=self.tree.yview) + yscrollbar.grid(row=1, column=1, sticky="ns") + self.tree.configure(yscrollcommand=yscrollbar.set) + + xscrollbar = Scrollbar(self, orient="horizontal", command=self.tree.xview) + xscrollbar.grid(row=2, sticky="ew", pady=5) + self.tree.configure(xscrollcommand=xscrollbar.set) + + def draw_buttons(self): + frame = tk.Frame(self) + for i in range(4): + frame.columnconfigure(i, weight=1) + frame.grid(row=3, sticky="ew") + b = tk.Button( + frame, + image=Images.get(ImageEnum.DOCUMENTNEW), + text="New", + compound=tk.LEFT, + command=self.click_new, + ) + b.grid(row=0, padx=2, sticky="ew") + b = tk.Button( + frame, + image=Images.get(ImageEnum.FILEOPEN), + text="Connect", + compound=tk.LEFT, + command=self.click_connect, + ) + b.grid(row=0, column=1, padx=2, sticky="ew") + b = tk.Button( + frame, + image=Images.get(ImageEnum.EDITDELETE), + text="Shutdown", + compound=tk.LEFT, + command=self.click_shutdown, + ) + b.grid(row=0, column=2, padx=2, sticky="ew") + b = tk.Button(frame, text="Cancel", command=self.click_new) + b.grid(row=0, column=3, padx=2, sticky="ew") + + def click_new(self): + self.app.core_grpc.create_new_session() + self.destroy() + + def click_select(self, event): + item = self.tree.selection() + session_id = int(self.tree.item(item, "text")) + self.selected = True + self.selected_id = session_id + + def click_connect(self): + """ + if no session is selected yet, create a new one else join that session + + :return: nothing + """ + if self.selected and self.selected_id is not None: + self.join_session(self.selected_id) + elif not self.selected and self.selected_id is None: + self.click_new() + else: + logging.error("sessions invalid state") + + def click_shutdown(self): + """ + if no session is currently selected create a new session else shut the selected + session down. + + :return: nothing + """ + if self.selected and self.selected_id is not None: + self.shutdown_session(self.selected_id) + elif not self.selected and self.selected_id is None: + self.click_new() + else: + logging.error("querysessiondrawing.py invalid state") + + def join_session(self, session_id): + response = self.app.core_grpc.core.get_session(session_id) + self.app.core_grpc.session_id = session_id + self.app.core_grpc.core.events(session_id, self.app.core_grpc.log_event) + logging.info("entering session_id %s.... Result: %s", session_id, response) + self.destroy() + + def on_selected(self, event): + item = self.tree.selection() + sid = int(self.tree.item(item, "text")) + self.join_session(sid) + + def shutdown_session(self, sid): + self.app.core_grpc.terminate_session(sid) + self.click_new() + self.destroy() diff --git a/coretk/coretk/graph_helper.py b/coretk/coretk/graph_helper.py index 64f38acf..ba58a435 100644 --- a/coretk/coretk/graph_helper.py +++ b/coretk/coretk/graph_helper.py @@ -94,7 +94,7 @@ class WlanAntennaManager: x - 16 + self.offset, y - 16, anchor=tk.CENTER, - image=Images.get(ImageEnum.ANTENNA.value), + image=Images.get(ImageEnum.ANTENNA), tags="antenna", ) ) diff --git a/coretk/coretk/images.py b/coretk/coretk/images.py index f865c191..6a817b6f 100644 --- a/coretk/coretk/images.py +++ b/coretk/coretk/images.py @@ -21,8 +21,8 @@ class Images: cls.images[name] = tk_image @classmethod - def get(cls, name): - return cls.images[name] + def get(cls, image): + return cls.images[image.value] @classmethod def convert_type_and_model_to_image(cls, node_type, node_model): @@ -35,28 +35,28 @@ class Images: :return: the matching image and its name """ if node_type == core_pb2.NodeType.SWITCH: - return Images.get(ImageEnum.SWITCH.value), "switch" + return Images.get(ImageEnum.SWITCH), "switch" if node_type == core_pb2.NodeType.HUB: - return Images.get(ImageEnum.HUB.value), "hub" + return Images.get(ImageEnum.HUB), "hub" if node_type == core_pb2.NodeType.WIRELESS_LAN: - return Images.get(ImageEnum.WLAN.value), "wlan" + return Images.get(ImageEnum.WLAN), "wlan" if node_type == core_pb2.NodeType.RJ45: - return Images.get(ImageEnum.RJ45.value), "rj45" + return Images.get(ImageEnum.RJ45), "rj45" if node_type == core_pb2.NodeType.TUNNEL: - return Images.get(ImageEnum.TUNNEL.value), "tunnel" + return Images.get(ImageEnum.TUNNEL), "tunnel" if node_type == core_pb2.NodeType.DEFAULT: if node_model == "router": - return Images.get(ImageEnum.ROUTER.value), "router" + return Images.get(ImageEnum.ROUTER), "router" if node_model == "host": - return Images.get((ImageEnum.HOST.value)), "host" + return Images.get(ImageEnum.HOST), "host" if node_model == "PC": - return Images.get(ImageEnum.PC.value), "PC" + return Images.get(ImageEnum.PC), "PC" if node_model == "mdr": - return Images.get(ImageEnum.MDR.value), "mdr" + return Images.get(ImageEnum.MDR), "mdr" if node_model == "prouter": - return Images.get(ImageEnum.PROUTER.value), "prouter" + return Images.get(ImageEnum.PROUTER), "prouter" if node_model == "OVS": - return Images.get(ImageEnum.OVS.value), "ovs" + return Images.get(ImageEnum.OVS), "ovs" else: logging.debug("INVALID INPUT OR NOT CONSIDERED YET") diff --git a/coretk/coretk/menuaction.py b/coretk/coretk/menuaction.py index 22329516..97ba9029 100644 --- a/coretk/coretk/menuaction.py +++ b/coretk/coretk/menuaction.py @@ -8,6 +8,7 @@ from tkinter import filedialog, messagebox from core.api.grpc import core_pb2 from coretk.dialogs.sessionoptions import SessionOptionsDialog +from coretk.dialogs.sessions import SessionsDialog from coretk.setwallpaper import CanvasWallpaper from coretk.sizeandscale import SizeAndScale @@ -291,10 +292,6 @@ def widgets_configure_throughput(): logging.debug("Click widgets configure throughput") -def session_change_sessions(): - logging.debug("Click session change sessions") - - def session_node_types(): logging.debug("Click session node types") @@ -433,5 +430,10 @@ class MenuAction: def session_options(self): logging.debug("Click session options") - dialog = SessionOptionsDialog(self.application) + dialog = SessionOptionsDialog(self.application, self.application) + dialog.show() + + def session_change_sessions(self): + logging.debug("Click session change sessions") + dialog = SessionsDialog(self.application, self.application) dialog.show() diff --git a/coretk/coretk/querysessiondrawing.py b/coretk/coretk/querysessiondrawing.py deleted file mode 100644 index 84786ba5..00000000 --- a/coretk/coretk/querysessiondrawing.py +++ /dev/null @@ -1,183 +0,0 @@ -import logging -import tkinter as tk -from tkinter.ttk import Scrollbar, Treeview - -from coretk.images import ImageEnum, Images - - -class SessionTable: - def __init__(self, grpc, master): - """ - create session table instance - :param coretk.coregrpc.CoreGrpc grpc: coregrpc - :param root.master master: - """ - self.grpc = grpc - self.selected = False - self.selected_sid = None - self.master = master - self.top = tk.Toplevel(self.master) - self.description_definition() - self.top.title("CORE sessions") - - self.tree = Treeview(self.top) - # self.tree.pack(side=tk.TOP) - self.tree.grid(row=1, column=0, columnspan=2) - self.draw_scrollbar() - self.draw() - - def description_definition(self): - """ - write a short description - :return: nothing - """ - lable = tk.Label( - self.top, - text="Below is a list of active CORE sessions. Double-click to " - "\nconnect to an existing session. Usually, only sessions in " - "\nthe RUNTIME state persist in the daemon, except for the " - "\none you might be concurrently editting.", - ) - lable.grid(sticky=tk.W) - - def column_definition(self): - # self.tree["columns"] = ("name", "nodecount", "filename", "date") - self.tree["columns"] = "nodecount" - self.tree.column("#0", width=300, minwidth=30) - # self.tree.column("name", width=72, miwidth=30) - self.tree.column("nodecount", width=300, minwidth=30) - # self.tree.column("filename", width=92, minwidth=30) - # self.tree.column("date", width=170, minwidth=30) - - def draw_scrollbar(self): - yscrollbar = Scrollbar(self.top, orient="vertical", command=self.tree.yview) - yscrollbar.grid(row=1, column=3, sticky=tk.N + tk.S + tk.W) - self.tree.configure(yscrollcommand=yscrollbar.set) - - xscrollbar = Scrollbar(self.top, orient="horizontal", command=self.tree.xview) - xscrollbar.grid(row=2, columnspan=2, sticky=tk.E + tk.W + tk.S) - self.tree.configure(xscrollcommand=xscrollbar.set) - - def heading_definition(self): - self.tree.heading("#0", text="ID", anchor=tk.W) - # self.tree.heading("name", text="Name", anchor=tk.CENTER) - self.tree.heading("nodecount", text="Node Count", anchor=tk.W) - # self.tree.heading("filename", text="Filename", anchor=tk.CENTER) - # self.tree.heading("date", text="Date", anchor=tk.CENTER) - - def enter_session(self, sid): - self.top.destroy() - response = self.grpc.core.get_session(sid) - self.grpc.session_id = sid - self.grpc.core.events(sid, self.grpc.log_event) - logging.info("Entering session_id %s.... Result: %s", sid, response) - - def new_session(self): - self.top.destroy() - self.grpc.create_new_session() - - def on_selected(self, event): - item = self.tree.selection() - sid = int(self.tree.item(item, "text")) - self.enter_session(sid) - - def click_select(self, event): - # logging.debug("Click on %s ", event) - item = self.tree.selection() - sid = int(self.tree.item(item, "text")) - self.selected = True - self.selected_sid = sid - - def session_definition(self): - response = self.grpc.core.get_sessions() - # logging.info("querysessiondrawing.py Get all sessions %s", response) - index = 1 - for session in response.sessions: - self.tree.insert( - "", index, None, text=str(session.id), values=(str(session.nodes)) - ) - index = index + 1 - self.tree.bind("", self.on_selected) - self.tree.bind("<>", self.click_select) - - def click_connect(self): - """ - if no session is selected yet, create a new one else join that session - - :return: nothing - """ - if self.selected and self.selected_sid is not None: - self.enter_session(self.selected_sid) - elif not self.selected and self.selected_sid is None: - self.new_session() - else: - logging.error("querysessiondrawing.py invalid state") - - def shutdown_session(self, sid): - self.grpc.terminate_session(sid) - self.new_session() - self.top.destroy() - - def click_shutdown(self): - """ - if no session is currently selected create a new session else shut the selected session down - - :return: nothing - """ - if self.selected and self.selected_sid is not None: - self.shutdown_session(self.selected_sid) - elif not self.selected and self.selected_sid is None: - self.new_session() - else: - logging.error("querysessiondrawing.py invalid state") - # if self.selected and self.selected_sid is not None: - - def draw_buttons(self): - f = tk.Frame(self.top) - f.grid(row=3, sticky=tk.W) - - b = tk.Button( - f, - image=Images.get(ImageEnum.DOCUMENTNEW.value), - text="New", - compound=tk.LEFT, - command=self.new_session, - ) - b.pack(side=tk.LEFT, padx=3, pady=4) - b = tk.Button( - f, - image=Images.get(ImageEnum.FILEOPEN.value), - text="Connect", - compound=tk.LEFT, - command=self.click_connect, - ) - b.pack(side=tk.LEFT, padx=3, pady=4) - b = tk.Button( - f, - image=Images.get(ImageEnum.EDITDELETE.value), - text="Shutdown", - compound=tk.LEFT, - command=self.click_shutdown, - ) - b.pack(side=tk.LEFT, padx=3, pady=4) - b = tk.Button(f, text="Cancel", command=self.new_session) - b.pack(side=tk.LEFT, padx=3, pady=4) - - def center(self): - window_width = self.master.winfo_width() - window_height = self.master.winfo_height() - self.top.update() - top_level_width = self.top.winfo_width() - top_level_height = self.top.winfo_height() - x = window_width / 2 - top_level_width / 2 - y = window_height / 2 - top_level_height / 2 - - self.top.geometry("+%d+%d" % (x, y)) - - def draw(self): - self.column_definition() - self.heading_definition() - self.session_definition() - self.draw_buttons() - self.center() - self.top.wait_window()