pygui: initial changes to allow multiple wired links between nodes, updated to commonize logic for creating edges and adjustments in the UI for several edge related cases

This commit is contained in:
Blake Harnden 2020-10-12 20:28:27 -07:00
parent 96dddb687d
commit 29bd6ef7f8
5 changed files with 119 additions and 114 deletions

View file

@ -87,14 +87,14 @@ class CoreClient:
self.read_config() self.read_config()
# helpers # helpers
self.iface_to_edge: Dict[Tuple[int, ...], Tuple[int, ...]] = {} self.iface_to_edge: Dict[Tuple[int, ...], CanvasEdge] = {}
self.ifaces_manager: InterfaceManager = InterfaceManager(self.app) self.ifaces_manager: InterfaceManager = InterfaceManager(self.app)
self.observer: Optional[str] = None self.observer: Optional[str] = None
# session data # session data
self.mobility_players: Dict[int, MobilityPlayer] = {} self.mobility_players: Dict[int, MobilityPlayer] = {}
self.canvas_nodes: Dict[int, CanvasNode] = {} self.canvas_nodes: Dict[int, CanvasNode] = {}
self.links: Dict[Tuple[int, int], CanvasEdge] = {} self.links: Dict[str, CanvasEdge] = {}
self.handling_throughputs: Optional[grpc.Future] = None self.handling_throughputs: Optional[grpc.Future] = None
self.handling_cpu_usage: Optional[grpc.Future] = None self.handling_cpu_usage: Optional[grpc.Future] = None
self.handling_events: Optional[grpc.Future] = None self.handling_events: Optional[grpc.Future] = None
@ -225,7 +225,9 @@ class CoreClient:
self.app.canvas.add_wired_edge(canvas_node1, canvas_node2, event.link) self.app.canvas.add_wired_edge(canvas_node1, canvas_node2, event.link)
self.app.canvas.organize() self.app.canvas.organize()
elif event.message_type == MessageType.DELETE: elif event.message_type == MessageType.DELETE:
self.app.canvas.delete_wired_edge(canvas_node1, canvas_node2) self.app.canvas.delete_wired_edge(
canvas_node1, canvas_node2, event.link
)
elif event.message_type == MessageType.NONE: elif event.message_type == MessageType.NONE:
self.app.canvas.update_wired_edge( self.app.canvas.update_wired_edge(
canvas_node1, canvas_node2, event.link canvas_node1, canvas_node2, event.link
@ -877,7 +879,7 @@ class CoreClient:
def create_link( def create_link(
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
) -> None: ) -> Link:
""" """
Create core link for a pair of canvas nodes, with token referencing Create core link for a pair of canvas nodes, with token referencing
the canvas edge. the canvas edge.
@ -888,15 +890,9 @@ class CoreClient:
src_iface = None src_iface = None
if NodeUtils.is_container_node(src_node.type): if NodeUtils.is_container_node(src_node.type):
src_iface = self.create_iface(canvas_src_node) src_iface = self.create_iface(canvas_src_node)
self.iface_to_edge[(src_node.id, src_iface.id)] = edge.token
edge.src_iface = src_iface
canvas_src_node.ifaces[src_iface.id] = src_iface
dst_iface = None dst_iface = None
if NodeUtils.is_container_node(dst_node.type): if NodeUtils.is_container_node(dst_node.type):
dst_iface = self.create_iface(canvas_dst_node) dst_iface = self.create_iface(canvas_dst_node)
self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token
edge.dst_iface = dst_iface
canvas_dst_node.ifaces[dst_iface.id] = dst_iface
link = Link( link = Link(
type=LinkType.WIRED, type=LinkType.WIRED,
node1_id=src_node.id, node1_id=src_node.id,
@ -904,9 +900,21 @@ class CoreClient:
iface1=src_iface, iface1=src_iface,
iface2=dst_iface, iface2=dst_iface,
) )
edge.set_link(link)
self.links[edge.token] = edge
logging.info("added link between %s and %s", src_node.name, dst_node.name) logging.info("added link between %s and %s", src_node.name, dst_node.name)
return link
def save_edge(
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
) -> None:
self.links[edge.token] = edge
src_node = canvas_src_node.core_node
dst_node = canvas_dst_node.core_node
if NodeUtils.is_container_node(src_node.type):
src_iface_id = edge.src_iface.id
self.iface_to_edge[(src_node.id, src_iface_id)] = edge
if NodeUtils.is_container_node(dst_node.type):
dst_iface_id = edge.dst_iface.id
self.iface_to_edge[(dst_node.id, dst_iface_id)] = edge
def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]: def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]:
configs = [] configs = []

