Merge pull request #439 from coreemu/pygui-findfeature

Pygui findfeature
This commit is contained in:
bharnden 2020-04-30 11:22:31 -07:00 committed by GitHub
commit cdde6988e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 173 additions and 3 deletions

View file

@ -0,0 +1,158 @@
import logging
import tkinter as tk
from tkinter import ttk
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
class FindDialog(Dialog):
def __init__(self, master, app) -> None:
super().__init__(master, app, "Find", modal=True)
self.find_text = tk.StringVar(value="")
self.tree = None
self.draw()
self.protocol("WM_DELETE_WINDOW", self.close_dialog)
self.bind("<Return>", self.find_node)
def draw(self) -> None:
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.top.rowconfigure(1, weight=5)
self.top.rowconfigure(2, weight=1)
# Find node frame
frame = ttk.Frame(self.top, padding=FRAME_PAD)
frame.grid(sticky="nsew")
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Find:")
label.grid()
entry = ttk.Entry(frame, textvariable=self.find_text)
entry.grid(row=0, column=1, sticky="nsew")
# node list frame
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1)
frame.grid(sticky="nsew", padx=PADX, pady=PADY)
self.tree = ttk.Treeview(
frame,
columns=("nodeid", "name", "location", "detail"),
show="headings",
selectmode=tk.BROWSE,
)
self.tree.grid(sticky="nsew", pady=PADY)
style = ttk.Style()
heading_size = int(self.app.guiconfig["scale"] * 10)
style.configure("Treeview.Heading", font=(None, heading_size, "bold"))
self.tree.column("nodeid", stretch=tk.YES, anchor="center")
self.tree.heading("nodeid", text="Node ID")
self.tree.column("name", stretch=tk.YES, anchor="center")
self.tree.heading("name", text="Name")
self.tree.column("location", stretch=tk.YES, anchor="center")
self.tree.heading("location", text="Location")
self.tree.column("detail", stretch=tk.YES, anchor="center")
self.tree.heading("detail", text="Detail")
self.tree.bind("<<TreeviewSelect>>", self.click_select)
yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview)
yscrollbar.grid(row=0, column=1, sticky="ns")
self.tree.configure(yscrollcommand=yscrollbar.set)
xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview)
xscrollbar.grid(row=1, sticky="ew")
self.tree.configure(xscrollcommand=xscrollbar.set)
# button frame
frame = ttk.Frame(self.top)
frame.grid(sticky="nsew")
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
button = ttk.Button(frame, text="Find", command=self.find_node)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.close_dialog)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
def clear_treeview_items(self) -> None:
"""
clear all items in the treeview
"""
for i in list(self.tree.get_children("")):
self.tree.delete(i)
def find_node(self, _event: tk.Event = None) -> None:
"""
Query nodes that have the same node name as our search key,
display results to tree view
"""
node_name = self.find_text.get().strip()
self.clear_treeview_items()
for node_id, node in sorted(
self.app.core.canvas_nodes.items(), key=lambda x: x[0]
):
name = node.core_node.name
if not node_name or node_name == name:
pos_x = round(node.core_node.position.x, 1)
pos_y = round(node.core_node.position.y, 1)
# TODO I am not sure what to insert for Detail column, leaving in blank for now
self.tree.insert(
"",
tk.END,
text=str(node_id),
values=(node_id, name, f"<{pos_x}, {pos_y}>", ""),
)
results = self.tree.get_children("")
if results:
self.tree.selection_set(results[0])
def close_dialog(self) -> None:
self.app.canvas.delete("find")
self.destroy()
def click_select(self, _event: tk.Event = None) -> None:
"""
find the node that matches search criteria, circle around that node
and scroll the x and y scrollbar to be able to see the node if
it is out of sight
"""
item = self.tree.selection()
if item:
self.app.canvas.delete("find")
node_id = int(self.tree.item(item, "text"))
canvas_node = self.app.core.canvas_nodes[node_id]
x0, y0, x1, y1 = self.app.canvas.bbox(canvas_node.id)
dist = 5 * self.app.guiconfig["scale"]
self.app.canvas.create_oval(
x0 - dist,
y0 - dist,
x1 + dist,
y1 + dist,
tags="find",
outline="red",
width=3.0 * self.app.guiconfig["scale"],
)
_x, _y, _, _ = self.app.canvas.bbox(canvas_node.id)
oid = self.app.canvas.find_withtag("rectangle")
x0, y0, x1, y1 = self.app.canvas.bbox(oid[0])
logging.debug("Dist to most left: %s", abs(x0 - _x))
logging.debug("White canvas width: %s", abs(x0 - x1))
# calculate the node's location
# (as fractions of white canvas's width and height)
# and instantly scroll the x and y scrollbar to that location
xscroll_fraction = abs(x0 - _x) / abs(x0 - x1)
yscroll_fraction = abs(y0 - _y) / abs(y0 - y1)
# scroll a little more to the left or a little bit up so that the node
# doesn't always fall in the most top-left corner
for i in range(2):
if xscroll_fraction > 0.05:
xscroll_fraction = xscroll_fraction - 0.05
if yscroll_fraction > 0.05:
yscroll_fraction = yscroll_fraction - 0.05
self.app.canvas.xview_moveto(xscroll_fraction)
self.app.canvas.yview_moveto(yscroll_fraction)

