diff --git a/coretk/coretk/app.py b/coretk/coretk/app.py index 276dbd6d..452d5397 100644 --- a/coretk/coretk/app.py +++ b/coretk/coretk/app.py @@ -1,5 +1,6 @@ import logging import tkinter as tk +from tkinter import ttk from coretk import appconfig from coretk.coreclient import CoreClient @@ -36,7 +37,7 @@ class Application(tk.Frame): self.master.title("CORE") self.master.geometry("1000x800") self.master.protocol("WM_DELETE_WINDOW", self.on_closing) - image = Images.get(ImageEnum.CORE) + image = Images.get(ImageEnum.CORE, 16) self.master.tk.call("wm", "iconphoto", self.master._w, image) self.pack(fill=tk.BOTH, expand=True) @@ -53,17 +54,17 @@ class Application(tk.Frame): self, self.core, background="#cccccc", scrollregion=(0, 0, 1200, 1000) ) self.canvas.pack(fill=tk.BOTH, expand=True) - scroll_x = tk.Scrollbar( + scroll_x = ttk.Scrollbar( self.canvas, orient=tk.HORIZONTAL, command=self.canvas.xview ) scroll_x.pack(side=tk.BOTTOM, fill=tk.X) - scroll_y = tk.Scrollbar(self.canvas, command=self.canvas.yview) + scroll_y = ttk.Scrollbar(self.canvas, command=self.canvas.yview) scroll_y.pack(side=tk.RIGHT, fill=tk.Y) self.canvas.configure(xscrollcommand=scroll_x.set) self.canvas.configure(yscrollcommand=scroll_y.set) def draw_status(self): - self.statusbar = tk.Frame(self) + self.statusbar = ttk.Frame(self) self.statusbar.pack(side=tk.BOTTOM, fill=tk.X) def on_closing(self): diff --git a/coretk/coretk/coreclient.py b/coretk/coretk/coreclient.py index 13b1c1cf..0d562f8a 100644 --- a/coretk/coretk/coreclient.py +++ b/coretk/coretk/coreclient.py @@ -8,7 +8,7 @@ from core.api.grpc import client, core_pb2 from coretk.coretocanvas import CoreToCanvasMapping from coretk.dialogs.sessions import SessionsDialog from coretk.emaneodelnodeconfig import EmaneModelNodeConfig -from coretk.images import Images +from coretk.images import NODE_WIDTH, Images from coretk.interface import Interface, InterfaceManager from coretk.mobilitynodeconfig import MobilityNodeConfig from coretk.servicenodeconfig import ServiceNodeConfig @@ -139,7 +139,7 @@ class CoreClient: # read custom nodes for config in self.app.config.get("nodes", []): image_file = config["image"] - image = Images.get_custom(image_file) + image = Images.get_custom(image_file, NODE_WIDTH) custom_node = CustomNode( config["name"], image, image_file, set(config["services"]) ) diff --git a/coretk/coretk/dialogs/canvasbackground.py b/coretk/coretk/dialogs/canvasbackground.py index ed7e44f6..8b1c4cf4 100644 --- a/coretk/coretk/dialogs/canvasbackground.py +++ b/coretk/coretk/dialogs/canvasbackground.py @@ -11,6 +11,8 @@ from PIL import Image, ImageTk from coretk.appconfig import BACKGROUNDS_PATH from coretk.dialogs.dialog import Dialog +PADX = 5 + class ScaleOption(enum.Enum): NONE = 0 @@ -65,10 +67,10 @@ class CanvasBackgroundDialog(Dialog): entry = ttk.Entry(frame, textvariable=self.file_name) entry.focus() - entry.grid(row=0, column=0, sticky="ew") + entry.grid(row=0, column=0, sticky="ew", padx=PADX) button = ttk.Button(frame, text="...", command=self.click_open_image) - button.grid(row=0, column=1, sticky="ew") + button.grid(row=0, column=1, sticky="ew", padx=PADX) button = ttk.Button(frame, text="Clear", command=self.click_clear) button.grid(row=0, column=2, sticky="ew") @@ -105,7 +107,7 @@ class CanvasBackgroundDialog(Dialog): checkbutton = ttk.Checkbutton( self, text="Show grid", variable=self.show_grid_var ) - checkbutton.grid(row=4, column=0, sticky="ew", padx=5) + checkbutton.grid(row=4, column=0, sticky="ew", padx=PADX) checkbutton = ttk.Checkbutton( self, @@ -113,7 +115,7 @@ class CanvasBackgroundDialog(Dialog): variable=self.adjust_to_dim_var, command=self.click_adjust_canvas, ) - checkbutton.grid(row=5, column=0, sticky="ew", padx=5) + checkbutton.grid(row=5, column=0, sticky="ew", padx=PADX) self.show_grid_var.set(1) self.adjust_to_dim_var.set(0) @@ -125,7 +127,7 @@ class CanvasBackgroundDialog(Dialog): frame.columnconfigure(1, weight=1) button = ttk.Button(frame, text="Apply", command=self.click_apply) - button.grid(row=0, column=0, sticky="ew") + button.grid(row=0, column=0, sticky="ew", padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") @@ -206,7 +208,6 @@ class CanvasBackgroundDialog(Dialog): return def upper_left(self, img): - print("upperleft") tk_img = ImageTk.PhotoImage(img) # crop image if it is bigger than canvas diff --git a/coretk/coretk/dialogs/canvassizeandscale.py b/coretk/coretk/dialogs/canvassizeandscale.py index 1d880639..1af71c93 100644 --- a/coretk/coretk/dialogs/canvassizeandscale.py +++ b/coretk/coretk/dialogs/canvassizeandscale.py @@ -8,6 +8,9 @@ from coretk.dialogs.canvasbackground import ScaleOption from coretk.dialogs.dialog import Dialog DRAW_OBJECT_TAGS = ["edge", "node", "nodename", "linkinfo", "antenna"] +FRAME_BAD = 5 +PAD = (0, 0, 5, 0) +PADX = 5 class SizeAndScaleDialog(Dialog): @@ -49,101 +52,105 @@ class SizeAndScaleDialog(Dialog): self.draw_buttons() def draw_size(self): - label = ttk.Label(self, text="Size", font=self.section_font) - label.grid(sticky="w") + label_frame = ttk.Labelframe(self, text="Size", padding=FRAME_BAD) + label_frame.grid(sticky="ew") + label_frame.columnconfigure(0, weight=1) # draw size row 1 - frame = ttk.Frame(self) + frame = ttk.Frame(label_frame) frame.grid(sticky="ew", pady=3) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="Width") - label.grid(row=0, column=0, sticky="w") + label.grid(row=0, column=0, sticky="w", padx=PADX) entry = ttk.Entry(frame, textvariable=self.pixel_width) - entry.grid(row=0, column=1, sticky="ew") + entry.grid(row=0, column=1, sticky="ew", padx=PADX) label = ttk.Label(frame, text="x Height") - label.grid(row=0, column=2, sticky="w") + label.grid(row=0, column=2, sticky="w", padx=PADX) entry = ttk.Entry(frame, textvariable=self.pixel_height) - entry.grid(row=0, column=3, sticky="ew") + entry.grid(row=0, column=3, sticky="ew", padx=PADX) label = ttk.Label(frame, text="Pixels") label.grid(row=0, column=4, sticky="w") # draw size row 2 - frame = ttk.Frame(self) + frame = ttk.Frame(label_frame) frame.grid(sticky="ew", pady=3) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="Width") - label.grid(row=0, column=0, sticky="w") + label.grid(row=0, column=0, sticky="w", padx=PADX) entry = ttk.Entry(frame, textvariable=self.meters_width) - entry.grid(row=0, column=1, sticky="ew") + entry.grid(row=0, column=1, sticky="ew", padx=PADX) label = ttk.Label(frame, text="x Height") - label.grid(row=0, column=2, sticky="w") + label.grid(row=0, column=2, sticky="w", padx=PADX) entry = ttk.Entry(frame, textvariable=self.meters_height) - entry.grid(row=0, column=3, sticky="ew") + entry.grid(row=0, column=3, sticky="ew", padx=PADX) label = ttk.Label(frame, text="Meters") label.grid(row=0, column=4, sticky="w") def draw_scale(self): - label = ttk.Label(self, text="Scale", font=self.section_font) - label.grid(sticky="w") + label_frame = ttk.Labelframe(self, text="Scale", padding=FRAME_BAD) + label_frame.grid(sticky="ew") + label_frame.columnconfigure(0, weight=1) - frame = ttk.Frame(self) + frame = ttk.Frame(label_frame) frame.grid(sticky="ew") frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="100 Pixels =") - label.grid(row=0, column=0, sticky="w") + label.grid(row=0, column=0, sticky="w", padx=PADX) entry = ttk.Entry(frame, textvariable=self.scale) - entry.grid(row=0, column=1, sticky="ew") + entry.grid(row=0, column=1, sticky="ew", padx=PADX) label = ttk.Label(frame, text="Meters") label.grid(row=0, column=2, sticky="w") def draw_reference_point(self): - label = ttk.Label(self, text="Reference point", font=self.section_font) - label.grid(sticky="w") - label = ttk.Label( - self, text="Default is (0, 0), the upper left corner of the canvas" - ) - label.grid(sticky="w") + label_frame = ttk.Labelframe(self, text="Reference Point", padding=FRAME_BAD) + label_frame.grid(sticky="ew") + label_frame.columnconfigure(0, weight=1) - frame = ttk.Frame(self) + label = ttk.Label( + label_frame, text="Default is (0, 0), the upper left corner of the canvas" + ) + label.grid() + + frame = ttk.Frame(label_frame) frame.grid(sticky="ew", pady=3) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="X") - label.grid(row=0, column=0, sticky="w") + label.grid(row=0, column=0, sticky="w", padx=PADX) x_var = tk.StringVar(value=0) entry = ttk.Entry(frame, textvariable=x_var) - entry.grid(row=0, column=1, sticky="ew") + entry.grid(row=0, column=1, sticky="ew", padx=PADX) label = ttk.Label(frame, text="Y") - label.grid(row=0, column=2, sticky="w") + label.grid(row=0, column=2, sticky="w", padx=PADX) y_var = tk.StringVar(value=0) entry = ttk.Entry(frame, textvariable=y_var) - entry.grid(row=0, column=3, sticky="ew") + entry.grid(row=0, column=3, sticky="ew", padx=PADX) - label = ttk.Label(self, text="Translates To") - label.grid(sticky="w") + label = ttk.Label(label_frame, text="Translates To") + label.grid() - frame = ttk.Frame(self) + frame = ttk.Frame(label_frame) frame.grid(sticky="ew", pady=3) frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) frame.columnconfigure(5, weight=1) label = ttk.Label(frame, text="Lat") - label.grid(row=0, column=0, sticky="w") + label.grid(row=0, column=0, sticky="w", padx=PADX) entry = ttk.Entry(frame, textvariable=self.lat) - entry.grid(row=0, column=1, sticky="ew") + entry.grid(row=0, column=1, sticky="ew", padx=PADX) label = ttk.Label(frame, text="Lon") - label.grid(row=0, column=2, sticky="w") + label.grid(row=0, column=2, sticky="w", padx=PADX) entry = ttk.Entry(frame, textvariable=self.lon) - entry.grid(row=0, column=3, sticky="ew") + entry.grid(row=0, column=3, sticky="ew", padx=PADX) label = ttk.Label(frame, text="Alt") - label.grid(row=0, column=4, sticky="w") + label.grid(row=0, column=4, sticky="w", padx=PADX) entry = ttk.Entry(frame, textvariable=self.alt) entry.grid(row=0, column=5, sticky="ew") @@ -160,10 +167,10 @@ class SizeAndScaleDialog(Dialog): frame.grid(sticky="ew") button = ttk.Button(frame, text="Apply", command=self.click_apply) - button.grid(row=0, column=0, pady=5, sticky="ew") + button.grid(row=0, column=0, sticky="ew", padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, pady=5, sticky="ew") + button.grid(row=0, column=1, sticky="ew") def redraw_grid(self): """ diff --git a/coretk/coretk/dialogs/dialog.py b/coretk/coretk/dialogs/dialog.py index f9cfcabe..c043a47a 100644 --- a/coretk/coretk/dialogs/dialog.py +++ b/coretk/coretk/dialogs/dialog.py @@ -2,16 +2,18 @@ import tkinter as tk from coretk.images import ImageEnum, Images +DIALOG_PAD = 5 + class Dialog(tk.Toplevel): def __init__(self, master, app, title, modal=False): - super().__init__(master, padx=5, pady=5) + super().__init__(master, padx=DIALOG_PAD, pady=DIALOG_PAD) self.withdraw() self.app = app self.modal = modal self.title(title) self.protocol("WM_DELETE_WINDOW", self.destroy) - image = Images.get(ImageEnum.CORE) + image = Images.get(ImageEnum.CORE, 16) self.tk.call("wm", "iconphoto", self._w, image) def show(self): diff --git a/coretk/coretk/dialogs/emaneconfig.py b/coretk/coretk/dialogs/emaneconfig.py index 7fd577e2..8673d3fc 100644 --- a/coretk/coretk/dialogs/emaneconfig.py +++ b/coretk/coretk/dialogs/emaneconfig.py @@ -182,25 +182,31 @@ class EmaneConfiguration(Dialog): def draw_option_buttons(self, parent): f = ttk.Frame(parent) + f.grid(row=4, column=0, sticky="nsew") f.columnconfigure(0, weight=1) f.columnconfigure(1, weight=1) + + image = Images.get(ImageEnum.EDITNODE, 16) b = ttk.Button( f, text=self.emane_models[0] + " options", - image=Images.get(ImageEnum.EDITNODE), + image=image, compound=tk.RIGHT, command=self.draw_model_options, ) + b.image = image b.grid(row=0, column=0, padx=10, pady=2, sticky="nsew") + + image = Images.get(ImageEnum.EDITNODE, 16) b = ttk.Button( f, text="EMANE options", - image=Images.get(ImageEnum.EDITNODE), + image=image, compound=tk.RIGHT, command=self.draw_emane_options, ) + b.image = image b.grid(row=0, column=1, padx=10, pady=2, sticky="nsew") - f.grid(row=4, column=0, sticky="nsew") def combobox_select(self, event): """ @@ -271,7 +277,7 @@ class EmaneConfiguration(Dialog): b = ttk.Button( f, - image=Images.get(ImageEnum.EDITNODE), + image=Images.get(ImageEnum.EDITNODE, 8), text="EMANE Wiki", compound=tk.RIGHT, command=lambda: webbrowser.open_new( diff --git a/coretk/coretk/dialogs/icondialog.py b/coretk/coretk/dialogs/icondialog.py index fb6fb6bb..15d8000b 100644 --- a/coretk/coretk/dialogs/icondialog.py +++ b/coretk/coretk/dialogs/icondialog.py @@ -54,7 +54,7 @@ class IconDialog(Dialog): ), ) if file_path: - self.image = Images.create(file_path) + self.image = Images.create(file_path, 32, 32) self.image_label.config(image=self.image) self.file_path.set(file_path) diff --git a/coretk/coretk/dialogs/mobilityconfig.py b/coretk/coretk/dialogs/mobilityconfig.py index 7f99df18..68c557ee 100644 --- a/coretk/coretk/dialogs/mobilityconfig.py +++ b/coretk/coretk/dialogs/mobilityconfig.py @@ -2,11 +2,10 @@ mobility configuration """ -import os import tkinter as tk -from pathlib import Path -from tkinter import filedialog +from tkinter import filedialog, ttk +from coretk import appconfig from coretk.dialogs.dialog import Dialog @@ -40,12 +39,12 @@ class MobilityConfiguration(Dialog): return var def open_file(self, entry): - configs_dir = os.path.join(Path.home(), ".core/configs") - if os.path.isdir(configs_dir): - filename = filedialog.askopenfilename(initialdir=configs_dir, title="Open") - if filename: - entry.delete(0, tk.END) - entry.insert(0, filename) + filename = filedialog.askopenfilename( + initialdir=str(appconfig.MOBILITY_PATH), title="Open" + ) + if filename: + entry.delete(0, tk.END) + entry.insert(0, filename) def set_loop_value(self, value): """ @@ -58,26 +57,26 @@ class MobilityConfiguration(Dialog): def create_label_entry_filebrowser( self, parent_frame, text_label, entry_text, filebrowser=False ): - f = tk.Frame(parent_frame, bg="#d9d9d9") - lbl = tk.Label(f, text=text_label, bg="#d9d9d9") + f = ttk.Frame(parent_frame, bg="#d9d9d9") + lbl = ttk.Label(f, text=text_label, bg="#d9d9d9") lbl.grid(padx=3, pady=3) # f.grid() - e = tk.Entry(f, textvariable=self.create_string_var(entry_text), bg="#ffffff") + e = ttk.Entry(f, textvariable=self.create_string_var(entry_text), bg="#ffffff") e.grid(row=0, column=1, padx=3, pady=3) if filebrowser: - b = tk.Button(f, text="...", command=lambda: self.open_file(e)) + b = ttk.Button(f, text="...", command=lambda: self.open_file(e)) b.grid(row=0, column=2, padx=3, pady=3) f.grid(sticky=tk.E) def mobility_script_parameters(self): - lbl = tk.Label(self, text="node ns2script") - lbl.grid(sticky=tk.W + tk.E) + lbl = ttk.Label(self, text="node ns2script") + lbl.grid(sticky="ew") - sb = tk.Scrollbar(self, orient=tk.VERTICAL) - sb.grid(row=1, column=1, sticky=tk.N + tk.S + tk.E) + sb = ttk.Scrollbar(self, orient=tk.VERTICAL) + sb.grid(row=1, column=1, sticky="ns") - f = tk.Frame(self, bg="#d9d9d9") - lbl = tk.Label( + f = ttk.Frame(self, bg="#d9d9d9") + lbl = ttk.Label( f, text="ns-2 Mobility Scripts Parameters", bg="#d9d9d9", relief=tk.RAISED ) lbl.grid(row=0, column=0, sticky=tk.W) @@ -99,21 +98,21 @@ class MobilityConfiguration(Dialog): f1, "Refresh time (ms)", self.node_config["refresh_ms"] ) - # f12 = tk.Frame(f1) + # f12 = ttk.Frame(f1) # - # lbl = tk.Label(f12, text="Refresh time (ms)") + # lbl = ttk.Label(f12, text="Refresh time (ms)") # lbl.grid() # - # e = tk.Entry(f12, textvariable=self.create_string_var("50")) + # e = ttk.Entry(f12, textvariable=self.create_string_var("50")) # e.grid(row=0, column=1) # f12.grid() - f13 = tk.Frame(f1) + f13 = ttk.Frame(f1) - lbl = tk.Label(f13, text="loop") + lbl = ttk.Label(f13, text="loop") lbl.grid() - om = tk.OptionMenu( + om = ttk.OptionMenu( f13, self.create_string_var("On"), "On", "Off", command=self.set_loop_value ) om.grid(row=0, column=1) @@ -123,24 +122,24 @@ class MobilityConfiguration(Dialog): self.create_label_entry_filebrowser( f1, "auto-start seconds (0.0 for runtime)", self.node_config["autostart"] ) - # f14 = tk.Frame(f1) + # f14 = ttk.Frame(f1) # - # lbl = tk.Label(f14, text="auto-start seconds (0.0 for runtime)") + # lbl = ttk.Label(f14, text="auto-start seconds (0.0 for runtime)") # lbl.grid() # - # e = tk.Entry(f14, textvariable=self.create_string_var("")) + # e = ttk.Entry(f14, textvariable=self.create_string_var("")) # e.grid(row=0, column=1) # # f14.grid() self.create_label_entry_filebrowser( f1, "node mapping (optional, e.g. 0:1, 1:2, 2:3)", self.node_config["map"] ) - # f15 = tk.Frame(f1) + # f15 = ttk.Frame(f1) # - # lbl = tk.Label(f15, text="node mapping (optional, e.g. 0:1, 1:2, 2:3)") + # lbl = ttk.Label(f15, text="node mapping (optional, e.g. 0:1, 1:2, 2:3)") # lbl.grid() # - # e = tk.Entry(f15, textvariable=self.create_string_var("")) + # e = ttk.Entry(f15, textvariable=self.create_string_var("")) # e.grid(row=0, column=1) # # f15.grid() @@ -230,9 +229,9 @@ class MobilityConfiguration(Dialog): :return: nothing """ - f = tk.Frame(self) - b = tk.Button(f, text="Apply", command=self.ns2script_apply) + f = ttk.Frame(self) + b = ttk.Button(f, text="Apply", command=self.ns2script_apply) b.grid() - b = tk.Button(f, text="Cancel", command=self.destroy) + b = ttk.Button(f, text="Cancel", command=self.destroy) b.grid(row=0, column=1) f.grid() diff --git a/coretk/coretk/dialogs/nodeconfig.py b/coretk/coretk/dialogs/nodeconfig.py index 89dd7d11..ce7261e2 100644 --- a/coretk/coretk/dialogs/nodeconfig.py +++ b/coretk/coretk/dialogs/nodeconfig.py @@ -1,13 +1,11 @@ import tkinter as tk from tkinter import ttk +from coretk.coreclient import DEFAULT_NODES from coretk.dialogs.dialog import Dialog from coretk.dialogs.icondialog import IconDialog from coretk.dialogs.nodeservice import NodeService -NETWORKNODETYPES = ["switch", "hub", "wlan", "rj45", "tunnel"] -DEFAULTNODES = ["router", "host", "PC"] - class NodeConfigDialog(Dialog): def __init__(self, master, app, canvas_node): @@ -34,17 +32,17 @@ class NodeConfigDialog(Dialog): self.draw_third_row() def draw_first_row(self): - 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=1) frame.columnconfigure(2, weight=1) - entry = tk.Entry(frame, textvariable=self.name) + entry = ttk.Entry(frame, textvariable=self.name) entry.grid(row=0, column=0, padx=2, sticky="ew") combobox = ttk.Combobox( - frame, textvariable=self.type, values=DEFAULTNODES, state="readonly" + frame, textvariable=self.type, values=DEFAULT_NODES, state="readonly" ) combobox.grid(row=0, column=1, padx=2, sticky="ew") @@ -57,15 +55,15 @@ class NodeConfigDialog(Dialog): combobox.grid(row=0, column=2, sticky="ew") def draw_second_row(self): - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(row=1, column=0, pady=2, sticky="ew") frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) - button = tk.Button(frame, text="Services", command=self.click_services) + button = ttk.Button(frame, text="Services", command=self.click_services) button.grid(row=0, column=0, padx=2, sticky="ew") - self.image_button = tk.Button( + self.image_button = ttk.Button( frame, text="Icon", image=self.image, @@ -75,15 +73,15 @@ class NodeConfigDialog(Dialog): self.image_button.grid(row=0, column=1, sticky="ew") def draw_third_row(self): - 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.config_apply) + button = ttk.Button(frame, text="Apply", command=self.config_apply) button.grid(row=0, column=0, padx=2, 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_services(self): diff --git a/coretk/coretk/dialogs/nodeservice.py b/coretk/coretk/dialogs/nodeservice.py index 52421ca0..49cc56d8 100644 --- a/coretk/coretk/dialogs/nodeservice.py +++ b/coretk/coretk/dialogs/nodeservice.py @@ -2,7 +2,7 @@ core node services """ import tkinter as tk -from tkinter import messagebox +from tkinter import messagebox, ttk from coretk.dialogs.dialog import Dialog from coretk.dialogs.serviceconfiguration import ServiceConfiguration @@ -10,20 +10,22 @@ from coretk.widgets import CheckboxList, ListboxScroll class NodeService(Dialog): - def __init__(self, master, app, canvas_node, current_services=set()): + def __init__(self, master, app, canvas_node, services=None): super().__init__(master, app, "Node Services", modal=True) self.canvas_node = canvas_node self.groups = None self.services = None self.current = None - self.current_services = current_services + if services is None: + services = set() + self.current_services = services self.draw() def draw(self): 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): @@ -45,15 +47,15 @@ class NodeService(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(3): frame.columnconfigure(i, weight=1) - button = tk.Button(frame, text="Configure", command=self.click_configure) + button = ttk.Button(frame, text="Configure", command=self.click_configure) button.grid(row=0, column=0, sticky="ew") - 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=1, 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=2, sticky="ew") # trigger group change diff --git a/coretk/coretk/dialogs/serviceconfiguration.py b/coretk/coretk/dialogs/serviceconfiguration.py index 7cd8e501..5cc41116 100644 --- a/coretk/coretk/dialogs/serviceconfiguration.py +++ b/coretk/coretk/dialogs/serviceconfiguration.py @@ -1,5 +1,5 @@ "Service configuration dialog" - +import logging import tkinter as tk from tkinter import ttk @@ -11,14 +11,12 @@ from coretk.widgets import ListboxScroll class ServiceConfiguration(Dialog): def __init__(self, master, app, service_name, canvas_node): - super().__init__(master, app, service_name + " service", modal=True) + super().__init__(master, app, f"{service_name} service", modal=True) self.app = app self.canvas_node = canvas_node self.service_name = service_name self.radiovar = tk.IntVar() self.radiovar.set(2) - self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW) - self.editdelete_img = Images.get(ImageEnum.EDITDELETE) self.metadata = "" self.filenames = [] self.dependencies = [] @@ -29,6 +27,8 @@ class ServiceConfiguration(Dialog): self.validation_mode = None self.validation_time = None self.validation_period = None + self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16) + self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16) self.tab_parent = None self.metadata_entry = None @@ -60,24 +60,23 @@ class ServiceConfiguration(Dialog): def draw(self): # self.columnconfigure(1, weight=1) - frame = tk.Frame(self) - frame1 = tk.Frame(frame) - label = tk.Label(frame1, text=self.service_name) + frame = ttk.Frame(self) + frame1 = ttk.Frame(frame) + label = ttk.Label(frame1, text=self.service_name) label.grid(row=0, column=0, sticky="ew") frame1.grid(row=0, column=0) - frame2 = tk.Frame(frame) + frame2 = ttk.Frame(frame) # frame2.columnconfigure(0, weight=1) # frame2.columnconfigure(1, weight=4) - label = tk.Label(frame2, text="Meta-data") + label = ttk.Label(frame2, text="Meta-data") label.grid(row=0, column=0) - self.metadata_entry = tk.Entry( - frame2, textvariable=tk.StringVar(value=self.metadata) - ) + + self.metadata_entry = ttk.Entry(frame2, textvariable=self.metadata) self.metadata_entry.grid(row=0, column=1) frame2.grid(row=1, column=0) frame.grid(row=0, column=0) - frame = tk.Frame(self) + frame = ttk.Frame(self) self.tab_parent = ttk.Notebook(frame) tab1 = ttk.Frame(self.tab_parent) tab2 = ttk.Frame(self.tab_parent) @@ -96,13 +95,13 @@ class ServiceConfiguration(Dialog): frame.grid(row=1, column=0, sticky="nsew") # tab 1 - label = tk.Label( + label = ttk.Label( tab1, text="Config files and scripts that are generated for this service." ) label.grid(row=0, column=0, sticky="nsew") - frame = tk.Frame(tab1) - label = tk.Label(frame, text="File name: ") + frame = ttk.Frame(tab1) + label = ttk.Label(frame, text="File name: ") label.grid(row=0, column=0) self.filename_combobox = ttk.Combobox(frame, values=self.filenames) self.filename_combobox.grid(row=0, column=1) @@ -111,48 +110,51 @@ class ServiceConfiguration(Dialog): self.filename_combobox.bind( "<>", self.display_service_file_data ) - - button = tk.Button(frame, image=self.documentnew_img) + button = ttk.Button(frame, image=self.documentnew_img) button.bind("", self.add_filename) button.grid(row=0, column=2) - button = tk.Button(frame, image=self.editdelete_img) + button = ttk.Button(frame, image=self.editdelete_img) button.bind("", self.delete_filename) button.grid(row=0, column=3) frame.grid(row=1, column=0, sticky="nsew") - frame = tk.Frame(tab1) - button = tk.Radiobutton( + frame = ttk.Frame(tab1) + button = ttk.Radiobutton( frame, variable=self.radiovar, text="Copy this source file:", - indicatoron=True, value=1, state="disabled", ) button.grid(row=0, column=0) - entry = tk.Entry(frame, state=tk.DISABLED) + entry = ttk.Entry(frame, state=tk.DISABLED) entry.grid(row=0, column=1) - button = tk.Button(frame, image=Images.get(ImageEnum.FILEOPEN)) + image = Images.get(ImageEnum.FILEOPEN, 16) + button = ttk.Button(frame, image=image) + button.image = image button.grid(row=0, column=2) frame.grid(row=2, column=0, sticky="nsew") - frame = tk.Frame(tab1) - button = tk.Radiobutton( + frame = ttk.Frame(tab1) + button = ttk.Radiobutton( frame, variable=self.radiovar, text="Use text below for file contents:", - indicatoron=True, value=2, ) button.grid(row=0, column=0) - button = tk.Button(frame, image=Images.get(ImageEnum.FILEOPEN)) + image = Images.get(ImageEnum.FILEOPEN, 16) + button = ttk.Button(frame, image=image) + button.image = image button.grid(row=0, column=1) - button = tk.Button(frame, image=Images.get(ImageEnum.DOCUMENTSAVE)) + image = Images.get(ImageEnum.DOCUMENTSAVE, 16) + button = ttk.Button(frame, image=image) + button.image = image button.grid(row=0, column=2) frame.grid(row=3, column=0, sticky="nsew") # tab 2 - label = tk.Label( + label = ttk.Label( tab2, text="Directories required by this service that are unique for each node.", ) @@ -162,23 +164,24 @@ class ServiceConfiguration(Dialog): for i in range(3): label_frame = None if i == 0: - label_frame = tk.LabelFrame(tab3, text="Startup commands") + label_frame = ttk.LabelFrame(tab3, text="Startup commands") commands = self.startup_commands + elif i == 1: - label_frame = tk.LabelFrame(tab3, text="Shutdown commands") + label_frame = ttk.LabelFrame(tab3, text="Shutdown commands") commands = self.shutdown_commands elif i == 2: - label_frame = tk.LabelFrame(tab3, text="Validation commands") + label_frame = ttk.LabelFrame(tab3, text="Validation commands") commands = self.validation_commands label_frame.columnconfigure(0, weight=1) - frame = tk.Frame(label_frame) + frame = ttk.Frame(label_frame) frame.columnconfigure(0, weight=1) - entry = tk.Entry(frame, textvariable=tk.StringVar()) + entry = ttk.Entry(frame, textvariable=tk.StringVar()) entry.grid(row=0, column=0, stick="nsew") - button = tk.Button(frame, image=self.documentnew_img) + button = ttk.Button(frame, image=self.documentnew_img) button.bind("", self.add_command) button.grid(row=0, column=1, sticky="nsew") - button = tk.Button(frame, image=self.editdelete_img) + button = ttk.Button(frame, image=self.editdelete_img) button.grid(row=0, column=2, sticky="nsew") button.bind("", self.delete_command) frame.grid(row=0, column=0, sticky="nsew") @@ -200,9 +203,9 @@ class ServiceConfiguration(Dialog): for i in range(2): label_frame = None if i == 0: - label_frame = tk.LabelFrame(tab4, text="Executables") + label_frame = ttk.LabelFrame(tab4, text="Executables") elif i == 1: - label_frame = tk.LabelFrame(tab4, text="Dependencies") + label_frame = ttk.LabelFrame(tab4, text="Dependencies") label_frame.columnconfigure(0, weight=1) listbox_scroll = ListboxScroll(label_frame) listbox_scroll.listbox.config(height=4) @@ -217,18 +220,18 @@ class ServiceConfiguration(Dialog): listbox_scroll.listbox.insert("end", dependency) for i in range(3): - frame = tk.Frame(tab4) + frame = ttk.Frame(tab4) frame.columnconfigure(0, weight=1) if i == 0: - label = tk.Label(frame, text="Validation time:") - self.validation_time_entry = tk.Entry( + label = ttk.Label(frame, text="Validation time:") + self.validation_time_entry = ttk.Entry( frame, state="disabled", textvariable=tk.StringVar(value=self.validation_time), ) self.validation_time_entry.grid(row=i, column=1) elif i == 1: - label = tk.Label(frame, text="Validation mode:") + label = ttk.Label(frame, text="Validation mode:") if self.validation_mode == core_pb2.ServiceValidationMode.BLOCKING: mode = "BLOCKING" elif ( @@ -237,36 +240,36 @@ class ServiceConfiguration(Dialog): mode = "NON_BLOCKING" elif self.validation_mode == core_pb2.ServiceValidationMode.TIMER: mode = "TIMER" - self.validation_mode_entry = tk.Entry( + self.validation_mode_entry = ttk.Entry( frame, state="disabled", textvariable=tk.StringVar(value=mode) ) self.validation_mode_entry.grid(row=i, column=1) elif i == 2: - label = tk.Label(frame, text="Validation period:") - self.validation_period_entry = tk.Entry( + label = ttk.Label(frame, text="Validation period:") + self.validation_period_entry = ttk.Entry( frame, state="disabled", textvariable=tk.StringVar() ) self.validation_period_entry.grid(row=i, column=1) label.grid(row=i, column=0) frame.grid(row=2 + i, column=0, sticky="nsew") - button = tk.Button( + button = ttk.Button( self, text="only store values that have changed from their defaults" ) button.grid(row=2, column=0) - frame = tk.Frame(self) - button = tk.Button(frame, text="Apply", command=self.click_apply) + frame = ttk.Frame(self) + button = ttk.Button(frame, text="Apply", command=self.click_apply) button.grid(row=0, column=0, sticky="nsew") - button = tk.Button( + button = ttk.Button( frame, text="Dafults", command=self.click_defaults, state="disabled" ) button.grid(row=0, column=1, sticky="nsew") - button = tk.Button( + button = ttk.Button( frame, text="Copy...", command=self.click_copy, state="disabled" ) button.grid(row=0, column=2, sticky="nsew") - button = tk.Button(frame, text="Cancel", command=self.destroy) + button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=3, sticky="nsew") frame.grid(row=3, column=0) @@ -330,20 +333,27 @@ class ServiceConfiguration(Dialog): validate_commands, shutdown_commands, ) + logging.info( + "%s, %s, %s, %s, %s", + metadata, + filenames, + startup_commands, + shutdown_commands, + validate_commands, + ) # wipe nodes and links when finished by setting to DEFINITION state self.app.core.client.set_session_state( self.app.core.session_id, core_pb2.SessionState.DEFINITION ) - print(metadata, filenames) def display_service_file_data(self, event): print("not implemented") def click_defaults(self): - print("not implemented") + logging.info("not implemented") def click_copy(self): - print("not implemented") + logging.info("not implemented") def click_cancel(self): - print("not implemented") + logging.info("not implemented") diff --git a/coretk/coretk/dialogs/sessions.py b/coretk/coretk/dialogs/sessions.py index 40d09c44..b9b7d77b 100644 --- a/coretk/coretk/dialogs/sessions.py +++ b/coretk/coretk/dialogs/sessions.py @@ -73,30 +73,36 @@ class SessionsDialog(Dialog): for i in range(4): frame.columnconfigure(i, weight=1) frame.grid(row=3, sticky="ew") + + image = Images.get(ImageEnum.DOCUMENTNEW, 16) b = ttk.Button( - frame, - image=Images.get(ImageEnum.DOCUMENTNEW), - text="New", - compound=tk.LEFT, - command=self.click_new, + frame, image=image, text="New", compound=tk.LEFT, command=self.click_new ) + b.image = image b.grid(row=0, padx=2, sticky="ew") + + image = Images.get(ImageEnum.FILEOPEN, 16) b = ttk.Button( frame, - image=Images.get(ImageEnum.FILEOPEN), + image=image, text="Connect", compound=tk.LEFT, command=self.click_connect, ) + b.image = image b.grid(row=0, column=1, padx=2, sticky="ew") + + image = Images.get(ImageEnum.EDITDELETE, 16) b = ttk.Button( frame, - image=Images.get(ImageEnum.EDITDELETE), + image=image, text="Shutdown", compound=tk.LEFT, command=self.click_shutdown, ) + b.image = image b.grid(row=0, column=2, padx=2, sticky="ew") + b = ttk.Button(frame, text="Cancel", command=self.click_new) b.grid(row=0, column=3, padx=2, sticky="ew") diff --git a/coretk/coretk/dialogs/wlanconfig.py b/coretk/coretk/dialogs/wlanconfig.py index 78dc2c22..d33f7ca0 100644 --- a/coretk/coretk/dialogs/wlanconfig.py +++ b/coretk/coretk/dialogs/wlanconfig.py @@ -3,6 +3,7 @@ wlan configuration """ import tkinter as tk +from tkinter import ttk from coretk.dialogs.dialog import Dialog from coretk.dialogs.icondialog import IconDialog @@ -10,12 +11,6 @@ from coretk.dialogs.icondialog import IconDialog class WlanConfigDialog(Dialog): def __init__(self, master, app, canvas_node, config): - """ - create an instance of WlanConfiguration - - :param coretk.grpah.CanvasGraph canvas: canvas object - :param coretk.graph.CanvasNode canvas_node: canvas node object - """ super().__init__( master, app, f"{canvas_node.name} Wlan Configuration", modal=True ) @@ -48,14 +43,14 @@ class WlanConfigDialog(Dialog): :return: nothing """ - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(pady=2, sticky="ew") frame.columnconfigure(0, weight=1) - entry = tk.Entry(frame, textvariable=self.name, bg="white") + entry = ttk.Entry(frame, textvariable=self.name) entry.grid(row=0, column=0, padx=2, sticky="ew") - self.image_button = tk.Button(frame, image=self.image, command=self.click_icon) + self.image_button = ttk.Button(frame, image=self.image, command=self.click_icon) self.image_button.grid(row=0, column=1, padx=3) def draw_wlan_config(self): @@ -64,15 +59,15 @@ class WlanConfigDialog(Dialog): :return: nothing """ - label = tk.Label(self, text="Wireless") + label = ttk.Label(self, text="Wireless") label.grid(sticky="w", pady=2) - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(pady=2, sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - label = tk.Label( + label = ttk.Label( frame, text=( "The basic range model calculates on/off " @@ -81,29 +76,29 @@ class WlanConfigDialog(Dialog): ) label.grid(row=0, columnspan=2, pady=2, sticky="ew") - label = tk.Label(frame, text="Range") + label = ttk.Label(frame, text="Range") label.grid(row=1, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.range_var) + entry = ttk.Entry(frame, textvariable=self.range_var) entry.grid(row=1, column=1, sticky="ew") - label = tk.Label(frame, text="Bandwidth (bps)") + label = ttk.Label(frame, text="Bandwidth (bps)") label.grid(row=2, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.bandwidth_var) + entry = ttk.Entry(frame, textvariable=self.bandwidth_var) entry.grid(row=2, column=1, sticky="ew") - label = tk.Label(frame, text="Delay (us)") + label = ttk.Label(frame, text="Delay (us)") label.grid(row=3, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.delay_var) + entry = ttk.Entry(frame, textvariable=self.delay_var) entry.grid(row=3, column=1, sticky="ew") - label = tk.Label(frame, text="Loss (%)") + label = ttk.Label(frame, text="Loss (%)") label.grid(row=4, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.loss_var) + entry = ttk.Entry(frame, textvariable=self.loss_var) entry.grid(row=4, column=1, sticky="ew") - label = tk.Label(frame, text="Jitter (us)") + label = ttk.Label(frame, text="Jitter (us)") label.grid(row=5, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.jitter_var) + entry = ttk.Entry(frame, textvariable=self.jitter_var) entry.grid(row=5, column=1, sticky="ew") def draw_subnet(self): @@ -113,19 +108,19 @@ class WlanConfigDialog(Dialog): :return: nothing """ - frame = tk.Frame(self) + frame = ttk.Frame(self) frame.grid(pady=3, sticky="ew") frame.columnconfigure(1, weight=1) frame.columnconfigure(3, weight=1) - label = tk.Label(frame, text="IPv4 Subnet") + label = ttk.Label(frame, text="IPv4 Subnet") label.grid(row=0, column=0, sticky="w") - entry = tk.Entry(frame, textvariable=self.ip4_subnet) + entry = ttk.Entry(frame, textvariable=self.ip4_subnet) entry.grid(row=0, column=1, sticky="ew") - label = tk.Label(frame, text="IPv6 Subnet") + label = ttk.Label(frame, text="IPv6 Subnet") label.grid(row=0, column=2, sticky="w") - entry = tk.Entry(frame, textvariable=self.ip6_subnet) + entry = ttk.Entry(frame, textvariable=self.ip6_subnet) entry.grid(row=0, column=3, sticky="ew") def draw_wlan_buttons(self): @@ -135,18 +130,18 @@ class WlanConfigDialog(Dialog): :return: """ - 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="ns-2 mobility script...") + button = ttk.Button(frame, text="ns-2 mobility script...") button.grid(row=0, column=0, padx=2, sticky="ew") - button = tk.Button(frame, text="Link to all routers") + button = ttk.Button(frame, text="Link to all routers") button.grid(row=0, column=1, padx=2, sticky="ew") - button = tk.Button(frame, text="Choose WLAN members") + button = ttk.Button(frame, text="Choose WLAN members") button.grid(row=0, column=2, padx=2, sticky="ew") def draw_apply_buttons(self): @@ -155,15 +150,15 @@ class WlanConfigDialog(Dialog): :return: nothing """ - 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="Apply", command=self.click_apply) + button = ttk.Button(frame, text="Apply", command=self.click_apply) button.grid(row=0, column=0, padx=2, 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, padx=2, sticky="ew") def click_icon(self): @@ -188,7 +183,6 @@ class WlanConfigDialog(Dialog): jitter = self.jitter_var.get() # set wireless node configuration here - wlanconfig_manager = self.app.core.wlanconfig_management wlanconfig_manager.set_custom_config( node_id=self.canvas_node.core_id, diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index e813ae48..5cdd2eec 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -146,9 +146,7 @@ class CanvasGraph(tk.Canvas): # peer to peer node is not drawn on the GUI if node.type != core_pb2.NodeType.PEER_TO_PEER: # draw nodes on the canvas - image, name = Images.convert_type_and_model_to_image( - node.type, node.model - ) + image, name = Images.node_icon(node.type, node.model) n = CanvasNode( node.position.x, node.position.y, image, name, self.master, node.id ) diff --git a/coretk/coretk/graph_helper.py b/coretk/coretk/graph_helper.py index bdedd65e..c1f59815 100644 --- a/coretk/coretk/graph_helper.py +++ b/coretk/coretk/graph_helper.py @@ -77,6 +77,7 @@ class WlanAntennaManager: self.quantity = 0 self._max = 5 self.antennas = [] + self.image = Images.get(ImageEnum.ANTENNA, 32) # distance between each antenna self.offset = 0 @@ -94,7 +95,7 @@ class WlanAntennaManager: x - 16 + self.offset, y - 16, anchor=tk.CENTER, - image=Images.get(ImageEnum.ANTENNA), + image=self.image, tags="antenna", ) ) diff --git a/coretk/coretk/images.py b/coretk/coretk/images.py index 726da5cc..7335e14e 100644 --- a/coretk/coretk/images.py +++ b/coretk/coretk/images.py @@ -6,35 +6,37 @@ from PIL import Image, ImageTk from core.api.grpc import core_pb2 from coretk.appconfig import LOCAL_ICONS_PATH +NODE_WIDTH = 32 + class Images: images = {} @classmethod - def create(cls, file_path): + def create(cls, file_path, width, height=None): + if height is None: + height = width image = Image.open(file_path) + image = image.resize((width, height), Image.ANTIALIAS) return ImageTk.PhotoImage(image) @classmethod def load_all(cls): for image in LOCAL_ICONS_PATH.glob("*"): - cls.load(image.stem, str(image)) + cls.images[image.stem] = str(image) @classmethod - def load(cls, name, file_path): - tk_image = cls.create(file_path) - cls.images[name] = tk_image + def get(cls, image_enum, width, height=None): + file_path = cls.images[image_enum.value] + return cls.create(file_path, width, height) @classmethod - def get(cls, image): - return cls.images[image.value] + def get_custom(cls, name, width, height): + file_path = cls.images[name] + return cls.create(file_path, width, height) @classmethod - def get_custom(cls, name): - return cls.images[name] - - @classmethod - def convert_type_and_model_to_image(cls, node_type, node_model): + def node_icon(cls, node_type, node_model): """ Retrieve image based on type and model :param core_pb2.NodeType node_type: core node type @@ -43,34 +45,48 @@ class Images: :rtype: tuple(PhotoImage, str) :return: the matching image and its name """ + image_enum = ImageEnum.ROUTER + name = "unknown" if node_type == core_pb2.NodeType.SWITCH: - return Images.get(ImageEnum.SWITCH), "switch" - if node_type == core_pb2.NodeType.HUB: - return Images.get(ImageEnum.HUB), "hub" - if node_type == core_pb2.NodeType.WIRELESS_LAN: - return Images.get(ImageEnum.WLAN), "wlan" - if node_type == core_pb2.NodeType.EMANE: - return Images.get(ImageEnum.EMANE), "emane" - - if node_type == core_pb2.NodeType.RJ45: - return Images.get(ImageEnum.RJ45), "rj45" - if node_type == core_pb2.NodeType.TUNNEL: - return Images.get(ImageEnum.TUNNEL), "tunnel" - if node_type == core_pb2.NodeType.DEFAULT: + image_enum = ImageEnum.SWITCH + name = "switch" + elif node_type == core_pb2.NodeType.HUB: + image_enum = ImageEnum.HUB + name = "hub" + elif node_type == core_pb2.NodeType.WIRELESS_LAN: + image_enum = ImageEnum.WLAN + name = "wlan" + elif node_type == core_pb2.NodeType.EMANE: + image_enum = ImageEnum.EMANE + name = "emane" + elif node_type == core_pb2.NodeType.RJ45: + image_enum = ImageEnum.RJ45 + name = "rj45" + elif node_type == core_pb2.NodeType.TUNNEL: + image_enum = ImageEnum.TUNNEL + name = "tunnel" + elif node_type == core_pb2.NodeType.DEFAULT: if node_model == "router": - return Images.get(ImageEnum.ROUTER), "router" - if node_model == "host": - return Images.get(ImageEnum.HOST), "host" - if node_model == "PC": - return Images.get(ImageEnum.PC), "PC" - if node_model == "mdr": - return Images.get(ImageEnum.MDR), "mdr" - if node_model == "prouter": - return Images.get(ImageEnum.PROUTER), "prouter" - if node_model == "OVS": - return Images.get(ImageEnum.OVS), "ovs" + image_enum = ImageEnum.ROUTER + name = "router" + elif node_model == "host": + image_enum = ImageEnum.HOST + name = "host" + elif node_model == "PC": + image_enum = ImageEnum.PC + name = "PC" + elif node_model == "mdr": + image_enum = ImageEnum.MDR + name = "mdr" + elif node_model == "prouter": + image_enum = ImageEnum.PROUTER + name = "prouter" + else: + logging.error("invalid node model: %s", node_model) else: - logging.debug("INVALID INPUT OR NOT CONSIDERED YET") + logging.error("invalid node type: %s", node_type) + + return Images.get(image_enum, NODE_WIDTH), name class ImageEnum(Enum): diff --git a/coretk/coretk/theme.py b/coretk/coretk/theme.py new file mode 100644 index 00000000..9525d35e --- /dev/null +++ b/coretk/coretk/theme.py @@ -0,0 +1,162 @@ +import tkinter as tk +from tkinter import ttk + + +class Colors: + disabledfg = "DarkGrey" + frame = "#424242" + dark = "#222222" + darker = "#121212" + darkest = "black" + lighter = "#626262" + lightest = "#ffffff" + selectbg = "#4a6984" + selectfg = "#ffffff" + white = "white" + black = "black" + + +style = ttk.Style() +style.theme_create( + "black", + "clam", + { + ".": { + "configure": { + "background": Colors.frame, + "foreground": Colors.white, + "bordercolor": Colors.darkest, + "darkcolor": Colors.dark, + "lightcolor": Colors.lighter, + "troughcolor": Colors.darker, + "selectbackground": Colors.selectbg, + "selectforeground": Colors.selectfg, + "selectborderwidth": 0, + "font": "TkDefaultFont", + }, + "map": { + "background": [("disabled", Colors.frame), ("active", Colors.lighter)], + "foreground": [("disabled", Colors.disabledfg)], + "selectbackground": [("!focus", Colors.darkest)], + "selectforeground": [("!focus", Colors.white)], + }, + }, + "TButton": { + "configure": {"width": 8, "padding": (5, 1), "relief": tk.RAISED}, + "map": { + "relief": [("pressed", tk.SUNKEN)], + "shiftrelief": [("pressed", 1)], + }, + }, + "TMenubutton": { + "configure": {"width": 11, "padding": (5, 1), "relief": tk.RAISED} + }, + "TCheckbutton": { + "configure": { + "indicatorbackground": Colors.white, + "indicatormargin": (1, 1, 4, 1), + } + }, + "TRadiobutton": { + "configure": { + "indicatorbackground": Colors.white, + "indicatormargin": (1, 1, 4, 1), + } + }, + "TEntry": { + "configure": { + "fieldbackground": Colors.white, + "foreground": Colors.black, + "padding": (2, 0), + } + }, + "TCombobox": { + "configure": { + "fieldbackground": Colors.white, + "foreground": Colors.black, + "padding": (2, 0), + } + }, + "TNotebook.Tab": { + "configure": {"padding": (6, 2, 6, 2)}, + "map": {"background": [("selected", Colors.lighter)]}, + }, + "Treeview": { + "configure": { + "fieldbackground": Colors.white, + "background": Colors.white, + "foreground": Colors.black, + }, + "map": { + "background": [("selected", Colors.selectbg)], + "foreground": [("selected", Colors.selectfg)], + }, + }, + }, +) +style.theme_use("black") + + +def update_menu(event): + bg = style.lookup(".", "background") + fg = style.lookup(".", "foreground") + abg = style.lookup(".", "lightcolor") + event.widget.config( + background=bg, foreground=fg, activebackground=abg, activeforeground=fg + ) + + +class Application(ttk.Frame): + def __init__(self, master=None): + super().__init__(master) + self.master.bind_class("Menu", "<>", update_menu) + self.master.geometry("800x600") + menu = tk.Menu(self.master) + menu.add_command(label="Command1") + menu.add_command(label="Command2") + submenu = tk.Menu(menu, tearoff=False) + submenu.add_command(label="Command1") + submenu.add_command(label="Command2") + menu.add_cascade(label="Submenu", menu=submenu) + self.master.config(menu=menu) + self.master.columnconfigure(0, weight=1) + self.master.rowconfigure(0, weight=1) + notebook = ttk.Notebook(self.master) + notebook.grid(sticky="nsew") + frame = ttk.Frame(notebook) + frame.grid(sticky="nsew") + ttk.Label(frame, text="Label").grid() + ttk.Entry(frame).grid() + ttk.Button(frame, text="Button").grid() + ttk.Combobox(frame, values=("one", "two", "three")).grid() + menubutton = ttk.Menubutton(frame, text="MenuButton") + menubutton.grid() + mbmenu = tk.Menu(menubutton, tearoff=False) + menubutton.config(menu=mbmenu) + mbmenu.add_command(label="Menu1") + mbmenu.add_command(label="Menu2") + submenu = tk.Menu(mbmenu, tearoff=False) + submenu.add_command(label="Command1") + submenu.add_command(label="Command2") + mbmenu.add_cascade(label="Submenu", menu=submenu) + ttk.Radiobutton(frame, text="Radio Button").grid() + ttk.Checkbutton(frame, text="Check Button").grid() + tv = ttk.Treeview(frame, columns=("one", "two", "three"), show="headings") + tv.grid() + tv.column("one", stretch=tk.YES) + tv.heading("one", text="ID") + tv.column("two", stretch=tk.YES) + tv.heading("two", text="State") + tv.column("three", stretch=tk.YES) + tv.heading("three", text="Node Count") + tv.insert("", tk.END, text="1", values=("v1", "v2", "v3")) + tv.insert("", tk.END, text="2", values=("v1", "v2", "v3")) + notebook.add(frame, text="Tab1") + frame = ttk.Frame(notebook) + frame.grid(sticky="nsew") + notebook.add(frame, text="Tab2") + + +if __name__ == "__main__": + app = Application() + app.mainloop() diff --git a/coretk/coretk/toolbar.py b/coretk/coretk/toolbar.py index 5b2c83bd..89393c3e 100644 --- a/coretk/coretk/toolbar.py +++ b/coretk/coretk/toolbar.py @@ -1,43 +1,44 @@ import logging import tkinter as tk from functools import partial +from tkinter import ttk from coretk.dialogs.customnodes import CustomNodesDialog from coretk.graph import GraphMode from coretk.images import ImageEnum, Images -from coretk.tooltip import CreateToolTip +from coretk.tooltip import Tooltip + +WIDTH = 32 -class Toolbar(tk.Frame): +def icon(image_enum): + return Images.get(image_enum, WIDTH) + + +class Toolbar(ttk.Frame): """ Core toolbar class """ - def __init__(self, master, app, cnf={}, **kwargs): + def __init__(self, master, app, **kwargs): """ Create a CoreToolbar instance :param tkinter.Frame edit_frame: edit frame """ - super().__init__(master, cnf, **kwargs) + super().__init__(master, **kwargs) self.app = app self.master = app.master - self.radio_value = tk.IntVar() - self.exec_radio_value = tk.IntVar() - # button dimension - self.width = 32 - self.height = 32 - - # Reference to the option menus - self.selection_tool_button = None - self.link_layer_option_menu = None - self.marker_option_menu = None - self.network_layer_option_menu = None + # design buttons + self.select_button = None + self.link_button = None self.node_button = None self.network_button = None self.annotation_button = None + # runtime buttons + # frames self.design_frame = None self.runtime_frame = None @@ -56,89 +57,77 @@ class Toolbar(tk.Frame): self.design_frame.tkraise() def draw_design_frame(self): - self.design_frame = tk.Frame(self) + self.design_frame = ttk.Frame(self) self.design_frame.grid(row=0, column=0, sticky="nsew") self.design_frame.columnconfigure(0, weight=1) - - self.create_regular_button( + self.create_button( self.design_frame, - Images.get(ImageEnum.START), - self.click_start_session_tool, + icon(ImageEnum.START), + self.click_start, "start the session", ) - self.create_radio_button( + self.select_button = self.create_button( self.design_frame, - Images.get(ImageEnum.SELECT), - self.click_selection_tool, - self.radio_value, - 1, + icon(ImageEnum.SELECT), + self.click_selection, "selection tool", ) - self.create_radio_button( - self.design_frame, - Images.get(ImageEnum.LINK), - self.click_link_tool, - self.radio_value, - 2, - "link tool", + self.link_button = self.create_button( + self.design_frame, icon(ImageEnum.LINK), self.click_link, "link tool" ) self.create_node_button() self.create_network_button() self.create_annotation_button() - self.radio_value.set(1) + + def design_select(self, button): + logging.info("selecting design button: %s", button) + self.select_button.state(["!pressed"]) + self.link_button.state(["!pressed"]) + self.node_button.state(["!pressed"]) + self.network_button.state(["!pressed"]) + self.annotation_button.state(["!pressed"]) + button.state(["pressed"]) def draw_runtime_frame(self): - self.runtime_frame = tk.Frame(self) + self.runtime_frame = ttk.Frame(self) self.runtime_frame.grid(row=0, column=0, sticky="nsew") self.runtime_frame.columnconfigure(0, weight=1) - self.create_regular_button( + self.create_button( self.runtime_frame, - Images.get(ImageEnum.STOP), - self.click_stop_button, + icon(ImageEnum.STOP), + self.click_stop, "stop the session", ) - self.create_radio_button( + self.create_button( self.runtime_frame, - Images.get(ImageEnum.SELECT), - self.click_selection_tool, - self.exec_radio_value, - 1, + icon(ImageEnum.SELECT), + self.click_selection, "selection tool", ) - self.create_observe_button() - self.create_radio_button( - self.runtime_frame, - Images.get(ImageEnum.PLOT), - self.click_plot_button, - self.exec_radio_value, - 2, - "plot", + # self.create_observe_button() + self.create_button( + self.runtime_frame, icon(ImageEnum.PLOT), self.click_plot_button, "plot" ) - self.create_radio_button( + self.create_button( self.runtime_frame, - Images.get(ImageEnum.MARKER), + icon(ImageEnum.MARKER), self.click_marker_button, - self.exec_radio_value, - 3, "marker", ) - self.create_radio_button( + self.create_button( self.runtime_frame, - Images.get(ImageEnum.TWONODE), + icon(ImageEnum.TWONODE), self.click_two_node_button, - self.exec_radio_value, - 4, "run command from one node to another", ) - self.create_regular_button( - self.runtime_frame, Images.get(ImageEnum.RUN), self.click_run_button, "run" + self.create_button( + self.runtime_frame, icon(ImageEnum.RUN), self.click_run_button, "run" ) - self.exec_radio_value.set(1) def draw_node_picker(self): self.hide_pickers() - self.node_picker = tk.Frame(self.master, padx=1, pady=1) + self.node_picker = ttk.Frame(self.master) nodes = [ (ImageEnum.ROUTER, "router"), (ImageEnum.HOST, "host"), @@ -148,26 +137,28 @@ class Toolbar(tk.Frame): ] # draw default nodes for image_enum, tooltip in nodes: - image = Images.get(image_enum) + image = icon(image_enum) func = partial(self.update_button, self.node_button, image, tooltip) - self.create_button(image, func, self.node_picker, tooltip) + self.create_picker_button(image, func, self.node_picker, tooltip) # draw custom nodes for name in sorted(self.app.core.custom_nodes): custom_node = self.app.core.custom_nodes[name] image = custom_node.image func = partial(self.update_button, self.node_button, image, name) - self.create_button(image, func, self.node_picker, name) + self.create_picker_button(image, func, self.node_picker, name) # draw edit node - image = Images.get(ImageEnum.EDITNODE) - self.create_button( + image = icon(ImageEnum.EDITNODE) + self.create_picker_button( image, self.click_edit_node, self.node_picker, "custom nodes" ) - self.show_picker(self.node_button, self.node_picker) + self.design_select(self.node_button) + self.node_button.after( + 0, lambda: self.show_picker(self.node_button, self.node_picker) + ) def show_picker(self, button, picker): - first_button = self.winfo_children()[0] - x = button.winfo_rootx() - first_button.winfo_rootx() + 40 - y = button.winfo_rooty() - first_button.winfo_rooty() - 1 + x = self.winfo_width() + 1 + y = button.winfo_rooty() - picker.master.winfo_rooty() - 1 picker.place(x=x, y=y) self.app.bind_all("", lambda e: self.hide_pickers()) picker.wait_visibility() @@ -175,7 +166,7 @@ class Toolbar(tk.Frame): self.wait_window(picker) self.app.unbind_all("") - def create_button(self, image, func, frame, tooltip): + def create_picker_button(self, image, func, frame, tooltip): """ Create button and put it on the frame @@ -185,37 +176,25 @@ class Toolbar(tk.Frame): :param str tooltip: tooltip text :return: nothing """ - button = tk.Button(frame, width=self.width, height=self.height, image=image) + button = ttk.Button(frame, image=image) + button.image = image button.bind("", lambda e: func()) button.grid(pady=1) - CreateToolTip(button, tooltip) + Tooltip(button, tooltip) - def create_radio_button(self, frame, image, func, variable, value, tooltip_msg): - button = tk.Radiobutton( - frame, - indicatoron=False, - width=self.width, - height=self.height, - image=image, - value=value, - variable=variable, - command=func, - ) - button.grid() - CreateToolTip(button, tooltip_msg) + def create_button(self, frame, image, func, tooltip): + button = ttk.Button(frame, image=image, command=func) + button.image = image + button.grid(sticky="ew") + Tooltip(button, tooltip) + return button - def create_regular_button(self, frame, image, func, tooltip): - button = tk.Button( - frame, width=self.width, height=self.height, image=image, command=func - ) - button.grid() - CreateToolTip(button, tooltip) - - def click_selection_tool(self): + def click_selection(self): logging.debug("clicked selection tool") + self.design_select(self.select_button) self.app.canvas.mode = GraphMode.SELECT - def click_start_session_tool(self): + def click_start(self): """ Start session handler redraw buttons, send node and link messages to grpc server. @@ -227,8 +206,9 @@ class Toolbar(tk.Frame): self.app.core.start_session() self.runtime_frame.tkraise() - def click_link_tool(self): + def click_link(self): logging.debug("Click LINK button") + self.design_select(self.link_button) self.app.canvas.mode = GraphMode.EDGE def click_edit_node(self): @@ -240,6 +220,7 @@ class Toolbar(tk.Frame): logging.info("update button(%s): %s", button, name) self.hide_pickers() button.configure(image=image) + button.image = image self.app.canvas.mode = GraphMode.NODE self.app.canvas.draw_node_image = image self.app.canvas.draw_node_name = name @@ -262,29 +243,22 @@ class Toolbar(tk.Frame): :return: nothing """ - router_image = Images.get(ImageEnum.ROUTER) - self.node_button = tk.Radiobutton( - self.design_frame, - indicatoron=False, - variable=self.radio_value, - value=3, - width=self.width, - height=self.height, - image=router_image, + image = icon(ImageEnum.ROUTER) + self.node_button = ttk.Button( + self.design_frame, image=image, command=self.draw_node_picker ) - self.node_button.bind("", lambda e: self.draw_node_picker()) - self.node_button.grid() - CreateToolTip(self.node_button, "Network-layer virtual nodes") + self.node_button.image = image + self.node_button.grid(sticky="ew") + Tooltip(self.node_button, "Network-layer virtual nodes") def draw_network_picker(self): """ - Draw the options for link-layer button + Draw the options for link-layer button. - :param tkinter.RadioButton link_layer_button: link-layer button :return: nothing """ self.hide_pickers() - self.network_picker = tk.Frame(self.master, padx=1, pady=1) + self.network_picker = ttk.Frame(self.master) nodes = [ (ImageEnum.HUB, "hub", "ethernet hub"), (ImageEnum.SWITCH, "switch", "ethernet switch"), @@ -294,14 +268,17 @@ class Toolbar(tk.Frame): (ImageEnum.TUNNEL, "tunnel", "tunnel tool"), ] for image_enum, name, tooltip in nodes: - image = Images.get(image_enum) - self.create_button( + image = icon(image_enum) + self.create_picker_button( image, partial(self.update_button, self.network_button, image, name), self.network_picker, tooltip, ) - self.show_picker(self.network_button, self.network_picker) + self.design_select(self.network_button) + self.network_button.after( + 0, lambda: self.show_picker(self.network_button, self.network_picker) + ) def create_network_button(self): """ @@ -309,31 +286,22 @@ class Toolbar(tk.Frame): :return: nothing """ - hub_image = Images.get(ImageEnum.HUB) - self.network_button = tk.Radiobutton( - self.design_frame, - indicatoron=False, - variable=self.radio_value, - value=4, - width=self.width, - height=self.height, - image=hub_image, + image = icon(ImageEnum.HUB) + self.network_button = ttk.Button( + self.design_frame, image=image, command=self.draw_network_picker ) - self.network_button.bind( - "", lambda e: self.draw_network_picker() - ) - self.network_button.grid() - CreateToolTip(self.network_button, "link-layer nodes") + self.network_button.image = image + self.network_button.grid(sticky="ew") + Tooltip(self.network_button, "link-layer nodes") def draw_annotation_picker(self): """ - Draw the options for marker button + Draw the options for marker button. - :param tkinter.Radiobutton main_button: the main button :return: nothing """ self.hide_pickers() - self.annotation_picker = tk.Frame(self.master, padx=1, pady=1) + self.annotation_picker = ttk.Frame(self.master) nodes = [ (ImageEnum.MARKER, "marker"), (ImageEnum.OVAL, "oval"), @@ -341,13 +309,17 @@ class Toolbar(tk.Frame): (ImageEnum.TEXT, "text"), ] for image_enum, tooltip in nodes: - self.create_button( - Images.get(image_enum), - partial(self.update_annotation, image_enum), + image = icon(image_enum) + self.create_picker_button( + image, + partial(self.update_annotation, image), self.annotation_picker, tooltip, ) - self.show_picker(self.annotation_button, self.annotation_picker) + self.design_select(self.annotation_button) + self.annotation_button.after( + 0, lambda: self.show_picker(self.annotation_button, self.annotation_picker) + ) def create_annotation_button(self): """ @@ -355,53 +327,39 @@ class Toolbar(tk.Frame): :return: nothing """ - marker_image = Images.get(ImageEnum.MARKER) - self.annotation_button = tk.Radiobutton( - self.design_frame, - indicatoron=False, - variable=self.radio_value, - value=5, - width=self.width, - height=self.height, - image=marker_image, + image = icon(ImageEnum.MARKER) + self.annotation_button = ttk.Button( + self.design_frame, image=image, command=self.draw_annotation_picker ) - self.annotation_button.bind( - "", lambda e: self.draw_annotation_picker() - ) - self.annotation_button.grid() - CreateToolTip(self.annotation_button, "background annotation tools") + self.annotation_button.image = image + self.annotation_button.grid(sticky="ew") + Tooltip(self.annotation_button, "background annotation tools") def create_observe_button(self): - menu_button = tk.Menubutton( - self.runtime_frame, - image=Images.get(ImageEnum.OBSERVE), - width=self.width, - height=self.height, - direction=tk.RIGHT, - relief=tk.RAISED, + menu_button = ttk.Menubutton( + self.runtime_frame, image=icon(ImageEnum.OBSERVE), direction=tk.RIGHT ) - menu_button.menu = tk.Menu(menu_button, tearoff=0) - menu_button["menu"] = menu_button.menu - menu_button.grid() + menu_button.grid(sticky="ew") + menu = tk.Menu(menu_button, tearoff=0) + menu_button["menu"] = menu + menu.add_command(label="None") + menu.add_command(label="processes") + menu.add_command(label="ifconfig") + menu.add_command(label="IPv4 routes") + menu.add_command(label="IPv6 routes") + menu.add_command(label="OSPFv2 neighbors") + menu.add_command(label="OSPFv3 neighbors") + menu.add_command(label="Listening sockets") + menu.add_command(label="IPv4 MFC entries") + menu.add_command(label="IPv6 MFC entries") + menu.add_command(label="firewall rules") + menu.add_command(label="IPSec policies") + menu.add_command(label="docker logs") + menu.add_command(label="OSPFv3 MDR level") + menu.add_command(label="PIM neighbors") + menu.add_command(label="Edit...") - menu_button.menu.add_command(label="None") - menu_button.menu.add_command(label="processes") - menu_button.menu.add_command(label="ifconfig") - menu_button.menu.add_command(label="IPv4 routes") - menu_button.menu.add_command(label="IPv6 routes") - menu_button.menu.add_command(label="OSPFv2 neighbors") - menu_button.menu.add_command(label="OSPFv3 neighbors") - menu_button.menu.add_command(label="Listening sockets") - menu_button.menu.add_command(label="IPv4 MFC entries") - menu_button.menu.add_command(label="IPv6 MFC entries") - menu_button.menu.add_command(label="firewall rules") - menu_button.menu.add_command(label="IPSec policies") - menu_button.menu.add_command(label="docker logs") - menu_button.menu.add_command(label="OSPFv3 MDR level") - menu_button.menu.add_command(label="PIM neighbors") - menu_button.menu.add_command(label="Edit...") - - def click_stop_button(self): + def click_stop(self): """ redraw buttons on the toolbar, send node and link messages to grpc server @@ -411,10 +369,11 @@ class Toolbar(tk.Frame): self.app.core.stop_session() self.design_frame.tkraise() - def update_annotation(self, image_enum): + def update_annotation(self, image): logging.info("clicked annotation: ") self.hide_pickers() - self.annotation_button.configure(image=Images.get(image_enum)) + self.annotation_button.configure(image=image) + self.annotation_button.image = image def click_run_button(self): logging.debug("Click on RUN button") diff --git a/coretk/coretk/tooltip.py b/coretk/coretk/tooltip.py index 6fbbc3c9..1877fd24 100644 --- a/coretk/coretk/tooltip.py +++ b/coretk/coretk/tooltip.py @@ -1,7 +1,8 @@ import tkinter as tk +from tkinter import ttk -class CreateToolTip(object): +class Tooltip(object): """ Create tool tip for a given widget """ @@ -9,10 +10,29 @@ class CreateToolTip(object): def __init__(self, widget, text="widget info"): self.widget = widget self.text = text - self.widget.bind("", self.enter) - self.widget.bind("", self.close) + self.widget.bind("", self.on_enter) + self.widget.bind("", self.on_leave) + self.waittime = 400 + self.id = None self.tw = None + def on_enter(self, event=None): + self.schedule() + + def on_leave(self, event=None): + self.unschedule() + self.close(event) + + def schedule(self): + self.unschedule() + self.id = self.widget.after(self.waittime, self.enter) + + def unschedule(self): + id_ = self.id + self.id = None + if id_: + self.widget.after_cancel(id_) + def enter(self, event=None): x, y, cx, cy = self.widget.bbox("insert") x += self.widget.winfo_rootx() @@ -21,13 +41,13 @@ class CreateToolTip(object): self.tw = tk.Toplevel(self.widget) self.tw.wm_overrideredirect(True) self.tw.wm_geometry("+%d+%d" % (x, y)) - label = tk.Label( + label = ttk.Label( self.tw, text=self.text, justify=tk.LEFT, - background="#ffffe6", - relief="solid", - borderwidth=1, + background="#FFFFEA", + relief=tk.SOLID, + borderwidth=0, ) label.grid(padx=1)