Merge branch 'develop' into enhancement/wlan-loss

This commit is contained in:
Blake Harnden 2020-04-30 11:24:45 -07:00
commit 6e2ed8f6ec
5 changed files with 204 additions and 26 deletions

View file

@ -357,6 +357,8 @@ class Session:
) )
interface = create_interface(node_one, net_one, interface_one) interface = create_interface(node_one, net_one, interface_one)
node_one_interface = interface node_one_interface = interface
wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
if not wireless_net:
link_config(net_one, interface, link_options) link_config(net_one, interface, link_options)
# network to node # network to node
@ -368,7 +370,8 @@ class Session:
) )
interface = create_interface(node_two, net_one, interface_two) interface = create_interface(node_two, net_one, interface_two)
node_two_interface = interface node_two_interface = interface
if not link_options.unidirectional: wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
if not link_options.unidirectional and not wireless_net:
link_config(net_one, interface, link_options) link_config(net_one, interface, link_options)
# network to network # network to network

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()

View file

@ -13,7 +13,7 @@ from core.errors import CoreXmlError
from core.nodes.base import CoreNetworkBase, CoreNodeBase, NodeBase from core.nodes.base import CoreNetworkBase, CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode from core.nodes.docker import DockerNode
from core.nodes.lxd import LxcNode from core.nodes.lxd import LxcNode
from core.nodes.network import CtrlNet from core.nodes.network import CtrlNet, WlanNode
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
if TYPE_CHECKING: if TYPE_CHECKING:
@ -559,7 +559,12 @@ class CoreXmlWriter:
) )
link_element.append(interface_two) link_element.append(interface_two)
# check for options # check for options, don't write for emane/wlan links
node_one = self.session.get_node(link_data.node1_id)
node_two = self.session.get_node(link_data.node2_id)
is_node_one_wireless = isinstance(node_one, (WlanNode, EmaneNet))
is_node_two_wireless = isinstance(node_two, (WlanNode, EmaneNet))
if not any([is_node_one_wireless, is_node_two_wireless]):
options = etree.Element("options") options = etree.Element("options")
add_attribute(options, "delay", link_data.delay) add_attribute(options, "delay", link_data.delay)
add_attribute(options, "bandwidth", link_data.bandwidth) add_attribute(options, "bandwidth", link_data.bandwidth)