diff --git a/coretk/coretk/canvasaction.py b/coretk/coretk/canvasaction.py new file mode 100644 index 00000000..addd2cd8 --- /dev/null +++ b/coretk/coretk/canvasaction.py @@ -0,0 +1,38 @@ +""" +canvas graph action +""" + +# import tkinter as tk + +from core.api.grpc import core_pb2 +from coretk.nodeconfigtable import NodeConfig +from coretk.wlanconfiguration import WlanConfiguration + +NODE_TO_TYPE = { + "router": core_pb2.NodeType.DEFAULT, + "wlan": core_pb2.NodeType.WIRELESS_LAN, +} + + +class CanvasAction: + def __init__(self, master, canvas): + self.master = master + + self.canvas = canvas + self.node_to_show_config = None + + def display_configuration(self, canvas_node): + pb_type = NODE_TO_TYPE[canvas_node.node_type] + self.node_to_show_config = canvas_node + if pb_type == core_pb2.NodeType.DEFAULT: + self.display_node_configuration() + elif pb_type == core_pb2.NodeType.WIRELESS_LAN: + self.display_wlan_configuration() + + def display_node_configuration(self): + NodeConfig(self.canvas, self.node_to_show_config) + self.node_to_show_config = None + + def display_wlan_configuration(self): + WlanConfiguration(self.canvas, self.node_to_show_config) + self.node_to_show_config = None diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index 4107401b..dd3bbc1c 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -3,12 +3,14 @@ import logging import tkinter as tk from core.api.grpc import core_pb2 +from coretk.canvasaction import CanvasAction from coretk.graph_helper import GraphHelper, WlanAntennaManager from coretk.grpcmanagement import GrpcManager from coretk.images import Images from coretk.interface import Interface from coretk.linkinfo import LinkInfo -from coretk.nodeconfigtable import NodeConfig + +# from coretk.nodeconfigtable import NodeConfig class GraphMode(enum.Enum): @@ -19,6 +21,12 @@ class GraphMode(enum.Enum): OTHER = 4 +CORE_NODES = ["router"] +CORE_WIRED_NETWORK_NODES = [] +CORE_WIRELESS_NODE = ["wlan"] +CORE_EMANE = ["emane"] + + class CanvasGraph(tk.Canvas): def __init__(self, master=None, grpc=None, cnf=None, **kwargs): if cnf is None: @@ -36,6 +44,7 @@ class CanvasGraph(tk.Canvas): self.grid = None self.meters_per_pixel = 1.5 + self.canvas_action = CanvasAction(master, self) self.setup_menus() self.setup_bindings() self.draw_grid() @@ -44,15 +53,30 @@ class CanvasGraph(tk.Canvas): self.grpc_manager = GrpcManager(grpc) self.helper = GraphHelper(self, grpc) + self.is_node_context_opened = False # self.core_id_to_canvas_id = {} # self.core_map = CoreToCanvasMapping() # self.draw_existing_component() + def test(self): + print("testing the button") + print(self.node_context.winfo_rootx()) + def setup_menus(self): self.node_context = tk.Menu(self.master) - self.node_context.add_command(label="One") - self.node_context.add_command(label="Two") - self.node_context.add_command(label="Three") + self.node_context.add_command( + label="Configure", command=self.canvas_action.display_node_configuration + ) + self.node_context.add_command(label="Select adjacent") + self.node_context.add_command(label="Create link to") + self.node_context.add_command(label="Assign to") + self.node_context.add_command(label="Move to") + self.node_context.add_command(label="Cut") + self.node_context.add_command(label="Copy") + self.node_context.add_command(label="Paste") + self.node_context.add_command(label="Delete") + self.node_context.add_command(label="Hide") + self.node_context.add_command(label="Services") def canvas_reset_and_redraw(self, new_grpc): """ @@ -74,14 +98,11 @@ class CanvasGraph(tk.Canvas): self.edges = {} self.drawing_edge = None - print("graph.py create a new grpc manager") self.grpc_manager = GrpcManager(new_grpc) # new grpc self.core_grpc = new_grpc - print("grpah.py draw existing component") self.draw_existing_component() - print(self.grpc_manager.edges) def setup_bindings(self): """ @@ -218,8 +239,6 @@ class CanvasGraph(tk.Canvas): ].interfaces.append(if2) # lift the nodes so they on top of the links - # for i in core_id_to_canvas_id.values(): - # self.lift(i) for i in self.find_withtag("node"): self.lift(i) @@ -272,16 +291,20 @@ class CanvasGraph(tk.Canvas): :param event: mouse event :return: nothing """ - self.focus_set() - self.selected = self.get_selected(event) - logging.debug(f"click release selected: {self.selected}") - if self.mode == GraphMode.EDGE: - self.handle_edge_release(event) - elif self.mode == GraphMode.NODE: - x, y = self.canvas_xy(event) - self.add_node(x, y, self.draw_node_image, self.draw_node_name) - elif self.mode == GraphMode.PICKNODE: - self.mode = GraphMode.NODE + if self.is_node_context_opened: + self.node_context.unpost() + self.is_node_context_opened = False + else: + self.focus_set() + self.selected = self.get_selected(event) + logging.debug(f"click release selected: {self.selected}") + if self.mode == GraphMode.EDGE: + self.handle_edge_release(event) + elif self.mode == GraphMode.NODE: + x, y = self.canvas_xy(event) + self.add_node(x, y, self.draw_node_image, self.draw_node_name) + elif self.mode == GraphMode.PICKNODE: + self.mode = GraphMode.NODE def handle_edge_release(self, event): edge = self.drawing_edge @@ -367,11 +390,17 @@ class CanvasGraph(tk.Canvas): self.coords(self.drawing_edge.id, x1, y1, x2, y2) def context(self, event): - selected = self.get_selected(event) - nodes = self.find_withtag("node") - if selected in nodes: - logging.debug(f"node context: {selected}") - self.node_context.post(event.x_root, event.y_root) + if not self.is_node_context_opened: + selected = self.get_selected(event) + nodes = self.find_withtag("node") + if selected in nodes: + logging.debug(f"node context: {selected}") + self.node_context.post(event.x_root, event.y_root) + self.canvas_action.node_to_show_config = self.nodes[selected] + self.is_node_context_opened = True + else: + self.node_context.unpost() + self.is_node_context_opened = False def add_node(self, x, y, image, node_name): plot_id = self.find_all()[0] @@ -482,8 +511,15 @@ class CanvasNode: if state == core_pb2.SessionState.RUNTIME: self.canvas.core_grpc.launch_terminal(node_id) else: - print("config table show up") - NodeConfig(self, self.image, self.node_type, self.name) + self.canvas.canvas_action.display_configuration(self) + # if self.node_type in CORE_NODES: + # self.canvas.canvas_action.node_to_show_config = self + # self.canvas.canvas_action.display_node_configuration() + # elif self.node_type in CORE_WIRED_NETWORK_NODES: + # return + # elif self.node_type in CORE_WIRELESS_NODE: + # return + # elif self def update_coords(self): self.x_coord, self.y_coord = self.canvas.coords(self.id) diff --git a/coretk/coretk/grpcmanagement.py b/coretk/coretk/grpcmanagement.py index 9638c7c5..4cfc6a16 100644 --- a/coretk/coretk/grpcmanagement.py +++ b/coretk/coretk/grpcmanagement.py @@ -169,7 +169,6 @@ class GrpcManager: # update the next available id core_id = core_node.id - print(core_id) if self.id is None or core_id >= self.id: self.id = core_id + 1 self.preexisting.append(core_id) diff --git a/coretk/coretk/imagemodification.py b/coretk/coretk/imagemodification.py new file mode 100644 index 00000000..646f31b7 --- /dev/null +++ b/coretk/coretk/imagemodification.py @@ -0,0 +1,91 @@ +""" +node image modification +""" + + +import os +import tkinter as tk +from tkinter import filedialog + +from PIL import Image, ImageTk + +PATH = os.path.abspath(os.path.dirname(__file__)) +ICONS_DIR = os.path.join(PATH, "icons") + + +class ImageModification: + def __init__(self, canvas, canvas_node, node_config): + """ + create an instance of ImageModification + :param coretk.graph.CanvasGraph canvas: canvas object + :param coretk.graph.CanvasNode canvas_node: node object + :param coretk.nodeconfigtable.NodeConfig node_config: node configuration object + """ + self.canvas = canvas + self.image = canvas_node.image + self.node_type = canvas_node.node_type + self.name = canvas_node.name + self.canvas_node = canvas_node + self.node_configuration = node_config + self.p_top = node_config.top + + self.top = tk.Toplevel() + self.top.title(self.name + " image") + self.image_modification() + + def open_icon_dir(self, toplevel, entry_text): + filename = filedialog.askopenfilename( + initialdir=ICONS_DIR, + title="Open", + filetypes=( + ("images", "*.gif *.jpg *.png *.bmp *pcx *.tga ..."), + ("All Files", "*"), + ), + ) + if len(filename) > 0: + img = Image.open(filename) + tk_img = ImageTk.PhotoImage(img) + lb = toplevel.grid_slaves(1, 0)[0] + lb.configure(image=tk_img) + lb.image = tk_img + entry_text.set(filename) + + def click_apply(self, toplevel, entry_text): + imgfile = entry_text.get() + if imgfile: + img = Image.open(imgfile) + tk_img = ImageTk.PhotoImage(img) + f = self.p_top.grid_slaves(row=0, column=0)[0] + lb = f.grid_slaves(row=0, column=3)[0] + lb.configure(image=tk_img) + lb.image = tk_img + self.image = tk_img + self.node_configuration.image = tk_img + toplevel.destroy() + + def image_modification(self): + f = tk.Frame(self.top) + entry_text = tk.StringVar() + image_file_label = tk.Label(f, text="Image file: ") + image_file_label.grid(row=0, column=0) + image_file_entry = tk.Entry(f, textvariable=entry_text, width=32, bg="white") + image_file_entry.grid(row=0, column=1) + image_file_button = tk.Button( + f, text="...", command=lambda: self.open_icon_dir(self.top, entry_text) + ) + image_file_button.grid(row=0, column=2) + f.grid() + + img = tk.Label(self.top, image=self.image) + img.grid() + + f = tk.Frame(self.top) + apply_button = tk.Button( + f, text="Apply", command=lambda: self.click_apply(self.top, entry_text) + ) + apply_button.grid(row=0, column=0) + apply_to_multiple_button = tk.Button(f, text="Apply to multiple...") + apply_to_multiple_button.grid(row=0, column=1) + cancel_button = tk.Button(f, text="Cancel", command=self.top.destroy) + cancel_button.grid(row=0, column=2) + f.grid() diff --git a/coretk/coretk/nodeconfigtable.py b/coretk/coretk/nodeconfigtable.py index 76550289..6d03f52f 100644 --- a/coretk/coretk/nodeconfigtable.py +++ b/coretk/coretk/nodeconfigtable.py @@ -8,6 +8,8 @@ from tkinter import filedialog from PIL import Image, ImageTk +from coretk.imagemodification import ImageModification + PATH = os.path.abspath(os.path.dirname(__file__)) ICONS_DIR = os.path.join(PATH, "icons") @@ -16,14 +18,15 @@ DEFAULTNODES = ["router", "host", "PC"] class NodeConfig: - def __init__(self, canvas_node, image, node_type, name): - self.image = image - self.node_type = node_type - self.name = name + def __init__(self, canvas, canvas_node): + self.canvas = canvas + self.image = canvas_node.image + self.node_type = canvas_node.node_type + self.name = canvas_node.name self.canvas_node = canvas_node self.top = tk.Toplevel() - self.top.title(node_type + " configuration") + self.top.title(canvas_node.node_type + " configuration") self.namevar = tk.StringVar(self.top, value="default name") self.name_and_image_definition() self.type_and_service_definition() @@ -58,65 +61,67 @@ class NodeConfig: toplevel.destroy() def img_modification(self): - print("image modification") t = tk.Toplevel() t.title(self.name + " image") f = tk.Frame(t) entry_text = tk.StringVar() image_file_label = tk.Label(f, text="Image file: ") - image_file_label.pack(side=tk.LEFT, padx=2, pady=2) - image_file_entry = tk.Entry(f, textvariable=entry_text, width=60) - image_file_entry.pack(side=tk.LEFT, padx=2, pady=2) + image_file_label.grid(row=0, column=0) + image_file_entry = tk.Entry(f, textvariable=entry_text, width=32, bg="white") + image_file_entry.grid(row=0, column=1) image_file_button = tk.Button( f, text="...", command=lambda: self.open_icon_dir(t, entry_text) ) - image_file_button.pack(side=tk.LEFT, padx=2, pady=2) - f.grid(sticky=tk.W + tk.E) + image_file_button.grid(row=0, column=2) + f.grid() img = tk.Label(t, image=self.image) - img.grid(sticky=tk.W + tk.E) + img.grid() f = tk.Frame(t) apply_button = tk.Button( f, text="Apply", command=lambda: self.click_apply(t, entry_text) ) - apply_button.pack(side=tk.LEFT, padx=2, pady=2) + apply_button.grid(row=0, column=0) apply_to_multiple_button = tk.Button(f, text="Apply to multiple...") - apply_to_multiple_button.pack(side=tk.LEFT, padx=2, pady=2) + apply_to_multiple_button.grid(row=0, column=1) cancel_button = tk.Button(f, text="Cancel", command=t.destroy) - cancel_button.pack(side=tk.LEFT, padx=2, pady=2) - f.grid(sticky=tk.E + tk.W) + cancel_button.grid(row=0, column=2) + f.grid() def name_and_image_definition(self): - name_label = tk.Label(self.top, text="Node name: ") - name_label.grid() - name_entry = tk.Entry(self.top, textvariable=self.namevar) - name_entry.grid(row=0, column=1) + f = tk.Frame(self.top, bg="#d9d9d9") + name_label = tk.Label(f, text="Node name: ", bg="#d9d9d9") + name_label.grid(padx=2, pady=2) + name_entry = tk.Entry(f, textvariable=self.namevar) + name_entry.grid(row=0, column=1, padx=2, pady=2) - core_button = tk.Button(self.top, text="None") - core_button.grid(row=0, column=2) + core_button = tk.Button(f, text="None") + core_button.grid(row=0, column=2, padx=2, pady=2) img_button = tk.Button( - self.top, + f, image=self.image, width=40, height=40, - command=self.img_modification, + command=lambda: ImageModification(self.canvas, self.canvas_node, self), + bg="#d9d9d9", ) - img_button.grid(row=0, column=3) + img_button.grid(row=0, column=3, padx=4, pady=4) + f.grid(padx=4, pady=4) def type_and_service_definition(self): f = tk.Frame(self.top) type_label = tk.Label(f, text="Type: ") - type_label.pack(side=tk.LEFT) + type_label.grid(row=0, column=0) type_button = tk.Button(f, text="None") - type_button.pack(side=tk.LEFT) + type_button.grid(row=0, column=1) service_button = tk.Button(f, text="Services...") - service_button.pack(side=tk.LEFT) + service_button.grid(row=0, column=2) - f.grid(row=1, column=1, columnspan=2, sticky=tk.W) + f.grid(padx=2, pady=2) def config_apply(self): """ @@ -140,10 +145,10 @@ class NodeConfig: def select_definition(self): f = tk.Frame(self.top) apply_button = tk.Button(f, text="Apply", command=self.config_apply) - apply_button.pack(side=tk.LEFT) + apply_button.grid(row=0, column=0) cancel_button = tk.Button(f, text="Cancel", command=self.config_cancel) - cancel_button.pack(side=tk.LEFT) - f.grid(row=3, column=1, sticky=tk.W) + cancel_button.grid(row=0, column=1) + f.grid() def network_node_config(self): self.name_and_image_definition() diff --git a/coretk/coretk/serviceconfiguration.py b/coretk/coretk/serviceconfiguration.py new file mode 100644 index 00000000..a539f1fb --- /dev/null +++ b/coretk/coretk/serviceconfiguration.py @@ -0,0 +1,10 @@ +""" +service configuration +""" + +# import tkinter as tk + + +class ServiceConfiguration: + def __init__(self): + return diff --git a/coretk/coretk/wlanconfiguration.py b/coretk/coretk/wlanconfiguration.py new file mode 100644 index 00000000..53e98ea5 --- /dev/null +++ b/coretk/coretk/wlanconfiguration.py @@ -0,0 +1,181 @@ +""" +wlan configuration +""" + +import ast +import tkinter as tk +from functools import partial + + +class WlanConfiguration: + def __init__(self, canvas, canvas_node): + + self.canvas = canvas + self.image = canvas_node.image + self.node_type = canvas_node.node_type + self.name = canvas_node.name + self.canvas_node = canvas_node + + self.top = tk.Toplevel() + self.top.title("wlan configuration") + self.node_name = tk.StringVar() + + self.range_var = tk.DoubleVar() + self.range_var.set(275.0) + self.bandwidth_var = tk.IntVar() + self.bandwidth_var.set(54000000) + + self.delay_var = tk.StringVar() + + self.image_modification() + self.wlan_configuration() + self.subnet() + self.wlan_options() + self.config_option() + + def image_modification(self): + f = tk.Frame(self.top, bg="#d9d9d9") + lbl = tk.Label(f, text="Node name: ", bg="#d9d9d9") + lbl.grid(row=0, column=0, padx=3, pady=3) + e = tk.Entry(f, textvariable=self.node_name, bg="white") + e.grid(row=0, column=1, padx=3, pady=3) + b = tk.Button(f, text="None") + b.grid(row=0, column=2, padx=3, pady=3) + b = tk.Button(f, text="not implemented") + b.grid(row=0, column=3, padx=3, pady=3) + f.grid(padx=2, pady=2, ipadx=2, ipady=2) + + def create_string_var(self, val): + v = tk.StringVar() + v.set(val) + return v + + def scrollbar_command(self, entry_widget, delta, event): + try: + value = int(entry_widget.get()) + except ValueError: + value = ast.literal_eval(entry_widget.get()) + entry_widget.delete(0, tk.END) + if event == "-1": + entry_widget.insert(tk.END, str(round(value + delta, 1))) + elif event == "1": + entry_widget.insert(tk.END, str(round(value - delta, 1))) + + def wlan_configuration(self): + lbl = tk.Label(self.top, text="Wireless") + lbl.grid(sticky=tk.W, padx=3, pady=3) + + f = tk.Frame( + self.top, + highlightbackground="#b3b3b3", + highlightcolor="#b3b3b3", + highlightthickness=0.5, + bd=0, + bg="#d9d9d9", + ) + + lbl = tk.Label( + f, + text="The basic range model calculates on/off connectivity based on pixel distance between nodes.", + bg="#d9d9d9", + ) + lbl.grid(padx=4, pady=4) + + f1 = tk.Frame(f, bg="#d9d9d9") + + lbl = tk.Label(f1, text="Range: ", bg="#d9d9d9") + lbl.grid(row=0, column=0) + + e = tk.Entry(f1, textvariable=self.range_var, width=5, bg="white") + e.grid(row=0, column=1) + + lbl = tk.Label(f1, text="Bandwidth (bps): ", bg="#d9d9d9") + lbl.grid(row=0, column=2) + + f11 = tk.Frame(f1, bg="#d9d9d9") + sb = tk.Scrollbar(f11, orient=tk.VERTICAL) + e = tk.Entry(f11, textvariable=self.bandwidth_var, width=10, bg="white") + sb.config(command=partial(self.scrollbar_command, e, 1000000)) + e.grid() + sb.grid(row=0, column=1) + f11.grid(row=0, column=3) + + # e = tk.Entry(f1, textvariable=self.bandwidth_var, width=10) + # e.grid(row=0, column=4) + f1.grid(sticky=tk.W, padx=4, pady=4) + + f2 = tk.Frame(f, bg="#d9d9d9") + lbl = tk.Label(f2, text="Delay (us): ", bg="#d9d9d9") + lbl.grid(row=0, column=0) + + f21 = tk.Frame(f2, bg="#d9d9d9") + sb = tk.Scrollbar(f21, orient=tk.VERTICAL) + e = tk.Entry(f21, textvariable=self.create_string_var(20000), bg="white") + sb.config(command=partial(self.scrollbar_command, e, 5000)) + e.grid() + sb.grid(row=0, column=1) + f21.grid(row=0, column=1) + + lbl = tk.Label(f2, text="Loss (%): ", bg="#d9d9d9") + lbl.grid(row=0, column=2) + + f22 = tk.Frame(f2, bg="#d9d9d9") + sb = tk.Scrollbar(f22, orient=tk.VERTICAL) + e = tk.Entry(f22, textvariable=self.create_string_var(0), bg="white") + sb.config(command=partial(self.scrollbar_command, e, 0.1)) + e.grid() + sb.grid(row=0, column=1) + f22.grid(row=0, column=3) + + # e = tk.Entry(f2, textvariable=self.create_string_var(0)) + # e.grid(row=0, column=3) + f2.grid(sticky=tk.W, padx=4, pady=4) + + f3 = tk.Frame(f, bg="#d9d9d9") + lbl = tk.Label(f3, text="Jitter (us): ", bg="#d9d9d9") + lbl.grid() + f31 = tk.Frame(f3, bg="#d9d9d9") + sb = tk.Scrollbar(f31, orient=tk.VERTICAL) + e = tk.Entry(f31, textvariable=self.create_string_var(0), bg="white") + sb.config(command=partial(self.scrollbar_command, e, 5000)) + e.grid() + sb.grid(row=0, column=1) + f31.grid(row=0, column=1) + + f3.grid(sticky=tk.W, padx=4, pady=4) + f.grid(padx=3, pady=3) + + def subnet(self): + f = tk.Frame(self.top) + f1 = tk.Frame(f) + lbl = tk.Label(f1, text="IPv4 subnet") + lbl.grid() + e = tk.Entry(f1, width=30, bg="white") + e.grid(row=0, column=1) + f1.grid() + + f2 = tk.Frame(f) + lbl = tk.Label(f2, text="IPv6 subnet") + lbl.grid() + e = tk.Entry(f2, width=30, bg="white") + e.grid(row=0, column=1) + f2.grid() + f.grid(sticky=tk.W, padx=3, pady=3) + + def wlan_options(self): + f = tk.Frame(self.top) + b = tk.Button(f, text="ns-2 mobility script...") + b.pack(side=tk.LEFT, padx=1) + b = tk.Button(f, text="Link to all routers") + b.pack(side=tk.LEFT, padx=1) + b = tk.Button(f, text="Choose WLAN members") + b.pack(side=tk.LEFT, padx=1) + f.grid(sticky=tk.W) + + def config_option(self): + f = tk.Frame(self.top, bg="#d9d9d9") + b = tk.Button(f, text="Apply", bg="#d9d9d9") + b.grid(padx=2, pady=2) + b = tk.Button(f, text="Cancel", bg="#d9d9d9", command=self.top.destroy) + b.grid(row=0, column=1, padx=2, pady=2) + f.grid(padx=4, pady=4)