View file

@ -68,10 +68,14 @@ class LinkConfigurationDialog(Dialog):
def draw(self) -> None: def draw(self) -> None:
self.top.columnconfigure(0, weight=1) self.top.columnconfigure(0, weight=1)
source_name = self.app.canvas.nodes[self.edge.src].core_node.name src_label = self.app.canvas.nodes[self.edge.src].core_node.name
dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name if self.edge.link.iface1:
src_label += f":{self.edge.link.iface1.name}"
dst_label = self.app.canvas.nodes[self.edge.dst].core_node.name
if self.edge.link.iface2:
dst_label += f":{self.edge.link.iface2.name}"
label = ttk.Label( label = ttk.Label(
self.top, text=f"Link from {source_name} to {dest_name}", anchor=tk.CENTER self.top, text=f"{src_label} to {dst_label}", anchor=tk.CENTER
) )
label.grid(row=0, column=0, sticky=tk.EW, pady=PADY) label.grid(row=0, column=0, sticky=tk.EW, pady=PADY)

View file

@ -22,11 +22,14 @@ WIRELESS_COLOR: str = "#009933"
ARC_DISTANCE: int = 50 ARC_DISTANCE: int = 50
def create_edge_token(src: int, dst: int, network: int = None) -> Tuple[int, ...]: def create_wireless_token(src: int, dst: int, network: int) -> str:
values = [src, dst] return f"{src}-{dst}-{network}"
if network is not None:
values.append(network)
return tuple(sorted(values)) def create_edge_token(src: int, dst: int, link: Link) -> str:
iface1_id = link.iface1.id if link.iface1 else None
iface2_id = link.iface2.id if link.iface2 else None
return f"{src}-{iface1_id}-{dst}-{iface2_id}"
def arc_edges(edges) -> None: def arc_edges(edges) -> None:
@ -67,17 +70,13 @@ class Edge:
self.src: int = src self.src: int = src
self.dst: int = dst self.dst: int = dst
self.arc: int = 0 self.arc: int = 0
self.token: Optional[Tuple[int, ...]] = None self.token: Optional[str] = None
self.src_label: Optional[int] = None self.src_label: Optional[int] = None
self.middle_label: Optional[int] = None self.middle_label: Optional[int] = None
self.dst_label: Optional[int] = None self.dst_label: Optional[int] = None
self.color: str = EDGE_COLOR self.color: str = EDGE_COLOR
self.width: int = EDGE_WIDTH self.width: int = EDGE_WIDTH
@classmethod
def create_token(cls, src: int, dst: int) -> Tuple[int, ...]:
return tuple(sorted([src, dst]))
def scaled_width(self) -> float: def scaled_width(self) -> float:
return self.width * self.canvas.app.app_scale return self.width * self.canvas.app.app_scale
@ -242,15 +241,17 @@ class CanvasWirelessEdge(Edge):
canvas: "CanvasGraph", canvas: "CanvasGraph",
src: int, src: int,
dst: int, dst: int,
network_id: int,
token: str,
src_pos: Tuple[float, float], src_pos: Tuple[float, float],
dst_pos: Tuple[float, float], dst_pos: Tuple[float, float],
token: Tuple[int, ...],
link: Link, link: Link,
) -> None: ) -> None:
logging.debug("drawing wireless link from node %s to node %s", src, dst) logging.debug("drawing wireless link from node %s to node %s", src, dst)
super().__init__(canvas, src, dst) super().__init__(canvas, src, dst)
self.network_id: int = network_id
self.link: Link = link self.link: Link = link
self.token: Tuple[int, ...] = token self.token: str = token
self.width: float = WIRELESS_WIDTH self.width: float = WIRELESS_WIDTH
color = link.color if link.color else WIRELESS_COLOR color = link.color if link.color else WIRELESS_COLOR
self.color: str = color self.color: str = color
@ -362,7 +363,6 @@ class CanvasEdge(Edge):
def complete(self, dst: int, linked_wireless: bool) -> None: def complete(self, dst: int, linked_wireless: bool) -> None:
self.dst = dst self.dst = dst
self.linked_wireless = linked_wireless self.linked_wireless = linked_wireless
self.token = create_edge_token(self.src, self.dst)
dst_pos = self.canvas.coords(self.dst) dst_pos = self.canvas.coords(self.dst)
self.move_dst(dst_pos) self.move_dst(dst_pos)
self.check_wireless() self.check_wireless()

