renamed python gui to be more similar to other core scripts and specific to it being python, some cleanup to pygui edge drawing and updates to allow for edges to have an arc to support multiple links between the same nodes

This commit is contained in:
Blake Harnden 2020-04-14 10:47:42 -07:00
parent 8c8024df10
commit cd8157eff7
6 changed files with 154 additions and 120 deletions

View file

@ -5,7 +5,7 @@ verify_ssl = true
[scripts]
core = "python scripts/core-daemon -f data/core.conf -l data/logging.conf"
coretk = "python scripts/coretk-gui"
core-pygui = "python scripts/core-pygui"
test = "pytest -v tests"
test-mock = "pytest -v --mock tests"
test-emane = "pytest -v tests/emane"

View file

@ -1,11 +1,13 @@
import logging
import math
import tkinter as tk
from typing import TYPE_CHECKING, Any, Tuple
from core.api.grpc import core_pb2
from core.gui import themes
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
from core.gui.graph import tags
from core.gui.nodeutils import EdgeUtils, NodeUtils
from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING:
from core.gui.graph.graph import CanvasGraph
@ -17,80 +19,142 @@ WIRELESS_WIDTH = 1.5
WIRELESS_COLOR = "#009933"
class CanvasWirelessEdge:
def __init__(
self,
token: Tuple[Any, ...],
position: Tuple[float, float, float, float],
src: int,
dst: int,
canvas: "CanvasGraph",
):
logging.debug("Draw wireless link from node %s to node %s", src, dst)
self.token = token
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) -> Tuple[int, ...]:
return tuple(sorted([src, dst]))
class Edge:
tag = tags.EDGE
def __init__(self, canvas: "CanvasGraph", src: int, dst: int = None) -> None:
self.canvas = canvas
self.id = None
self.src = src
self.dst = dst
self.canvas = canvas
self.arc = 0
self.token = None
self.color = EDGE_COLOR
self.width = EDGE_WIDTH
@classmethod
def create_token(cls, src: int, dst: int) -> Tuple[int, ...]:
return tuple(sorted([src, dst]))
def _get_midpoint(
self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float]
) -> Tuple[float, float]:
src_x, src_y = src_pos
dst_x, dst_y = dst_pos
t = math.atan2(dst_y - src_y, dst_x - src_y)
x_mp = (src_x + dst_x) / 2 + self.arc * math.sin(t)
y_mp = (src_y + dst_y) / 2 - self.arc * math.cos(t)
return x_mp, y_mp
def draw(self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float]) -> None:
mid_pos = self._get_midpoint(src_pos, dst_pos)
self.id = self.canvas.create_line(
*position,
tags=tags.WIRELESS_EDGE,
width=WIRELESS_WIDTH * self.canvas.app.app_scale,
fill=WIRELESS_COLOR,
*src_pos,
*mid_pos,
*dst_pos,
smooth=True,
tags=self.tag,
width=self.width * self.canvas.app.app_scale,
fill=self.color,
)
def delete(self):
def move_node(self, node_id: int, x: float, y: float) -> None:
if self.src == node_id:
self.move_src(x, y)
else:
self.move_dst(x, y)
def move_dst(self, x: float, y: float) -> None:
dst_pos = (x, y)
src_x, src_y, _, _, _, _ = self.canvas.coords(self.id)
src_pos = (src_x, src_y)
mid_pos = self._get_midpoint(src_pos, dst_pos)
self.canvas.coords(self.id, *src_pos, *mid_pos, *dst_pos)
def move_src(self, x: float, y: float) -> None:
src_pos = (x, y)
_, _, _, _, dst_x, dst_y = self.canvas.coords(self.id)
dst_pos = (dst_x, dst_y)
mid_pos = self._get_midpoint(src_pos, dst_pos)
self.canvas.coords(self.id, *src_pos, *mid_pos, *dst_pos)
def delete(self) -> None:
self.canvas.delete(self.id)
class CanvasEdge:
class CanvasWirelessEdge(Edge):
tag = tags.WIRELESS_EDGE
def __init__(
self,
canvas: "CanvasGraph",
src: int,
dst: int,
src_pos: Tuple[float, float],
dst_pos: Tuple[float, float],
token: Tuple[Any, ...],
) -> None:
logging.debug("drawing wireless link from node %s to node %s", src, dst)
super().__init__(canvas, src, dst)
self.token = token
self.width = WIRELESS_WIDTH
self.color = WIRELESS_COLOR
self.draw(src_pos, dst_pos)
class CanvasEdge(Edge):
"""
Canvas edge class
"""
def __init__(
self,
x1: float,
y1: float,
x2: float,
y2: float,
src: int,
canvas: "CanvasGraph",
):
src: int,
src_pos: Tuple[float, float],
dst_pos: Tuple[float, float],
) -> None:
"""
Create an instance of canvas edge object
"""
self.src = src
self.dst = None
super().__init__(canvas, src)
self.src_interface = None
self.dst_interface = None
self.canvas = canvas
self.id = self.canvas.create_line(
x1,
y1,
x2,
y2,
tags=tags.EDGE,
width=EDGE_WIDTH * self.canvas.app.app_scale,
fill=EDGE_COLOR,
)
self.text_src = None
self.text_dst = None
self.text_middle = None
self.token = None
self.link = None
self.asymmetric_link = None
self.throughput = None
self.draw(src_pos, dst_pos)
self.set_binding()
def set_binding(self):
def move_node(self, node_id: int, x: float, y: float) -> None:
super().move_node(node_id, x, y)
self.update_labels()
def set_binding(self) -> None:
self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.create_context)
def set_link(self, link):
def set_link(self, link) -> None:
self.link = link
self.draw_labels()
def get_coordinates(self) -> [float, float, float, float]:
x1, y1, x2, y2 = self.canvas.coords(self.id)
x1, y1, _, _, x2, y2 = self.canvas.coords(self.id)
v1 = x2 - x1
v2 = y2 - y1
ux = TEXT_DISTANCE * v1
@ -107,24 +171,16 @@ class CanvasEdge:
y = (y1 + y2) / 2
return x, y
def create_labels(self):
def create_labels(self) -> Tuple[str, str]:
label_one = None
if self.link.HasField("interface_one"):
label_one = self.create_label(self.link.interface_one)
label_one = interface_label(self.link.interface_one)
label_two = None
if self.link.HasField("interface_two"):
label_two = self.create_label(self.link.interface_two)
label_two = interface_label(self.link.interface_two)
return label_one, label_two
def create_label(self, interface):
label = ""
if interface.ip4:
label = f"{interface.ip4}/{interface.ip4mask}"
if interface.ip6:
label = f"{label}\n{interface.ip6}/{interface.ip6mask}"
return label
def draw_labels(self):
def draw_labels(self) -> None:
x1, y1, x2, y2 = self.get_coordinates()
label_one, label_two = self.create_labels()
self.text_src = self.canvas.create_text(
@ -144,12 +200,12 @@ class CanvasEdge:
tags=tags.LINK_INFO,
)
def redraw(self):
def redraw(self) -> None:
label_one, label_two = self.create_labels()
self.canvas.itemconfig(self.text_src, text=label_one)
self.canvas.itemconfig(self.text_dst, text=label_two)
def update_labels(self):
def update_labels(self) -> None:
"""
Move edge labels based on current position.
"""
@ -160,7 +216,7 @@ class CanvasEdge:
x, y = self.get_midpoint()
self.canvas.coords(self.text_middle, x, y)
def set_throughput(self, throughput: float):
def set_throughput(self, throughput: float) -> None:
throughput = 0.001 * throughput
value = f"{throughput:.3f} kbps"
if self.text_middle is None:
@ -179,18 +235,17 @@ class CanvasEdge:
width = EDGE_WIDTH
self.canvas.itemconfig(self.id, fill=color, width=width)
def complete(self, dst: int):
def complete(self, dst: int) -> None:
self.dst = dst
self.token = EdgeUtils.get_token(self.src, self.dst)
self.token = create_edge_token(self.src, self.dst)
x, y = self.canvas.coords(self.dst)
x1, y1, _, _ = self.canvas.coords(self.id)
self.canvas.coords(self.id, x1, y1, x, y)
self.move_dst(x, y)
self.check_wireless()
self.canvas.tag_raise(self.src)
self.canvas.tag_raise(self.dst)
logging.debug("Draw wired link from node %s to node %s", self.src, dst)
def is_wireless(self) -> [bool, bool]:
def is_wireless(self) -> bool:
src_node = self.canvas.nodes[self.src]
dst_node = self.canvas.nodes[self.dst]
src_node_type = src_node.core_node.type
@ -210,12 +265,12 @@ class CanvasEdge:
wlan_network[self.dst].add(self.src)
return is_src_wireless or is_dst_wireless
def check_wireless(self):
def check_wireless(self) -> None:
if self.is_wireless():
self.canvas.itemconfig(self.id, state=tk.HIDDEN)
self._check_antenna()
def _check_antenna(self):
def _check_antenna(self) -> None:
src_node = self.canvas.nodes[self.src]
dst_node = self.canvas.nodes[self.dst]
src_node_type = src_node.core_node.type
@ -230,20 +285,19 @@ class CanvasEdge:
else:
src_node.add_antenna()
def delete(self):
def delete(self) -> None:
logging.debug("Delete canvas edge, id: %s", self.id)
self.canvas.delete(self.id)
if self.link:
self.canvas.delete(self.text_src)
self.canvas.delete(self.text_dst)
super().delete()
self.canvas.delete(self.text_src)
self.canvas.delete(self.text_dst)
self.canvas.delete(self.text_middle)
def reset(self):
def reset(self) -> None:
self.canvas.delete(self.text_middle)
self.text_middle = None
self.canvas.itemconfig(self.id, fill=EDGE_COLOR, width=EDGE_WIDTH)
def create_context(self, event: tk.Event):
def create_context(self, event: tk.Event) -> None:
context = tk.Menu(self.canvas)
themes.style_menu(context)
context.add_command(label="Configure", command=self.configure)
@ -256,6 +310,6 @@ class CanvasEdge:
context.entryconfigure(3, state="disabled")
context.post(event.x_root, event.y_root)
def configure(self):
def configure(self) -> None:
dialog = LinkConfigurationDialog(self.canvas, self.canvas.app, self)
dialog.show()

