diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index a7dd6549..0f40a594 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -78,11 +78,11 @@ class Application(tk.Frame): def draw(self): self.master.option_add("*tearOff", tk.FALSE) - self.menubar = Menubar(self.master, self) self.toolbar = Toolbar(self, self) self.toolbar.pack(side=tk.LEFT, fill=tk.Y, ipadx=2, ipady=2) self.draw_canvas() self.draw_status() + self.menubar = Menubar(self.master, self) def draw_canvas(self): width = self.guiconfig["preferences"]["width"] diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 76bbc424..b8914808 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -369,21 +369,16 @@ class CoreClient: logging.debug("canvas metadata: %s", canvas_config) if canvas_config: canvas_config = json.loads(canvas_config) - gridlines = canvas_config.get("gridlines", True) self.app.canvas.show_grid.set(gridlines) - fit_image = canvas_config.get("fit_image", False) self.app.canvas.adjust_to_dim.set(fit_image) - wallpaper_style = canvas_config.get("wallpaper-style", 1) self.app.canvas.scale_option.set(wallpaper_style) - width = self.app.guiconfig["preferences"]["width"] height = self.app.guiconfig["preferences"]["height"] dimensions = canvas_config.get("dimensions", [width, height]) self.app.canvas.redraw_canvas(dimensions) - wallpaper = canvas_config.get("wallpaper") if wallpaper: wallpaper = str(appconfig.BACKGROUNDS_PATH.joinpath(wallpaper)) diff --git a/daemon/core/gui/dialogs/canvaswallpaper.py b/daemon/core/gui/dialogs/canvaswallpaper.py index 093d93b0..fe3fbd79 100644 --- a/daemon/core/gui/dialogs/canvaswallpaper.py +++ b/daemon/core/gui/dialogs/canvaswallpaper.py @@ -24,7 +24,6 @@ class CanvasWallpaperDialog(Dialog): super().__init__(master, app, "Canvas Background", modal=True) self.canvas = self.app.canvas self.scale_option = tk.IntVar(value=self.canvas.scale_option.get()) - self.show_grid = tk.BooleanVar(value=self.canvas.show_grid.get()) self.adjust_to_dim = tk.BooleanVar(value=self.canvas.adjust_to_dim.get()) self.filename = tk.StringVar(value=self.canvas.wallpaper_file) self.image_label = None @@ -103,11 +102,6 @@ class CanvasWallpaperDialog(Dialog): self.options.append(button) def draw_additional_options(self): - checkbutton = ttk.Checkbutton( - self.top, text="Show grid", variable=self.show_grid - ) - checkbutton.grid(sticky="ew", padx=PADX) - checkbutton = ttk.Checkbutton( self.top, text="Adjust canvas size to image dimensions", @@ -163,17 +157,13 @@ class CanvasWallpaperDialog(Dialog): def click_apply(self): self.canvas.scale_option.set(self.scale_option.get()) - self.canvas.show_grid.set(self.show_grid.get()) self.canvas.adjust_to_dim.set(self.adjust_to_dim.get()) - self.canvas.update_grid() - + self.canvas.show_grid.click_handler() filename = self.filename.get() if not filename: filename = None - try: self.canvas.set_wallpaper(filename) except FileNotFoundError: logging.error("invalid background: %s", filename) - self.destroy() diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 1606da88..b70fe6b2 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -20,15 +20,6 @@ WIRELESS_COLOR = "#009933" ARC_DISTANCE = 50 -def interface_label(interface: core_pb2.Interface) -> str: - label = "" - if interface.ip4: - label = f"{interface.ip4}/{interface.ip4mask}" - if interface.ip6: - label = f"{label}\n{interface.ip6}/{interface.ip6mask}" - return label - - def create_edge_token(src: int, dst: int, network: int = None) -> Tuple[int, ...]: values = [src, dst] if network is not None: @@ -143,7 +134,12 @@ class Edge: if self.middle_label is None: x, y = self.middle_label_pos() self.middle_label = self.canvas.create_text( - x, y, font=self.canvas.app.edge_font, text=text + x, + y, + font=self.canvas.app.edge_font, + text=text, + tags=tags.LINK_LABEL, + state=self.canvas.show_link_labels.state(), ) else: self.canvas.itemconfig(self.middle_label, text=text) @@ -168,7 +164,8 @@ class Edge: text=text, justify=tk.CENTER, font=self.canvas.app.edge_font, - tags=tags.LINK_INFO, + tags=tags.LINK_LABEL, + state=self.canvas.show_link_labels.state(), ) else: self.canvas.itemconfig(self.src_label, text=text) @@ -181,7 +178,8 @@ class Edge: text=text, justify=tk.CENTER, font=self.canvas.app.edge_font, - tags=tags.LINK_INFO, + tags=tags.LINK_LABEL, + state=self.canvas.show_link_labels.state(), ) else: self.canvas.itemconfig(self.dst_label, text=text) @@ -278,13 +276,25 @@ class CanvasEdge(Edge): self.link = link self.draw_labels() + def interface_label(self, interface: core_pb2.Interface) -> str: + label = "" + if interface.name and self.canvas.show_interface_names.get(): + label = f"{interface.name}" + if interface.ip4 and self.canvas.show_ip4s.get(): + label = f"{label}\n" if label else "" + label += f"{interface.ip4}/{interface.ip4mask}" + if interface.ip6 and self.canvas.show_ip6s.get(): + label = f"{label}\n" if label else "" + label += f"{interface.ip6}/{interface.ip6mask}" + return label + def create_node_labels(self) -> Tuple[str, str]: label_one = None if self.link.HasField("interface_one"): - label_one = interface_label(self.link.interface_one) + label_one = self.interface_label(self.link.interface_one) label_two = None if self.link.HasField("interface_two"): - label_two = interface_label(self.link.interface_two) + label_two = self.interface_label(self.link.interface_two) return label_one, label_two def draw_labels(self) -> None: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 19fde443..3b1efaea 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -1,5 +1,6 @@ import logging import tkinter as tk +from tkinter import BooleanVar from typing import TYPE_CHECKING, Tuple from PIL import Image, ImageTk @@ -30,6 +31,19 @@ ZOOM_OUT = 0.9 ICON_SIZE = 48 +class ShowVar(BooleanVar): + def __init__(self, canvas: "CanvasGraph", tag: str, value: bool) -> None: + super().__init__(value=value) + self.canvas = canvas + self.tag = tag + + def state(self) -> str: + return tk.NORMAL if self.get() else tk.HIDDEN + + def click_handler(self): + self.canvas.itemconfigure(self.tag, state=self.state()) + + class CanvasGraph(tk.Canvas): def __init__( self, master: "Application", core: "CoreClient", width: int, height: int @@ -69,7 +83,6 @@ class CanvasGraph(tk.Canvas): self.wallpaper_drawn = None self.wallpaper_file = "" self.scale_option = tk.IntVar(value=1) - self.show_grid = tk.BooleanVar(value=True) self.adjust_to_dim = tk.BooleanVar(value=False) # throughput related @@ -77,6 +90,17 @@ class CanvasGraph(tk.Canvas): self.throughput_width = 10 self.throughput_color = "#FF0000" + # drawing related + self.show_node_labels = ShowVar(self, tags.NODE_LABEL, value=True) + self.show_link_labels = ShowVar(self, tags.LINK_LABEL, value=True) + self.show_grid = ShowVar(self, tags.GRIDLINE, value=True) + self.show_shapes = ShowVar(self, tags.SHAPE, value=True) + self.show_shape_labels = ShowVar(self, tags.SHAPE_TEXT, value=True) + self.show_marker = ShowVar(self, tags.MARKER, value=True) + self.show_interface_names = BooleanVar(value=False) + self.show_ip4s = BooleanVar(value=True) + self.show_ip6s = BooleanVar(value=True) + # bindings self.setup_bindings() @@ -562,6 +586,7 @@ class CanvasGraph(tk.Canvas): fill=self.app.toolbar.marker_tool.color, outline="", tags=tags.MARKER, + state=self.show_marker.state(), ) return if selected is None: @@ -818,7 +843,7 @@ class CanvasGraph(tk.Canvas): # redraw gridlines to new canvas size self.delete(tags.GRIDLINE) self.draw_grid() - self.update_grid() + self.app.canvas.show_grid.click_handler() def redraw_wallpaper(self): if self.adjust_to_dim.get(): @@ -840,13 +865,6 @@ class CanvasGraph(tk.Canvas): for component in tags.ABOVE_WALLPAPER_TAGS: self.tag_raise(component) - def update_grid(self): - logging.debug("updating grid show grid: %s", self.show_grid.get()) - if self.show_grid.get(): - self.itemconfig(tags.GRIDLINE, state=tk.NORMAL) - else: - self.itemconfig(tags.GRIDLINE, state=tk.HIDDEN) - def set_wallpaper(self, filename: str): logging.debug("setting wallpaper: %s", filename) if filename: @@ -906,7 +924,8 @@ class CanvasGraph(tk.Canvas): self.master, scaled_x, scaled_y, copy, self.nodes[canvas_nid].image ) - # add new node to modified_service_nodes set if that set contains the to_copy node + # add new node to modified_service_nodes set if that set contains the + # to_copy node if self.app.core.service_been_modified(core_node.id): self.app.core.modified_service_nodes.add(copy.id) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 47bac1b8..c9cc3dd0 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -47,9 +47,10 @@ class CanvasNode: x, label_y, text=self.core_node.name, - tags=tags.NODE_NAME, + tags=tags.NODE_LABEL, font=self.app.icon_text_font, fill="#0000CD", + state=self.canvas.show_node_labels.state(), ) self.tooltip = CanvasTooltip(self.canvas) self.edges = set() @@ -195,7 +196,6 @@ class CanvasNode: label="Mobility Player", command=self.show_mobility_player ) context.add_command(label="Select Adjacent", state=tk.DISABLED) - context.add_command(label="Hide", state=tk.DISABLED) if NodeUtils.is_container_node(self.core_node.type): context.add_command(label="Shell Window", state=tk.DISABLED) context.add_command(label="Tcpdump", state=tk.DISABLED) @@ -228,7 +228,6 @@ class CanvasNode: edit_menu.add_command(label="Cut", state=tk.DISABLED) edit_menu.add_command(label="Copy", command=self.canvas_copy) edit_menu.add_command(label="Delete", command=self.canvas_delete) - edit_menu.add_command(label="Hide", state=tk.DISABLED) context.add_cascade(label="Edit", menu=edit_menu) return context diff --git a/daemon/core/gui/graph/shape.py b/daemon/core/gui/graph/shape.py index 82b58a71..12f70884 100644 --- a/daemon/core/gui/graph/shape.py +++ b/daemon/core/gui/graph/shape.py @@ -85,6 +85,7 @@ class Shape: fill=self.shape_data.fill_color, outline=self.shape_data.border_color, width=self.shape_data.border_width, + state=self.canvas.show_shapes.state(), ) self.draw_shape_text() elif self.shape_type == ShapeType.RECTANGLE: @@ -98,6 +99,7 @@ class Shape: fill=self.shape_data.fill_color, outline=self.shape_data.border_color, width=self.shape_data.border_width, + state=self.canvas.show_shapes.state(), ) self.draw_shape_text() elif self.shape_type == ShapeType.TEXT: @@ -109,6 +111,7 @@ class Shape: text=self.shape_data.text, fill=self.shape_data.text_color, font=font, + state=self.canvas.show_shapes.state(), ) else: logging.error("unknown shape type: %s", self.shape_type) @@ -136,6 +139,7 @@ class Shape: text=self.shape_data.text, fill=self.shape_data.text_color, font=font, + state=self.canvas.show_shape_labels.state(), ) def shape_motion(self, x1: float, y1: float): diff --git a/daemon/core/gui/graph/tags.py b/daemon/core/gui/graph/tags.py index 53d547ac..5caf802a 100644 --- a/daemon/core/gui/graph/tags.py +++ b/daemon/core/gui/graph/tags.py @@ -2,10 +2,10 @@ GRIDLINE = "gridline" SHAPE = "shape" SHAPE_TEXT = "shapetext" EDGE = "edge" -LINK_INFO = "linkinfo" +LINK_LABEL = "linklabel" WIRELESS_EDGE = "wireless" ANTENNA = "antenna" -NODE_NAME = "nodename" +NODE_LABEL = "nodename" NODE = "node" WALLPAPER = "wallpaper" SELECTION = "selectednodes" @@ -15,19 +15,19 @@ ABOVE_WALLPAPER_TAGS = [ SHAPE, SHAPE_TEXT, EDGE, - LINK_INFO, + LINK_LABEL, WIRELESS_EDGE, ANTENNA, NODE, - NODE_NAME, + NODE_LABEL, ] -ABOVE_SHAPE = [GRIDLINE, EDGE, LINK_INFO, WIRELESS_EDGE, ANTENNA, NODE, NODE_NAME] +ABOVE_SHAPE = [GRIDLINE, EDGE, LINK_LABEL, WIRELESS_EDGE, ANTENNA, NODE, NODE_LABEL] COMPONENT_TAGS = [ EDGE, NODE, - NODE_NAME, + NODE_LABEL, WALLPAPER, - LINK_INFO, + LINK_LABEL, ANTENNA, WIRELESS_EDGE, SELECTION, diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 08056071..2a2cde5a 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -3,7 +3,7 @@ import os import tkinter as tk import webbrowser from functools import partial -from tkinter import filedialog, messagebox +from tkinter import BooleanVar, filedialog, messagebox from typing import TYPE_CHECKING from core.gui.appconfig import XMLS_PATH @@ -41,8 +41,10 @@ class Menubar(tk.Menu): self.master.config(menu=self) self.app = app self.core = app.core + self.canvas = app.canvas self.recent_menu = None self.edit_menu = None + self.show_annotations = BooleanVar(value=True) self.draw() def draw(self) -> None: @@ -129,15 +131,41 @@ class Menubar(tk.Menu): Create view menu """ menu = tk.Menu(self) - menu.add_command(label="All", state=tk.DISABLED) - menu.add_command(label="None", state=tk.DISABLED) - menu.add_separator() - menu.add_command(label="Interface Names", state=tk.DISABLED) - menu.add_command(label="IPv4 Addresses", state=tk.DISABLED) - menu.add_command(label="IPv6 Addresses", state=tk.DISABLED) - menu.add_command(label="Node Labels", state=tk.DISABLED) - menu.add_command(label="Annotations", state=tk.DISABLED) - menu.add_command(label="Grid", state=tk.DISABLED) + menu.add_checkbutton( + label="Interface Names", + command=self.click_edge_label_change, + variable=self.canvas.show_interface_names, + ) + menu.add_checkbutton( + label="IPv4 Addresses", + command=self.click_edge_label_change, + variable=self.canvas.show_ip4s, + ) + menu.add_checkbutton( + label="IPv6 Addresses", + command=self.click_edge_label_change, + variable=self.canvas.show_ip6s, + ) + menu.add_checkbutton( + label="Node Labels", + command=self.canvas.show_node_labels.click_handler, + variable=self.canvas.show_node_labels, + ) + menu.add_checkbutton( + label="Link Labels", + command=self.canvas.show_link_labels.click_handler, + variable=self.canvas.show_link_labels, + ) + menu.add_checkbutton( + label="Annotations", + command=self.click_show_annotations, + variable=self.show_annotations, + ) + menu.add_checkbutton( + label="Canvas Grid", + command=self.canvas.show_grid.click_handler, + variable=self.canvas.show_grid, + ) self.add_cascade(label="View", menu=menu) def draw_tools_menu(self) -> None: @@ -430,3 +458,16 @@ class Menubar(tk.Menu): x = (col * layout_size) + padding y = (row * layout_size) + padding node.move(x, y) + + def click_edge_label_change(self) -> None: + for edge in self.canvas.edges.values(): + edge.draw_labels() + + def click_show_annotations(self) -> None: + value = self.show_annotations.get() + self.canvas.show_shapes.set(value) + self.canvas.show_shape_labels.set(value) + self.canvas.show_marker.set(value) + self.canvas.show_shapes.click_handler() + self.canvas.show_shape_labels.click_handler() + self.canvas.show_marker.click_handler()