View file

@ -21,8 +21,10 @@ from core.gui.graph.edges import (
EDGE_WIDTH, EDGE_WIDTH,
CanvasEdge, CanvasEdge,
CanvasWirelessEdge, CanvasWirelessEdge,
Edge,
arc_edges, arc_edges,
create_edge_token, create_edge_token,
create_wireless_token,
) )
from core.gui.graph.enums import GraphMode, ScaleOption from core.gui.graph.enums import GraphMode, ScaleOption
from core.gui.graph.node import CanvasNode from core.gui.graph.node import CanvasNode
@ -69,9 +71,9 @@ class CanvasGraph(tk.Canvas):
self.selected: Optional[int] = None self.selected: Optional[int] = None
self.node_draw: Optional[NodeDraw] = None self.node_draw: Optional[NodeDraw] = None
self.nodes: Dict[int, CanvasNode] = {} self.nodes: Dict[int, CanvasNode] = {}
self.edges: Dict[int, CanvasEdge] = {} self.edges: Dict[str, CanvasEdge] = {}
self.shapes: Dict[int, Shape] = {} self.shapes: Dict[int, Shape] = {}
self.wireless_edges: Dict[Tuple[int, ...], CanvasWirelessEdge] = {} self.wireless_edges: Dict[str, CanvasWirelessEdge] = {}
# map wireless/EMANE node to the set of MDRs connected to that node # map wireless/EMANE node to the set of MDRs connected to that node
self.wireless_network: Dict[int, Set[int]] = {} self.wireless_network: Dict[int, Set[int]] = {}
@ -206,14 +208,9 @@ class CanvasGraph(tk.Canvas):
iface_id = iface_throughput.iface_id iface_id = iface_throughput.iface_id
throughput = iface_throughput.throughput throughput = iface_throughput.throughput
iface_to_edge_id = (node_id, iface_id) iface_to_edge_id = (node_id, iface_id)
token = self.core.iface_to_edge.get(iface_to_edge_id) edge = self.core.iface_to_edge.get(iface_to_edge_id)
if not token:
continue
edge = self.edges.get(token)
if edge: if edge:
edge.set_throughput(throughput) edge.set_throughput(throughput)
else:
del self.core.iface_to_edge[iface_to_edge_id]
def draw_grid(self) -> None: def draw_grid(self) -> None:
""" """
@ -230,7 +227,7 @@ class CanvasGraph(tk.Canvas):
self.tag_lower(self.rect) self.tag_lower(self.rect)
def add_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: def add_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
token = create_edge_token(src.id, dst.id) token = create_edge_token(src.id, dst.id, link)
if token in self.edges and link.options.unidirectional: if token in self.edges and link.options.unidirectional:
edge = self.edges[token] edge = self.edges[token]
edge.asymmetric_link = link edge.asymmetric_link = link
@ -240,72 +237,50 @@ class CanvasGraph(tk.Canvas):
src_pos = (node1.position.x, node1.position.y) src_pos = (node1.position.x, node1.position.y)
dst_pos = (node2.position.x, node2.position.y) dst_pos = (node2.position.x, node2.position.y)
edge = CanvasEdge(self, src.id, src_pos, dst_pos) edge = CanvasEdge(self, src.id, src_pos, dst_pos)
edge.linked_wireless = self.is_linked_wireless(src.id, dst.id) self.complete_edge(src, dst, edge)
edge.token = token
edge.dst = dst.id
edge.set_link(link)
edge.check_wireless()
src.edges.add(edge)
dst.edges.add(edge)
self.edges[edge.token] = edge
self.core.links[edge.token] = edge
if link.iface1:
iface1 = link.iface1
self.core.iface_to_edge[(node1.id, iface1.id)] = token
src.ifaces[iface1.id] = iface1
edge.src_iface = iface1
if link.iface2:
iface2 = link.iface2
self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token
dst.ifaces[iface2.id] = iface2
edge.dst_iface = iface2
def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode) -> None: def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
token = create_edge_token(src.id, dst.id) token = create_edge_token(src.id, dst.id, link)
edge = self.edges.get(token) edge = self.edges.get(token)
if not edge: if edge:
return self.delete_edge(edge)
self.delete_edge(edge)
def update_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: def update_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
token = create_edge_token(src.id, dst.id) token = create_edge_token(src.id, dst.id, link)
edge = self.edges.get(token) edge = self.edges.get(token)
if not edge: if edge:
return edge.link.options = deepcopy(link.options)
edge.link.options = deepcopy(link.options)
def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
network_id = link.network_id if link.network_id else None network_id = link.network_id if link.network_id else None
token = create_edge_token(src.id, dst.id, network_id) token = create_wireless_token(src.id, dst.id, network_id)
if token in self.wireless_edges: if token in self.wireless_edges:
logging.warning("ignoring link that already exists: %s", link) logging.warning("ignoring link that already exists: %s", link)
return return
src_pos = self.coords(src.id) src_pos = self.coords(src.id)
dst_pos = self.coords(dst.id) dst_pos = self.coords(dst.id)
edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token, link) edge = CanvasWirelessEdge(
self, src.id, dst.id, network_id, token, src_pos, dst_pos, link
)
self.wireless_edges[token] = edge self.wireless_edges[token] = edge
src.wireless_edges.add(edge) src.wireless_edges.add(edge)
dst.wireless_edges.add(edge) dst.wireless_edges.add(edge)
self.tag_raise(src.id) self.tag_raise(src.id)
self.tag_raise(dst.id) self.tag_raise(dst.id)
# update arcs when there are multiple links self.arc_common_edges(edge)
common_edges = list(src.wireless_edges & dst.wireless_edges)
arc_edges(common_edges)
def delete_wireless_edge( def delete_wireless_edge(
self, src: CanvasNode, dst: CanvasNode, link: Link self, src: CanvasNode, dst: CanvasNode, link: Link
) -> None: ) -> None:
network_id = link.network_id if link.network_id else None network_id = link.network_id if link.network_id else None
token = create_edge_token(src.id, dst.id, network_id) token = create_wireless_token(src.id, dst.id, network_id)
if token not in self.wireless_edges: if token not in self.wireless_edges:
return return
edge = self.wireless_edges.pop(token) edge = self.wireless_edges.pop(token)
edge.delete() edge.delete()
src.wireless_edges.remove(edge) src.wireless_edges.remove(edge)
dst.wireless_edges.remove(edge) dst.wireless_edges.remove(edge)
# update arcs when there are multiple links self.arc_common_edges(edge)
common_edges = list(src.wireless_edges & dst.wireless_edges)
arc_edges(common_edges)
def update_wireless_edge( def update_wireless_edge(
self, src: CanvasNode, dst: CanvasNode, link: Link self, src: CanvasNode, dst: CanvasNode, link: Link
@ -313,7 +288,7 @@ class CanvasGraph(tk.Canvas):
if not link.label: if not link.label:
return return
network_id = link.network_id if link.network_id else None network_id = link.network_id if link.network_id else None
token = create_edge_token(src.id, dst.id, network_id) token = create_wireless_token(src.id, dst.id, network_id)
if token not in self.wireless_edges: if token not in self.wireless_edges:
self.add_wireless_edge(src, dst, link) self.add_wireless_edge(src, dst, link)
else: else:
@ -454,12 +429,6 @@ class CanvasGraph(tk.Canvas):
edge.delete() edge.delete()
return return
# ignore repeated edges
token = create_edge_token(edge.src, self.selected)
if token in self.edges:
edge.delete()
return
# rj45 nodes can only support one link # rj45 nodes can only support one link
if NodeUtils.is_rj45_node(src_node.core_node.type) and src_node.edges: if NodeUtils.is_rj45_node(src_node.core_node.type) and src_node.edges:
edge.delete() edge.delete()
@ -468,14 +437,15 @@ class CanvasGraph(tk.Canvas):
edge.delete() edge.delete()
return return
# set dst node and snap edge to center # finalize edge creation
linked_wireless = self.is_linked_wireless(src_node.id, self.selected) self.complete_edge(src_node, dst_node, edge)
edge.complete(self.selected, linked_wireless)
self.edges[edge.token] = edge def arc_common_edges(self, edge: Edge) -> None:
src_node.edges.add(edge) src_node = self.nodes[edge.src]
dst_node.edges.add(edge) dst_node = self.nodes[edge.dst]
self.core.create_link(edge, src_node, dst_node) common_edges = list(src_node.edges & dst_node.edges)
common_edges += list(src_node.wireless_edges & dst_node.wireless_edges)
arc_edges(common_edges)
def select_object(self, object_id: int, choose_multiple: bool = False) -> None: def select_object(self, object_id: int, choose_multiple: bool = False) -> None:
""" """
@ -572,6 +542,7 @@ class CanvasGraph(tk.Canvas):
if dst_wireless: if dst_wireless:
src_node.delete_antenna() src_node.delete_antenna()
self.core.deleted_canvas_edges([edge]) self.core.deleted_canvas_edges([edge])
self.arc_common_edges(edge)
def zoom(self, event: tk.Event, factor: float = None) -> None: def zoom(self, event: tk.Event, factor: float = None) -> None:
if not factor: if not factor:
@ -903,20 +874,41 @@ class CanvasGraph(tk.Canvas):
def is_selection_mode(self) -> bool: def is_selection_mode(self) -> bool:
return self.mode == GraphMode.SELECT return self.mode == GraphMode.SELECT
def create_edge(self, source: CanvasNode, dest: CanvasNode) -> None: def create_edge(self, src: CanvasNode, dst: CanvasNode) -> CanvasEdge:
""" """
create an edge between source node and destination node create an edge between source node and destination node
""" """
token = create_edge_token(source.id, dest.id) pos = (src.core_node.position.x, src.core_node.position.y)
if token not in self.edges: edge = CanvasEdge(self, src.id, pos, pos)
pos = (source.core_node.position.x, source.core_node.position.y) self.complete_edge(src, dst, edge)
edge = CanvasEdge(self, source.id, pos, pos) return edge
linked_wireless = self.is_linked_wireless(source.id, dest.id)
edge.complete(dest.id, linked_wireless) def complete_edge(
self.edges[edge.token] = edge self,
self.nodes[source.id].edges.add(edge) src: CanvasNode,
self.nodes[dest.id].edges.add(edge) dst: CanvasNode,
self.core.create_link(edge, source, dest) edge: CanvasEdge,
link: Optional[Link] = None,
) -> None:
linked_wireless = self.is_linked_wireless(src.id, dst.id)
edge.complete(dst.id, linked_wireless)
src.edges.add(edge)
dst.edges.add(edge)
if link is None:
link = self.core.create_link(edge, src, dst)
if link.iface1:
iface1 = link.iface1
src.ifaces[iface1.id] = iface1
edge.src_iface = iface1
if link.iface2:
iface2 = link.iface2
dst.ifaces[iface2.id] = iface2
edge.dst_iface = iface2
edge.set_link(link)
edge.token = create_edge_token(src.id, dst.id, edge.link)
self.arc_common_edges(edge)
self.edges[edge.token] = edge
self.core.save_edge(edge, src, dst)
def copy(self) -> None: def copy(self) -> None:
if self.core.is_runtime(): if self.core.is_runtime():
@ -970,13 +962,12 @@ class CanvasGraph(tk.Canvas):
if edge.src not in to_copy_ids or edge.dst not in to_copy_ids: if edge.src not in to_copy_ids or edge.dst not in to_copy_ids:
if canvas_node.id == edge.src: if canvas_node.id == edge.src:
dst_node = self.nodes[edge.dst] dst_node = self.nodes[edge.dst]
self.create_edge(node, dst_node) copy_edge = self.create_edge(node, dst_node)
token = create_edge_token(node.id, dst_node.id)
elif canvas_node.id == edge.dst: elif canvas_node.id == edge.dst:
src_node = self.nodes[edge.src] src_node = self.nodes[edge.src]
self.create_edge(src_node, node) copy_edge = self.create_edge(src_node, node)
token = create_edge_token(src_node.id, node.id) else:
copy_edge = self.edges[token] continue
copy_link = copy_edge.link copy_link = copy_edge.link
iface1_id = copy_link.iface1.id if copy_link.iface1 else None iface1_id = copy_link.iface1.id if copy_link.iface1 else None
iface2_id = copy_link.iface2.id if copy_link.iface2 else None iface2_id = copy_link.iface2.id if copy_link.iface2 else None
@ -1003,13 +994,11 @@ class CanvasGraph(tk.Canvas):
# copy link and link config # copy link and link config
for edge in to_copy_edges: for edge in to_copy_edges:
src_node_id = copy_map[edge.token[0]] src_node_id = copy_map[edge.src]
dst_node_id = copy_map[edge.token[1]] dst_node_id = copy_map[edge.dst]
src_node_copy = self.nodes[src_node_id] src_node_copy = self.nodes[src_node_id]
dst_node_copy = self.nodes[dst_node_id] dst_node_copy = self.nodes[dst_node_id]
self.create_edge(src_node_copy, dst_node_copy) copy_edge = self.create_edge(src_node_copy, dst_node_copy)
token = create_edge_token(src_node_copy.id, dst_node_copy.id)
copy_edge = self.edges[token]
copy_link = copy_edge.link copy_link = copy_edge.link
iface1_id = copy_link.iface1.id if copy_link.iface1 else None iface1_id = copy_link.iface1.id if copy_link.iface1 else None
iface2_id = copy_link.iface2.id if copy_link.iface2 else None iface2_id = copy_link.iface2.id if copy_link.iface2 else None

View file

@ -247,14 +247,18 @@ class CanvasNode:
) )
unlink_menu = tk.Menu(self.context) unlink_menu = tk.Menu(self.context)
for edge in self.edges: for edge in self.edges:
other_id = edge.src link = edge.link
if self.id == other_id: if self.id == edge.src:
other_id = edge.dst other_id = edge.dst
other_iface = link.iface2.name if link.iface2 else None
else:
other_id = edge.src
other_iface = link.iface1.name if link.iface1 else None
other_node = self.canvas.nodes[other_id] other_node = self.canvas.nodes[other_id]
other_name = other_node.core_node.name
label = f"{other_name}:{other_iface}" if other_iface else other_name
func_unlink = functools.partial(self.click_unlink, edge) func_unlink = functools.partial(self.click_unlink, edge)
unlink_menu.add_command( unlink_menu.add_command(label=label, command=func_unlink)
label=other_node.core_node.name, command=func_unlink
)
themes.style_menu(unlink_menu) themes.style_menu(unlink_menu)
self.context.add_cascade(label="Unlink", menu=unlink_menu) self.context.add_cascade(label="Unlink", menu=unlink_menu)
edit_menu = tk.Menu(self.context) edit_menu = tk.Menu(self.context)