pygui: added class variable type hinting to core.gui.graph
This commit is contained in:
parent
0356f3b19c
commit
11be40bc90
8 changed files with 256 additions and 249 deletions
|
@ -79,7 +79,7 @@ class CoreClient:
|
|||
self.read_config()
|
||||
|
||||
# helpers
|
||||
self.iface_to_edge: Dict[Tuple[int, int], Tuple[int, int]] = {}
|
||||
self.iface_to_edge: Dict[Tuple[int, ...], Tuple[int, ...]] = {}
|
||||
self.ifaces_manager: InterfaceManager = InterfaceManager(self.app)
|
||||
|
||||
# session data
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import logging
|
||||
import math
|
||||
import tkinter as tk
|
||||
from typing import TYPE_CHECKING, Any, Tuple
|
||||
from typing import TYPE_CHECKING, Optional, Tuple
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from core.api.grpc.core_pb2 import Interface, Link
|
||||
from core.gui import themes
|
||||
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
|
||||
from core.gui.graph import tags
|
||||
|
@ -12,12 +13,12 @@ from core.gui.nodeutils import NodeUtils
|
|||
if TYPE_CHECKING:
|
||||
from core.gui.graph.graph import CanvasGraph
|
||||
|
||||
TEXT_DISTANCE = 0.30
|
||||
EDGE_WIDTH = 3
|
||||
EDGE_COLOR = "#ff0000"
|
||||
WIRELESS_WIDTH = 1.5
|
||||
WIRELESS_COLOR = "#009933"
|
||||
ARC_DISTANCE = 50
|
||||
TEXT_DISTANCE: float = 0.30
|
||||
EDGE_WIDTH: int = 3
|
||||
EDGE_COLOR: str = "#ff0000"
|
||||
WIRELESS_WIDTH: float = 1.5
|
||||
WIRELESS_COLOR: str = "#009933"
|
||||
ARC_DISTANCE: int = 50
|
||||
|
||||
|
||||
def create_edge_token(src: int, dst: int, network: int = None) -> Tuple[int, ...]:
|
||||
|
@ -57,20 +58,20 @@ def arc_edges(edges) -> None:
|
|||
|
||||
|
||||
class Edge:
|
||||
tag = tags.EDGE
|
||||
tag: str = 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.arc = 0
|
||||
self.token = None
|
||||
self.src_label = None
|
||||
self.middle_label = None
|
||||
self.dst_label = None
|
||||
self.color = EDGE_COLOR
|
||||
self.width = EDGE_WIDTH
|
||||
self.id: Optional[int] = None
|
||||
self.src: int = src
|
||||
self.dst: int = dst
|
||||
self.arc: int = 0
|
||||
self.token: Optional[Tuple[int, ...]] = None
|
||||
self.src_label: Optional[int] = None
|
||||
self.middle_label: Optional[int] = None
|
||||
self.dst_label: Optional[int] = None
|
||||
self.color: str = EDGE_COLOR
|
||||
self.width: int = EDGE_WIDTH
|
||||
|
||||
@classmethod
|
||||
def create_token(cls, src: int, dst: int) -> Tuple[int, ...]:
|
||||
|
@ -120,7 +121,7 @@ class Edge:
|
|||
fill=self.color,
|
||||
)
|
||||
|
||||
def redraw(self):
|
||||
def redraw(self) -> None:
|
||||
self.canvas.itemconfig(self.id, width=self.scaled_width(), fill=self.color)
|
||||
src_x, src_y, _, _, _, _ = self.canvas.coords(self.id)
|
||||
src_pos = src_x, src_y
|
||||
|
@ -233,13 +234,13 @@ class CanvasWirelessEdge(Edge):
|
|||
dst: int,
|
||||
src_pos: Tuple[float, float],
|
||||
dst_pos: Tuple[float, float],
|
||||
token: Tuple[Any, ...],
|
||||
token: Tuple[int, ...],
|
||||
) -> 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.token: Tuple[int, ...] = token
|
||||
self.width: float = WIRELESS_WIDTH
|
||||
self.color: str = WIRELESS_COLOR
|
||||
self.draw(src_pos, dst_pos)
|
||||
|
||||
|
||||
|
@ -259,19 +260,19 @@ class CanvasEdge(Edge):
|
|||
Create an instance of canvas edge object
|
||||
"""
|
||||
super().__init__(canvas, src)
|
||||
self.src_iface = None
|
||||
self.dst_iface = None
|
||||
self.text_src = None
|
||||
self.text_dst = None
|
||||
self.link = None
|
||||
self.asymmetric_link = None
|
||||
self.throughput = None
|
||||
self.src_iface: Optional[Interface] = None
|
||||
self.dst_iface: Optional[Interface] = None
|
||||
self.text_src: Optional[int] = None
|
||||
self.text_dst: Optional[int] = None
|
||||
self.link: Optional[Link] = None
|
||||
self.asymmetric_link: Optional[Link] = None
|
||||
self.throughput: Optional[float] = None
|
||||
self.draw(src_pos, dst_pos)
|
||||
self.set_binding()
|
||||
self.context = tk.Menu(self.canvas)
|
||||
self.context: tk.Menu = tk.Menu(self.canvas)
|
||||
self.create_context()
|
||||
|
||||
def create_context(self):
|
||||
def create_context(self) -> None:
|
||||
themes.style_menu(self.context)
|
||||
self.context.add_command(label="Configure", command=self.click_configure)
|
||||
self.context.add_command(label="Delete", command=self.click_delete)
|
||||
|
@ -279,7 +280,7 @@ class CanvasEdge(Edge):
|
|||
def set_binding(self) -> None:
|
||||
self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.show_context)
|
||||
|
||||
def set_link(self, link) -> None:
|
||||
def set_link(self, link: Link) -> None:
|
||||
self.link = link
|
||||
self.draw_labels()
|
||||
|
||||
|
@ -383,7 +384,7 @@ class CanvasEdge(Edge):
|
|||
self.context.entryconfigure(1, state=state)
|
||||
self.context.tk_popup(event.x_root, event.y_root)
|
||||
|
||||
def click_delete(self):
|
||||
def click_delete(self) -> None:
|
||||
self.canvas.delete_edge(self)
|
||||
|
||||
def click_configure(self) -> None:
|
||||
|
|
|
@ -2,11 +2,12 @@ import logging
|
|||
import tkinter as tk
|
||||
from copy import deepcopy
|
||||
from tkinter import BooleanVar
|
||||
from typing import TYPE_CHECKING, Optional, Tuple
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from PIL import Image, ImageTk
|
||||
from PIL import Image
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from core.api.grpc.core_pb2 import Interface, Link, LinkType, Session, ThroughputsEvent
|
||||
from core.gui.dialogs.shapemod import ShapeDialog
|
||||
from core.gui.graph import tags
|
||||
from core.gui.graph.edges import (
|
||||
|
@ -21,7 +22,7 @@ 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, TypeToImage
|
||||
from core.gui.nodeutils import NodeUtils
|
||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -48,58 +49,59 @@ class ShowVar(BooleanVar):
|
|||
|
||||
|
||||
class CanvasGraph(tk.Canvas):
|
||||
def __init__(self, master: tk.Widget, app: "Application", core: "CoreClient"):
|
||||
def __init__(
|
||||
self, master: tk.BaseWidget, app: "Application", core: "CoreClient"
|
||||
) -> None:
|
||||
super().__init__(master, highlightthickness=0, background="#cccccc")
|
||||
self.app = app
|
||||
self.core = core
|
||||
self.mode = GraphMode.SELECT
|
||||
self.annotation_type = None
|
||||
self.selection = {}
|
||||
self.select_box = None
|
||||
self.selected = None
|
||||
self.node_draw = None
|
||||
self.nodes = {}
|
||||
self.edges = {}
|
||||
self.shapes = {}
|
||||
self.wireless_edges = {}
|
||||
self.app: "Application" = app
|
||||
self.core: "CoreClient" = core
|
||||
self.mode: GraphMode = GraphMode.SELECT
|
||||
self.annotation_type: Optional[ShapeType] = None
|
||||
self.selection: Dict[int, int] = {}
|
||||
self.select_box: Optional[Shape] = None
|
||||
self.selected: Optional[int] = None
|
||||
self.node_draw: Optional[NodeDraw] = None
|
||||
self.nodes: Dict[int, CanvasNode] = {}
|
||||
self.edges: Dict[int, CanvasEdge] = {}
|
||||
self.shapes: Dict[int, Shape] = {}
|
||||
self.wireless_edges: Dict[Tuple[int, ...], CanvasWirelessEdge] = {}
|
||||
|
||||
# map wireless/EMANE node to the set of MDRs connected to that node
|
||||
self.wireless_network = {}
|
||||
self.wireless_network: Dict[int, Set[int]] = {}
|
||||
|
||||
self.drawing_edge = None
|
||||
self.rect = None
|
||||
self.shape_drawing = False
|
||||
self.drawing_edge: Optional[CanvasEdge] = None
|
||||
self.rect: Optional[int] = None
|
||||
self.shape_drawing: bool = False
|
||||
width = self.app.guiconfig.preferences.width
|
||||
height = self.app.guiconfig.preferences.height
|
||||
self.default_dimensions = (width, height)
|
||||
self.current_dimensions = self.default_dimensions
|
||||
self.ratio = 1.0
|
||||
self.offset = (0, 0)
|
||||
self.cursor = (0, 0)
|
||||
self.marker_tool = None
|
||||
self.to_copy = []
|
||||
self.default_dimensions: Tuple[int, int] = (width, height)
|
||||
self.current_dimensions: Tuple[int, int] = self.default_dimensions
|
||||
self.ratio: float = 1.0
|
||||
self.offset: Tuple[int, int] = (0, 0)
|
||||
self.cursor: Tuple[int, int] = (0, 0)
|
||||
self.to_copy: List[CanvasNode] = []
|
||||
|
||||
# background related
|
||||
self.wallpaper_id = None
|
||||
self.wallpaper = None
|
||||
self.wallpaper_drawn = None
|
||||
self.wallpaper_file = ""
|
||||
self.scale_option = tk.IntVar(value=1)
|
||||
self.adjust_to_dim = tk.BooleanVar(value=False)
|
||||
self.wallpaper_id: Optional[int] = None
|
||||
self.wallpaper: Optional[Image.Image] = None
|
||||
self.wallpaper_drawn: Optional[PhotoImage] = None
|
||||
self.wallpaper_file: str = ""
|
||||
self.scale_option: tk.IntVar = tk.IntVar(value=1)
|
||||
self.adjust_to_dim: tk.BooleanVar = tk.BooleanVar(value=False)
|
||||
|
||||
# throughput related
|
||||
self.throughput_threshold = 250.0
|
||||
self.throughput_width = 10
|
||||
self.throughput_color = "#FF0000"
|
||||
self.throughput_threshold: float = 250.0
|
||||
self.throughput_width: int = 10
|
||||
self.throughput_color: str = "#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_annotations = ShowVar(self, tags.ANNOTATION, value=True)
|
||||
self.show_iface_names = BooleanVar(value=False)
|
||||
self.show_ip4s = BooleanVar(value=True)
|
||||
self.show_ip6s = BooleanVar(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_grid: ShowVar = ShowVar(self, tags.GRIDLINE, value=True)
|
||||
self.show_annotations: ShowVar = ShowVar(self, tags.ANNOTATION, 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
|
||||
self.setup_bindings()
|
||||
|
@ -108,7 +110,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.draw_canvas()
|
||||
self.draw_grid()
|
||||
|
||||
def draw_canvas(self, dimensions: Tuple[int, int] = None):
|
||||
def draw_canvas(self, dimensions: Tuple[int, int] = None) -> None:
|
||||
if self.rect is not None:
|
||||
self.delete(self.rect)
|
||||
if not dimensions:
|
||||
|
@ -125,7 +127,7 @@ class CanvasGraph(tk.Canvas):
|
|||
)
|
||||
self.configure(scrollregion=self.bbox(tk.ALL))
|
||||
|
||||
def reset_and_redraw(self, session: core_pb2.Session):
|
||||
def reset_and_redraw(self, session: Session) -> None:
|
||||
"""
|
||||
Reset the private variables CanvasGraph object, redraw nodes given the new grpc
|
||||
client.
|
||||
|
@ -157,7 +159,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.drawing_edge = None
|
||||
self.draw_session(session)
|
||||
|
||||
def setup_bindings(self):
|
||||
def setup_bindings(self) -> None:
|
||||
"""
|
||||
Bind any mouse events or hot keys to the matching action
|
||||
"""
|
||||
|
@ -173,28 +175,28 @@ class CanvasGraph(tk.Canvas):
|
|||
self.bind("<ButtonPress-3>", lambda e: self.scan_mark(e.x, e.y))
|
||||
self.bind("<B3-Motion>", lambda e: self.scan_dragto(e.x, e.y, gain=1))
|
||||
|
||||
def get_actual_coords(self, x: float, y: float) -> [float, float]:
|
||||
def get_actual_coords(self, x: float, y: float) -> Tuple[float, float]:
|
||||
actual_x = (x - self.offset[0]) / self.ratio
|
||||
actual_y = (y - self.offset[1]) / self.ratio
|
||||
return actual_x, actual_y
|
||||
|
||||
def get_scaled_coords(self, x: float, y: float) -> [float, float]:
|
||||
def get_scaled_coords(self, x: float, y: float) -> Tuple[float, float]:
|
||||
scaled_x = (x * self.ratio) + self.offset[0]
|
||||
scaled_y = (y * self.ratio) + self.offset[1]
|
||||
return scaled_x, scaled_y
|
||||
|
||||
def inside_canvas(self, x: float, y: float) -> [bool, bool]:
|
||||
def inside_canvas(self, x: float, y: float) -> Tuple[bool, bool]:
|
||||
x1, y1, x2, y2 = self.bbox(self.rect)
|
||||
valid_x = x1 <= x <= x2
|
||||
valid_y = y1 <= y <= y2
|
||||
return valid_x and valid_y
|
||||
|
||||
def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> [bool, bool]:
|
||||
def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> Tuple[bool, bool]:
|
||||
valid_topleft = self.inside_canvas(x1, y1)
|
||||
valid_bottomright = self.inside_canvas(x2, y2)
|
||||
return valid_topleft and valid_bottomright
|
||||
|
||||
def set_throughputs(self, throughputs_event: core_pb2.ThroughputsEvent):
|
||||
def set_throughputs(self, throughputs_event: ThroughputsEvent) -> None:
|
||||
for iface_throughput in throughputs_event.iface_throughputs:
|
||||
node_id = iface_throughput.node_id
|
||||
iface_id = iface_throughput.iface_id
|
||||
|
@ -209,7 +211,7 @@ class CanvasGraph(tk.Canvas):
|
|||
else:
|
||||
del self.core.iface_to_edge[iface_to_edge_id]
|
||||
|
||||
def draw_grid(self):
|
||||
def draw_grid(self) -> None:
|
||||
"""
|
||||
Create grid.
|
||||
"""
|
||||
|
@ -223,9 +225,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.tag_lower(tags.GRIDLINE)
|
||||
self.tag_lower(self.rect)
|
||||
|
||||
def add_wireless_edge(
|
||||
self, src: CanvasNode, dst: CanvasNode, link: core_pb2.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
|
||||
token = create_edge_token(src.id, dst.id, network_id)
|
||||
if token in self.wireless_edges:
|
||||
|
@ -248,7 +248,7 @@ class CanvasGraph(tk.Canvas):
|
|||
arc_edges(common_edges)
|
||||
|
||||
def delete_wireless_edge(
|
||||
self, src: CanvasNode, dst: CanvasNode, link: core_pb2.Link
|
||||
self, src: CanvasNode, dst: CanvasNode, link: Link
|
||||
) -> None:
|
||||
network_id = link.network_id if link.network_id else None
|
||||
token = create_edge_token(src.id, dst.id, network_id)
|
||||
|
@ -263,7 +263,7 @@ class CanvasGraph(tk.Canvas):
|
|||
arc_edges(common_edges)
|
||||
|
||||
def update_wireless_edge(
|
||||
self, src: CanvasNode, dst: CanvasNode, link: core_pb2.Link
|
||||
self, src: CanvasNode, dst: CanvasNode, link: Link
|
||||
) -> None:
|
||||
if not link.label:
|
||||
return
|
||||
|
@ -275,7 +275,7 @@ class CanvasGraph(tk.Canvas):
|
|||
edge = self.wireless_edges[token]
|
||||
edge.middle_label_text(link.label)
|
||||
|
||||
def draw_session(self, session: core_pb2.Session):
|
||||
def draw_session(self, session: Session) -> None:
|
||||
"""
|
||||
Draw existing session.
|
||||
"""
|
||||
|
@ -306,7 +306,7 @@ class CanvasGraph(tk.Canvas):
|
|||
node2 = canvas_node2.core_node
|
||||
token = create_edge_token(canvas_node1.id, canvas_node2.id)
|
||||
|
||||
if link.type == core_pb2.LinkType.WIRELESS:
|
||||
if link.type == LinkType.WIRELESS:
|
||||
self.add_wireless_edge(canvas_node1, canvas_node2, link)
|
||||
else:
|
||||
if token not in self.edges:
|
||||
|
@ -337,7 +337,7 @@ class CanvasGraph(tk.Canvas):
|
|||
else:
|
||||
logging.error("duplicate link received: %s", link)
|
||||
|
||||
def stopped_session(self):
|
||||
def stopped_session(self) -> None:
|
||||
# clear wireless edges
|
||||
for edge in self.wireless_edges.values():
|
||||
edge.delete()
|
||||
|
@ -351,7 +351,7 @@ class CanvasGraph(tk.Canvas):
|
|||
for edge in self.edges.values():
|
||||
edge.reset()
|
||||
|
||||
def canvas_xy(self, event: tk.Event) -> [float, float]:
|
||||
def canvas_xy(self, event: tk.Event) -> Tuple[float, float]:
|
||||
"""
|
||||
Convert window coordinate to canvas coordinate
|
||||
"""
|
||||
|
@ -379,7 +379,7 @@ class CanvasGraph(tk.Canvas):
|
|||
|
||||
return selected
|
||||
|
||||
def click_release(self, event: tk.Event):
|
||||
def click_release(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Draw a node or finish drawing an edge according to the current graph mode
|
||||
"""
|
||||
|
@ -418,7 +418,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.mode = GraphMode.NODE
|
||||
self.selected = None
|
||||
|
||||
def handle_edge_release(self, _event: tk.Event):
|
||||
def handle_edge_release(self, _event: tk.Event) -> None:
|
||||
edge = self.drawing_edge
|
||||
self.drawing_edge = None
|
||||
|
||||
|
@ -454,7 +454,7 @@ class CanvasGraph(tk.Canvas):
|
|||
node_dst.edges.add(edge)
|
||||
self.core.create_link(edge, node_src, node_dst)
|
||||
|
||||
def select_object(self, object_id: int, choose_multiple: bool = False):
|
||||
def select_object(self, object_id: int, choose_multiple: bool = False) -> None:
|
||||
"""
|
||||
create a bounding box when a node is selected
|
||||
"""
|
||||
|
@ -475,7 +475,7 @@ class CanvasGraph(tk.Canvas):
|
|||
selection_id = self.selection.pop(object_id)
|
||||
self.delete(selection_id)
|
||||
|
||||
def clear_selection(self):
|
||||
def clear_selection(self) -> None:
|
||||
"""
|
||||
Clear current selection boxes.
|
||||
"""
|
||||
|
@ -483,7 +483,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.delete(_id)
|
||||
self.selection.clear()
|
||||
|
||||
def move_selection(self, object_id: int, x_offset: float, y_offset: float):
|
||||
def move_selection(self, object_id: int, x_offset: float, y_offset: float) -> None:
|
||||
select_id = self.selection.get(object_id)
|
||||
if select_id is not None:
|
||||
self.move(select_id, x_offset, y_offset)
|
||||
|
@ -531,7 +531,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.core.deleted_graph_nodes(nodes)
|
||||
self.core.deleted_graph_edges(edges)
|
||||
|
||||
def delete_edge(self, edge: CanvasEdge):
|
||||
def delete_edge(self, edge: CanvasEdge) -> None:
|
||||
edge.delete()
|
||||
del self.edges[edge.token]
|
||||
src_node = self.nodes[edge.src]
|
||||
|
@ -550,7 +550,7 @@ class CanvasGraph(tk.Canvas):
|
|||
src_node.delete_antenna()
|
||||
self.core.deleted_graph_edges([edge])
|
||||
|
||||
def zoom(self, event: tk.Event, factor: float = None):
|
||||
def zoom(self, event: tk.Event, factor: float = None) -> None:
|
||||
if not factor:
|
||||
factor = ZOOM_IN if event.delta > 0 else ZOOM_OUT
|
||||
event.x, event.y = self.canvasx(event.x), self.canvasy(event.y)
|
||||
|
@ -568,7 +568,7 @@ class CanvasGraph(tk.Canvas):
|
|||
if self.wallpaper:
|
||||
self.redraw_wallpaper()
|
||||
|
||||
def click_press(self, event: tk.Event):
|
||||
def click_press(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Start drawing an edge if mouse click is on a node
|
||||
"""
|
||||
|
@ -630,7 +630,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.select_box = shape
|
||||
self.clear_selection()
|
||||
|
||||
def ctrl_click(self, event: tk.Event):
|
||||
def ctrl_click(self, event: tk.Event) -> None:
|
||||
# update cursor location
|
||||
x, y = self.canvas_xy(event)
|
||||
if not self.inside_canvas(x, y):
|
||||
|
@ -648,7 +648,7 @@ class CanvasGraph(tk.Canvas):
|
|||
):
|
||||
self.select_object(selected, choose_multiple=True)
|
||||
|
||||
def click_motion(self, event: tk.Event):
|
||||
def click_motion(self, event: tk.Event) -> None:
|
||||
x, y = self.canvas_xy(event)
|
||||
if not self.inside_canvas(x, y):
|
||||
if self.select_box:
|
||||
|
@ -701,7 +701,7 @@ class CanvasGraph(tk.Canvas):
|
|||
if self.select_box and self.mode == GraphMode.SELECT:
|
||||
self.select_box.shape_motion(x, y)
|
||||
|
||||
def press_delete(self, _event: tk.Event):
|
||||
def press_delete(self, _event: tk.Event) -> None:
|
||||
"""
|
||||
delete selected nodes and any data that relates to it
|
||||
"""
|
||||
|
@ -711,7 +711,7 @@ class CanvasGraph(tk.Canvas):
|
|||
else:
|
||||
logging.debug("node deletion is disabled during runtime state")
|
||||
|
||||
def double_click(self, event: tk.Event):
|
||||
def double_click(self, event: tk.Event) -> None:
|
||||
selected = self.get_selected(event)
|
||||
if selected is not None and selected in self.shapes:
|
||||
shape = self.shapes[selected]
|
||||
|
@ -737,7 +737,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.core.canvas_nodes[core_node.id] = node
|
||||
self.nodes[node.id] = node
|
||||
|
||||
def width_and_height(self):
|
||||
def width_and_height(self) -> Tuple[int, int]:
|
||||
"""
|
||||
retrieve canvas width and height in pixels
|
||||
"""
|
||||
|
@ -753,8 +753,8 @@ class CanvasGraph(tk.Canvas):
|
|||
return image
|
||||
|
||||
def draw_wallpaper(
|
||||
self, image: ImageTk.PhotoImage, x: float = None, y: float = None
|
||||
):
|
||||
self, image: PhotoImage, x: float = None, y: float = None
|
||||
) -> None:
|
||||
if x is None and y is None:
|
||||
x1, y1, x2, y2 = self.bbox(self.rect)
|
||||
x = (x1 + x2) / 2
|
||||
|
@ -762,7 +762,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.wallpaper_id = self.create_image((x, y), image=image, tags=tags.WALLPAPER)
|
||||
self.wallpaper_drawn = image
|
||||
|
||||
def wallpaper_upper_left(self):
|
||||
def wallpaper_upper_left(self) -> None:
|
||||
self.delete(self.wallpaper_id)
|
||||
|
||||
# create new scaled image, cropped if needed
|
||||
|
@ -775,7 +775,7 @@ class CanvasGraph(tk.Canvas):
|
|||
if image.height > height:
|
||||
cropy = image.height
|
||||
cropped = image.crop((0, 0, cropx, cropy))
|
||||
image = ImageTk.PhotoImage(cropped)
|
||||
image = PhotoImage(cropped)
|
||||
|
||||
# draw on canvas
|
||||
x1, y1, _, _ = self.bbox(self.rect)
|
||||
|
@ -783,7 +783,7 @@ class CanvasGraph(tk.Canvas):
|
|||
y = (cropy / 2) + y1
|
||||
self.draw_wallpaper(image, x, y)
|
||||
|
||||
def wallpaper_center(self):
|
||||
def wallpaper_center(self) -> None:
|
||||
"""
|
||||
place the image at the center of canvas
|
||||
"""
|
||||
|
@ -803,26 +803,26 @@ class CanvasGraph(tk.Canvas):
|
|||
x2 = image.width - cropx
|
||||
y2 = image.height - cropy
|
||||
cropped = image.crop((x1, y1, x2, y2))
|
||||
image = ImageTk.PhotoImage(cropped)
|
||||
image = PhotoImage(cropped)
|
||||
self.draw_wallpaper(image)
|
||||
|
||||
def wallpaper_scaled(self):
|
||||
def wallpaper_scaled(self) -> None:
|
||||
"""
|
||||
scale image based on canvas dimension
|
||||
"""
|
||||
self.delete(self.wallpaper_id)
|
||||
canvas_w, canvas_h = self.width_and_height()
|
||||
image = self.wallpaper.resize((int(canvas_w), int(canvas_h)), Image.ANTIALIAS)
|
||||
image = ImageTk.PhotoImage(image)
|
||||
image = PhotoImage(image)
|
||||
self.draw_wallpaper(image)
|
||||
|
||||
def resize_to_wallpaper(self):
|
||||
def resize_to_wallpaper(self) -> None:
|
||||
self.delete(self.wallpaper_id)
|
||||
image = ImageTk.PhotoImage(self.wallpaper)
|
||||
image = PhotoImage(self.wallpaper)
|
||||
self.redraw_canvas((image.width(), image.height()))
|
||||
self.draw_wallpaper(image)
|
||||
|
||||
def redraw_canvas(self, dimensions: Tuple[int, int] = None):
|
||||
def redraw_canvas(self, dimensions: Tuple[int, int] = None) -> None:
|
||||
logging.debug("redrawing canvas to dimensions: %s", dimensions)
|
||||
|
||||
# reset scale and move back to original position
|
||||
|
@ -843,7 +843,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.draw_grid()
|
||||
self.app.canvas.show_grid.click_handler()
|
||||
|
||||
def redraw_wallpaper(self):
|
||||
def redraw_wallpaper(self) -> None:
|
||||
if self.adjust_to_dim.get():
|
||||
logging.debug("drawing wallpaper to canvas dimensions")
|
||||
self.resize_to_wallpaper()
|
||||
|
@ -864,7 +864,7 @@ class CanvasGraph(tk.Canvas):
|
|||
for tag in tags.ORGANIZE_TAGS:
|
||||
self.tag_raise(tag)
|
||||
|
||||
def set_wallpaper(self, filename: Optional[str]):
|
||||
def set_wallpaper(self, filename: Optional[str]) -> None:
|
||||
logging.debug("setting wallpaper: %s", filename)
|
||||
if filename:
|
||||
img = Image.open(filename)
|
||||
|
@ -880,7 +880,7 @@ class CanvasGraph(tk.Canvas):
|
|||
def is_selection_mode(self) -> bool:
|
||||
return self.mode == GraphMode.SELECT
|
||||
|
||||
def create_edge(self, source: CanvasNode, dest: CanvasNode):
|
||||
def create_edge(self, source: CanvasNode, dest: CanvasNode) -> None:
|
||||
"""
|
||||
create an edge between source node and destination node
|
||||
"""
|
||||
|
@ -894,7 +894,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.nodes[dest.id].edges.add(edge)
|
||||
self.core.create_link(edge, source, dest)
|
||||
|
||||
def copy(self):
|
||||
def copy(self) -> None:
|
||||
if self.core.is_runtime():
|
||||
logging.debug("copy is disabled during runtime state")
|
||||
return
|
||||
|
@ -905,7 +905,7 @@ class CanvasGraph(tk.Canvas):
|
|||
canvas_node = self.nodes[node_id]
|
||||
self.to_copy.append(canvas_node)
|
||||
|
||||
def paste(self):
|
||||
def paste(self) -> None:
|
||||
if self.core.is_runtime():
|
||||
logging.debug("paste is disabled during runtime state")
|
||||
return
|
||||
|
@ -972,11 +972,11 @@ class CanvasGraph(tk.Canvas):
|
|||
else:
|
||||
asym_iface1 = None
|
||||
if iface1_id:
|
||||
asym_iface1 = core_pb2.Interface(id=iface1_id)
|
||||
asym_iface1 = Interface(id=iface1_id)
|
||||
asym_iface2 = None
|
||||
if iface2_id:
|
||||
asym_iface2 = core_pb2.Interface(id=iface2_id)
|
||||
copy_edge.asymmetric_link = core_pb2.Link(
|
||||
asym_iface2 = Interface(id=iface2_id)
|
||||
copy_edge.asymmetric_link = Link(
|
||||
node1_id=copy_link.node2_id,
|
||||
node2_id=copy_link.node1_id,
|
||||
iface1=asym_iface1,
|
||||
|
@ -990,7 +990,7 @@ class CanvasGraph(tk.Canvas):
|
|||
)
|
||||
self.tag_raise(tags.NODE)
|
||||
|
||||
def scale_graph(self):
|
||||
def scale_graph(self) -> None:
|
||||
for nid, canvas_node in self.nodes.items():
|
||||
img = None
|
||||
if NodeUtils.is_custom(
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import functools
|
||||
import logging
|
||||
import tkinter as tk
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
import grpc
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from core.api.grpc.core_pb2 import NodeType
|
||||
from core.api.grpc.common_pb2 import ConfigOption
|
||||
from core.api.grpc.core_pb2 import Interface, Node, NodeType
|
||||
from core.api.grpc.services_pb2 import NodeServiceData
|
||||
from core.gui import themes
|
||||
from core.gui.dialogs.emaneconfig import EmaneConfigDialog
|
||||
from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
|
||||
|
@ -15,36 +17,31 @@ from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog
|
|||
from core.gui.dialogs.nodeservice import NodeServiceDialog
|
||||
from core.gui.dialogs.wlanconfig import WlanConfigDialog
|
||||
from core.gui.graph import tags
|
||||
from core.gui.graph.edges import CanvasEdge
|
||||
from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge
|
||||
from core.gui.graph.tooltip import CanvasTooltip
|
||||
from core.gui.images import ImageEnum
|
||||
from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
from PIL.ImageTk import PhotoImage
|
||||
from core.gui.graph.graph import CanvasGraph
|
||||
|
||||
NODE_TEXT_OFFSET = 5
|
||||
NODE_TEXT_OFFSET: int = 5
|
||||
|
||||
|
||||
class CanvasNode:
|
||||
def __init__(
|
||||
self,
|
||||
app: "Application",
|
||||
x: float,
|
||||
y: float,
|
||||
core_node: core_pb2.Node,
|
||||
image: "PhotoImage",
|
||||
self, app: "Application", x: float, y: float, core_node: Node, image: PhotoImage
|
||||
):
|
||||
self.app = app
|
||||
self.canvas = app.canvas
|
||||
self.image = image
|
||||
self.core_node = core_node
|
||||
self.id = self.canvas.create_image(
|
||||
self.app: "Application" = app
|
||||
self.canvas: "CanvasGraph" = app.canvas
|
||||
self.image: PhotoImage = image
|
||||
self.core_node: Node = core_node
|
||||
self.id: int = self.canvas.create_image(
|
||||
x, y, anchor=tk.CENTER, image=self.image, tags=tags.NODE
|
||||
)
|
||||
label_y = self._get_label_y()
|
||||
self.text_id = self.canvas.create_text(
|
||||
self.text_id: int = self.canvas.create_text(
|
||||
x,
|
||||
label_y,
|
||||
text=self.core_node.name,
|
||||
|
@ -53,21 +50,21 @@ class CanvasNode:
|
|||
fill="#0000CD",
|
||||
state=self.canvas.show_node_labels.state(),
|
||||
)
|
||||
self.tooltip = CanvasTooltip(self.canvas)
|
||||
self.edges = set()
|
||||
self.ifaces = {}
|
||||
self.wireless_edges = set()
|
||||
self.antennas = []
|
||||
self.antenna_images = {}
|
||||
self.tooltip: CanvasTooltip = CanvasTooltip(self.canvas)
|
||||
self.edges: Set[CanvasEdge] = set()
|
||||
self.ifaces: Dict[int, Interface] = {}
|
||||
self.wireless_edges: Set[CanvasWirelessEdge] = set()
|
||||
self.antennas: List[int] = []
|
||||
self.antenna_images: Dict[int, PhotoImage] = {}
|
||||
# possible configurations
|
||||
self.emane_model_configs = {}
|
||||
self.wlan_config = {}
|
||||
self.mobility_config = {}
|
||||
self.service_configs = {}
|
||||
self.service_file_configs = {}
|
||||
self.config_service_configs = {}
|
||||
self.emane_model_configs: Dict[Tuple[str, Optional[int]], ConfigOption] = {}
|
||||
self.wlan_config: Dict[str, ConfigOption] = {}
|
||||
self.mobility_config: Dict[str, ConfigOption] = {}
|
||||
self.service_configs: Dict[str, NodeServiceData] = {}
|
||||
self.service_file_configs: Dict[str, Dict[str, str]] = {}
|
||||
self.config_service_configs: Dict[str, Any] = {}
|
||||
self.setup_bindings()
|
||||
self.context = tk.Menu(self.canvas)
|
||||
self.context: tk.Menu = tk.Menu(self.canvas)
|
||||
themes.style_menu(self.context)
|
||||
|
||||
def next_iface_id(self) -> int:
|
||||
|
@ -76,19 +73,19 @@ class CanvasNode:
|
|||
i += 1
|
||||
return i
|
||||
|
||||
def setup_bindings(self):
|
||||
def setup_bindings(self) -> None:
|
||||
self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click)
|
||||
self.canvas.tag_bind(self.id, "<Enter>", self.on_enter)
|
||||
self.canvas.tag_bind(self.id, "<Leave>", self.on_leave)
|
||||
self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.show_context)
|
||||
|
||||
def delete(self):
|
||||
def delete(self) -> None:
|
||||
logging.debug("Delete canvas node for %s", self.core_node)
|
||||
self.canvas.delete(self.id)
|
||||
self.canvas.delete(self.text_id)
|
||||
self.delete_antennas()
|
||||
|
||||
def add_antenna(self):
|
||||
def add_antenna(self) -> None:
|
||||
x, y = self.canvas.coords(self.id)
|
||||
offset = len(self.antennas) * 8 * self.app.app_scale
|
||||
img = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE)
|
||||
|
@ -102,7 +99,7 @@ class CanvasNode:
|
|||
self.antennas.append(antenna_id)
|
||||
self.antenna_images[antenna_id] = img
|
||||
|
||||
def delete_antenna(self):
|
||||
def delete_antenna(self) -> None:
|
||||
"""
|
||||
delete one antenna
|
||||
"""
|
||||
|
@ -112,7 +109,7 @@ class CanvasNode:
|
|||
self.canvas.delete(antenna_id)
|
||||
self.antenna_images.pop(antenna_id, None)
|
||||
|
||||
def delete_antennas(self):
|
||||
def delete_antennas(self) -> None:
|
||||
"""
|
||||
delete all antennas
|
||||
"""
|
||||
|
@ -122,30 +119,30 @@ class CanvasNode:
|
|||
self.antennas.clear()
|
||||
self.antenna_images.clear()
|
||||
|
||||
def redraw(self):
|
||||
def redraw(self) -> None:
|
||||
self.canvas.itemconfig(self.id, image=self.image)
|
||||
self.canvas.itemconfig(self.text_id, text=self.core_node.name)
|
||||
for edge in self.edges:
|
||||
edge.redraw()
|
||||
|
||||
def _get_label_y(self):
|
||||
def _get_label_y(self) -> int:
|
||||
image_box = self.canvas.bbox(self.id)
|
||||
return image_box[3] + NODE_TEXT_OFFSET
|
||||
|
||||
def scale_text(self):
|
||||
def scale_text(self) -> None:
|
||||
text_bound = self.canvas.bbox(self.text_id)
|
||||
prev_y = (text_bound[3] + text_bound[1]) / 2
|
||||
new_y = self._get_label_y()
|
||||
self.canvas.move(self.text_id, 0, new_y - prev_y)
|
||||
|
||||
def move(self, x: int, y: int):
|
||||
def move(self, x: int, y: int) -> None:
|
||||
x, y = self.canvas.get_scaled_coords(x, y)
|
||||
current_x, current_y = self.canvas.coords(self.id)
|
||||
x_offset = x - current_x
|
||||
y_offset = y - current_y
|
||||
self.motion(x_offset, y_offset, update=False)
|
||||
|
||||
def motion(self, x_offset: int, y_offset: int, update: bool = True):
|
||||
def motion(self, x_offset: float, y_offset: float, update: bool = True) -> None:
|
||||
original_position = self.canvas.coords(self.id)
|
||||
self.canvas.move(self.id, x_offset, y_offset)
|
||||
pos = self.canvas.coords(self.id)
|
||||
|
@ -177,7 +174,7 @@ class CanvasNode:
|
|||
if self.app.core.is_runtime() and update:
|
||||
self.app.core.edit_node(self.core_node)
|
||||
|
||||
def on_enter(self, event: tk.Event):
|
||||
def on_enter(self, event: tk.Event) -> None:
|
||||
if self.app.core.is_runtime() and self.app.core.observer:
|
||||
self.tooltip.text.set("waiting...")
|
||||
self.tooltip.on_enter(event)
|
||||
|
@ -187,10 +184,10 @@ class CanvasNode:
|
|||
except grpc.RpcError as e:
|
||||
self.app.show_grpc_exception("Observer Error", e)
|
||||
|
||||
def on_leave(self, event: tk.Event):
|
||||
def on_leave(self, event: tk.Event) -> None:
|
||||
self.tooltip.on_leave(event)
|
||||
|
||||
def double_click(self, event: tk.Event):
|
||||
def double_click(self, event: tk.Event) -> None:
|
||||
if self.app.core.is_runtime():
|
||||
self.canvas.core.launch_terminal(self.core_node.id)
|
||||
else:
|
||||
|
@ -270,37 +267,37 @@ class CanvasNode:
|
|||
self.canvas.selection[self.id] = self
|
||||
self.canvas.copy()
|
||||
|
||||
def show_config(self):
|
||||
def show_config(self) -> None:
|
||||
dialog = NodeConfigDialog(self.app, self)
|
||||
dialog.show()
|
||||
|
||||
def show_wlan_config(self):
|
||||
def show_wlan_config(self) -> None:
|
||||
dialog = WlanConfigDialog(self.app, self)
|
||||
if not dialog.has_error:
|
||||
dialog.show()
|
||||
|
||||
def show_mobility_config(self):
|
||||
def show_mobility_config(self) -> None:
|
||||
dialog = MobilityConfigDialog(self.app, self)
|
||||
if not dialog.has_error:
|
||||
dialog.show()
|
||||
|
||||
def show_mobility_player(self):
|
||||
def show_mobility_player(self) -> None:
|
||||
mobility_player = self.app.core.mobility_players[self.core_node.id]
|
||||
mobility_player.show()
|
||||
|
||||
def show_emane_config(self):
|
||||
def show_emane_config(self) -> None:
|
||||
dialog = EmaneConfigDialog(self.app, self)
|
||||
dialog.show()
|
||||
|
||||
def show_services(self):
|
||||
def show_services(self) -> None:
|
||||
dialog = NodeServiceDialog(self.app, self)
|
||||
dialog.show()
|
||||
|
||||
def show_config_services(self):
|
||||
def show_config_services(self) -> None:
|
||||
dialog = NodeConfigServiceDialog(self.app, self)
|
||||
dialog.show()
|
||||
|
||||
def has_emane_link(self, iface_id: int) -> core_pb2.Node:
|
||||
def has_emane_link(self, iface_id: int) -> Node:
|
||||
result = None
|
||||
for edge in self.edges:
|
||||
if self.id == edge.src:
|
||||
|
@ -317,14 +314,14 @@ class CanvasNode:
|
|||
break
|
||||
return result
|
||||
|
||||
def wireless_link_selected(self):
|
||||
def wireless_link_selected(self) -> None:
|
||||
nodes = [x for x in self.canvas.selection if x in self.canvas.nodes]
|
||||
for node_id in nodes:
|
||||
canvas_node = self.canvas.nodes[node_id]
|
||||
self.canvas.create_edge(self, canvas_node)
|
||||
self.canvas.clear_selection()
|
||||
|
||||
def scale_antennas(self):
|
||||
def scale_antennas(self) -> None:
|
||||
for i in range(len(self.antennas)):
|
||||
antenna_id = self.antennas[i]
|
||||
image = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import logging
|
||||
from typing import TYPE_CHECKING, Dict, List, Union
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
||||
|
||||
from core.gui.dialogs.shapemod import ShapeDialog
|
||||
from core.gui.graph import tags
|
||||
|
@ -23,17 +23,17 @@ class AnnotationData:
|
|||
bold: bool = False,
|
||||
italic: bool = False,
|
||||
underline: bool = False,
|
||||
):
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.font_size = font_size
|
||||
self.text_color = text_color
|
||||
self.fill_color = fill_color
|
||||
self.border_color = border_color
|
||||
self.border_width = border_width
|
||||
self.bold = bold
|
||||
self.italic = italic
|
||||
self.underline = underline
|
||||
) -> None:
|
||||
self.text: str = text
|
||||
self.font: str = font
|
||||
self.font_size: int = font_size
|
||||
self.text_color: str = text_color
|
||||
self.fill_color: str = fill_color
|
||||
self.border_color: str = border_color
|
||||
self.border_width: int = border_width
|
||||
self.bold: bool = bold
|
||||
self.italic: bool = italic
|
||||
self.underline: bool = underline
|
||||
|
||||
|
||||
class Shape:
|
||||
|
@ -47,29 +47,29 @@ class Shape:
|
|||
x2: float = None,
|
||||
y2: float = None,
|
||||
data: AnnotationData = None,
|
||||
):
|
||||
self.app = app
|
||||
self.canvas = canvas
|
||||
self.shape_type = shape_type
|
||||
self.id = None
|
||||
self.text_id = None
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
) -> None:
|
||||
self.app: "Application" = app
|
||||
self.canvas: "CanvasGraph" = canvas
|
||||
self.shape_type: ShapeType = shape_type
|
||||
self.id: Optional[int] = None
|
||||
self.text_id: Optional[int] = None
|
||||
self.x1: float = x1
|
||||
self.y1: float = y1
|
||||
if x2 is None:
|
||||
x2 = x1
|
||||
self.x2 = x2
|
||||
self.x2: float = x2
|
||||
if y2 is None:
|
||||
y2 = y1
|
||||
self.y2 = y2
|
||||
self.y2: float = y2
|
||||
if data is None:
|
||||
self.created = False
|
||||
self.shape_data = AnnotationData()
|
||||
self.created: bool = False
|
||||
self.shape_data: AnnotationData = AnnotationData()
|
||||
else:
|
||||
self.created = True
|
||||
self.created: bool = True
|
||||
self.shape_data = data
|
||||
self.draw()
|
||||
|
||||
def draw(self):
|
||||
def draw(self) -> None:
|
||||
if self.created:
|
||||
dash = None
|
||||
else:
|
||||
|
@ -127,7 +127,7 @@ class Shape:
|
|||
font.append("underline")
|
||||
return font
|
||||
|
||||
def draw_shape_text(self):
|
||||
def draw_shape_text(self) -> None:
|
||||
if self.shape_data.text:
|
||||
x = (self.x1 + self.x2) / 2
|
||||
y = self.y1 + 1.5 * self.shape_data.font_size
|
||||
|
@ -142,18 +142,18 @@ class Shape:
|
|||
state=self.canvas.show_annotations.state(),
|
||||
)
|
||||
|
||||
def shape_motion(self, x1: float, y1: float):
|
||||
def shape_motion(self, x1: float, y1: float) -> None:
|
||||
self.canvas.coords(self.id, self.x1, self.y1, x1, y1)
|
||||
|
||||
def shape_complete(self, x: float, y: float):
|
||||
def shape_complete(self, x: float, y: float) -> None:
|
||||
self.canvas.organize()
|
||||
s = ShapeDialog(self.app, self)
|
||||
s.show()
|
||||
|
||||
def disappear(self):
|
||||
def disappear(self) -> None:
|
||||
self.canvas.delete(self.id)
|
||||
|
||||
def motion(self, x_offset: float, y_offset: float):
|
||||
def motion(self, x_offset: float, y_offset: float) -> None:
|
||||
original_position = self.canvas.coords(self.id)
|
||||
self.canvas.move(self.id, x_offset, y_offset)
|
||||
coords = self.canvas.coords(self.id)
|
||||
|
@ -166,7 +166,7 @@ class Shape:
|
|||
if self.text_id is not None:
|
||||
self.canvas.move(self.text_id, x_offset, y_offset)
|
||||
|
||||
def delete(self):
|
||||
def delete(self) -> None:
|
||||
logging.debug("Delete shape, id(%s)", self.id)
|
||||
self.canvas.delete(self.id)
|
||||
self.canvas.delete(self.text_id)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import enum
|
||||
from typing import Set
|
||||
|
||||
|
||||
class ShapeType(enum.Enum):
|
||||
|
@ -8,7 +9,7 @@ class ShapeType(enum.Enum):
|
|||
TEXT = "text"
|
||||
|
||||
|
||||
SHAPES = {ShapeType.OVAL, ShapeType.RECTANGLE}
|
||||
SHAPES: Set[ShapeType] = {ShapeType.OVAL, ShapeType.RECTANGLE}
|
||||
|
||||
|
||||
def is_draw_shape(shape_type: ShapeType) -> bool:
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
ANNOTATION = "annotation"
|
||||
GRIDLINE = "gridline"
|
||||
SHAPE = "shape"
|
||||
SHAPE_TEXT = "shapetext"
|
||||
EDGE = "edge"
|
||||
LINK_LABEL = "linklabel"
|
||||
WIRELESS_EDGE = "wireless"
|
||||
ANTENNA = "antenna"
|
||||
NODE_LABEL = "nodename"
|
||||
NODE = "node"
|
||||
WALLPAPER = "wallpaper"
|
||||
SELECTION = "selectednodes"
|
||||
MARKER = "marker"
|
||||
ORGANIZE_TAGS = [
|
||||
from typing import List
|
||||
|
||||
ANNOTATION: str = "annotation"
|
||||
GRIDLINE: str = "gridline"
|
||||
SHAPE: str = "shape"
|
||||
SHAPE_TEXT: str = "shapetext"
|
||||
EDGE: str = "edge"
|
||||
LINK_LABEL: str = "linklabel"
|
||||
WIRELESS_EDGE: str = "wireless"
|
||||
ANTENNA: str = "antenna"
|
||||
NODE_LABEL: str = "nodename"
|
||||
NODE: str = "node"
|
||||
WALLPAPER: str = "wallpaper"
|
||||
SELECTION: str = "selectednodes"
|
||||
MARKER: str = "marker"
|
||||
ORGANIZE_TAGS: List[str] = [
|
||||
WALLPAPER,
|
||||
GRIDLINE,
|
||||
SHAPE,
|
||||
|
@ -25,7 +27,7 @@ ORGANIZE_TAGS = [
|
|||
SELECTION,
|
||||
MARKER,
|
||||
]
|
||||
RESET_TAGS = [
|
||||
RESET_TAGS: List[str] = [
|
||||
EDGE,
|
||||
NODE,
|
||||
NODE_LABEL,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Optional, Tuple
|
||||
|
||||
from core.gui.themes import Styles
|
||||
|
||||
|
@ -27,39 +27,45 @@ class CanvasTooltip:
|
|||
self,
|
||||
canvas: "CanvasGraph",
|
||||
*,
|
||||
pad=(5, 3, 5, 3),
|
||||
pad: Tuple[int, int, int, int] = (5, 3, 5, 3),
|
||||
waittime: int = 400,
|
||||
wraplength: int = 600
|
||||
):
|
||||
) -> None:
|
||||
# in miliseconds, originally 500
|
||||
self.waittime = waittime
|
||||
self.waittime: int = waittime
|
||||
# in pixels, originally 180
|
||||
self.wraplength = wraplength
|
||||
self.canvas = canvas
|
||||
self.text = tk.StringVar()
|
||||
self.pad = pad
|
||||
self.id = None
|
||||
self.tw = None
|
||||
self.wraplength: int = wraplength
|
||||
self.canvas: "CanvasGraph" = canvas
|
||||
self.text: tk.StringVar = tk.StringVar()
|
||||
self.pad: Tuple[int, int, int, int] = pad
|
||||
self.id: Optional[str] = None
|
||||
self.tw: Optional[tk.Toplevel] = None
|
||||
|
||||
def on_enter(self, event: tk.Event = None):
|
||||
def on_enter(self, event: tk.Event = None) -> None:
|
||||
self.schedule()
|
||||
|
||||
def on_leave(self, event: tk.Event = None):
|
||||
def on_leave(self, event: tk.Event = None) -> None:
|
||||
self.unschedule()
|
||||
self.hide()
|
||||
|
||||
def schedule(self):
|
||||
def schedule(self) -> None:
|
||||
self.unschedule()
|
||||
self.id = self.canvas.after(self.waittime, self.show)
|
||||
|
||||
def unschedule(self):
|
||||
def unschedule(self) -> None:
|
||||
id_ = self.id
|
||||
self.id = None
|
||||
if id_:
|
||||
self.canvas.after_cancel(id_)
|
||||
|
||||
def show(self, event: tk.Event = None):
|
||||
def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)):
|
||||
def show(self, event: tk.Event = None) -> None:
|
||||
def tip_pos_calculator(
|
||||
canvas: "CanvasGraph",
|
||||
label: ttk.Label,
|
||||
*,
|
||||
tip_delta: Tuple[int, int] = (10, 5),
|
||||
pad: Tuple[int, int, int, int] = (5, 3, 5, 3)
|
||||
):
|
||||
c = canvas
|
||||
s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight()
|
||||
width, height = (
|
||||
|
@ -108,7 +114,7 @@ class CanvasTooltip:
|
|||
x, y = tip_pos_calculator(canvas, label, pad=pad)
|
||||
self.tw.wm_geometry("+%d+%d" % (x, y))
|
||||
|
||||
def hide(self):
|
||||
def hide(self) -> None:
|
||||
if self.tw:
|
||||
self.tw.destroy()
|
||||
self.tw = None
|
||||
|
|
Loading…
Reference in a new issue