pygui: further refactoring to work towards multiple canvas support
This commit is contained in:
parent
e34c00a431
commit
886bfc093b
8 changed files with 161 additions and 134 deletions
|
@ -14,6 +14,7 @@ from core.gui.dialogs.error import ErrorDialog
|
||||||
from core.gui.frames.base import InfoFrameBase
|
from core.gui.frames.base import InfoFrameBase
|
||||||
from core.gui.frames.default import DefaultInfoFrame
|
from core.gui.frames.default import DefaultInfoFrame
|
||||||
from core.gui.graph.graph import CanvasGraph
|
from core.gui.graph.graph import CanvasGraph
|
||||||
|
from core.gui.graph.manager import CanvasManager
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.menubar import Menubar
|
from core.gui.menubar import Menubar
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
@ -35,6 +36,7 @@ class Application(ttk.Frame):
|
||||||
self.menubar: Optional[Menubar] = None
|
self.menubar: Optional[Menubar] = None
|
||||||
self.toolbar: Optional[Toolbar] = None
|
self.toolbar: Optional[Toolbar] = None
|
||||||
self.right_frame: Optional[ttk.Frame] = None
|
self.right_frame: Optional[ttk.Frame] = None
|
||||||
|
self.manager: Optional[CanvasManager] = None
|
||||||
self.canvas: Optional[CanvasGraph] = None
|
self.canvas: Optional[CanvasGraph] = None
|
||||||
self.statusbar: Optional[StatusBar] = None
|
self.statusbar: Optional[StatusBar] = None
|
||||||
self.progress: Optional[Progressbar] = None
|
self.progress: Optional[Progressbar] = None
|
||||||
|
@ -140,16 +142,18 @@ class Application(ttk.Frame):
|
||||||
canvas_frame.rowconfigure(0, weight=1)
|
canvas_frame.rowconfigure(0, weight=1)
|
||||||
canvas_frame.columnconfigure(0, weight=1)
|
canvas_frame.columnconfigure(0, weight=1)
|
||||||
canvas_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=1)
|
canvas_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=1)
|
||||||
self.canvas = CanvasGraph(canvas_frame, self, self.core)
|
self.manager = CanvasManager(canvas_frame, self, self.core)
|
||||||
self.canvas.grid(sticky=tk.NSEW)
|
self.manager.notebook.grid(sticky=tk.NSEW)
|
||||||
scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview)
|
# self.canvas = CanvasGraph(canvas_frame, self, self.core)
|
||||||
scroll_y.grid(row=0, column=1, sticky=tk.NS)
|
# self.canvas.grid(sticky=tk.NSEW)
|
||||||
scroll_x = ttk.Scrollbar(
|
# scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview)
|
||||||
canvas_frame, orient=tk.HORIZONTAL, command=self.canvas.xview
|
# scroll_y.grid(row=0, column=1, sticky=tk.NS)
|
||||||
)
|
# scroll_x = ttk.Scrollbar(
|
||||||
scroll_x.grid(row=1, column=0, sticky=tk.EW)
|
# canvas_frame, orient=tk.HORIZONTAL, command=self.canvas.xview
|
||||||
self.canvas.configure(xscrollcommand=scroll_x.set)
|
# )
|
||||||
self.canvas.configure(yscrollcommand=scroll_y.set)
|
# scroll_x.grid(row=1, column=0, sticky=tk.EW)
|
||||||
|
# self.canvas.configure(xscrollcommand=scroll_x.set)
|
||||||
|
# self.canvas.configure(yscrollcommand=scroll_y.set)
|
||||||
|
|
||||||
def draw_status(self) -> None:
|
def draw_status(self) -> None:
|
||||||
self.statusbar = StatusBar(self.right_frame, self)
|
self.statusbar = StatusBar(self.right_frame, self)
|
||||||
|
|
|
@ -47,6 +47,7 @@ from core.gui.dialogs.error import ErrorDialog
|
||||||
from core.gui.dialogs.mobilityplayer import MobilityPlayer
|
from core.gui.dialogs.mobilityplayer import MobilityPlayer
|
||||||
from core.gui.dialogs.sessions import SessionsDialog
|
from core.gui.dialogs.sessions import SessionsDialog
|
||||||
from core.gui.graph.edges import CanvasEdge
|
from core.gui.graph.edges import CanvasEdge
|
||||||
|
from core.gui.graph.graph import CanvasGraph
|
||||||
from core.gui.graph.node import CanvasNode
|
from core.gui.graph.node import CanvasNode
|
||||||
from core.gui.graph.shape import AnnotationData, Shape
|
from core.gui.graph.shape import AnnotationData, Shape
|
||||||
from core.gui.graph.shapeutils import ShapeType
|
from core.gui.graph.shapeutils import ShapeType
|
||||||
|
@ -315,9 +316,7 @@ class CoreClient:
|
||||||
self.session.id, self.handle_events
|
self.session.id, self.handle_events
|
||||||
)
|
)
|
||||||
self.ifaces_manager.joined(self.session.links)
|
self.ifaces_manager.joined(self.session.links)
|
||||||
self.app.canvas.reset_and_redraw(self.session)
|
self.app.manager.join(self.session)
|
||||||
self.parse_metadata()
|
|
||||||
self.app.canvas.organize()
|
|
||||||
if self.is_runtime():
|
if self.is_runtime():
|
||||||
self.show_mobility_players()
|
self.show_mobility_players()
|
||||||
self.app.after(0, self.app.joined_session_update)
|
self.app.after(0, self.app.joined_session_update)
|
||||||
|
@ -327,7 +326,7 @@ class CoreClient:
|
||||||
def is_runtime(self) -> bool:
|
def is_runtime(self) -> bool:
|
||||||
return self.session and self.session.state == SessionState.RUNTIME
|
return self.session and self.session.state == SessionState.RUNTIME
|
||||||
|
|
||||||
def parse_metadata(self) -> None:
|
def parse_metadata(self, canvas: CanvasGraph) -> None:
|
||||||
# canvas setting
|
# canvas setting
|
||||||
config = self.session.metadata
|
config = self.session.metadata
|
||||||
canvas_config = config.get("canvas")
|
canvas_config = config.get("canvas")
|
||||||
|
@ -347,10 +346,10 @@ class CoreClient:
|
||||||
wallpaper = canvas_config.get("wallpaper")
|
wallpaper = canvas_config.get("wallpaper")
|
||||||
if wallpaper:
|
if wallpaper:
|
||||||
wallpaper = str(appconfig.BACKGROUNDS_PATH.joinpath(wallpaper))
|
wallpaper = str(appconfig.BACKGROUNDS_PATH.joinpath(wallpaper))
|
||||||
self.app.canvas.set_wallpaper(wallpaper)
|
canvas.set_wallpaper(wallpaper)
|
||||||
else:
|
else:
|
||||||
self.app.canvas.redraw_canvas()
|
canvas.redraw_canvas()
|
||||||
self.app.canvas.set_wallpaper(None)
|
canvas.set_wallpaper(None)
|
||||||
|
|
||||||
# load saved shapes
|
# load saved shapes
|
||||||
shapes_config = config.get("shapes")
|
shapes_config = config.get("shapes")
|
||||||
|
@ -377,7 +376,7 @@ class CoreClient:
|
||||||
shape = Shape(
|
shape = Shape(
|
||||||
self.app, self.app.canvas, shape_type, *coords, data=data
|
self.app, self.app.canvas, shape_type, *coords, data=data
|
||||||
)
|
)
|
||||||
self.app.canvas.shapes[shape.id] = shape
|
canvas.shapes[shape.id] = shape
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logging.exception("unknown shape: %s", shape_type)
|
logging.exception("unknown shape: %s", shape_type)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from tkinter import BooleanVar
|
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -31,10 +30,11 @@ from core.gui.graph.node import CanvasNode
|
||||||
from core.gui.graph.shape import Shape
|
from core.gui.graph.shape import Shape
|
||||||
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
|
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
|
||||||
from core.gui.images import ImageEnum, TypeToImage
|
from core.gui.images import ImageEnum, TypeToImage
|
||||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.gui.app import Application
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.manager import CanvasManager
|
||||||
from core.gui.coreclient import CoreClient
|
from core.gui.coreclient import CoreClient
|
||||||
|
|
||||||
ZOOM_IN = 1.1
|
ZOOM_IN = 1.1
|
||||||
|
@ -44,32 +44,23 @@ MOVE_NODE_MODES = {GraphMode.NODE, GraphMode.SELECT}
|
||||||
MOVE_SHAPE_MODES = {GraphMode.ANNOTATION, GraphMode.SELECT}
|
MOVE_SHAPE_MODES = {GraphMode.ANNOTATION, GraphMode.SELECT}
|
||||||
|
|
||||||
|
|
||||||
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) -> None:
|
|
||||||
self.canvas.itemconfigure(self.tag, state=self.state())
|
|
||||||
|
|
||||||
|
|
||||||
class CanvasGraph(tk.Canvas):
|
class CanvasGraph(tk.Canvas):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, master: tk.BaseWidget, app: "Application", core: "CoreClient"
|
self,
|
||||||
|
master: tk.BaseWidget,
|
||||||
|
app: "Application",
|
||||||
|
canvas_manager: "CanvasManager",
|
||||||
|
core: "CoreClient",
|
||||||
|
_id: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(master, highlightthickness=0, background="#cccccc")
|
super().__init__(master, highlightthickness=0, background="#cccccc")
|
||||||
|
self.id: int = _id
|
||||||
self.app: "Application" = app
|
self.app: "Application" = app
|
||||||
|
self.manager: "CanvasManager" = canvas_manager
|
||||||
self.core: "CoreClient" = core
|
self.core: "CoreClient" = core
|
||||||
self.mode: GraphMode = GraphMode.SELECT
|
|
||||||
self.annotation_type: Optional[ShapeType] = None
|
|
||||||
self.selection: Dict[int, int] = {}
|
self.selection: Dict[int, int] = {}
|
||||||
self.select_box: Optional[Shape] = None
|
self.select_box: Optional[Shape] = None
|
||||||
self.selected: Optional[int] = None
|
self.selected: Optional[int] = None
|
||||||
self.node_draw: Optional[NodeDraw] = None
|
|
||||||
self.nodes: Dict[int, CanvasNode] = {}
|
self.nodes: Dict[int, CanvasNode] = {}
|
||||||
self.edges: Dict[str, CanvasEdge] = {}
|
self.edges: Dict[str, CanvasEdge] = {}
|
||||||
self.shapes: Dict[int, Shape] = {}
|
self.shapes: Dict[int, Shape] = {}
|
||||||
|
@ -103,18 +94,6 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.throughput_width: int = 10
|
self.throughput_width: int = 10
|
||||||
self.throughput_color: str = "#FF0000"
|
self.throughput_color: str = "#FF0000"
|
||||||
|
|
||||||
# drawing related
|
|
||||||
self.show_node_labels: ShowVar = ShowVar(self, tags.NODE_LABEL, value=True)
|
|
||||||
self.show_link_labels: ShowVar = ShowVar(self, tags.LINK_LABEL, value=True)
|
|
||||||
self.show_links: ShowVar = ShowVar(self, tags.EDGE, value=True)
|
|
||||||
self.show_wireless: ShowVar = ShowVar(self, tags.WIRELESS_EDGE, value=True)
|
|
||||||
self.show_grid: ShowVar = ShowVar(self, tags.GRIDLINE, value=True)
|
|
||||||
self.show_annotations: ShowVar = ShowVar(self, tags.ANNOTATION, value=True)
|
|
||||||
self.show_loss_links: ShowVar = ShowVar(self, tags.LOSS_EDGES, value=True)
|
|
||||||
self.show_iface_names: BooleanVar = BooleanVar(value=False)
|
|
||||||
self.show_ip4s: BooleanVar = BooleanVar(value=True)
|
|
||||||
self.show_ip6s: BooleanVar = BooleanVar(value=True)
|
|
||||||
|
|
||||||
# bindings
|
# bindings
|
||||||
self.setup_bindings()
|
self.setup_bindings()
|
||||||
|
|
||||||
|
@ -140,24 +119,11 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.configure(scrollregion=self.bbox(tk.ALL))
|
self.configure(scrollregion=self.bbox(tk.ALL))
|
||||||
|
|
||||||
def reset_and_redraw(self, session: Session) -> None:
|
def reset_and_redraw(self, session: Session) -> None:
|
||||||
# reset view options to default state
|
|
||||||
self.show_node_labels.set(True)
|
|
||||||
self.show_link_labels.set(True)
|
|
||||||
self.show_grid.set(True)
|
|
||||||
self.show_annotations.set(True)
|
|
||||||
self.show_iface_names.set(False)
|
|
||||||
self.show_ip4s.set(True)
|
|
||||||
self.show_ip6s.set(True)
|
|
||||||
self.show_loss_links.set(True)
|
|
||||||
|
|
||||||
# delete any existing drawn items
|
# delete any existing drawn items
|
||||||
for tag in tags.RESET_TAGS:
|
for tag in tags.RESET_TAGS:
|
||||||
self.delete(tag)
|
self.delete(tag)
|
||||||
|
|
||||||
# set the private variables to default value
|
# set the private variables to default value
|
||||||
self.mode = GraphMode.SELECT
|
|
||||||
self.annotation_type = None
|
|
||||||
self.node_draw = None
|
|
||||||
self.selected = None
|
self.selected = None
|
||||||
self.nodes.clear()
|
self.nodes.clear()
|
||||||
self.edges.clear()
|
self.edges.clear()
|
||||||
|
@ -307,7 +273,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE)
|
image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE)
|
||||||
x = core_node.position.x
|
x = core_node.position.x
|
||||||
y = core_node.position.y
|
y = core_node.position.y
|
||||||
node = CanvasNode(self.app, x, y, core_node, image)
|
node = CanvasNode(self.app, self, x, y, core_node, image)
|
||||||
self.nodes[node.id] = node
|
self.nodes[node.id] = node
|
||||||
self.core.set_canvas_node(core_node, node)
|
self.core.set_canvas_node(core_node, node)
|
||||||
|
|
||||||
|
@ -381,13 +347,13 @@ class CanvasGraph(tk.Canvas):
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
if not self.inside_canvas(x, y):
|
if not self.inside_canvas(x, y):
|
||||||
return
|
return
|
||||||
if self.mode == GraphMode.ANNOTATION:
|
if self.manager.mode == GraphMode.ANNOTATION:
|
||||||
self.focus_set()
|
self.focus_set()
|
||||||
if self.shape_drawing:
|
if self.shape_drawing:
|
||||||
shape = self.shapes[self.selected]
|
shape = self.shapes[self.selected]
|
||||||
shape.shape_complete(x, y)
|
shape.shape_complete(x, y)
|
||||||
self.shape_drawing = False
|
self.shape_drawing = False
|
||||||
elif self.mode == GraphMode.SELECT:
|
elif self.manager.mode == GraphMode.SELECT:
|
||||||
self.focus_set()
|
self.focus_set()
|
||||||
if self.select_box:
|
if self.select_box:
|
||||||
x0, y0, x1, y1 = self.coords(self.select_box.id)
|
x0, y0, x1, y1 = self.coords(self.select_box.id)
|
||||||
|
@ -403,13 +369,15 @@ class CanvasGraph(tk.Canvas):
|
||||||
else:
|
else:
|
||||||
self.focus_set()
|
self.focus_set()
|
||||||
self.selected = self.get_selected(event)
|
self.selected = self.get_selected(event)
|
||||||
logging.debug(f"click release selected({self.selected}) mode({self.mode})")
|
logging.debug(
|
||||||
if self.mode == GraphMode.EDGE:
|
"click release selected(%s) mode(%s)", self.selected, self.manager.mode
|
||||||
|
)
|
||||||
|
if self.manager.mode == GraphMode.EDGE:
|
||||||
self.handle_edge_release(event)
|
self.handle_edge_release(event)
|
||||||
elif self.mode == GraphMode.NODE:
|
elif self.manager.mode == GraphMode.NODE:
|
||||||
self.add_node(x, y)
|
self.add_node(x, y)
|
||||||
elif self.mode == GraphMode.PICKNODE:
|
elif self.manager.mode == GraphMode.PICKNODE:
|
||||||
self.mode = GraphMode.NODE
|
self.manager.mode = GraphMode.NODE
|
||||||
self.selected = None
|
self.selected = None
|
||||||
|
|
||||||
def handle_edge_release(self, _event: tk.Event) -> None:
|
def handle_edge_release(self, _event: tk.Event) -> None:
|
||||||
|
@ -588,13 +556,13 @@ class CanvasGraph(tk.Canvas):
|
||||||
y_check = self.cursor[1] - self.offset[1]
|
y_check = self.cursor[1] - self.offset[1]
|
||||||
logging.debug("click press offset(%s, %s)", x_check, y_check)
|
logging.debug("click press offset(%s, %s)", x_check, y_check)
|
||||||
is_node = selected in self.nodes
|
is_node = selected in self.nodes
|
||||||
if self.mode == GraphMode.EDGE and is_node:
|
if self.manager.mode == GraphMode.EDGE and is_node:
|
||||||
pos = self.coords(selected)
|
pos = self.coords(selected)
|
||||||
self.drawing_edge = CanvasEdge(self, selected, pos, pos)
|
self.drawing_edge = CanvasEdge(self, selected, pos, pos)
|
||||||
self.organize()
|
self.organize()
|
||||||
|
|
||||||
if self.mode == GraphMode.ANNOTATION:
|
if self.manager.mode == GraphMode.ANNOTATION:
|
||||||
if is_marker(self.annotation_type):
|
if is_marker(self.manager.annotation_type):
|
||||||
r = self.app.toolbar.marker_frame.size.get()
|
r = self.app.toolbar.marker_frame.size.get()
|
||||||
self.create_oval(
|
self.create_oval(
|
||||||
x - r,
|
x - r,
|
||||||
|
@ -604,11 +572,11 @@ class CanvasGraph(tk.Canvas):
|
||||||
fill=self.app.toolbar.marker_frame.color,
|
fill=self.app.toolbar.marker_frame.color,
|
||||||
outline="",
|
outline="",
|
||||||
tags=(tags.MARKER, tags.ANNOTATION),
|
tags=(tags.MARKER, tags.ANNOTATION),
|
||||||
state=self.show_annotations.state(),
|
state=self.manager.show_annotations.state(),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if selected is None:
|
if selected is None:
|
||||||
shape = Shape(self.app, self, self.annotation_type, x, y)
|
shape = Shape(self.app, self, self.manager.annotation_type, x, y)
|
||||||
self.selected = shape.id
|
self.selected = shape.id
|
||||||
self.shape_drawing = True
|
self.shape_drawing = True
|
||||||
self.shapes[shape.id] = shape
|
self.shapes[shape.id] = shape
|
||||||
|
@ -630,7 +598,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
node.core_node.position.y,
|
node.core_node.position.y,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if self.mode == GraphMode.SELECT:
|
if self.manager.mode == GraphMode.SELECT:
|
||||||
shape = Shape(self.app, self, ShapeType.RECTANGLE, x, y)
|
shape = Shape(self.app, self, ShapeType.RECTANGLE, x, y)
|
||||||
self.select_box = shape
|
self.select_box = shape
|
||||||
self.clear_selection()
|
self.clear_selection()
|
||||||
|
@ -659,7 +627,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
if self.select_box:
|
if self.select_box:
|
||||||
self.select_box.delete()
|
self.select_box.delete()
|
||||||
self.select_box = None
|
self.select_box = None
|
||||||
if is_draw_shape(self.annotation_type) and self.shape_drawing:
|
if is_draw_shape(self.manager.annotation_type) and self.shape_drawing:
|
||||||
shape = self.shapes.pop(self.selected)
|
shape = self.shapes.pop(self.selected)
|
||||||
shape.delete()
|
shape.delete()
|
||||||
self.shape_drawing = False
|
self.shape_drawing = False
|
||||||
|
@ -669,14 +637,14 @@ class CanvasGraph(tk.Canvas):
|
||||||
y_offset = y - self.cursor[1]
|
y_offset = y - self.cursor[1]
|
||||||
self.cursor = x, y
|
self.cursor = x, y
|
||||||
|
|
||||||
if self.mode == GraphMode.EDGE and self.drawing_edge is not None:
|
if self.manager.mode == GraphMode.EDGE and self.drawing_edge is not None:
|
||||||
self.drawing_edge.move_dst(self.cursor)
|
self.drawing_edge.move_dst(self.cursor)
|
||||||
if self.mode == GraphMode.ANNOTATION:
|
if self.manager.mode == GraphMode.ANNOTATION:
|
||||||
if is_draw_shape(self.annotation_type) and self.shape_drawing:
|
if is_draw_shape(self.manager.annotation_type) and self.shape_drawing:
|
||||||
shape = self.shapes[self.selected]
|
shape = self.shapes[self.selected]
|
||||||
shape.shape_motion(x, y)
|
shape.shape_motion(x, y)
|
||||||
return
|
return
|
||||||
elif is_marker(self.annotation_type):
|
elif is_marker(self.manager.annotation_type):
|
||||||
r = self.app.toolbar.marker_frame.size.get()
|
r = self.app.toolbar.marker_frame.size.get()
|
||||||
self.create_oval(
|
self.create_oval(
|
||||||
x - r,
|
x - r,
|
||||||
|
@ -689,21 +657,21 @@ class CanvasGraph(tk.Canvas):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.mode == GraphMode.EDGE:
|
if self.manager.mode == GraphMode.EDGE:
|
||||||
return
|
return
|
||||||
|
|
||||||
# move selected objects
|
# move selected objects
|
||||||
if self.selection:
|
if self.selection:
|
||||||
for selected_id in self.selection:
|
for selected_id in self.selection:
|
||||||
if self.mode in MOVE_SHAPE_MODES and selected_id in self.shapes:
|
if self.manager.mode in MOVE_SHAPE_MODES and selected_id in self.shapes:
|
||||||
shape = self.shapes[selected_id]
|
shape = self.shapes[selected_id]
|
||||||
shape.motion(x_offset, y_offset)
|
shape.motion(x_offset, y_offset)
|
||||||
|
|
||||||
if self.mode in MOVE_NODE_MODES and selected_id in self.nodes:
|
if self.manager.mode in MOVE_NODE_MODES and selected_id in self.nodes:
|
||||||
node = self.nodes[selected_id]
|
node = self.nodes[selected_id]
|
||||||
node.motion(x_offset, y_offset, update=self.core.is_runtime())
|
node.motion(x_offset, y_offset, update=self.core.is_runtime())
|
||||||
else:
|
else:
|
||||||
if self.select_box and self.mode == GraphMode.SELECT:
|
if self.select_box and self.manager.mode == GraphMode.SELECT:
|
||||||
self.select_box.shape_motion(x, y)
|
self.select_box.shape_motion(x, y)
|
||||||
|
|
||||||
def press_delete(self, _event: tk.Event) -> None:
|
def press_delete(self, _event: tk.Event) -> None:
|
||||||
|
@ -729,17 +697,22 @@ class CanvasGraph(tk.Canvas):
|
||||||
return
|
return
|
||||||
actual_x, actual_y = self.get_actual_coords(x, y)
|
actual_x, actual_y = self.get_actual_coords(x, y)
|
||||||
core_node = self.core.create_node(
|
core_node = self.core.create_node(
|
||||||
actual_x, actual_y, self.node_draw.node_type, self.node_draw.model
|
actual_x,
|
||||||
|
actual_y,
|
||||||
|
self.manager.node_draw.node_type,
|
||||||
|
self.manager.node_draw.model,
|
||||||
)
|
)
|
||||||
if not core_node:
|
if not core_node:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
image_enum = self.node_draw.image_enum
|
image_enum = self.manager.node_draw.image_enum
|
||||||
self.node_draw.image = self.app.get_icon(image_enum, ICON_SIZE)
|
self.manager.node_draw.image = self.app.get_icon(image_enum, ICON_SIZE)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
image_file = self.node_draw.image_file
|
image_file = self.manager.node_draw.image_file
|
||||||
self.node_draw.image = self.app.get_custom_icon(image_file, ICON_SIZE)
|
self.manager.node_draw.image = self.app.get_custom_icon(
|
||||||
node = CanvasNode(self.app, x, y, core_node, self.node_draw.image)
|
image_file, ICON_SIZE
|
||||||
|
)
|
||||||
|
node = CanvasNode(self.app, self, x, y, core_node, self.manager.node_draw.image)
|
||||||
self.nodes[node.id] = node
|
self.nodes[node.id] = node
|
||||||
self.core.set_canvas_node(core_node, node)
|
self.core.set_canvas_node(core_node, node)
|
||||||
|
|
||||||
|
@ -847,7 +820,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
# redraw gridlines to new canvas size
|
# redraw gridlines to new canvas size
|
||||||
self.delete(tags.GRIDLINE)
|
self.delete(tags.GRIDLINE)
|
||||||
self.draw_grid()
|
self.draw_grid()
|
||||||
self.app.canvas.show_grid.click_handler()
|
self.app.manager.show_grid.click_handler()
|
||||||
|
|
||||||
def redraw_wallpaper(self) -> None:
|
def redraw_wallpaper(self) -> None:
|
||||||
if self.adjust_to_dim.get():
|
if self.adjust_to_dim.get():
|
||||||
|
@ -884,7 +857,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.wallpaper_file = None
|
self.wallpaper_file = None
|
||||||
|
|
||||||
def is_selection_mode(self) -> bool:
|
def is_selection_mode(self) -> bool:
|
||||||
return self.mode == GraphMode.SELECT
|
return self.manager.mode == GraphMode.SELECT
|
||||||
|
|
||||||
def create_edge(self, src: CanvasNode, dst: CanvasNode) -> CanvasEdge:
|
def create_edge(self, src: CanvasNode, dst: CanvasNode) -> CanvasEdge:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import BooleanVar
|
from tkinter import BooleanVar, ttk
|
||||||
from typing import Dict, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple
|
||||||
|
|
||||||
from core.gui.app import Application
|
from core.api.grpc.wrappers import Session
|
||||||
from core.gui.coreclient import CoreClient
|
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
from core.gui.graph.edges import CanvasEdge
|
|
||||||
from core.gui.graph.enums import GraphMode
|
from core.gui.graph.enums import GraphMode
|
||||||
from core.gui.graph.graph import CanvasGraph
|
from core.gui.graph.graph import CanvasGraph
|
||||||
from core.gui.graph.node import CanvasNode
|
|
||||||
from core.gui.graph.shape import Shape
|
|
||||||
from core.gui.graph.shapeutils import ShapeType
|
from core.gui.graph.shapeutils import ShapeType
|
||||||
|
from core.gui.nodeutils import NodeDraw
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.coreclient import CoreClient
|
||||||
|
|
||||||
|
|
||||||
class ShowVar(BooleanVar):
|
class ShowVar(BooleanVar):
|
||||||
|
@ -38,18 +40,18 @@ class CanvasManager:
|
||||||
# canvas interactions
|
# canvas interactions
|
||||||
self.mode: GraphMode = GraphMode.SELECT
|
self.mode: GraphMode = GraphMode.SELECT
|
||||||
self.annotation_type: Optional[ShapeType] = None
|
self.annotation_type: Optional[ShapeType] = None
|
||||||
|
self.node_draw: Optional[NodeDraw] = None
|
||||||
self.canvases: Dict[int, CanvasGraph] = {}
|
self.canvases: Dict[int, CanvasGraph] = {}
|
||||||
|
|
||||||
# canvas object storage
|
# canvas object storage
|
||||||
self.nodes: Dict[int, CanvasNode] = {}
|
# TODO: validate this
|
||||||
self.edges: Dict[str, CanvasEdge] = {}
|
|
||||||
self.shapes: Dict[int, Shape] = {}
|
|
||||||
self.wireless_network: Dict[int, Set[int]] = {}
|
self.wireless_network: Dict[int, Set[int]] = {}
|
||||||
|
|
||||||
# global canvas settings
|
# global canvas settings
|
||||||
width = self.app.guiconfig.preferences.width
|
self.default_dimensions: Tuple[int, int] = (
|
||||||
height = self.app.guiconfig.preferences.height
|
self.app.guiconfig.preferences.width,
|
||||||
self.default_dimensions: Tuple[int, int] = (width, height)
|
self.app.guiconfig.preferences.height,
|
||||||
|
)
|
||||||
self.show_node_labels: ShowVar = ShowVar(self, tags.NODE_LABEL, value=True)
|
self.show_node_labels: ShowVar = ShowVar(self, tags.NODE_LABEL, value=True)
|
||||||
self.show_link_labels: ShowVar = ShowVar(self, tags.LINK_LABEL, value=True)
|
self.show_link_labels: ShowVar = ShowVar(self, tags.LINK_LABEL, value=True)
|
||||||
self.show_links: ShowVar = ShowVar(self, tags.EDGE, value=True)
|
self.show_links: ShowVar = ShowVar(self, tags.EDGE, value=True)
|
||||||
|
@ -65,3 +67,46 @@ class CanvasManager:
|
||||||
self.throughput_threshold: float = 250.0
|
self.throughput_threshold: float = 250.0
|
||||||
self.throughput_width: int = 10
|
self.throughput_width: int = 10
|
||||||
self.throughput_color: str = "#FF0000"
|
self.throughput_color: str = "#FF0000"
|
||||||
|
|
||||||
|
# widget
|
||||||
|
self.notebook: Optional[ttk.Notebook] = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self) -> None:
|
||||||
|
self.notebook = ttk.Notebook(self.master)
|
||||||
|
self.notebook.grid(sticky=tk.NSEW)
|
||||||
|
|
||||||
|
def join(self, session: Session) -> None:
|
||||||
|
# clear out all canvas
|
||||||
|
for tab_id in self.notebook.tabs():
|
||||||
|
self.notebook.forget(tab_id)
|
||||||
|
self.canvases.clear()
|
||||||
|
|
||||||
|
# reset settings
|
||||||
|
self.show_node_labels.set(True)
|
||||||
|
self.show_link_labels.set(True)
|
||||||
|
self.show_grid.set(True)
|
||||||
|
self.show_annotations.set(True)
|
||||||
|
self.show_iface_names.set(False)
|
||||||
|
self.show_ip4s.set(True)
|
||||||
|
self.show_ip6s.set(True)
|
||||||
|
self.show_loss_links.set(True)
|
||||||
|
self.mode = GraphMode.SELECT
|
||||||
|
self.annotation_type = None
|
||||||
|
self.node_draw = None
|
||||||
|
|
||||||
|
# draw initial tab(s) and session
|
||||||
|
tab = ttk.Frame(self.notebook, padding=0)
|
||||||
|
tab.grid(sticky=tk.NSEW)
|
||||||
|
tab.columnconfigure(0, weight=1)
|
||||||
|
tab.rowconfigure(0, weight=1)
|
||||||
|
tab_id = len(self.notebook.tabs())
|
||||||
|
self.notebook.add(tab, text=f"Canvas {tab_id}")
|
||||||
|
logging.info("canvas tab id: %s", tab_id)
|
||||||
|
canvas = CanvasGraph(tab, self.app, self, self.core, tab_id)
|
||||||
|
canvas.grid(sticky=tk.NSEW)
|
||||||
|
self.canvases[tab_id] = canvas
|
||||||
|
|
||||||
|
canvas.reset_and_redraw(session)
|
||||||
|
self.core.parse_metadata(canvas)
|
||||||
|
canvas.organize()
|
||||||
|
|
|
@ -31,10 +31,16 @@ NODE_TEXT_OFFSET: int = 5
|
||||||
|
|
||||||
class CanvasNode:
|
class CanvasNode:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, app: "Application", x: float, y: float, core_node: Node, image: PhotoImage
|
self,
|
||||||
|
app: "Application",
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
x: float,
|
||||||
|
y: float,
|
||||||
|
core_node: Node,
|
||||||
|
image: PhotoImage,
|
||||||
):
|
):
|
||||||
self.app: "Application" = app
|
self.app: "Application" = app
|
||||||
self.canvas: "CanvasGraph" = app.canvas
|
self.canvas: "CanvasGraph" = canvas
|
||||||
self.image: PhotoImage = image
|
self.image: PhotoImage = image
|
||||||
self.core_node: Node = core_node
|
self.core_node: Node = core_node
|
||||||
self.id: int = self.canvas.create_image(
|
self.id: int = self.canvas.create_image(
|
||||||
|
@ -49,7 +55,7 @@ class CanvasNode:
|
||||||
tags=tags.NODE_LABEL,
|
tags=tags.NODE_LABEL,
|
||||||
font=self.app.icon_text_font,
|
font=self.app.icon_text_font,
|
||||||
fill="#0000CD",
|
fill="#0000CD",
|
||||||
state=self.canvas.show_node_labels.state(),
|
state=self.app.manager.show_node_labels.state(),
|
||||||
)
|
)
|
||||||
self.tooltip: CanvasTooltip = CanvasTooltip(self.canvas)
|
self.tooltip: CanvasTooltip = CanvasTooltip(self.canvas)
|
||||||
self.edges: Set[CanvasEdge] = set()
|
self.edges: Set[CanvasEdge] = set()
|
||||||
|
|
|
@ -22,7 +22,7 @@ from core.gui.dialogs.servers import ServersDialog
|
||||||
from core.gui.dialogs.sessionoptions import SessionOptionsDialog
|
from core.gui.dialogs.sessionoptions import SessionOptionsDialog
|
||||||
from core.gui.dialogs.sessions import SessionsDialog
|
from core.gui.dialogs.sessions import SessionsDialog
|
||||||
from core.gui.dialogs.throughput import ThroughputDialog
|
from core.gui.dialogs.throughput import ThroughputDialog
|
||||||
from core.gui.graph.graph import CanvasGraph
|
from core.gui.graph.manager import CanvasManager
|
||||||
from core.gui.nodeutils import ICON_SIZE
|
from core.gui.nodeutils import ICON_SIZE
|
||||||
from core.gui.observers import ObserversMenu
|
from core.gui.observers import ObserversMenu
|
||||||
from core.gui.task import ProgressTask
|
from core.gui.task import ProgressTask
|
||||||
|
@ -45,7 +45,7 @@ class Menubar(tk.Menu):
|
||||||
super().__init__(app)
|
super().__init__(app)
|
||||||
self.app: "Application" = app
|
self.app: "Application" = app
|
||||||
self.core: CoreClient = app.core
|
self.core: CoreClient = app.core
|
||||||
self.canvas: CanvasGraph = app.canvas
|
self.canvas_manager: CanvasManager = app.manager
|
||||||
self.recent_menu: Optional[tk.Menu] = None
|
self.recent_menu: Optional[tk.Menu] = None
|
||||||
self.edit_menu: Optional[tk.Menu] = None
|
self.edit_menu: Optional[tk.Menu] = None
|
||||||
self.observers_menu: Optional[ObserversMenu] = None
|
self.observers_menu: Optional[ObserversMenu] = None
|
||||||
|
@ -145,52 +145,52 @@ class Menubar(tk.Menu):
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="Interface Names",
|
label="Interface Names",
|
||||||
command=self.click_edge_label_change,
|
command=self.click_edge_label_change,
|
||||||
variable=self.canvas.show_iface_names,
|
variable=self.canvas_manager.show_iface_names,
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="IPv4 Addresses",
|
label="IPv4 Addresses",
|
||||||
command=self.click_edge_label_change,
|
command=self.click_edge_label_change,
|
||||||
variable=self.canvas.show_ip4s,
|
variable=self.canvas_manager.show_ip4s,
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="IPv6 Addresses",
|
label="IPv6 Addresses",
|
||||||
command=self.click_edge_label_change,
|
command=self.click_edge_label_change,
|
||||||
variable=self.canvas.show_ip6s,
|
variable=self.canvas_manager.show_ip6s,
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="Node Labels",
|
label="Node Labels",
|
||||||
command=self.canvas.show_node_labels.click_handler,
|
command=self.canvas_manager.show_node_labels.click_handler,
|
||||||
variable=self.canvas.show_node_labels,
|
variable=self.canvas_manager.show_node_labels,
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="Link Labels",
|
label="Link Labels",
|
||||||
command=self.canvas.show_link_labels.click_handler,
|
command=self.canvas_manager.show_link_labels.click_handler,
|
||||||
variable=self.canvas.show_link_labels,
|
variable=self.canvas_manager.show_link_labels,
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="Links",
|
label="Links",
|
||||||
command=self.canvas.show_links.click_handler,
|
command=self.canvas_manager.show_links.click_handler,
|
||||||
variable=self.canvas.show_links,
|
variable=self.canvas_manager.show_links,
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="Loss Links",
|
label="Loss Links",
|
||||||
command=self.canvas.show_loss_links.click_handler,
|
command=self.canvas_manager.show_loss_links.click_handler,
|
||||||
variable=self.canvas.show_loss_links,
|
variable=self.canvas_manager.show_loss_links,
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="Wireless Links",
|
label="Wireless Links",
|
||||||
command=self.canvas.show_wireless.click_handler,
|
command=self.canvas_manager.show_wireless.click_handler,
|
||||||
variable=self.canvas.show_wireless,
|
variable=self.canvas_manager.show_wireless,
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="Annotations",
|
label="Annotations",
|
||||||
command=self.canvas.show_annotations.click_handler,
|
command=self.canvas_manager.show_annotations.click_handler,
|
||||||
variable=self.canvas.show_annotations,
|
variable=self.canvas_manager.show_annotations,
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(
|
||||||
label="Canvas Grid",
|
label="Canvas Grid",
|
||||||
command=self.canvas.show_grid.click_handler,
|
command=self.canvas_manager.show_grid.click_handler,
|
||||||
variable=self.canvas.show_grid,
|
variable=self.canvas_manager.show_grid,
|
||||||
)
|
)
|
||||||
self.add_cascade(label="View", menu=menu)
|
self.add_cascade(label="View", menu=menu)
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ class StatusBar(ttk.Frame):
|
||||||
|
|
||||||
self.zoom = ttk.Label(self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE)
|
self.zoom = ttk.Label(self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE)
|
||||||
self.zoom.grid(row=0, column=1, sticky=tk.EW)
|
self.zoom.grid(row=0, column=1, sticky=tk.EW)
|
||||||
self.set_zoom(self.app.canvas.ratio)
|
# self.set_zoom(self.app.canvas.ratio)
|
||||||
|
|
||||||
self.cpu_label = ttk.Label(
|
self.cpu_label = ttk.Label(
|
||||||
self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE
|
self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE
|
||||||
|
|
|
@ -257,8 +257,8 @@ class Toolbar(ttk.Frame):
|
||||||
|
|
||||||
def draw_node_picker(self) -> None:
|
def draw_node_picker(self) -> None:
|
||||||
self.hide_marker()
|
self.hide_marker()
|
||||||
self.app.canvas.mode = GraphMode.NODE
|
self.app.manager.mode = GraphMode.NODE
|
||||||
self.app.canvas.node_draw = self.current_node
|
self.app.manager.node_draw = self.current_node
|
||||||
self.design_frame.select_radio(self.node_button)
|
self.design_frame.select_radio(self.node_button)
|
||||||
self.picker = PickerFrame(self.app, self.node_button)
|
self.picker = PickerFrame(self.app, self.node_button)
|
||||||
# draw default nodes
|
# draw default nodes
|
||||||
|
@ -278,7 +278,7 @@ class Toolbar(ttk.Frame):
|
||||||
|
|
||||||
def click_selection(self) -> None:
|
def click_selection(self) -> None:
|
||||||
self.design_frame.select_radio(self.select_button)
|
self.design_frame.select_radio(self.select_button)
|
||||||
self.app.canvas.mode = GraphMode.SELECT
|
self.app.manager.mode = GraphMode.SELECT
|
||||||
self.hide_marker()
|
self.hide_marker()
|
||||||
|
|
||||||
def click_runtime_selection(self) -> None:
|
def click_runtime_selection(self) -> None:
|
||||||
|
@ -324,7 +324,7 @@ class Toolbar(ttk.Frame):
|
||||||
|
|
||||||
def click_link(self) -> None:
|
def click_link(self) -> None:
|
||||||
self.design_frame.select_radio(self.link_button)
|
self.design_frame.select_radio(self.link_button)
|
||||||
self.app.canvas.mode = GraphMode.EDGE
|
self.app.manager.mode = GraphMode.EDGE
|
||||||
self.hide_marker()
|
self.hide_marker()
|
||||||
|
|
||||||
def update_button(
|
def update_button(
|
||||||
|
@ -337,7 +337,7 @@ class Toolbar(ttk.Frame):
|
||||||
logging.debug("update button(%s): %s", button, node_draw)
|
logging.debug("update button(%s): %s", button, node_draw)
|
||||||
button.configure(image=image)
|
button.configure(image=image)
|
||||||
button.image = image
|
button.image = image
|
||||||
self.app.canvas.node_draw = node_draw
|
self.app.manager.node_draw = node_draw
|
||||||
if type_enum == NodeTypeEnum.NODE:
|
if type_enum == NodeTypeEnum.NODE:
|
||||||
self.current_node = node_draw
|
self.current_node = node_draw
|
||||||
elif type_enum == NodeTypeEnum.NETWORK:
|
elif type_enum == NodeTypeEnum.NETWORK:
|
||||||
|
|
Loading…
Add table
Reference in a new issue