diff --git a/coretk/coretk/app.py b/coretk/coretk/app.py index a3fa4e34..1b27587f 100644 --- a/coretk/coretk/app.py +++ b/coretk/coretk/app.py @@ -21,7 +21,7 @@ class Application(tk.Frame): def setup_app(self): self.master.title("CORE") - self.master.geometry("800x600") + self.master.geometry("1000x800") image = Images.get("core") self.master.tk.call("wm", "iconphoto", self.master._w, image) self.pack(fill=tk.BOTH, expand=True) @@ -40,11 +40,11 @@ class Application(tk.Frame): core_editbar.create_toolbar() self.canvas = CanvasGraph( - self, background="#cccccc", scrollregion=(0, 0, 1000, 1000) + master=self, background="#cccccc", scrollregion=(0, 0, 1000, 1000) ) self.canvas.pack(fill=tk.BOTH, expand=True) - # self.canvas.create_line(0, 0, 10, 10) + # self.canvas.create_rectangle(0, 0, 1000, 750, outline="#000000", fill="#ffffff", width=1) scroll_x = tk.Scrollbar( self.canvas, orient=tk.HORIZONTAL, command=self.canvas.xview diff --git a/coretk/coretk/coretoolbar.py b/coretk/coretk/coretoolbar.py index 74d7979b..df646488 100644 --- a/coretk/coretk/coretoolbar.py +++ b/coretk/coretk/coretoolbar.py @@ -1,6 +1,7 @@ import logging import tkinter as tk +from coretk.graph import GraphMode from coretk.images import Images from coretk.tooltip import CreateToolTip @@ -34,6 +35,9 @@ class CoreToolbar(object): self.marker_option_menu = None self.network_layer_option_menu = None + # variables used by canvas graph + self.mode = GraphMode.SELECT + def load_toolbar_images(self): """ Load the images that appear in core toolbar @@ -66,6 +70,24 @@ class CoreToolbar(object): Images.load("stop", "stop.gif") Images.load("observe", "observe.gif") + def get_graph_mode(self): + """ + Retrieve current graph mode + + :rtype: int + :return: current graph mode + """ + return self.mode + + def set_graph_mode(self, mode): + """ + Set graph mode + + :param int mode: graph mode + :return: nothing + """ + self.mode = mode + def destroy_previous_frame(self): """ Destroy any extra frame from previous before drawing a new one @@ -94,7 +116,7 @@ class CoreToolbar(object): if i.winfo_name() != "!frame": i.destroy() - def create_button(self, img, func, frame, main_button): + def create_button(self, img, func, frame, main_button, btt_message): """ Create button and put it on the frame @@ -106,6 +128,7 @@ class CoreToolbar(object): """ button = tk.Button(frame, width=self.width, height=self.height, image=img) button.pack(side=tk.LEFT, pady=1) + CreateToolTip(button, btt_message) button.bind("", lambda mb: func(main_button)) def create_radio_button(self, frame, image, func, variable, value, tooltip_msg): @@ -122,11 +145,12 @@ class CoreToolbar(object): button.pack(side=tk.TOP, pady=1) CreateToolTip(button, tooltip_msg) - def create_regular_button(self, frame, image, func): + def create_regular_button(self, frame, image, func, btt_message): button = tk.Button( frame, width=self.width, height=self.height, image=image, command=func ) button.pack(side=tk.TOP, pady=1) + CreateToolTip(button, btt_message) def draw_button_menu_frame(self, edit_frame, option_frame, main_button): """ @@ -165,6 +189,7 @@ class CoreToolbar(object): def click_selection_tool(self): logging.debug("Click SELECTION TOOL") + self.set_graph_mode(GraphMode.SELECT) def click_start_stop_session_tool(self): logging.debug("Click START STOP SESSION button") @@ -173,6 +198,7 @@ class CoreToolbar(object): def click_link_tool(self): logging.debug("Click LINK button") + self.set_graph_mode(GraphMode.EDGE) def pick_router(self, main_button): self.network_layer_option_menu.destroy() @@ -237,9 +263,22 @@ class CoreToolbar(object): self.pick_ovs, self.pick_editnode, ] + tooltip_list = [ + "router", + "host", + "PC", + "mdr", + "prouter", + "OVS", + "edit node types", + ] for i in range(len(img_list)): self.create_button( - img_list[i], func_list[i], option_frame, network_layer_button + img_list[i], + func_list[i], + option_frame, + network_layer_button, + tooltip_list[i], ) # place frame at a calculated position as well as keep a reference of that frame @@ -320,9 +359,20 @@ class CoreToolbar(object): self.pick_rj45, self.pick_tunnel, ] + tooltip_list = [ + "ethernet hub", + "ethernet switch", + "wireless LAN", + "rj45 physical interface tool", + "tunnel tool", + ] for i in range(len(img_list)): self.create_button( - img_list[i], func_list[i], option_frame, link_layer_button + img_list[i], + func_list[i], + option_frame, + link_layer_button, + tooltip_list[i], ) # place frame at a calculated position as well as keep a reference of the frame @@ -358,7 +408,6 @@ class CoreToolbar(object): self.marker_option_menu.destroy() main_button.configure(image=Images.get("marker")) logging.debug("Pick MARKER") - return "break" def pick_oval(self, main_button): self.marker_option_menu.destroy() @@ -397,8 +446,11 @@ class CoreToolbar(object): self.pick_rectangle, self.pick_text, ] + tooltip_list = ["marker", "oval", "rectangle", "text"] for i in range(len(img_list)): - self.create_button(img_list[i], func_list[i], option_frame, main_button) + self.create_button( + img_list[i], func_list[i], option_frame, main_button, tooltip_list[i] + ) # place the frame at a calculated position as well as keep a reference of that frame self.draw_button_menu_frame(self.edit_frame, option_frame, main_button) @@ -432,7 +484,10 @@ class CoreToolbar(object): def create_toolbar(self): self.load_toolbar_images() self.create_regular_button( - self.edit_frame, Images.get("start"), self.click_start_stop_session_tool + self.edit_frame, + Images.get("start"), + self.click_start_stop_session_tool, + "start the session", ) self.create_radio_button( self.edit_frame, @@ -503,7 +558,10 @@ class CoreToolbar(object): def create_runtime_toolbar(self): self.create_regular_button( - self.edit_frame, Images.get("stop"), self.click_stop_button + self.edit_frame, + Images.get("stop"), + self.click_stop_button, + "stop the session", ) self.create_radio_button( self.edit_frame, @@ -539,5 +597,5 @@ class CoreToolbar(object): "run command from one node to another", ) self.create_regular_button( - self.edit_frame, Images.get("run"), self.click_run_button + self.edit_frame, Images.get("run"), self.click_run_button, "run" ) diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index 9bd599e9..bf85b8cb 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -17,14 +17,42 @@ class CanvasGraph(tk.Canvas): cnf = {} kwargs["highlightthickness"] = 0 super().__init__(master, cnf, **kwargs) + self.mode = GraphMode.SELECT self.selected = None self.node_context = None self.nodes = {} self.edges = {} self.drawing_edge = None + self.setup_menus() self.setup_bindings() + self.draw_grid() + + def draw_grid(self, width=1000, height=750): + """ + Create grid + + :param int width: the width + :param int height: the height + + :return: nothing + """ + rectangle_id = self.create_rectangle( + 0, + 0, + width, + height, + outline="#000000", + fill="#ffffff", + width=1, + tags="rectangle", + ) + self.tag_lower(rectangle_id) + for i in range(0, width, 27): + self.create_line(i, 0, i, height, dash=(2, 4), tags="grid line") + for i in range(0, height, 27): + self.create_line(0, i, width, i, dash=(2, 4), tags="grid line") def setup_menus(self): self.node_context = tk.Menu(self.master) @@ -33,6 +61,11 @@ class CanvasGraph(tk.Canvas): self.node_context.add_command(label="Three") def setup_bindings(self): + """ + Bind any mouse events or hot keys to the matching action + + :return: nothing + """ self.bind("", self.click_press) self.bind("", self.click_release) self.bind("", self.click_motion) @@ -42,11 +75,25 @@ class CanvasGraph(tk.Canvas): self.bind("n", self.set_mode) def canvas_xy(self, event): + """ + Convert window coordinate to canvas coordinate + + :param event: + :rtype: (int, int) + :return: x, y canvas coordinate + """ x = self.canvasx(event.x) y = self.canvasy(event.y) return x, y def get_selected(self, event): + """ + Retrieve the item id that is on the mouse position + + :param event: mouse event + :rtype: int + :return: the item that the mouse point to + """ overlapping = self.find_overlapping(event.x, event.y, event.x, event.y) nodes = set(self.find_withtag("node")) selected = None @@ -64,6 +111,12 @@ class CanvasGraph(tk.Canvas): return selected def click_release(self, event): + """ + Draw a node or finish drawing an edge according to the current graph mode + + :param event: mouse event + :return: nothing + """ self.focus_set() self.selected = self.get_selected(event) logging.debug(f"click release selected: {self.selected}") @@ -109,6 +162,12 @@ class CanvasGraph(tk.Canvas): logging.debug(f"edges: {self.find_withtag('edge')}") def click_press(self, event): + """ + Start drawing an edge if mouse click is on a node + + :param event: mouse event + :return: nothing + """ logging.debug(f"click press: {event}") selected = self.get_selected(event) is_node = selected in self.find_withtag("node") @@ -117,6 +176,12 @@ class CanvasGraph(tk.Canvas): self.drawing_edge = CanvasEdge(x, y, x, y, selected, self) def click_motion(self, event): + """ + Redraw drawing edge according to the current position of the mouse + + :param event: mouse event + :return: nothing + """ if self.mode == GraphMode.EDGE and self.drawing_edge is not None: x2, y2 = self.canvas_xy(event) x1, y1, _, _ = self.coords(self.drawing_edge.id) @@ -130,6 +195,12 @@ class CanvasGraph(tk.Canvas): self.node_context.post(event.x_root, event.y_root) def set_mode(self, event): + """ + Set canvas mode according to the hot key that has been pressed + + :param event: key event + :return: nothing + """ logging.debug(f"mode event: {event}") if event.char == "e": self.mode = GraphMode.EDGE @@ -147,21 +218,41 @@ class CanvasGraph(tk.Canvas): class CanvasEdge: + """ + Canvas edge class + """ + width = 3 def __init__(self, x1, y1, x2, y2, src, canvas): + """ + Create an instance of canvas edge object + :param int x1: source x-coord + :param int y1: source y-coord + :param int x2: destination x-coord + :param int y2: destination y-coord + :param int src: source id + :param tkinter.Canvas canvas: canvas object + """ self.src = src self.dst = None self.canvas = canvas self.id = self.canvas.create_line(x1, y1, x2, y2, tags="edge", width=self.width) self.token = None - self.canvas.tag_lower(self.id) + + # TODO resolve this + # self.canvas.tag_lower(self.id) def complete(self, dst, x, y): self.dst = dst self.token = tuple(sorted((self.src, self.dst))) x1, y1, _, _ = self.canvas.coords(self.id) self.canvas.coords(self.id, x1, y1, x, y) + self.canvas.lift(self.src) + self.canvas.lift(self.dst) + # self.canvas.create_line(0,0,10,10) + # print(x1,y1,x,y) + # self.canvas.create_line(x1+1, y1+1, x+1, y+1) def delete(self): self.canvas.delete(self.id)