pygui improved node context to properly use tk_popup, avoiding bandage code to compensate for other issues

This commit is contained in:
Blake Harnden 2020-05-02 09:20:36 -07:00
parent 65466909d3
commit ac2d60dad6
3 changed files with 62 additions and 94 deletions

View file

@ -58,7 +58,6 @@ class CanvasGraph(tk.Canvas):
self.select_box = None self.select_box = None
self.selected = None self.selected = None
self.node_draw = None self.node_draw = None
self.context = None
self.nodes = {} self.nodes = {}
self.edges = {} self.edges = {}
self.shapes = {} self.shapes = {}
@ -130,9 +129,6 @@ class CanvasGraph(tk.Canvas):
client. client.
:param session: session to draw :param session: session to draw
""" """
# hide context
self.hide_context()
# reset view options to default state # reset view options to default state
self.show_node_labels.set(True) self.show_node_labels.set(True)
self.show_link_labels.set(True) self.show_link_labels.set(True)
@ -166,7 +162,6 @@ class CanvasGraph(tk.Canvas):
self.bind("<ButtonPress-1>", self.click_press) self.bind("<ButtonPress-1>", self.click_press)
self.bind("<ButtonRelease-1>", self.click_release) self.bind("<ButtonRelease-1>", self.click_release)
self.bind("<B1-Motion>", self.click_motion) self.bind("<B1-Motion>", self.click_motion)
self.bind("<ButtonRelease-3>", self.click_context)
self.bind("<Delete>", self.press_delete) self.bind("<Delete>", self.press_delete)
self.bind("<Control-1>", self.ctrl_click) self.bind("<Control-1>", self.ctrl_click)
self.bind("<Double-Button-1>", self.double_click) self.bind("<Double-Button-1>", self.double_click)
@ -176,11 +171,6 @@ class CanvasGraph(tk.Canvas):
self.bind("<ButtonPress-3>", lambda e: self.scan_mark(e.x, e.y)) 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)) self.bind("<B3-Motion>", lambda e: self.scan_dragto(e.x, e.y, gain=1))
def hide_context(self, event=None):
if self.context:
self.context.unpost()
self.context = None
def get_actual_coords(self, x: float, y: float) -> [float, float]: def get_actual_coords(self, x: float, y: float) -> [float, float]:
actual_x = (x - self.offset[0]) / self.ratio actual_x = (x - self.offset[0]) / self.ratio
actual_y = (y - self.offset[1]) / self.ratio actual_y = (y - self.offset[1]) / self.ratio
@ -396,41 +386,35 @@ 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.context: self.focus_set()
self.hide_context() if self.shape_drawing:
shape = self.shapes[self.selected]
shape.shape_complete(x, y)
self.shape_drawing = False
elif self.mode == GraphMode.SELECT:
self.focus_set()
if self.select_box:
x0, y0, x1, y1 = self.coords(self.select_box.id)
inside = [
x
for x in self.find_enclosed(x0, y0, x1, y1)
if "node" in self.gettags(x) or "shape" in self.gettags(x)
]
for i in inside:
self.select_object(i, True)
self.select_box.disappear()
self.select_box = None
else: else:
if self.mode == GraphMode.ANNOTATION: self.focus_set()
self.focus_set() self.selected = self.get_selected(event)
if self.shape_drawing: logging.debug(f"click release selected({self.selected}) mode({self.mode})")
shape = self.shapes[self.selected] if self.mode == GraphMode.EDGE:
shape.shape_complete(x, y) self.handle_edge_release(event)
self.shape_drawing = False elif self.mode == GraphMode.NODE:
elif self.mode == GraphMode.SELECT: self.add_node(x, y)
self.focus_set() elif self.mode == GraphMode.PICKNODE:
if self.select_box: self.mode = GraphMode.NODE
x0, y0, x1, y1 = self.coords(self.select_box.id)
inside = [
x
for x in self.find_enclosed(x0, y0, x1, y1)
if "node" in self.gettags(x) or "shape" in self.gettags(x)
]
for i in inside:
self.select_object(i, True)
self.select_box.disappear()
self.select_box = None
else:
self.focus_set()
self.selected = self.get_selected(event)
logging.debug(
f"click release selected({self.selected}) mode({self.mode})"
)
if self.mode == GraphMode.EDGE:
self.handle_edge_release(event)
elif self.mode == GraphMode.NODE:
self.add_node(x, y)
elif self.mode == GraphMode.PICKNODE:
self.mode = GraphMode.NODE
self.selected = None self.selected = None
def handle_edge_release(self, _event: tk.Event): def handle_edge_release(self, _event: tk.Event):
@ -717,19 +701,6 @@ class CanvasGraph(tk.Canvas):
if self.select_box and self.mode == GraphMode.SELECT: if self.select_box and self.mode == GraphMode.SELECT:
self.select_box.shape_motion(x, y) self.select_box.shape_motion(x, y)
def click_context(self, event: tk.Event):
logging.info("context: %s", self.context)
if not self.context:
selected = self.get_selected(event)
canvas_node = self.nodes.get(selected)
if canvas_node:
logging.debug("node context: %s", selected)
self.context = canvas_node.create_context()
self.context.bind("<Unmap>", self.hide_context)
self.context.post(event.x_root, event.y_root)
else:
self.hide_context()
def press_delete(self, _event: tk.Event): def press_delete(self, _event: tk.Event):
""" """
delete selected nodes and any data that relates to it delete selected nodes and any data that relates to it