View file

@ -72,12 +72,15 @@ class SessionsDialog(Dialog):
show="headings", show="headings",
selectmode=tk.BROWSE, selectmode=tk.BROWSE,
) )
style = ttk.Style()
heading_size = int(self.app.guiconfig["scale"] * 10)
style.configure("Treeview.Heading", font=(None, heading_size, "bold"))
self.tree.grid(sticky="nsew") self.tree.grid(sticky="nsew")
self.tree.column("id", stretch=tk.YES) self.tree.column("id", stretch=tk.YES, anchor="center")
self.tree.heading("id", text="ID") self.tree.heading("id", text="ID")
self.tree.column("state", stretch=tk.YES) self.tree.column("state", stretch=tk.YES, anchor="center")
self.tree.heading("state", text="State") self.tree.heading("state", text="State")
self.tree.column("nodes", stretch=tk.YES) self.tree.column("nodes", stretch=tk.YES, anchor="center")
self.tree.heading("nodes", text="Node Count") self.tree.heading("nodes", text="Node Count")
for index, session in enumerate(self.sessions): for index, session in enumerate(self.sessions):
@ -213,3 +216,5 @@ class SessionsDialog(Dialog):
def on_closing(self) -> None: def on_closing(self) -> None:
if self.is_start_app and messagebox.askokcancel("Exit", "Quit?", parent=self): if self.is_start_app and messagebox.askokcancel("Exit", "Quit?", parent=self):
self.click_exit() self.click_exit()
if not self.is_start_app:
self.destroy()

View file

@ -11,6 +11,7 @@ from core.gui.dialogs.about import AboutDialog
from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog
from core.gui.dialogs.canvaswallpaper import CanvasWallpaperDialog from core.gui.dialogs.canvaswallpaper import CanvasWallpaperDialog
from core.gui.dialogs.executepython import ExecutePythonDialog from core.gui.dialogs.executepython import ExecutePythonDialog
from core.gui.dialogs.find import FindDialog
from core.gui.dialogs.hooks import HooksDialog from core.gui.dialogs.hooks import HooksDialog
from core.gui.dialogs.ipdialog import IpConfigDialog from core.gui.dialogs.ipdialog import IpConfigDialog
from core.gui.dialogs.macdialog import MacConfigDialog from core.gui.dialogs.macdialog import MacConfigDialog
@ -114,6 +115,7 @@ class Menubar(tk.Menu):
Create edit menu Create edit menu
""" """
menu = tk.Menu(self) menu = tk.Menu(self)
menu.add_command(label="Find", accelerator="Ctrl+F", command=self.click_find)
menu.add_command(label="Preferences", command=self.click_preferences) menu.add_command(label="Preferences", command=self.click_preferences)
menu.add_command(label="Undo", accelerator="Ctrl+Z", state=tk.DISABLED) menu.add_command(label="Undo", accelerator="Ctrl+Z", state=tk.DISABLED)
menu.add_command(label="Redo", accelerator="Ctrl+Y", state=tk.DISABLED) menu.add_command(label="Redo", accelerator="Ctrl+Y", state=tk.DISABLED)
@ -125,6 +127,7 @@ class Menubar(tk.Menu):
label="Delete", accelerator="Ctrl+D", command=self.click_delete label="Delete", accelerator="Ctrl+D", command=self.click_delete
) )
self.add_cascade(label="Edit", menu=menu) self.add_cascade(label="Edit", menu=menu)
self.app.master.bind_all("<Control-f>", self.click_find)
self.app.master.bind_all("<Control-x>", self.click_cut) self.app.master.bind_all("<Control-x>", self.click_cut)
self.app.master.bind_all("<Control-c>", self.click_copy) self.app.master.bind_all("<Control-c>", self.click_copy)
self.app.master.bind_all("<Control-v>", self.click_paste) self.app.master.bind_all("<Control-v>", self.click_paste)
@ -397,6 +400,10 @@ class Menubar(tk.Menu):
self.core.create_new_session() self.core.create_new_session()
self.core.xml_file = None self.core.xml_file = None
def click_find(self, _event: tk.Event = None) -> None:
dialog = FindDialog(self.app, self.app)
dialog.show()
def click_preferences(self) -> None: def click_preferences(self) -> None:
dialog = PreferencesDialog(self.app, self.app) dialog = PreferencesDialog(self.app, self.app)
dialog.show() dialog.show()