diff --git a/coretk/coretk/app.py b/coretk/coretk/app.py index 7330f21a..276dbd6d 100644 --- a/coretk/coretk/app.py +++ b/coretk/coretk/app.py @@ -1,7 +1,7 @@ import logging import tkinter as tk -from coretk import appdirs +from coretk import appconfig from coretk.coreclient import CoreClient from coretk.graph import CanvasGraph from coretk.images import ImageEnum, Images @@ -26,7 +26,7 @@ class Application(tk.Frame): self.radiovar = tk.IntVar(value=1) self.show_grid_var = tk.IntVar(value=1) self.adjust_to_dim_var = tk.IntVar(value=0) - self.config = appdirs.read_config() + self.config = appconfig.read() self.core = CoreClient(self) self.setup_app() self.draw() @@ -70,10 +70,13 @@ class Application(tk.Frame): menu_action = MenuAction(self, self.master) menu_action.on_quit() + def save_config(self): + appconfig.save(self.config) + if __name__ == "__main__": log_format = "%(asctime)s - %(levelname)s - %(module)s:%(funcName)s - %(message)s" logging.basicConfig(level=logging.DEBUG, format=log_format) - appdirs.check_directory() + appconfig.check_directory() app = Application() app.mainloop() diff --git a/coretk/coretk/appdirs.py b/coretk/coretk/appconfig.py similarity index 70% rename from coretk/coretk/appdirs.py rename to coretk/coretk/appconfig.py index 553b0949..67f97181 100644 --- a/coretk/coretk/appdirs.py +++ b/coretk/coretk/appconfig.py @@ -1,4 +1,5 @@ import logging +import os import shutil from pathlib import Path @@ -18,6 +19,20 @@ CONFIG_PATH = HOME_PATH.joinpath("gui.yaml") LOCAL_ICONS_PATH = Path(__file__).parent.joinpath("icons").absolute() LOCAL_BACKGROUND_PATH = Path(__file__).parent.joinpath("backgrounds").absolute() +# configuration data +TERMINALS = [ + "$TERM", + "gnome-terminal --window --", + "lxterminal -e", + "konsole -e", + "xterm -e", + "aterm -e", + "eterm -e", + "rxvt -e", + "xfce4-terminal -x", +] +EDITORS = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"] + class IndentDumper(yaml.Dumper): def increase_indent(self, flow=False, indentless=False): @@ -42,18 +57,33 @@ def check_directory(): for background in LOCAL_BACKGROUND_PATH.glob("*"): new_background = BACKGROUNDS_PATH.joinpath(background.name) shutil.copy(background, new_background) + + if "TERM" in os.environ: + terminal = TERMINALS[0] + else: + terminal = TERMINALS[1] + if "EDITOR" in os.environ: + editor = EDITORS[0] + else: + editor = EDITORS[1] config = { + "preferences": { + "editor": editor, + "terminal": terminal, + "gui3d": "/usr/local/bin/std3d.sh", + }, "servers": [{"name": "example", "address": "127.0.0.1", "port": 50051}], "nodes": [], + "observers": [{"name": "hello", "cmd": "echo hello"}], } - save_config(config) + save(config) -def read_config(): +def read(): with CONFIG_PATH.open("r") as f: return yaml.load(f, Loader=yaml.SafeLoader) -def save_config(config): +def save(config): with CONFIG_PATH.open("w") as f: yaml.dump(config, f, Dumper=IndentDumper, default_flow_style=False) diff --git a/coretk/coretk/canvastooltip.py b/coretk/coretk/canvastooltip.py index 42270809..8ace41d0 100644 --- a/coretk/coretk/canvastooltip.py +++ b/coretk/coretk/canvastooltip.py @@ -18,25 +18,14 @@ class CanvasTooltip: """ def __init__( - self, - canvas, - tag_or_id, - *, - bg="#FFFFEA", - pad=(5, 3, 5, 3), - text="canvas info", - waittime=400, - wraplength=250 + self, canvas, *, bg="#FFFFEA", pad=(5, 3, 5, 3), waittime=400, wraplength=600 ): # in miliseconds, originally 500 self.waittime = waittime # in pixels, originally 180 self.wraplength = wraplength self.canvas = canvas - self.text = text - self.canvas.tag_bind(tag_or_id, "<Enter>", self.on_enter) - self.canvas.tag_bind(tag_or_id, "<Leave>", self.on_leave) - self.canvas.tag_bind(tag_or_id, "<ButtonPress>", self.on_leave) + self.text = tk.StringVar() self.bg = bg self.pad = pad self.id = None @@ -61,18 +50,13 @@ class CanvasTooltip: def show(self, event=None): def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)): - c = canvas - s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight() - width, height = ( pad[0] + label.winfo_reqwidth() + pad[2], pad[1] + label.winfo_reqheight() + pad[3], ) - mouse_x, mouse_y = c.winfo_pointerxy() - x1, y1 = mouse_x + tip_delta[0], mouse_y + tip_delta[1] x2, y2 = x1 + width, y1 + height @@ -84,20 +68,14 @@ class CanvasTooltip: y_delta = 0 offscreen = (x_delta, y_delta) != (0, 0) - if offscreen: - if x_delta: x1 = mouse_x - tip_delta[0] - width - if y_delta: y1 = mouse_y - tip_delta[1] - height - offscreen_again = y1 < 0 # out on the top - if offscreen_again: y1 = 0 - return x1, y1 bg = self.bg @@ -111,21 +89,18 @@ class CanvasTooltip: self.tw.wm_overrideredirect(True) win = tk.Frame(self.tw, background=bg, borderwidth=0) + win.grid() label = ttk.Label( win, - text=self.text, + textvariable=self.text, justify=tk.LEFT, background=bg, relief=tk.SOLID, borderwidth=0, wraplength=self.wraplength, ) - label.grid(padx=(pad[0], pad[2]), pady=(pad[1], pad[3]), sticky=tk.NSEW) - win.grid() - x, y = tip_pos_calculator(canvas, label) - self.tw.wm_geometry("+%d+%d" % (x, y)) def hide(self): diff --git a/coretk/coretk/coreclient.py b/coretk/coretk/coreclient.py index c2ab3a2f..d924e305 100644 --- a/coretk/coretk/coreclient.py +++ b/coretk/coretk/coreclient.py @@ -16,6 +16,17 @@ from coretk.wlannodeconfig import WlanNodeConfig NETWORK_NODES = {"switch", "hub", "wlan", "rj45", "tunnel", "emane"} DEFAULT_NODES = {"router", "host", "PC", "mdr", "prouter"} +OBSERVERS = { + "processes": "ps", + "ifconfig": "ifconfig", + "IPV4 Routes": "ip -4 ro", + "IPV6 Routes": "ip -6 ro", + "Listening sockets": "netstat -tuwnl", + "IPv4 MFC entries": "ip -4 mroute show", + "IPv6 MFC entries": "ip -6 mroute show", + "firewall rules": "iptables -L", + "IPSec policies": "setkey -DP", +} class Node: @@ -74,6 +85,12 @@ class CustomNode: self.services = services +class Observer: + def __init__(self, name, cmd): + self.name = name + self.cmd = cmd + + class CoreClient: def __init__(self, app): """ @@ -86,13 +103,16 @@ class CoreClient: self.master = app.master self.interface_helper = None self.services = {} + self.observer = None # loaded configuration data self.servers = {} self.custom_nodes = {} + self.custom_observers = {} self.read_config() # data for managing the current session + self.state = None self.nodes = {} self.edges = {} self.hooks = {} @@ -107,27 +127,36 @@ class CoreClient: self.emane_config = None self.serviceconfig_manager = ServiceNodeConfig(app) + def set_observer(self, value): + self.observer = value + def read_config(self): # read distributed server - for server_config in self.app.config["servers"]: - server = CoreServer( - server_config["name"], server_config["address"], server_config["port"] - ) + for config in self.app.config.get("servers", []): + server = CoreServer(config["name"], config["address"], config["port"]) self.servers[server.name] = server # read custom nodes - for node in self.app.config["nodes"]: - image_file = node["image"] + for config in self.app.config.get("nodes", []): + image_file = config["image"] image = Images.get_custom(image_file) custom_node = CustomNode( - node["name"], image, image_file, set(node["services"]) + config["name"], image, image_file, set(config["services"]) ) self.custom_nodes[custom_node.name] = custom_node + # read observers + for config in self.app.config.get("observers", []): + observer = Observer(config["name"], config["cmd"]) + self.custom_observers[observer.name] = observer + def handle_events(self, event): logging.info("event: %s", event) - if event.link_event is not None: + if event.HasField("link_event"): self.app.canvas.wireless_draw.hangle_link_event(event.link_event) + elif event.HasField("session_event"): + if event.session_event.event <= core_pb2.SessionState.SHUTDOWN: + self.state = event.session_event.event def handle_throughputs(self, event): interface_throughputs = event.interface_throughputs @@ -161,7 +190,7 @@ class CoreClient: response = self.client.get_session(self.session_id) logging.info("joining session(%s): %s", self.session_id, response) session = response.session - session_state = session.state + self.state = session.state self.client.events(self.session_id, self.handle_events) # get hooks @@ -209,11 +238,14 @@ class CoreClient: self.app.canvas.canvas_reset_and_redraw(session) # draw tool bar appropritate with session state - if session_state == core_pb2.SessionState.RUNTIME: + if self.is_runtime(): self.app.toolbar.runtime_frame.tkraise() else: self.app.toolbar.design_frame.tkraise() + def is_runtime(self): + return self.state == core_pb2.SessionState.RUNTIME + def create_new_session(self): """ Create a new session @@ -769,3 +801,7 @@ class CoreClient: ) configs.append(config_proto) return configs + + def run(self, node_id): + logging.info("running node(%s) cmd: %s", node_id, self.observer) + return self.client.node_command(self.session_id, node_id, self.observer).output diff --git a/coretk/coretk/dialogs/canvasbackground.py b/coretk/coretk/dialogs/canvasbackground.py index fe9b5f6a..ed7e44f6 100644 --- a/coretk/coretk/dialogs/canvasbackground.py +++ b/coretk/coretk/dialogs/canvasbackground.py @@ -4,11 +4,11 @@ set wallpaper import enum import logging import tkinter as tk -from tkinter import filedialog +from tkinter import filedialog, ttk from PIL import Image, ImageTk -from coretk.appdirs import BACKGROUNDS_PATH +from coretk.appconfig import BACKGROUNDS_PATH from coretk.dialogs.dialog import Dialog @@ -39,7 +39,6 @@ class CanvasBackgroundDialog(Dialog): def draw(self): self.columnconfigure(0, weight=1) - self.rowconfigure(0, weight=1) self.draw_image() self.draw_image_label() self.draw_image_selection() @@ -48,65 +47,67 @@ class CanvasBackgroundDialog(Dialog): self.draw_buttons() def draw_image(self): - self.image_label = tk.Label( - self, text="(image preview)", height=8, width=32, bg="white" + self.image_label = ttk.Label( + self, text="(image preview)", width=32, anchor=tk.CENTER ) - self.image_label.grid(row=0, column=0, pady=5, sticky="nsew") + self.image_label.grid(row=0, column=0, pady=5) def draw_image_label(self): - label = tk.Label(self, text="Image filename: ") + label = ttk.Label(self, text="Image filename: ") label.grid(row=1, column=0, sticky="ew") def draw_image_selection(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.columnconfigure(0, weight=2) frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=1) frame.grid(row=2, column=0, sticky="ew") - entry = tk.Entry(frame, textvariable=self.file_name) + entry = ttk.Entry(frame, textvariable=self.file_name) entry.focus() entry.grid(row=0, column=0, sticky="ew") - button = tk.Button(frame, text="...", command=self.click_open_image) + button = ttk.Button(frame, text="...", command=self.click_open_image) button.grid(row=0, column=1, sticky="ew") - button = tk.Button(frame, text="Clear", command=self.click_clear) + button = ttk.Button(frame, text="Clear", command=self.click_clear) button.grid(row=0, column=2, sticky="ew") def draw_options(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=1) frame.columnconfigure(3, weight=1) frame.grid(row=3, column=0, sticky="ew") - button = tk.Radiobutton( + button = ttk.Radiobutton( frame, text="upper-left", value=1, variable=self.radiovar ) button.grid(row=0, column=0, sticky="ew") self.options.append(button) - button = tk.Radiobutton(frame, text="centered", value=2, variable=self.radiovar) + button = ttk.Radiobutton( + frame, text="centered", value=2, variable=self.radiovar + ) button.grid(row=0, column=1, sticky="ew") self.options.append(button) - button = tk.Radiobutton(frame, text="scaled", value=3, variable=self.radiovar) + button = ttk.Radiobutton(frame, text="scaled", value=3, variable=self.radiovar) button.grid(row=0, column=2, sticky="ew") self.options.append(button) - button = tk.Radiobutton(frame, text="titled", value=4, variable=self.radiovar) + button = ttk.Radiobutton(frame, text="titled", value=4, variable=self.radiovar) button.grid(row=0, column=3, sticky="ew") self.options.append(button) def draw_additional_options(self): - checkbutton = tk.Checkbutton( + checkbutton = ttk.Checkbutton( self, text="Show grid", variable=self.show_grid_var ) checkbutton.grid(row=4, column=0, sticky="ew", padx=5) - checkbutton = tk.Checkbutton( + checkbutton = ttk.Checkbutton( self, text="Adjust canvas size to image dimensions", variable=self.adjust_to_dim_var, @@ -118,15 +119,15 @@ class CanvasBackgroundDialog(Dialog): self.adjust_to_dim_var.set(0) def draw_buttons(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(row=6, column=0, pady=5, sticky="ew") frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) - button = tk.Button(frame, text="Apply", command=self.click_apply) + button = ttk.Button(frame, text="Apply", command=self.click_apply) button.grid(row=0, column=0, sticky="ew") - button = tk.Button(frame, text="Cancel", command=self.destroy) + button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") def click_open_image(self): @@ -144,7 +145,7 @@ class CanvasBackgroundDialog(Dialog): img = Image.open(filename) img = img.resize((width, height), Image.ANTIALIAS) tk_img = ImageTk.PhotoImage(img) - self.image_label.config(image=tk_img, width=width, height=height) + self.image_label.config(image=tk_img, width=width) self.image_label.image = tk_img def click_clear(self): @@ -156,7 +157,7 @@ class CanvasBackgroundDialog(Dialog): # delete entry self.file_name.set("") # delete display image - self.image_label.config(image="", width=32, height=8) + self.image_label.config(image="", width=32) def click_adjust_canvas(self): # deselect all radio buttons and grey them out diff --git a/coretk/coretk/dialogs/canvassizeandscale.py b/coretk/coretk/dialogs/canvassizeandscale.py index 20b464d0..1d880639 100644 --- a/coretk/coretk/dialogs/canvassizeandscale.py +++ b/coretk/coretk/dialogs/canvassizeandscale.py @@ -2,7 +2,7 @@ size and scale """ import tkinter as tk -from tkinter import font +from tkinter import font, ttk from coretk.dialogs.canvasbackground import ScaleOption from coretk.dialogs.dialog import Dialog @@ -49,120 +49,120 @@ class SizeAndScaleDialog(Dialog): self.draw_buttons() def draw_size(self): - label = tk.Label(self, text="Size", font=self.section_font) + label = ttk.Label(self, text="Size", font=self.section_font) label.grid(sticky="w") # draw size row 1 - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(sticky="ew", pady=3) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) - label = tk.Label(frame, text="Width") + label = ttk.Label(frame, text="Width") label.grid(row=0, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.pixel_width) + entry = ttk.Entry(frame, textvariable=self.pixel_width) entry.grid(row=0, column=1, sticky="ew") - label = tk.Label(frame, text="x Height") + label = ttk.Label(frame, text="x Height") label.grid(row=0, column=2, sticky="w") - entry = tk.Entry(frame, textvariable=self.pixel_height) + entry = ttk.Entry(frame, textvariable=self.pixel_height) entry.grid(row=0, column=3, sticky="ew") - label = tk.Label(frame, text="Pixels") + label = ttk.Label(frame, text="Pixels") label.grid(row=0, column=4, sticky="w") # draw size row 2 - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(sticky="ew", pady=3) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) - label = tk.Label(frame, text="Width") + label = ttk.Label(frame, text="Width") label.grid(row=0, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.meters_width) + entry = ttk.Entry(frame, textvariable=self.meters_width) entry.grid(row=0, column=1, sticky="ew") - label = tk.Label(frame, text="x Height") + label = ttk.Label(frame, text="x Height") label.grid(row=0, column=2, sticky="w") - entry = tk.Entry(frame, textvariable=self.meters_height) + entry = ttk.Entry(frame, textvariable=self.meters_height) entry.grid(row=0, column=3, sticky="ew") - label = tk.Label(frame, text="Meters") + label = ttk.Label(frame, text="Meters") label.grid(row=0, column=4, sticky="w") def draw_scale(self): - label = tk.Label(self, text="Scale", font=self.section_font) + label = ttk.Label(self, text="Scale", font=self.section_font) label.grid(sticky="w") - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(sticky="ew") frame.columnconfigure(1, weight=1) - label = tk.Label(frame, text="100 Pixels =") + label = ttk.Label(frame, text="100 Pixels =") label.grid(row=0, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.scale) + entry = ttk.Entry(frame, textvariable=self.scale) entry.grid(row=0, column=1, sticky="ew") - label = tk.Label(frame, text="Meters") + label = ttk.Label(frame, text="Meters") label.grid(row=0, column=2, sticky="w") def draw_reference_point(self): - label = tk.Label(self, text="Reference point", font=self.section_font) + label = ttk.Label(self, text="Reference point", font=self.section_font) label.grid(sticky="w") - label = tk.Label( + label = ttk.Label( self, text="Default is (0, 0), the upper left corner of the canvas" ) label.grid(sticky="w") - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(sticky="ew", pady=3) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) - label = tk.Label(frame, text="X") + label = ttk.Label(frame, text="X") label.grid(row=0, column=0, sticky="w") x_var = tk.StringVar(value=0) - entry = tk.Entry(frame, textvariable=x_var) + entry = ttk.Entry(frame, textvariable=x_var) entry.grid(row=0, column=1, sticky="ew") - label = tk.Label(frame, text="Y") + label = ttk.Label(frame, text="Y") label.grid(row=0, column=2, sticky="w") y_var = tk.StringVar(value=0) - entry = tk.Entry(frame, textvariable=y_var) + entry = ttk.Entry(frame, textvariable=y_var) entry.grid(row=0, column=3, sticky="ew") - label = tk.Label(self, text="Translates To") + label = ttk.Label(self, text="Translates To") label.grid(sticky="w") - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(sticky="ew", pady=3) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) frame.columnconfigure(5, weight=1) - label = tk.Label(frame, text="Lat") + label = ttk.Label(frame, text="Lat") label.grid(row=0, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.lat) + entry = ttk.Entry(frame, textvariable=self.lat) entry.grid(row=0, column=1, sticky="ew") - label = tk.Label(frame, text="Lon") + label = ttk.Label(frame, text="Lon") label.grid(row=0, column=2, sticky="w") - entry = tk.Entry(frame, textvariable=self.lon) + entry = ttk.Entry(frame, textvariable=self.lon) entry.grid(row=0, column=3, sticky="ew") - label = tk.Label(frame, text="Alt") + label = ttk.Label(frame, text="Alt") label.grid(row=0, column=4, sticky="w") - entry = tk.Entry(frame, textvariable=self.alt) + entry = ttk.Entry(frame, textvariable=self.alt) entry.grid(row=0, column=5, sticky="ew") def draw_save_as_default(self): - button = tk.Checkbutton( + button = ttk.Checkbutton( self, text="Save as default?", variable=self.save_default ) button.grid(sticky="w", pady=3) def draw_buttons(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) frame.grid(sticky="ew") - button = tk.Button(frame, text="Apply", command=self.click_apply) + button = ttk.Button(frame, text="Apply", command=self.click_apply) button.grid(row=0, column=0, pady=5, sticky="ew") - button = tk.Button(frame, text="Cancel", command=self.destroy) + button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, pady=5, sticky="ew") def redraw_grid(self): diff --git a/coretk/coretk/dialogs/customnodes.py b/coretk/coretk/dialogs/customnodes.py index 20c5ae95..f5c1ab78 100644 --- a/coretk/coretk/dialogs/customnodes.py +++ b/coretk/coretk/dialogs/customnodes.py @@ -1,8 +1,8 @@ import logging import tkinter as tk from pathlib import Path +from tkinter import ttk -from coretk import appdirs from coretk.coreclient import CustomNode from coretk.dialogs.dialog import Dialog from coretk.dialogs.icondialog import IconDialog @@ -22,7 +22,7 @@ class ServicesSelectDialog(Dialog): self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(stick="nsew") frame.rowconfigure(0, weight=1) for i in range(3): @@ -44,13 +44,13 @@ class ServicesSelectDialog(Dialog): for service in sorted(self.current_services): self.current.listbox.insert(tk.END, service) - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(stick="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = tk.Button(frame, text="Save", command=self.destroy) + button = ttk.Button(frame, text="Save", command=self.destroy) button.grid(row=0, column=0, sticky="ew") - button = tk.Button(frame, text="Cancel", command=self.click_cancel) + button = ttk.Button(frame, text="Cancel", command=self.click_cancel) button.grid(row=0, column=1, sticky="ew") # trigger group change @@ -103,7 +103,7 @@ class CustomNodesDialog(Dialog): self.draw_buttons() def draw_node_config(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(sticky="nsew") frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) @@ -114,45 +114,45 @@ class CustomNodesDialog(Dialog): for name in sorted(self.app.core.custom_nodes): self.nodes_list.listbox.insert(tk.END, name) - frame = tk.Frame(frame) + frame = ttk.Frame(frame) frame.grid(row=0, column=2, sticky="nsew") frame.columnconfigure(0, weight=1) - entry = tk.Entry(frame, textvariable=self.name) + entry = ttk.Entry(frame, textvariable=self.name) entry.grid(sticky="ew") - self.image_button = tk.Button(frame, text="Icon", command=self.click_icon) + self.image_button = ttk.Button(frame, text="Icon", command=self.click_icon) self.image_button.grid(sticky="ew") - button = tk.Button(frame, text="Services", command=self.click_services) + button = ttk.Button(frame, text="Services", command=self.click_services) button.grid(sticky="ew") def draw_node_buttons(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(pady=2, sticky="ew") for i in range(3): frame.columnconfigure(i, weight=1) - button = tk.Button(frame, text="Create", command=self.click_create) + button = ttk.Button(frame, text="Create", command=self.click_create) button.grid(row=0, column=0, sticky="ew") - self.edit_button = tk.Button( + self.edit_button = ttk.Button( frame, text="Edit", state=tk.DISABLED, command=self.click_edit ) self.edit_button.grid(row=0, column=1, sticky="ew") - self.delete_button = tk.Button( + self.delete_button = ttk.Button( frame, text="Delete", state=tk.DISABLED, command=self.click_delete ) self.delete_button.grid(row=0, column=2, sticky="ew") def draw_buttons(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = tk.Button(frame, text="Save", command=self.click_save) + button = ttk.Button(frame, text="Save", command=self.click_save) button.grid(row=0, column=0, sticky="ew") - button = tk.Button(frame, text="Cancel", command=self.destroy) + button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") def reset_values(self): @@ -189,7 +189,8 @@ class CustomNodesDialog(Dialog): } ) logging.info("saving custom nodes: %s", self.app.config["nodes"]) - appdirs.save_config(self.app.config) + self.app.save_config() + self.destroy() def click_create(self): name = self.name.get() diff --git a/coretk/coretk/dialogs/emaneconfig.py b/coretk/coretk/dialogs/emaneconfig.py index 6950dc10..7fd577e2 100644 --- a/coretk/coretk/dialogs/emaneconfig.py +++ b/coretk/coretk/dialogs/emaneconfig.py @@ -58,18 +58,18 @@ class EmaneConfiguration(Dialog): print("not implemented") def node_name_and_image(self): - f = tk.Frame(self, bg="#d9d9d9") + f = ttk.Frame(self) - lbl = tk.Label(f, text="Node name:", bg="#d9d9d9") + lbl = ttk.Label(f, text="Node name:") lbl.grid(row=0, column=0, padx=2, pady=2) - e = tk.Entry(f, textvariable=self.create_text_variable(""), bg="white") + e = ttk.Entry(f, textvariable=self.create_text_variable("")) e.grid(row=0, column=1, padx=2, pady=2) cbb = ttk.Combobox(f, values=["(none)", "core1", "core2"], state="readonly") cbb.current(0) cbb.grid(row=0, column=2, padx=2, pady=2) - b = tk.Button(f, image=self.canvas_node.image) + b = ttk.Button(f, image=self.canvas_node.image) b.grid(row=0, column=3, padx=2, pady=2) f.grid(row=0, column=0, sticky="nsew") @@ -96,13 +96,13 @@ class EmaneConfiguration(Dialog): self.emane_config_frame.draw_config() self.emane_config_frame.grid(sticky="nsew") - frame = tk.Frame(self.emane_dialog) + frame = ttk.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 = ttk.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 = ttk.Button(frame, text="Cancel", command=self.emane_dialog.destroy) b2.grid(row=0, column=1, sticky="ew") self.emane_dialog.show() @@ -170,35 +170,33 @@ class EmaneConfiguration(Dialog): self.model_config_frame.grid(sticky="nsew") self.model_config_frame.draw_config() - frame = tk.Frame(self.emane_model_dialog) + frame = ttk.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 = ttk.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 = ttk.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): - f = tk.Frame(parent, bg="#d9d9d9") + f = ttk.Frame(parent) f.columnconfigure(0, weight=1) f.columnconfigure(1, weight=1) - b = tk.Button( + b = ttk.Button( f, text=self.emane_models[0] + " options", image=Images.get(ImageEnum.EDITNODE), compound=tk.RIGHT, - bg="#d9d9d9", command=self.draw_model_options, ) b.grid(row=0, column=0, padx=10, pady=2, sticky="nsew") - b = tk.Button( + b = ttk.Button( f, text="EMANE options", image=Images.get(ImageEnum.EDITNODE), compound=tk.RIGHT, - bg="#d9d9d9", command=self.draw_emane_options, ) b.grid(row=0, column=1, padx=10, pady=2, sticky="nsew") @@ -233,21 +231,14 @@ class EmaneConfiguration(Dialog): self.emane_models = [x.split("_")[1] for x in response.models] # create combo box and its binding - f = tk.Frame( - parent, - bg="#d9d9d9", - highlightbackground="#b3b3b3", - highlightcolor="#b3b3b3", - highlightthickness=0.5, - bd=0, - ) + f = ttk.Frame(parent) self.emane_model_combobox = ttk.Combobox( f, values=self.emane_models, state="readonly" ) self.emane_model_combobox.grid() self.emane_model_combobox.current(0) self.emane_model_combobox.bind("<<ComboboxSelected>>", self.combobox_select) - f.grid(row=3, column=0, sticky=tk.W + tk.E) + f.grid(row=3, column=0, sticky="ew") def draw_text_label_and_entry(self, parent, label_text, entry_text): """ @@ -257,10 +248,10 @@ class EmaneConfiguration(Dialog): """ var = tk.StringVar() var.set(entry_text) - f = tk.Frame(parent) - lbl = tk.Label(f, text=label_text) + f = ttk.Frame(parent) + lbl = ttk.Label(f, text=label_text) lbl.grid(row=0, column=0) - e = tk.Entry(f, textvariable=var, bg="white") + e = ttk.Entry(f, textvariable=var) e.grid(row=0, column=1) f.grid(stick=tk.W, padx=2, pady=2) @@ -271,44 +262,33 @@ class EmaneConfiguration(Dialog): :return: nothing """ # draw label - lbl = tk.Label(self, text="Emane") + lbl = ttk.Label(self, text="Emane") lbl.grid(row=1, column=0) # main frame that has emane wiki, a short description, emane models and the configure buttons - f = tk.Frame( - self, - bg="#d9d9d9", - highlightbackground="#b3b3b3", - highlightcolor="#b3b3b3", - highlightthickness=0.5, - bd=0, - relief=tk.RAISED, - ) + f = ttk.Frame(self) f.columnconfigure(0, weight=1) - b = tk.Button( + b = ttk.Button( f, image=Images.get(ImageEnum.EDITNODE), text="EMANE Wiki", compound=tk.RIGHT, - relief=tk.RAISED, - bg="#d9d9d9", command=lambda: webbrowser.open_new( "https://github.com/adjacentlink/emane/wiki" ), ) - b.grid(row=0, column=0, sticky=tk.W) + b.grid(row=0, column=0, sticky="w") - lbl = tk.Label( + lbl = ttk.Label( f, text="The EMANE emulation system provides more complex wireless radio emulation " "\nusing pluggable MAC and PHY modules. Refer to the wiki for configuration option details", - bg="#d9d9d9", ) lbl.grid(row=1, column=0, sticky="nsew") - lbl = tk.Label(f, text="EMANE Models", bg="#d9d9d9") - lbl.grid(row=2, column=0, sticky=tk.W) + lbl = ttk.Label(f, text="EMANE Models") + lbl.grid(row=2, column=0, sticky="w") self.draw_emane_models(f) self.draw_option_buttons(f) @@ -325,12 +305,12 @@ class EmaneConfiguration(Dialog): :return: """ - f = tk.Frame(self, bg="#d9d9d9") + f = ttk.Frame(self) f.columnconfigure(0, weight=1) f.columnconfigure(1, weight=1) - b = tk.Button(f, text="Link to all routers", bg="#d9d9d9") + b = ttk.Button(f, text="Link to all routers") b.grid(row=0, column=0, padx=10, pady=2, sticky="nsew") - b = tk.Button(f, text="Choose WLAN members", bg="#d9d9d9") + b = ttk.Button(f, text="Choose WLAN members") b.grid(row=0, column=1, padx=10, pady=2, sticky="nsew") f.grid(row=5, column=0, sticky="nsew") @@ -340,12 +320,12 @@ class EmaneConfiguration(Dialog): self.destroy() def draw_apply_and_cancel(self): - f = tk.Frame(self, bg="#d9d9d9") + f = ttk.Frame(self) f.columnconfigure(0, weight=1) f.columnconfigure(1, weight=1) - b = tk.Button(f, text="Apply", bg="#d9d9d9", command=self.apply) + b = ttk.Button(f, text="Apply", command=self.apply) b.grid(row=0, column=0, padx=10, pady=2, sticky="nsew") - b = tk.Button(f, text="Cancel", bg="#d9d9d9", command=self.destroy) + b = ttk.Button(f, text="Cancel", command=self.destroy) b.grid(row=0, column=1, padx=10, pady=2, sticky="nsew") f.grid(sticky="nsew") diff --git a/coretk/coretk/dialogs/hooks.py b/coretk/coretk/dialogs/hooks.py index 99647635..5ae9da8a 100644 --- a/coretk/coretk/dialogs/hooks.py +++ b/coretk/coretk/dialogs/hooks.py @@ -19,14 +19,14 @@ class HookDialog(Dialog): self.rowconfigure(1, weight=1) # name and states - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(row=0, sticky="ew", pady=2) frame.columnconfigure(0, weight=2) frame.columnconfigure(1, weight=7) frame.columnconfigure(2, weight=1) - label = tk.Label(frame, text="Name") + label = ttk.Label(frame, text="Name") label.grid(row=0, column=0, sticky="ew") - entry = tk.Entry(frame, textvariable=self.name) + entry = ttk.Entry(frame, textvariable=self.name) entry.grid(row=0, column=1, sticky="ew") values = tuple(x for x in core_pb2.SessionState.Enum.keys() if x != "NONE") initial_state = core_pb2.SessionState.Enum.Name(core_pb2.SessionState.RUNTIME) @@ -39,7 +39,7 @@ class HookDialog(Dialog): combobox.bind("<<ComboboxSelected>>", self.state_change) # data - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) frame.grid(row=1, sticky="nsew", pady=2) @@ -53,19 +53,19 @@ class HookDialog(Dialog): ), ) self.data.grid(row=0, column=0, sticky="nsew") - scrollbar = tk.Scrollbar(frame) + scrollbar = ttk.Scrollbar(frame) scrollbar.grid(row=0, column=1, sticky="ns") self.data.config(yscrollcommand=scrollbar.set) scrollbar.config(command=self.data.yview) # button row - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(row=2, sticky="ew", pady=2) for i in range(2): frame.columnconfigure(i, weight=1) - button = tk.Button(frame, text="Save", command=lambda: self.save()) + button = ttk.Button(frame, text="Save", command=lambda: self.save()) button.grid(row=0, column=0, sticky="ew") - button = tk.Button(frame, text="Cancel", command=lambda: self.destroy()) + button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy()) button.grid(row=0, column=1, sticky="ew") def state_change(self, event): @@ -106,21 +106,21 @@ class HooksDialog(Dialog): self.listbox.bind("<<ListboxSelect>>", self.select) for hook_file in self.app.core.hooks: self.listbox.insert(tk.END, hook_file) - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(row=1, sticky="ew") for i in range(4): frame.columnconfigure(i, weight=1) - button = tk.Button(frame, text="Create", command=self.click_create) + button = ttk.Button(frame, text="Create", command=self.click_create) button.grid(row=0, column=0, sticky="ew") - self.edit_button = tk.Button( + self.edit_button = ttk.Button( frame, text="Edit", state=tk.DISABLED, command=self.click_edit ) self.edit_button.grid(row=0, column=1, sticky="ew") - self.delete_button = tk.Button( + self.delete_button = ttk.Button( frame, text="Delete", state=tk.DISABLED, command=self.click_delete ) self.delete_button.grid(row=0, column=2, sticky="ew") - button = tk.Button(frame, text="Cancel", command=lambda: self.destroy()) + button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy()) button.grid(row=0, column=3, sticky="ew") def click_create(self): diff --git a/coretk/coretk/dialogs/icondialog.py b/coretk/coretk/dialogs/icondialog.py index 4d26e29f..fb6fb6bb 100644 --- a/coretk/coretk/dialogs/icondialog.py +++ b/coretk/coretk/dialogs/icondialog.py @@ -1,7 +1,7 @@ import tkinter as tk -from tkinter import filedialog +from tkinter import filedialog, ttk -from coretk.appdirs import ICONS_PATH +from coretk.appconfig import ICONS_PATH from coretk.dialogs.dialog import Dialog from coretk.images import Images @@ -18,30 +18,30 @@ class IconDialog(Dialog): self.columnconfigure(0, weight=1) # row one - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(row=0, column=0, pady=2, sticky="ew") frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=3) - label = tk.Label(frame, text="Image") + label = ttk.Label(frame, text="Image") label.grid(row=0, column=0, sticky="ew") - entry = tk.Entry(frame, textvariable=self.file_path) + entry = ttk.Entry(frame, textvariable=self.file_path) entry.grid(row=0, column=1, sticky="ew") - button = tk.Button(frame, text="...", command=self.click_file) + button = ttk.Button(frame, text="...", command=self.click_file) button.grid(row=0, column=2) # row two - self.image_label = tk.Label(self, image=self.image) + self.image_label = ttk.Label(self, image=self.image, anchor=tk.CENTER) self.image_label.grid(row=1, column=0, pady=2, sticky="ew") # row three - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(row=2, column=0, sticky="ew") frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) - button = tk.Button(frame, text="Apply", command=self.destroy) + button = ttk.Button(frame, text="Apply", command=self.destroy) button.grid(row=0, column=0, sticky="ew") - button = tk.Button(frame, text="Cancel", command=self.click_cancel) + button = ttk.Button(frame, text="Cancel", command=self.click_cancel) button.grid(row=0, column=1, sticky="ew") def click_file(self): diff --git a/coretk/coretk/dialogs/observers.py b/coretk/coretk/dialogs/observers.py new file mode 100644 index 00000000..6c57f08e --- /dev/null +++ b/coretk/coretk/dialogs/observers.py @@ -0,0 +1,149 @@ +import tkinter as tk +from tkinter import ttk + +from coretk.coreclient import Observer +from coretk.dialogs.dialog import Dialog + + +class ObserverDialog(Dialog): + def __init__(self, master, app): + super().__init__(master, app, "Observer Widgets", modal=True) + self.observers = None + self.save_button = None + self.delete_button = None + self.selected = None + self.selected_index = None + self.name = tk.StringVar() + self.cmd = tk.StringVar() + self.draw() + + def draw(self): + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + self.draw_listbox() + self.draw_form_fields() + self.draw_config_buttons() + self.draw_apply_buttons() + + def draw_listbox(self): + frame = ttk.Frame(self) + frame.grid(sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.rowconfigure(0, weight=1) + + scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL) + scrollbar.grid(row=0, column=1, sticky="ns") + + self.observers = tk.Listbox( + frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set + ) + self.observers.grid(row=0, column=0, sticky="nsew") + self.observers.bind("<<ListboxSelect>>", self.handle_observer_change) + for name in sorted(self.app.core.custom_observers): + self.observers.insert(tk.END, name) + + scrollbar.config(command=self.observers.yview) + + def draw_form_fields(self): + frame = ttk.Frame(self) + frame.grid(sticky="ew") + frame.columnconfigure(1, weight=1) + + label = ttk.Label(frame, text="Name") + label.grid(row=0, column=0, sticky="w") + entry = ttk.Entry(frame, textvariable=self.name) + entry.grid(row=0, column=1, sticky="ew") + + label = ttk.Label(frame, text="Command") + label.grid(row=1, column=0, sticky="w") + entry = ttk.Entry(frame, textvariable=self.cmd) + entry.grid(row=1, column=1, sticky="ew") + + def draw_config_buttons(self): + frame = ttk.Frame(self) + frame.grid(pady=2, sticky="ew") + for i in range(3): + frame.columnconfigure(i, weight=1) + + button = ttk.Button(frame, text="Create", command=self.click_create) + button.grid(row=0, column=0, sticky="ew") + + self.save_button = ttk.Button( + frame, text="Save", state=tk.DISABLED, command=self.click_save + ) + self.save_button.grid(row=0, column=1, sticky="ew") + + self.delete_button = ttk.Button( + frame, text="Delete", state=tk.DISABLED, command=self.click_delete + ) + self.delete_button.grid(row=0, column=2, sticky="ew") + + def draw_apply_buttons(self): + frame = ttk.Frame(self) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + + button = ttk.Button(frame, text="Save", command=self.click_save_config) + button.grid(row=0, column=0, sticky="ew") + + button = ttk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, sticky="ew") + + def click_save_config(self): + observers = [] + for name in sorted(self.app.core.custom_observers): + observer = self.app.core.custom_observers[name] + observers.append({"name": observer.name, "cmd": observer.cmd}) + self.app.config["observers"] = observers + self.app.save_config() + self.destroy() + + def click_create(self): + name = self.name.get() + if name not in self.app.core.custom_observers: + cmd = self.cmd.get() + observer = Observer(name, cmd) + self.app.core.custom_observers[name] = observer + self.observers.insert(tk.END, name) + + def click_save(self): + name = self.name.get() + if self.selected: + previous_name = self.selected + self.selected = name + observer = self.app.core.custom_observers.pop(previous_name) + observer.name = name + observer.cmd = self.cmd.get() + self.app.core.custom_observers[name] = observer + self.observers.delete(self.selected_index) + self.observers.insert(self.selected_index, name) + self.observers.selection_set(self.selected_index) + + def click_delete(self): + if self.selected: + self.observers.delete(self.selected_index) + del self.app.core.custom_observers[self.selected] + self.selected = None + self.selected_index = None + self.name.set("") + self.cmd.set("") + self.observers.selection_clear(0, tk.END) + self.save_button.config(state=tk.DISABLED) + self.delete_button.config(state=tk.DISABLED) + + def handle_observer_change(self, event): + selection = self.observers.curselection() + if selection: + self.selected_index = selection[0] + self.selected = self.observers.get(self.selected_index) + observer = self.app.core.custom_observers[self.selected] + self.name.set(observer.name) + self.cmd.set(observer.cmd) + self.save_button.config(state=tk.NORMAL) + self.delete_button.config(state=tk.NORMAL) + else: + self.selected_index = None + self.selected = None + self.save_button.config(state=tk.DISABLED) + self.delete_button.config(state=tk.DISABLED) diff --git a/coretk/coretk/dialogs/observerwidgets.py b/coretk/coretk/dialogs/observerwidgets.py deleted file mode 100644 index c1c4d170..00000000 --- a/coretk/coretk/dialogs/observerwidgets.py +++ /dev/null @@ -1,148 +0,0 @@ -import tkinter as tk - -from coretk.dialogs.dialog import Dialog - - -class Widget: - def __init__(self, name, command): - self.name = name - self.command = command - - -class ObserverWidgetsDialog(Dialog): - def __init__(self, master, app): - super().__init__(master, app, "Observer Widgets", modal=True) - self.config_widgets = {} - self.widgets = None - self.save_button = None - self.delete_button = None - self.selected = None - self.selected_index = None - self.name = tk.StringVar() - self.command = tk.StringVar() - self.draw() - - def draw(self): - self.columnconfigure(0, weight=1) - self.rowconfigure(0, weight=1) - self.draw_widgets() - self.draw_widget_fields() - self.draw_widget_buttons() - self.draw_apply_buttons() - - def draw_widgets(self): - frame = tk.Frame(self) - frame.grid(sticky="nsew") - frame.columnconfigure(0, weight=1) - frame.rowconfigure(0, weight=1) - - scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL) - scrollbar.grid(row=0, column=1, sticky="ns") - - self.widgets = tk.Listbox( - frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set - ) - self.widgets.grid(row=0, column=0, sticky="nsew") - self.widgets.bind("<<ListboxSelect>>", self.handle_widget_change) - - scrollbar.config(command=self.widgets.yview) - - def draw_widget_fields(self): - frame = tk.Frame(self) - frame.grid(sticky="ew") - frame.columnconfigure(1, weight=1) - - label = tk.Label(frame, text="Name") - label.grid(row=0, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.name) - entry.grid(row=0, column=1, sticky="ew") - - label = tk.Label(frame, text="Command") - label.grid(row=1, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.command) - entry.grid(row=1, column=1, sticky="ew") - - def draw_widget_buttons(self): - frame = tk.Frame(self) - frame.grid(pady=2, sticky="ew") - for i in range(3): - frame.columnconfigure(i, weight=1) - - button = tk.Button(frame, text="Create", command=self.click_create) - button.grid(row=0, column=0, sticky="ew") - - self.save_button = tk.Button( - frame, text="Save", state=tk.DISABLED, command=self.click_save - ) - self.save_button.grid(row=0, column=1, sticky="ew") - - self.delete_button = tk.Button( - frame, text="Delete", state=tk.DISABLED, command=self.click_delete - ) - self.delete_button.grid(row=0, column=2, sticky="ew") - - def draw_apply_buttons(self): - frame = tk.Frame(self) - frame.grid(sticky="ew") - for i in range(2): - frame.columnconfigure(i, weight=1) - - button = tk.Button( - frame, text="Save Configuration", command=self.click_save_configuration - ) - button.grid(row=0, column=0, sticky="ew") - - button = tk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky="ew") - - def click_save_configuration(self): - pass - - def click_create(self): - name = self.name.get() - if name not in self.config_widgets: - command = self.command.get() - widget = Widget(name, command) - self.config_widgets[name] = widget - self.widgets.insert(tk.END, name) - - def click_save(self): - name = self.name.get() - if self.selected: - previous_name = self.selected - self.selected = name - widget = self.config_widgets.pop(previous_name) - widget.name = name - widget.command = self.command.get() - self.config_widgets[name] = widget - self.widgets.delete(self.selected_index) - self.widgets.insert(self.selected_index, name) - self.widgets.selection_set(self.selected_index) - - def click_delete(self): - if self.selected: - self.widgets.delete(self.selected_index) - del self.config_widgets[self.selected] - self.selected = None - self.selected_index = None - self.name.set("") - self.command.set("") - self.widgets.selection_clear(0, tk.END) - self.save_button.config(state=tk.DISABLED) - self.delete_button.config(state=tk.DISABLED) - - def handle_widget_change(self, event): - selection = self.widgets.curselection() - if selection: - self.selected_index = selection[0] - self.selected = self.widgets.get(self.selected_index) - widget = self.config_widgets[self.selected] - self.name.set(widget.name) - self.command.set(widget.command) - self.save_button.config(state=tk.NORMAL) - self.delete_button.config(state=tk.NORMAL) - else: - self.selected_index = None - self.selected = None - self.save_button.config(state=tk.DISABLED) - self.delete_button.config(state=tk.DISABLED) diff --git a/coretk/coretk/dialogs/preferences.py b/coretk/coretk/dialogs/preferences.py new file mode 100644 index 00000000..74293a30 --- /dev/null +++ b/coretk/coretk/dialogs/preferences.py @@ -0,0 +1,67 @@ +import tkinter as tk +from tkinter import ttk + +from coretk import appconfig +from coretk.dialogs.dialog import Dialog + + +class PreferencesDialog(Dialog): + def __init__(self, master, app): + super().__init__(master, app, "Preferences", modal=True) + preferences = self.app.config["preferences"] + self.editor = tk.StringVar(value=preferences["editor"]) + self.terminal = tk.StringVar(value=preferences["terminal"]) + self.gui3d = tk.StringVar(value=preferences["gui3d"]) + self.draw() + + def draw(self): + self.columnconfigure(0, weight=1) + self.draw_programs() + self.draw_buttons() + + def draw_programs(self): + frame = ttk.LabelFrame(self, text="Programs") + frame.grid(sticky="ew", pady=2) + frame.columnconfigure(1, weight=1) + + label = ttk.Label(frame, text="Editor") + label.grid(row=0, column=0, pady=2, padx=2, sticky="w") + combobox = ttk.Combobox( + frame, textvariable=self.editor, values=appconfig.EDITORS, state="readonly" + ) + combobox.grid(row=0, column=1, sticky="ew") + + label = ttk.Label(frame, text="Terminal") + label.grid(row=1, column=0, pady=2, padx=2, sticky="w") + combobox = ttk.Combobox( + frame, + textvariable=self.terminal, + values=appconfig.TERMINALS, + state="readonly", + ) + combobox.grid(row=1, column=1, sticky="ew") + + label = ttk.Label(frame, text="3D GUI") + label.grid(row=2, column=0, pady=2, padx=2, sticky="w") + entry = ttk.Entry(frame, textvariable=self.gui3d) + entry.grid(row=2, column=1, sticky="ew") + + def draw_buttons(self): + frame = ttk.Frame(self) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + + button = ttk.Button(frame, text="Save", command=self.click_save) + button.grid(row=0, column=0, sticky="ew") + + button = ttk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, sticky="ew") + + def click_save(self): + preferences = self.app.config["preferences"] + preferences["terminal"] = self.terminal.get() + preferences["editor"] = self.editor.get() + preferences["gui3d"] = self.gui3d.get() + self.app.save_config() + self.destroy() diff --git a/coretk/coretk/dialogs/servers.py b/coretk/coretk/dialogs/servers.py index 3ff95cde..25f3e826 100644 --- a/coretk/coretk/dialogs/servers.py +++ b/coretk/coretk/dialogs/servers.py @@ -1,6 +1,6 @@ import tkinter as tk +from tkinter import ttk -from coretk import appdirs from coretk.coreclient import CoreServer from coretk.dialogs.dialog import Dialog @@ -31,12 +31,12 @@ class ServersDialog(Dialog): self.draw_apply_buttons() def draw_servers(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(pady=2, sticky="nsew") frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) - scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL) + scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL) scrollbar.grid(row=0, column=1, sticky="ns") self.servers = tk.Listbox( @@ -51,61 +51,61 @@ class ServersDialog(Dialog): scrollbar.config(command=self.servers.yview) def draw_server_configuration(self): - label = tk.Label(self, text="Server Configuration") + label = ttk.Label(self, text="Server Configuration") label.grid(pady=2, sticky="ew") - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(pady=2, sticky="ew") frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) frame.columnconfigure(5, weight=1) - label = tk.Label(frame, text="Name") + label = ttk.Label(frame, text="Name") label.grid(row=0, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.name) + entry = ttk.Entry(frame, textvariable=self.name) entry.grid(row=0, column=1, sticky="ew") - label = tk.Label(frame, text="Address") + label = ttk.Label(frame, text="Address") label.grid(row=0, column=2, sticky="w") - entry = tk.Entry(frame, textvariable=self.address) + entry = ttk.Entry(frame, textvariable=self.address) entry.grid(row=0, column=3, sticky="ew") - label = tk.Label(frame, text="Port") + label = ttk.Label(frame, text="Port") label.grid(row=0, column=4, sticky="w") - entry = tk.Entry(frame, textvariable=self.port) + entry = ttk.Entry(frame, textvariable=self.port) entry.grid(row=0, column=5, sticky="ew") def draw_servers_buttons(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(pady=2, sticky="ew") for i in range(3): frame.columnconfigure(i, weight=1) - button = tk.Button(frame, text="Create", command=self.click_create) + button = ttk.Button(frame, text="Create", command=self.click_create) button.grid(row=0, column=0, sticky="ew") - self.save_button = tk.Button( + self.save_button = ttk.Button( frame, text="Save", state=tk.DISABLED, command=self.click_save ) self.save_button.grid(row=0, column=1, sticky="ew") - self.delete_button = tk.Button( + self.delete_button = ttk.Button( frame, text="Delete", state=tk.DISABLED, command=self.click_delete ) self.delete_button.grid(row=0, column=2, sticky="ew") def draw_apply_buttons(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = tk.Button( + button = ttk.Button( frame, text="Save Configuration", command=self.click_save_configuration ) button.grid(row=0, column=0, sticky="ew") - button = tk.Button(frame, text="Cancel", command=self.destroy) + button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") def click_save_configuration(self): @@ -116,7 +116,8 @@ class ServersDialog(Dialog): {"name": server.name, "address": server.address, "port": server.port} ) self.app.config["servers"] = servers - appdirs.save_config(self.app.config) + self.app.save_config() + self.destroy() def click_create(self): name = self.name.get() diff --git a/coretk/coretk/dialogs/sessionoptions.py b/coretk/coretk/dialogs/sessionoptions.py index 8702d581..45f26a58 100644 --- a/coretk/coretk/dialogs/sessionoptions.py +++ b/coretk/coretk/dialogs/sessionoptions.py @@ -1,5 +1,5 @@ import logging -import tkinter as tk +from tkinter import ttk from coretk.dialogs.dialog import Dialog from coretk.widgets import ConfigFrame @@ -26,13 +26,13 @@ class SessionOptionsDialog(Dialog): self.config_frame.draw_config() self.config_frame.grid(sticky="nsew") - frame = tk.Frame(self) + frame = ttk.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 = ttk.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 = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, pady=PAD_Y, padx=PAD_X, sticky="ew") def save(self): diff --git a/coretk/coretk/dialogs/sessions.py b/coretk/coretk/dialogs/sessions.py index b7c4d128..40d09c44 100644 --- a/coretk/coretk/dialogs/sessions.py +++ b/coretk/coretk/dialogs/sessions.py @@ -1,6 +1,6 @@ import logging import tkinter as tk -from tkinter.ttk import Scrollbar, Treeview +from tkinter import ttk from core.api.grpc import core_pb2 from coretk.dialogs.dialog import Dialog @@ -9,12 +9,6 @@ from coretk.images import ImageEnum, Images class SessionsDialog(Dialog): def __init__(self, master, app): - """ - create session table instance - - :param coretk.coreclient.CoreClient grpc: coregrpc - :param root.master master: - """ super().__init__(master, app, "Sessions", modal=True) self.selected = False self.selected_id = None @@ -32,7 +26,7 @@ class SessionsDialog(Dialog): write a short description :return: nothing """ - label = tk.Label( + label = ttk.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" @@ -42,7 +36,9 @@ class SessionsDialog(Dialog): label.grid(row=0, sticky="ew", pady=5) def draw_tree(self): - self.tree = Treeview(self, columns=("id", "state", "nodes"), show="headings") + self.tree = ttk.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") @@ -64,20 +60,20 @@ class SessionsDialog(Dialog): self.tree.bind("<Double-1>", self.on_selected) self.tree.bind("<<TreeviewSelect>>", self.click_select) - yscrollbar = Scrollbar(self, orient="vertical", command=self.tree.yview) + yscrollbar = ttk.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 = ttk.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) + frame = ttk.Frame(self) for i in range(4): frame.columnconfigure(i, weight=1) frame.grid(row=3, sticky="ew") - b = tk.Button( + b = ttk.Button( frame, image=Images.get(ImageEnum.DOCUMENTNEW), text="New", @@ -85,7 +81,7 @@ class SessionsDialog(Dialog): command=self.click_new, ) b.grid(row=0, padx=2, sticky="ew") - b = tk.Button( + b = ttk.Button( frame, image=Images.get(ImageEnum.FILEOPEN), text="Connect", @@ -93,7 +89,7 @@ class SessionsDialog(Dialog): command=self.click_connect, ) b.grid(row=0, column=1, padx=2, sticky="ew") - b = tk.Button( + b = ttk.Button( frame, image=Images.get(ImageEnum.EDITDELETE), text="Shutdown", @@ -101,7 +97,7 @@ class SessionsDialog(Dialog): 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 = ttk.Button(frame, text="Cancel", command=self.click_new) b.grid(row=0, column=3, padx=2, sticky="ew") def click_new(self): diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index 80360c44..e813ae48 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -150,7 +150,7 @@ class CanvasGraph(tk.Canvas): node.type, node.model ) n = CanvasNode( - node.position.x, node.position.y, image, name, self, node.id + node.position.x, node.position.y, image, name, self.master, node.id ) self.nodes[n.id] = n core_id_to_canvas_id[node.id] = n.id @@ -419,14 +419,7 @@ class CanvasGraph(tk.Canvas): plot_id = self.find_all()[0] logging.info("add node event: %s - %s", plot_id, self.selected) if self.selected == plot_id: - node = CanvasNode( - x=x, - y=y, - image=image, - node_type=node_name, - canvas=self, - core_id=self.core.peek_id(), - ) + node = CanvasNode(x, y, image, node_name, self.master, self.core.peek_id()) self.nodes[node.id] = node self.core.add_graph_node(self.core.session_id, node.id, x, y, node_name) return node @@ -491,10 +484,11 @@ class CanvasEdge: class CanvasNode: - def __init__(self, x, y, image, node_type, canvas, core_id): + def __init__(self, x, y, image, node_type, app, core_id): self.image = image self.node_type = node_type - self.canvas = canvas + self.app = app + self.canvas = app.canvas self.id = self.canvas.create_image( x, y, anchor=tk.CENTER, image=self.image, tags="node" ) @@ -506,19 +500,30 @@ class CanvasNode: x, y + 20, text=self.name, tags="nodename" ) self.antenna_draw = WlanAntennaManager(self.canvas, self.id) - + self.tooltip = CanvasTooltip(self.canvas) self.canvas.tag_bind(self.id, "<ButtonPress-1>", self.click_press) self.canvas.tag_bind(self.id, "<ButtonRelease-1>", self.click_release) self.canvas.tag_bind(self.id, "<B1-Motion>", self.motion) self.canvas.tag_bind(self.id, "<Button-3>", self.context) self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click) self.canvas.tag_bind(self.id, "<Control-1>", self.select_multiple) - self.tooltip = CanvasTooltip(self.canvas, self.id, text=self.name) + self.canvas.tag_bind(self.id, "<Enter>", self.on_enter) + self.canvas.tag_bind(self.id, "<Leave>", self.on_leave) self.edges = set() self.wlans = [] self.moving = None + def on_enter(self, event): + if self.app.core.is_runtime() and self.app.core.observer: + self.tooltip.text.set("waiting...") + self.tooltip.on_enter(event) + output = self.app.core.run(self.core_id) + self.tooltip.text.set(output) + + def on_leave(self, event): + self.tooltip.on_leave(event) + def click(self, event): print("click") diff --git a/coretk/coretk/images.py b/coretk/coretk/images.py index 03e7fc01..726da5cc 100644 --- a/coretk/coretk/images.py +++ b/coretk/coretk/images.py @@ -4,7 +4,7 @@ from enum import Enum from PIL import Image, ImageTk from core.api.grpc import core_pb2 -from coretk.appdirs import LOCAL_ICONS_PATH +from coretk.appconfig import LOCAL_ICONS_PATH class Images: diff --git a/coretk/coretk/menuaction.py b/coretk/coretk/menuaction.py index 3b33377c..98b3d254 100644 --- a/coretk/coretk/menuaction.py +++ b/coretk/coretk/menuaction.py @@ -7,11 +7,12 @@ import webbrowser from tkinter import filedialog, messagebox from core.api.grpc import core_pb2 -from coretk.appdirs import XML_PATH +from coretk.appconfig import XML_PATH from coretk.dialogs.canvasbackground import CanvasBackgroundDialog from coretk.dialogs.canvassizeandscale import SizeAndScaleDialog from coretk.dialogs.hooks import HooksDialog -from coretk.dialogs.observerwidgets import ObserverWidgetsDialog +from coretk.dialogs.observers import ObserverDialog +from coretk.dialogs.preferences import PreferencesDialog from coretk.dialogs.servers import ServersDialog from coretk.dialogs.sessionoptions import SessionOptionsDialog from coretk.dialogs.sessions import SessionsDialog @@ -83,6 +84,10 @@ class MenuAction: self.prompt_save_running_session() self.app.core.open_xml(file_path) + def gui_preferences(self): + dialog = PreferencesDialog(self.app, self.app) + dialog.show() + def canvas_size_and_scale(self): dialog = SizeAndScaleDialog(self.app, self.app) dialog.show() @@ -118,5 +123,5 @@ class MenuAction: dialog.show() def edit_observer_widgets(self): - dialog = ObserverWidgetsDialog(self.app, self.app) + dialog = ObserverDialog(self.app, self.app) dialog.show() diff --git a/coretk/coretk/menubar.py b/coretk/coretk/menubar.py index 3ef38d73..621510e2 100644 --- a/coretk/coretk/menubar.py +++ b/coretk/coretk/menubar.py @@ -1,6 +1,8 @@ import tkinter as tk +from functools import partial import coretk.menuaction as action +from coretk.coreclient import OBSERVERS class Menubar(tk.Menu): @@ -92,7 +94,9 @@ class Menubar(tk.Menu): menu.add_separator() menu.add_command(label="Find...", accelerator="Ctrl+F", state=tk.DISABLED) menu.add_command(label="Clear marker", state=tk.DISABLED) - menu.add_command(label="Preferences...", state=tk.DISABLED) + menu.add_command( + label="Preferences...", command=self.menuaction.gui_preferences + ) self.add_cascade(label="Edit", menu=menu) def draw_canvas_menu(self): @@ -364,23 +368,35 @@ class Menubar(tk.Menu): :param tkinter.Menu widget_menu: widget_menu :return: nothing """ + var = tk.StringVar(value="none") menu = tk.Menu(widget_menu) - menu.add_command(label="None", state=tk.DISABLED) - menu.add_command(label="processes", state=tk.DISABLED) - menu.add_command(label="ifconfig", state=tk.DISABLED) - menu.add_command(label="IPv4 routes", state=tk.DISABLED) - menu.add_command(label="IPv6 routes", state=tk.DISABLED) - menu.add_command(label="OSPFv2 neighbors", state=tk.DISABLED) - menu.add_command(label="OSPFv3 neighbors", state=tk.DISABLED) - menu.add_command(label="Listening sockets", state=tk.DISABLED) - menu.add_command(label="IPv4 MFC entries", state=tk.DISABLED) - menu.add_command(label="IPv6 MFC entries", state=tk.DISABLED) - menu.add_command(label="firewall rules", state=tk.DISABLED) - menu.add_command(label="IPsec policies", state=tk.DISABLED) - menu.add_command(label="docker logs", state=tk.DISABLED) - menu.add_command(label="OSPFv3 MDR level", state=tk.DISABLED) - menu.add_command(label="PIM neighbors", state=tk.DISABLED) - menu.add_command(label="Edit...", command=self.menuaction.edit_observer_widgets) + menu.var = var + menu.add_command( + label="Edit Observers", command=self.menuaction.edit_observer_widgets + ) + menu.add_separator() + menu.add_radiobutton( + label="None", + variable=var, + value="none", + command=lambda: self.app.core.set_observer(None), + ) + for name in sorted(OBSERVERS): + cmd = OBSERVERS[name] + menu.add_radiobutton( + label=name, + variable=var, + value=name, + command=partial(self.app.core.set_observer, cmd), + ) + for name in sorted(self.app.core.custom_observers): + observer = self.app.core.custom_observers[name] + menu.add_radiobutton( + label=name, + variable=var, + value=name, + command=partial(self.app.core.set_observer, observer.cmd), + ) widget_menu.add_cascade(label="Observer Widgets", menu=menu) def create_adjacency_menu(self, widget_menu): diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index 236dbc24..9ae9e851 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -26,7 +26,7 @@ class FrameScroll(tk.LabelFrame): 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.scrollbar = ttk.Scrollbar( self, orient="vertical", command=self.canvas.yview ) self.scrollbar.grid(row=0, column=1, sticky="ns") @@ -70,11 +70,11 @@ class ConfigFrame(FrameScroll): for group_name in sorted(group_mapping): group = group_mapping[group_name] - frame = tk.Frame(self.frame) + frame = ttk.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 = ttk.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: @@ -96,15 +96,15 @@ class ConfigFrame(FrameScroll): 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 = ttk.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 = ttk.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 = ttk.Entry(frame, textvariable=value) entry.grid(row=index, column=1, sticky="ew", pady=pady) else: logging.error("unhandled config option type: %s", option.type) @@ -131,7 +131,7 @@ class ListboxScroll(tk.LabelFrame): super().__init__(master, cnf, **kw) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.scrollbar = tk.Scrollbar(self, orient=tk.VERTICAL) + self.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL) self.scrollbar.grid(row=0, column=1, sticky="ns") self.listbox = tk.Listbox( self, selectmode=tk.SINGLE, yscrollcommand=self.scrollbar.set @@ -149,5 +149,5 @@ class CheckboxList(FrameScroll): 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 = ttk.Checkbutton(self.frame, text=name, variable=var, command=func) checkbox.grid(sticky="w") diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index c00cc3af..8c987371 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -184,9 +184,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("stop session: %s", request) session = self.get_session(request.session_id, context) session.data_collect() - session.set_state(EventTypes.DATACOLLECT_STATE) + session.set_state(EventTypes.DATACOLLECT_STATE, send_event=True) session.clear() - session.set_state(EventTypes.SHUTDOWN_STATE) + session.set_state(EventTypes.SHUTDOWN_STATE, send_event=True) return core_pb2.StopSessionResponse(result=True) def CreateSession(self, request, context):