View file

@ -68,11 +68,14 @@ class CanvasNode:
self.service_file_configs = {} self.service_file_configs = {}
self.config_service_configs = {} self.config_service_configs = {}
self.setup_bindings() self.setup_bindings()
self.context = tk.Menu(self.canvas)
themes.style_menu(self.context)
def setup_bindings(self): def setup_bindings(self):
self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click) 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, "<Enter>", self.on_enter)
self.canvas.tag_bind(self.id, "<Leave>", self.on_leave) 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):
logging.debug("Delete canvas node for %s", self.core_node) logging.debug("Delete canvas node for %s", self.core_node)
@ -188,51 +191,55 @@ class CanvasNode:
else: else:
self.show_config() self.show_config()
def create_context(self) -> tk.Menu: def show_context(self, event: tk.Event) -> None:
# clear existing menu
self.context.delete(0, tk.END)
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
is_emane = self.core_node.type == NodeType.EMANE is_emane = self.core_node.type == NodeType.EMANE
context = tk.Menu(self.canvas)
themes.style_menu(context)
if self.app.core.is_runtime(): if self.app.core.is_runtime():
context.add_command(label="Configure", command=self.show_config) self.context.add_command(label="Configure", command=self.show_config)
if NodeUtils.is_container_node(self.core_node.type): if NodeUtils.is_container_node(self.core_node.type):
context.add_command(label="Services", state=tk.DISABLED) self.context.add_command(label="Services", state=tk.DISABLED)
context.add_command(label="Config Services", state=tk.DISABLED) self.context.add_command(label="Config Services", state=tk.DISABLED)
if is_wlan: if is_wlan:
context.add_command(label="WLAN Config", command=self.show_wlan_config) self.context.add_command(
label="WLAN Config", command=self.show_wlan_config
)
if is_wlan and self.core_node.id in self.app.core.mobility_players: if is_wlan and self.core_node.id in self.app.core.mobility_players:
context.add_command( self.context.add_command(
label="Mobility Player", command=self.show_mobility_player label="Mobility Player", command=self.show_mobility_player
) )
context.add_command(label="Select Adjacent", state=tk.DISABLED) self.context.add_command(label="Select Adjacent", state=tk.DISABLED)
if NodeUtils.is_container_node(self.core_node.type): if NodeUtils.is_container_node(self.core_node.type):
context.add_command(label="Shell Window", state=tk.DISABLED) self.context.add_command(label="Shell Window", state=tk.DISABLED)
context.add_command(label="Tcpdump", state=tk.DISABLED) self.context.add_command(label="Tcpdump", state=tk.DISABLED)
context.add_command(label="Tshark", state=tk.DISABLED) self.context.add_command(label="Tshark", state=tk.DISABLED)
context.add_command(label="Wireshark", state=tk.DISABLED) self.context.add_command(label="Wireshark", state=tk.DISABLED)
context.add_command(label="View Log", state=tk.DISABLED) self.context.add_command(label="View Log", state=tk.DISABLED)
else: else:
context.add_command(label="Configure", command=self.show_config) self.context.add_command(label="Configure", command=self.show_config)
if NodeUtils.is_container_node(self.core_node.type): if NodeUtils.is_container_node(self.core_node.type):
context.add_command(label="Services", command=self.show_services) self.context.add_command(label="Services", command=self.show_services)
context.add_command( self.context.add_command(
label="Config Services", command=self.show_config_services label="Config Services", command=self.show_config_services
) )
if is_emane: if is_emane:
context.add_command( self.context.add_command(
label="EMANE Config", command=self.show_emane_config label="EMANE Config", command=self.show_emane_config
) )
if is_wlan: if is_wlan:
context.add_command(label="WLAN Config", command=self.show_wlan_config) self.context.add_command(
context.add_command( label="WLAN Config", command=self.show_wlan_config
)
self.context.add_command(
label="Mobility Config", command=self.show_mobility_config label="Mobility Config", command=self.show_mobility_config
) )
if NodeUtils.is_wireless_node(self.core_node.type): if NodeUtils.is_wireless_node(self.core_node.type):
context.add_command( self.context.add_command(
label="Link To Selected", command=self.wireless_link_selected label="Link To Selected", command=self.wireless_link_selected
) )
context.add_command(label="Select Members", state=tk.DISABLED) self.context.add_command(label="Select Members", state=tk.DISABLED)
unlink_menu = tk.Menu(context) unlink_menu = tk.Menu(self.context)
for edge in self.edges: for edge in self.edges:
other_id = edge.src other_id = edge.src
if self.id == other_id: if self.id == other_id:
@ -243,14 +250,14 @@ class CanvasNode:
label=other_node.core_node.name, command=func_unlink label=other_node.core_node.name, command=func_unlink
) )
themes.style_menu(unlink_menu) themes.style_menu(unlink_menu)
context.add_cascade(label="Unlink", menu=unlink_menu) self.context.add_cascade(label="Unlink", menu=unlink_menu)
edit_menu = tk.Menu(context) edit_menu = tk.Menu(self.context)
themes.style_menu(edit_menu) themes.style_menu(edit_menu)
edit_menu.add_command(label="Cut", command=self.click_cut) edit_menu.add_command(label="Cut", command=self.click_cut)
edit_menu.add_command(label="Copy", command=self.canvas_copy) edit_menu.add_command(label="Copy", command=self.canvas_copy)
edit_menu.add_command(label="Delete", command=self.canvas_delete) edit_menu.add_command(label="Delete", command=self.canvas_delete)
context.add_cascade(label="Edit", menu=edit_menu) self.context.add_cascade(label="Edit", menu=edit_menu)
return context self.context.tk_popup(event.x_root, event.y_root)
def click_cut(self) -> None: def click_cut(self) -> None:
self.canvas_copy() self.canvas_copy()
@ -270,39 +277,32 @@ class CanvasNode:
self.canvas.copy() self.canvas.copy()
def show_config(self): def show_config(self):
self.canvas.context = None
dialog = NodeConfigDialog(self.app, self.app, self) dialog = NodeConfigDialog(self.app, self.app, self)
dialog.show() dialog.show()
def show_wlan_config(self): def show_wlan_config(self):
self.canvas.context = None
dialog = WlanConfigDialog(self.app, self.app, self) dialog = WlanConfigDialog(self.app, self.app, self)
if not dialog.has_error: if not dialog.has_error:
dialog.show() dialog.show()
def show_mobility_config(self): def show_mobility_config(self):
self.canvas.context = None
dialog = MobilityConfigDialog(self.app, self.app, self) dialog = MobilityConfigDialog(self.app, self.app, self)
if not dialog.has_error: if not dialog.has_error:
dialog.show() dialog.show()
def show_mobility_player(self): def show_mobility_player(self):
self.canvas.context = None
mobility_player = self.app.core.mobility_players[self.core_node.id] mobility_player = self.app.core.mobility_players[self.core_node.id]
mobility_player.show() mobility_player.show()
def show_emane_config(self): def show_emane_config(self):
self.canvas.context = None
dialog = EmaneConfigDialog(self.app, self.app, self) dialog = EmaneConfigDialog(self.app, self.app, self)
dialog.show() dialog.show()
def show_services(self): def show_services(self):
self.canvas.context = None
dialog = NodeServiceDialog(self.app.master, self.app, self) dialog = NodeServiceDialog(self.app.master, self.app, self)
dialog.show() dialog.show()
def show_config_services(self): def show_config_services(self):
self.canvas.context = None
dialog = NodeConfigServiceDialog(self.app.master, self.app, self) dialog = NodeConfigServiceDialog(self.app.master, self.app, self)
dialog.show() dialog.show()
@ -324,7 +324,6 @@ class CanvasNode:
return result return result
def wireless_link_selected(self): def wireless_link_selected(self):
self.canvas.context = None
for canvas_nid in [ for canvas_nid in [
x for x in self.canvas.selection if "node" in self.canvas.gettags(x) x for x in self.canvas.selection if "node" in self.canvas.gettags(x)
]: ]:

View file

@ -263,7 +263,6 @@ class Toolbar(ttk.Frame):
Start session handler redraw buttons, send node and link messages to grpc Start session handler redraw buttons, send node and link messages to grpc
server. server.
""" """
self.app.canvas.hide_context()
self.app.menubar.change_menubar_item_state(is_runtime=True) self.app.menubar.change_menubar_item_state(is_runtime=True)
self.app.statusbar.progress_bar.start(5) self.app.statusbar.progress_bar.start(5)
self.app.canvas.mode = GraphMode.SELECT self.app.canvas.mode = GraphMode.SELECT
@ -453,7 +452,6 @@ class Toolbar(ttk.Frame):
redraw buttons on the toolbar, send node and link messages to grpc server redraw buttons on the toolbar, send node and link messages to grpc server
""" """
logging.info("Click stop button") logging.info("Click stop button")
self.app.canvas.hide_context()
self.app.menubar.change_menubar_item_state(is_runtime=False) self.app.menubar.change_menubar_item_state(is_runtime=False)
self.app.statusbar.progress_bar.start(5) self.app.statusbar.progress_bar.start(5)
self.time = time.perf_counter() self.time = time.perf_counter()