View file

@ -7,13 +7,18 @@ from PIL import Image, ImageTk
from core.api.grpc import core_pb2
from core.gui.dialogs.shapemod import ShapeDialog
from core.gui.graph import tags
from core.gui.graph.edges import EDGE_WIDTH, CanvasEdge, CanvasWirelessEdge
from core.gui.graph.edges import (
EDGE_WIDTH,
CanvasEdge,
CanvasWirelessEdge,
create_edge_token,
)
from core.gui.graph.enums import GraphMode, ScaleOption
from core.gui.graph.node import CanvasNode
from core.gui.graph.shape import Shape
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
from core.gui.images import ImageEnum, Images, TypeToImage
from core.gui.nodeutils import EdgeUtils, NodeUtils
from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING:
from core.gui.app import Application
@ -197,11 +202,10 @@ class CanvasGraph(tk.Canvas):
"""
add a wireless edge between 2 canvas nodes
"""
token = EdgeUtils.get_token(src.id, dst.id)
x1, y1 = self.coords(src.id)
x2, y2 = self.coords(dst.id)
position = (x1, y1, x2, y2)
edge = CanvasWirelessEdge(token, position, src.id, dst.id, self)
token = create_edge_token(src.id, dst.id)
src_pos = self.coords(src.id)
dst_pos = self.coords(dst.id)
edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token)
self.wireless_edges[token] = edge
src.wireless_edges.add(edge)
dst.wireless_edges.add(edge)
@ -209,7 +213,7 @@ class CanvasGraph(tk.Canvas):
self.tag_raise(dst.id)
def delete_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
token = EdgeUtils.get_token(src.id, dst.id)
token = create_edge_token(src.id, dst.id)
edge = self.wireless_edges.pop(token)
edge.delete()
src.wireless_edges.remove(edge)
@ -246,20 +250,15 @@ class CanvasGraph(tk.Canvas):
node_one = canvas_node_one.core_node
canvas_node_two = self.core.canvas_nodes[link.node_two_id]
node_two = canvas_node_two.core_node
token = EdgeUtils.get_token(canvas_node_one.id, canvas_node_two.id)
token = create_edge_token(canvas_node_one.id, canvas_node_two.id)
if link.type == core_pb2.LinkType.WIRELESS:
self.add_wireless_edge(canvas_node_one, canvas_node_two)
else:
if token not in self.edges:
edge = CanvasEdge(
node_one.position.x,
node_one.position.y,
node_two.position.x,
node_two.position.y,
canvas_node_one.id,
self,
)
src_pos = (node_one.position.x, node_one.position.y)
dst_pos = (node_two.position.x, node_two.position.y)
edge = CanvasEdge(self, canvas_node_one.id, src_pos, dst_pos)
edge.token = token
edge.dst = canvas_node_two.id
edge.set_link(link)
@ -391,7 +390,7 @@ class CanvasGraph(tk.Canvas):
return
# ignore repeated edges
token = EdgeUtils.get_token(edge.src, self.selected)
token = create_edge_token(edge.src, self.selected)
if token in self.edges:
edge.delete()
return
@ -520,11 +519,10 @@ class CanvasGraph(tk.Canvas):
logging.debug("click press offset(%s, %s)", x_check, y_check)
is_node = selected in self.nodes
if self.mode == GraphMode.EDGE and is_node:
x, y = self.coords(selected)
self.drawing_edge = CanvasEdge(x, y, x, y, selected, self)
pos = self.coords(selected)
self.drawing_edge = CanvasEdge(self, selected, pos, pos)
if self.mode == GraphMode.ANNOTATION:
if is_marker(self.annotation_type):
r = self.app.toolbar.marker_tool.radius
self.create_oval(
@ -603,8 +601,7 @@ class CanvasGraph(tk.Canvas):
self.cursor = x, y
if self.mode == GraphMode.EDGE and self.drawing_edge is not None:
x1, y1, _, _ = self.coords(self.drawing_edge.id)
self.coords(self.drawing_edge.id, x1, y1, x, y)
self.drawing_edge.move_dst(x, y)
if self.mode == GraphMode.ANNOTATION:
if is_draw_shape(self.annotation_type) and self.shape_drawing:
shape = self.shapes[self.selected]
@ -841,11 +838,10 @@ class CanvasGraph(tk.Canvas):
"""
create an edge between source node and destination node
"""
if (source.id, dest.id) not in self.edges:
pos0 = source.core_node.position
x0 = pos0.x
y0 = pos0.y
edge = CanvasEdge(x0, y0, x0, y0, source.id, self)
token = create_edge_token(source.id, dest.id)
if token not in self.edges:
pos = (source.core_node.position.x, source.core_node.position.y)
edge = CanvasEdge(self, source.id, pos, pos)
edge.complete(dest.id)
self.edges[edge.token] = edge
self.nodes[source.id].edges.add(edge)
@ -905,7 +901,7 @@ class CanvasGraph(tk.Canvas):
dest_node_copy = self.nodes[copy_map[edge.token[1]]]
self.create_edge(source_node_copy, dest_node_copy)
copy_edge = self.edges[
EdgeUtils.get_token(source_node_copy.id, dest_node_copy.id)
create_edge_token(source_node_copy.id, dest_node_copy.id)
]
copy_link = copy_edge.link
options = edge.link.options

View file

@ -148,19 +148,9 @@ class CanvasNode:
# move edges
for edge in self.edges:
x1, y1, x2, y2 = self.canvas.coords(edge.id)
if edge.src == self.id:
self.canvas.coords(edge.id, x, y, x2, y2)
else:
self.canvas.coords(edge.id, x1, y1, x, y)
edge.update_labels()
edge.move_node(self.id, x, y)
for edge in self.wireless_edges:
x1, y1, x2, y2 = self.canvas.coords(edge.id)
if edge.src == self.id:
self.canvas.coords(edge.id, x, y, x2, y2)
else:
self.canvas.coords(edge.id, x1, y1, x, y)
edge.move_node(self.id, x, y)
# set actual coords for node and update core is running
real_x, real_y = self.canvas.get_actual_coords(x, y)

View file

@ -1,5 +1,5 @@
import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union
from core.api.grpc.core_pb2 import NodeType
from core.gui.images import ImageEnum, Images, TypeToImage
@ -172,9 +172,3 @@ class NodeUtils:
cls.NETWORK_NODES.append(node_draw)
cls.NODE_ICONS[(node_type, None)] = node_draw.image
cls.ANTENNA_ICON = Images.get(ImageEnum.ANTENNA, ANTENNA_SIZE)
class EdgeUtils:
@classmethod
def get_token(cls, src: int, dst: int) -> Tuple[int, ...]:
return tuple(sorted([src, dst]))