Merge pull request #371 from coreemu/coretk-enhance/scaling

Coretk enhance/scaling
This commit is contained in:
bharnden 2020-02-20 10:29:21 -08:00 committed by GitHub
commit ceb3d072da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 325 additions and 83 deletions

View file

@ -1,5 +1,5 @@
import tkinter as tk
from tkinter import ttk
from tkinter import font, ttk
from core.gui import appconfig, themes
from core.gui.coreclient import CoreClient
@ -29,8 +29,15 @@ class Application(tk.Frame):
self.statusbar = None
self.validation = None
# fonts
self.fonts_size = None
self.icon_text_font = None
self.edge_font = None
# setup
self.guiconfig = appconfig.read()
self.app_scale = self.guiconfig["scale"]
self.setup_scaling()
self.style = ttk.Style()
self.setup_theme()
self.core = CoreClient(self, proxy)
@ -38,6 +45,14 @@ class Application(tk.Frame):
self.draw()
self.core.set_up()
def setup_scaling(self):
self.fonts_size = {name: font.nametofont(name)["size"] for name in font.names()}
themes.scale_fonts(self.fonts_size, self.app_scale)
self.icon_text_font = font.Font(
family="TkIconFont", size=int(12 * self.app_scale)
)
self.edge_font = font.Font(family="TkDefaultFont", size=int(8 * self.app_scale))
def setup_theme(self):
themes.load(self.style)
self.master.bind_class("Menu", "<<ThemeChanged>>", themes.theme_change_menu)
@ -56,9 +71,11 @@ class Application(tk.Frame):
def center(self):
screen_width = self.master.winfo_screenwidth()
screen_height = self.master.winfo_screenheight()
x = int((screen_width / 2) - (WIDTH / 2))
y = int((screen_height / 2) - (HEIGHT / 2))
self.master.geometry(f"{WIDTH}x{HEIGHT}+{x}+{y}")
x = int((screen_width / 2) - (WIDTH * self.app_scale / 2))
y = int((screen_height / 2) - (HEIGHT * self.app_scale / 2))
self.master.geometry(
f"{int(WIDTH * self.app_scale)}x{int(HEIGHT * self.app_scale)}+{x}+{y}"
)
def draw(self):
self.master.option_add("*tearOff", tk.FALSE)

View file

@ -96,6 +96,7 @@ def check_directory():
"nodes": [],
"recentfiles": [],
"observers": [{"name": "hello", "cmd": "echo hello"}],
"scale": 1.0,
}
save(config)

View file

@ -794,7 +794,7 @@ class CoreClient:
image=image,
emane=emane,
)
if NodeUtils.is_custom(model):
if NodeUtils.is_custom(node_type, model):
services = NodeUtils.get_custom_node_services(self.app.guiconfig, model)
node.services[:] = services
logging.info(

View file

@ -100,17 +100,17 @@ class MobilityPlayerDialog(Dialog):
for i in range(3):
frame.columnconfigure(i, weight=1)
image = Images.get(ImageEnum.START, width=ICON_SIZE)
image = Images.get(ImageEnum.START, width=int(ICON_SIZE * self.app.app_scale))
self.play_button = ttk.Button(frame, image=image, command=self.click_play)
self.play_button.image = image
self.play_button.grid(row=0, column=0, sticky="ew", padx=PADX)
image = Images.get(ImageEnum.PAUSE, width=ICON_SIZE)
image = Images.get(ImageEnum.PAUSE, width=int(ICON_SIZE * self.app.app_scale))
self.pause_button = ttk.Button(frame, image=image, command=self.click_pause)
self.pause_button.image = image
self.pause_button.grid(row=0, column=1, sticky="ew", padx=PADX)
image = Images.get(ImageEnum.STOP, width=ICON_SIZE)
image = Images.get(ImageEnum.STOP, width=int(ICON_SIZE * self.app.app_scale))
self.stop_button = ttk.Button(frame, image=image, command=self.click_stop)
self.stop_button.image = image
self.stop_button.grid(row=0, column=2, sticky="ew", padx=PADX)

View file

@ -38,7 +38,7 @@ class NodeServiceDialog(Dialog):
if len(services) == 0:
# not custom node type and node's services haven't been modified before
if not NodeUtils.is_custom(
canvas_node.core_node.model
canvas_node.core_node.type, canvas_node.core_node.model
) and not self.app.core.service_been_modified(self.node_id):
services = set(self.app.core.default_services[model])
# services of default type nodes were modified to be empty

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
from core.gui import appconfig
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.themes import FRAME_PAD, PADX, PADY, scale_fonts
if TYPE_CHECKING:
from core.gui.app import Application
@ -14,6 +14,7 @@ if TYPE_CHECKING:
class PreferencesDialog(Dialog):
def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "Preferences", modal=True)
self.gui_scale = tk.DoubleVar(value=self.app.app_scale)
preferences = self.app.guiconfig["preferences"]
self.editor = tk.StringVar(value=preferences["editor"])
self.theme = tk.StringVar(value=preferences["theme"])
@ -64,6 +65,26 @@ class PreferencesDialog(Dialog):
entry = ttk.Entry(frame, textvariable=self.gui3d)
entry.grid(row=3, column=1, sticky="ew")
label = ttk.Label(frame, text="Scaling")
label.grid(row=4, column=0, pady=PADY, padx=PADX, sticky="w")
scale_frame = ttk.Frame(frame)
scale_frame.grid(row=4, column=1, sticky="ew")
scale_frame.columnconfigure(0, weight=1)
scale = ttk.Scale(
scale_frame,
from_=0.5,
to=5,
value=1,
orient=tk.HORIZONTAL,
variable=self.gui_scale,
)
scale.grid(row=0, column=0, sticky="ew")
entry = ttk.Entry(
scale_frame, textvariable=self.gui_scale, width=4, state="disabled"
)
entry.grid(row=0, column=1)
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
@ -87,5 +108,27 @@ class PreferencesDialog(Dialog):
preferences["editor"] = self.editor.get()
preferences["gui3d"] = self.gui3d.get()
preferences["theme"] = self.theme.get()
self.gui_scale.set(round(self.gui_scale.get(), 2))
app_scale = self.gui_scale.get()
self.app.guiconfig["scale"] = app_scale
self.app.save_config()
self.scale_adjust()
self.destroy()
def scale_adjust(self):
app_scale = self.gui_scale.get()
self.app.app_scale = app_scale
self.app.master.tk.call("tk", "scaling", app_scale)
# scale fonts
scale_fonts(self.app.fonts_size, app_scale)
self.app.icon_text_font.config(size=int(12 * app_scale))
self.app.edge_font.config(size=int(8 * app_scale))
# scale application window
self.app.center()
# scale toolbar and canvas items
self.app.toolbar.scale()
self.app.canvas.scale_graph()

View file

@ -1,6 +1,5 @@
import logging
import tkinter as tk
from tkinter.font import Font
from typing import TYPE_CHECKING, Any, Tuple
from core.gui import themes
@ -14,6 +13,8 @@ if TYPE_CHECKING:
TEXT_DISTANCE = 0.30
EDGE_WIDTH = 3
EDGE_COLOR = "#ff0000"
WIRELESS_WIDTH = 1.5
WIRELESS_COLOR = "#009933"
class CanvasWirelessEdge:
@ -31,7 +32,10 @@ class CanvasWirelessEdge:
self.dst = dst
self.canvas = canvas
self.id = self.canvas.create_line(
*position, tags=tags.WIRELESS_EDGE, width=1.5, fill="#009933"
*position,
tags=tags.WIRELESS_EDGE,
width=WIRELESS_WIDTH * self.canvas.app.app_scale,
fill=WIRELESS_COLOR,
)
def delete(self):
@ -61,13 +65,18 @@ class CanvasEdge:
self.dst_interface = None
self.canvas = canvas
self.id = self.canvas.create_line(
x1, y1, x2, y2, tags=tags.EDGE, width=EDGE_WIDTH, fill=EDGE_COLOR
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.font = Font(size=8)
self.link = None
self.asymmetric_link = None
self.throughput = None
@ -117,7 +126,7 @@ class CanvasEdge:
y1,
text=label_one,
justify=tk.CENTER,
font=self.font,
font=self.canvas.app.edge_font,
tags=tags.LINK_INFO,
)
self.text_dst = self.canvas.create_text(
@ -125,7 +134,7 @@ class CanvasEdge:
y2,
text=label_two,
justify=tk.CENTER,
font=self.font,
font=self.canvas.app.edge_font,
tags=tags.LINK_INFO,
)
@ -146,7 +155,7 @@ class CanvasEdge:
if self.text_middle is None:
x, y = self.get_midpoint()
self.text_middle = self.canvas.create_text(
x, y, tags=tags.THROUGHPUT, font=self.font, text=value
x, y, tags=tags.THROUGHPUT, font=self.canvas.app.edge_font, text=value
)
else:
self.canvas.itemconfig(self.text_middle, text=value)

View file

@ -7,12 +7,12 @@ 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 CanvasEdge, CanvasWirelessEdge
from core.gui.graph.edges import EDGE_WIDTH, CanvasEdge, CanvasWirelessEdge
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
from core.gui.images import ImageEnum, Images, TypeToImage
from core.gui.nodeutils import EdgeUtils, NodeUtils
if TYPE_CHECKING:
@ -220,10 +220,14 @@ class CanvasGraph(tk.Canvas):
# peer to peer node is not drawn on the GUI
if NodeUtils.is_ignore_node(core_node.type):
continue
image = NodeUtils.node_image(core_node, self.app.guiconfig)
image = NodeUtils.node_image(
core_node, self.app.guiconfig, self.app.app_scale
)
# if the gui can't find node's image, default to the "edit-node" image
if not image:
image = Images.get(ImageEnum.EDITNODE, ICON_SIZE)
image = Images.get(
ImageEnum.EDITNODE, int(ICON_SIZE * self.app.app_scale)
)
x = core_node.position.x
y = core_node.position.y
node = CanvasNode(self.master, x, y, core_node, image)
@ -663,6 +667,14 @@ class CanvasGraph(tk.Canvas):
core_node = self.core.create_node(
actual_x, actual_y, self.node_draw.node_type, self.node_draw.model
)
try:
self.node_draw.image = Images.get(
self.node_draw.image_enum, int(ICON_SIZE * self.app.app_scale)
)
except AttributeError:
self.node_draw.image = Images.get_custom(
self.node_draw.image_file, int(ICON_SIZE * self.app.app_scale)
)
node = CanvasNode(self.master, x, y, core_node, self.node_draw.image)
self.core.canvas_nodes[core_node.id] = node
self.nodes[node.id] = node
@ -911,3 +923,28 @@ class CanvasGraph(tk.Canvas):
width=self.itemcget(edge.id, "width"),
fill=self.itemcget(edge.id, "fill"),
)
def scale_graph(self):
for nid, canvas_node in self.nodes.items():
img = None
if NodeUtils.is_custom(
canvas_node.core_node.type, canvas_node.core_node.model
):
for custom_node in self.app.guiconfig["nodes"]:
if custom_node["name"] == canvas_node.core_node.model:
img = Images.get_custom(
custom_node["image"], int(ICON_SIZE * self.app.app_scale)
)
else:
image_enum = TypeToImage.get(
canvas_node.core_node.type, canvas_node.core_node.model
)
img = Images.get(image_enum, int(ICON_SIZE * self.app.app_scale))
self.itemconfig(nid, image=img)
canvas_node.image = img
canvas_node.scale_text()
canvas_node.scale_antennas()
for edge_id in self.find_withtag(tags.EDGE):
self.itemconfig(edge_id, width=int(EDGE_WIDTH * self.app.app_scale))

View file

@ -1,6 +1,5 @@
import logging
import tkinter as tk
from tkinter import font
from typing import TYPE_CHECKING
import grpc
@ -17,7 +16,8 @@ from core.gui.dialogs.wlanconfig import WlanConfigDialog
from core.gui.errors import show_grpc_error
from core.gui.graph import tags
from core.gui.graph.tooltip import CanvasTooltip
from core.gui.nodeutils import NodeUtils
from core.gui.images import ImageEnum, Images
from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
if TYPE_CHECKING:
from core.gui.app import Application
@ -42,21 +42,21 @@ class CanvasNode:
self.id = self.canvas.create_image(
x, y, anchor=tk.CENTER, image=self.image, tags=tags.NODE
)
text_font = font.Font(family="TkIconFont", size=12)
label_y = self._get_label_y()
self.text_id = self.canvas.create_text(
x,
label_y,
text=self.core_node.name,
tags=tags.NODE_NAME,
font=text_font,
font=self.app.icon_text_font,
fill="#0000CD",
)
self.tooltip = CanvasTooltip(self.canvas)
self.edges = set()
self.interfaces = []
self.wireless_edges = set()
self.antennae = []
self.antennas = []
self.antenna_images = {}
self.setup_bindings()
def setup_bindings(self):
@ -72,33 +72,37 @@ class CanvasNode:
def add_antenna(self):
x, y = self.canvas.coords(self.id)
offset = len(self.antennae) * 8
offset = len(self.antennas) * 8 * self.app.app_scale
img = Images.get(ImageEnum.ANTENNA, int(ANTENNA_SIZE * self.app.app_scale))
antenna_id = self.canvas.create_image(
x - 16 + offset,
y - 23,
y - int(23 * self.app.app_scale),
anchor=tk.CENTER,
image=NodeUtils.ANTENNA_ICON,
image=img,
tags=tags.ANTENNA,
)
self.antennae.append(antenna_id)
self.antennas.append(antenna_id)
self.antenna_images[antenna_id] = img
def delete_antenna(self):
"""
delete one antenna
"""
logging.debug("Delete an antenna on %s", self.core_node.name)
if self.antennae:
antenna_id = self.antennae.pop()
if self.antennas:
antenna_id = self.antennas.pop()
self.canvas.delete(antenna_id)
self.antenna_images.pop(antenna_id, None)
def delete_antennas(self):
"""
delete all antennas
"""
logging.debug("Remove all antennas for %s", self.core_node.name)
for antenna_id in self.antennae:
for antenna_id in self.antennas:
self.canvas.delete(antenna_id)
self.antennae.clear()
self.antennas.clear()
self.antenna_images.clear()
def redraw(self):
self.canvas.itemconfig(self.id, image=self.image)
@ -108,6 +112,12 @@ class CanvasNode:
image_box = self.canvas.bbox(self.id)
return image_box[3] + NODE_TEXT_OFFSET
def scale_text(self):
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):
x, y = self.canvas.get_scaled_coords(x, y)
current_x, current_y = self.canvas.coords(self.id)
@ -131,7 +141,7 @@ class CanvasNode:
self.canvas.move_selection(self.id, x_offset, y_offset)
# move antennae
for antenna_id in self.antennae:
for antenna_id in self.antennas:
self.canvas.move(antenna_id, x_offset, y_offset)
# move edges
@ -295,3 +305,17 @@ class CanvasNode:
if core_node.type == core_pb2.NodeType.DEFAULT and core_node.model == "mdr":
self.canvas.create_edge(self, self.canvas.nodes[canvas_nid])
self.canvas.clear_selection()
def scale_antennas(self):
for i in range(len(self.antennas)):
antenna_id = self.antennas[i]
image = Images.get(
ImageEnum.ANTENNA, int(ANTENNA_SIZE * self.app.app_scale)
)
self.canvas.itemconfig(antenna_id, image=image)
self.antenna_images[antenna_id] = image
node_x, node_y = self.canvas.coords(self.id)
x, y = self.canvas.coords(antenna_id)
dx = node_x - 16 + (i * 8 * self.app.app_scale) - x
dy = node_y - int(23 * self.app.app_scale) - y
self.canvas.move(antenna_id, dx, dy)

View file

@ -3,6 +3,7 @@ from tkinter import messagebox
from PIL import Image, ImageTk
from core.api.grpc import core_pb2
from core.gui.appconfig import LOCAL_ICONS_PATH
@ -90,3 +91,23 @@ class ImageEnum(Enum):
SHUTDOWN = "shutdown"
CANCEL = "cancel"
ERROR = "error"
class TypeToImage:
type_to_image = {
(core_pb2.NodeType.DEFAULT, "router"): ImageEnum.ROUTER,
(core_pb2.NodeType.DEFAULT, "PC"): ImageEnum.PC,
(core_pb2.NodeType.DEFAULT, "host"): ImageEnum.HOST,
(core_pb2.NodeType.DEFAULT, "mdr"): ImageEnum.MDR,
(core_pb2.NodeType.DEFAULT, "prouter"): ImageEnum.PROUTER,
(core_pb2.NodeType.HUB, ""): ImageEnum.HUB,
(core_pb2.NodeType.SWITCH, ""): ImageEnum.SWITCH,
(core_pb2.NodeType.WIRELESS_LAN, ""): ImageEnum.WLAN,
(core_pb2.NodeType.EMANE, ""): ImageEnum.EMANE,
(core_pb2.NodeType.RJ45, ""): ImageEnum.RJ45,
(core_pb2.NodeType.TUNNEL, ""): ImageEnum.TUNNEL,
}
@classmethod
def get(cls, node_type, model):
return cls.type_to_image.get((node_type, model), None)

View file

@ -2,7 +2,7 @@ import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union
from core.api.grpc.core_pb2 import NodeType
from core.gui.images import ImageEnum, Images
from core.gui.images import ImageEnum, Images, TypeToImage
if TYPE_CHECKING:
from core.api.grpc import core_pb2
@ -96,32 +96,35 @@ class NodeUtils:
node_type: NodeType,
model: str,
gui_config: Dict[str, List[Dict[str, str]]],
scale=1.0,
) -> "ImageTk.PhotoImage":
if model == "":
model = None
try:
image = cls.NODE_ICONS[(node_type, model)]
return image
except KeyError:
image_enum = TypeToImage.get(node_type, model)
if image_enum:
return Images.get(image_enum, int(ICON_SIZE * scale))
else:
image_stem = cls.get_image_file(gui_config, model)
if image_stem:
return Images.get_with_image_file(image_stem, ICON_SIZE)
return Images.get_with_image_file(image_stem, int(ICON_SIZE * scale))
@classmethod
def node_image(
cls, core_node: "core_pb2.Node", gui_config: Dict[str, List[Dict[str, str]]]
cls,
core_node: "core_pb2.Node",
gui_config: Dict[str, List[Dict[str, str]]],
scale=1.0,
) -> "ImageTk.PhotoImage":
image = cls.node_icon(core_node.type, core_node.model, gui_config)
image = cls.node_icon(core_node.type, core_node.model, gui_config, scale)
if core_node.icon:
try:
image = Images.create(core_node.icon, ICON_SIZE)
image = Images.create(core_node.icon, int(ICON_SIZE * scale))
except OSError:
logging.error("invalid icon: %s", core_node.icon)
return image
@classmethod
def is_custom(cls, model: str) -> bool:
return model not in cls.NODE_MODELS
def is_custom(cls, node_type: NodeType, model: str) -> bool:
return node_type == NodeType.DEFAULT and model not in cls.NODE_MODELS
@classmethod
def get_custom_node_services(

View file

@ -29,7 +29,7 @@ class StatusBar(ttk.Frame):
def draw(self):
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=7)
self.columnconfigure(1, weight=5)
self.columnconfigure(2, weight=1)
self.columnconfigure(3, weight=1)
self.columnconfigure(4, weight=1)

View file

@ -1,5 +1,5 @@
import tkinter as tk
from tkinter import ttk
from tkinter import font, ttk
THEME_DARK = "black"
PADX = (0, 5)
@ -176,25 +176,35 @@ def style_listbox(widget: tk.Widget):
def theme_change(event: tk.Event):
style = ttk.Style()
style.configure(Styles.picker_button, font=("TkDefaultFont", 8, "normal"))
style.configure(Styles.picker_button, font="TkSmallCaptionFont")
style.configure(
Styles.green_alert,
background="green",
padding=0,
relief=tk.NONE,
font=("TkDefaultFont", 8, "normal"),
font="TkSmallCaptionFont",
)
style.configure(
Styles.yellow_alert,
background="yellow",
padding=0,
relief=tk.NONE,
font=("TkDefaultFont", 8, "normal"),
font="TkSmallCaptionFont",
)
style.configure(
Styles.red_alert,
background="red",
padding=0,
relief=tk.NONE,
font=("TkDefaultFont", 8, "normal"),
font="TkSmallCaptionFont",
)
def scale_fonts(fonts_size, scale):
for name in font.names():
f = font.nametofont(name)
if name in fonts_size:
if name == "TkSmallCaptionFont":
f.config(size=int(fonts_size[name] * scale * 8 / 9))
else:
f.config(size=int(fonts_size[name] * scale))

View file

@ -1,9 +1,9 @@
import logging
import time
import tkinter as tk
from enum import Enum
from functools import partial
from tkinter import ttk
from tkinter.font import Font
from typing import TYPE_CHECKING, Callable
from core.api.grpc import core_pb2
@ -25,6 +25,12 @@ TOOLBAR_SIZE = 32
PICKER_SIZE = 24
class NodeTypeEnum(Enum):
NODE = 0
NETWORK = 1
OTHER = 2
def icon(image_enum, width=TOOLBAR_SIZE):
return Images.get(image_enum, width)
@ -43,10 +49,8 @@ class Toolbar(ttk.Frame):
self.master = app.master
self.time = None
# picker data
self.picker_font = Font(size=8)
# design buttons
self.play_button = None
self.select_button = None
self.link_button = None
self.node_button = None
@ -71,9 +75,18 @@ class Toolbar(ttk.Frame):
# dialog
self.marker_tool = None
# these variables help keep track of what images being drawn so that scaling is possible
# since ImageTk.PhotoImage does not have resize method
self.node_enum = None
self.network_enum = None
self.annotation_enum = None
# draw components
self.draw()
def get_icon(self, image_enum, width=TOOLBAR_SIZE):
return Images.get(image_enum, int(width * self.app.app_scale))
def draw(self):
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
@ -85,20 +98,23 @@ class Toolbar(ttk.Frame):
self.design_frame = ttk.Frame(self)
self.design_frame.grid(row=0, column=0, sticky="nsew")
self.design_frame.columnconfigure(0, weight=1)
self.create_button(
self.play_button = self.create_button(
self.design_frame,
icon(ImageEnum.START),
self.get_icon(ImageEnum.START),
self.click_start,
"start the session",
)
self.select_button = self.create_button(
self.design_frame,
icon(ImageEnum.SELECT),
self.get_icon(ImageEnum.SELECT),
self.click_selection,
"selection tool",
)
self.link_button = self.create_button(
self.design_frame, icon(ImageEnum.LINK), self.click_link, "link tool"
self.design_frame,
self.get_icon(ImageEnum.LINK),
self.click_link,
"link tool",
)
self.create_node_button()
self.create_network_button()
@ -130,18 +146,21 @@ class Toolbar(ttk.Frame):
self.stop_button = self.create_button(
self.runtime_frame,
icon(ImageEnum.STOP),
self.get_icon(ImageEnum.STOP),
self.click_stop,
"stop the session",
)
self.runtime_select_button = self.create_button(
self.runtime_frame,
icon(ImageEnum.SELECT),
self.get_icon(ImageEnum.SELECT),
self.click_runtime_selection,
"selection tool",
)
self.plot_button = self.create_button(
self.runtime_frame, icon(ImageEnum.PLOT), self.click_plot_button, "plot"
self.runtime_frame,
self.get_icon(ImageEnum.PLOT),
self.click_plot_button,
"plot",
)
self.runtime_marker_button = self.create_button(
self.runtime_frame,
@ -164,23 +183,38 @@ class Toolbar(ttk.Frame):
self.node_picker = ttk.Frame(self.master)
# draw default nodes
for node_draw in NodeUtils.NODES:
toolbar_image = icon(node_draw.image_enum)
image = icon(node_draw.image_enum, PICKER_SIZE)
toolbar_image = self.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
image = self.get_icon(node_draw.image_enum, PICKER_SIZE)
func = partial(
self.update_button, self.node_button, toolbar_image, node_draw
self.update_button,
self.node_button,
toolbar_image,
node_draw,
NodeTypeEnum.NODE,
node_draw.image_enum,
)
self.create_picker_button(image, func, self.node_picker, node_draw.label)
# draw custom nodes
for name in sorted(self.app.core.custom_nodes):
node_draw = self.app.core.custom_nodes[name]
toolbar_image = Images.get_custom(node_draw.image_file, TOOLBAR_SIZE)
image = Images.get_custom(node_draw.image_file, PICKER_SIZE)
toolbar_image = Images.get_custom(
node_draw.image_file, int(TOOLBAR_SIZE * self.app.app_scale)
)
image = Images.get_custom(
node_draw.image_file, int(PICKER_SIZE * self.app.app_scale)
)
func = partial(
self.update_button, self.node_button, toolbar_image, node_draw
self.update_button,
self.node_button,
toolbar_image,
node_draw,
NodeTypeEnum,
node_draw.image_file,
)
self.create_picker_button(image, func, self.node_picker, name)
# draw edit node
image = icon(ImageEnum.EDITNODE, PICKER_SIZE)
# image = icon(ImageEnum.EDITNODE, PICKER_SIZE)
image = self.get_icon(ImageEnum.EDITNODE, PICKER_SIZE)
self.create_picker_button(
image, self.click_edit_node, self.node_picker, "Custom"
)
@ -281,13 +315,24 @@ class Toolbar(ttk.Frame):
dialog = CustomNodesDialog(self.app, self.app)
dialog.show()
def update_button(self, button: ttk.Button, image: "ImageTk", node_draw: NodeDraw):
def update_button(
self,
button: ttk.Button,
image: "ImageTk",
node_draw: NodeDraw,
type_enum,
image_enum,
):
logging.debug("update button(%s): %s", button, node_draw)
self.hide_pickers()
button.configure(image=image)
button.image = image
self.app.canvas.mode = GraphMode.NODE
self.app.canvas.node_draw = node_draw
if type_enum == NodeTypeEnum.NODE:
self.node_enum = image_enum
elif type_enum == NodeTypeEnum.NETWORK:
self.network_enum = image_enum
def hide_pickers(self):
logging.debug("hiding pickers")
@ -305,13 +350,14 @@ class Toolbar(ttk.Frame):
"""
Create network layer button
"""
image = icon(ImageEnum.ROUTER)
image = self.get_icon(ImageEnum.ROUTER, TOOLBAR_SIZE)
self.node_button = ttk.Button(
self.design_frame, image=image, command=self.draw_node_picker
)
self.node_button.image = image
self.node_button.grid(sticky="ew")
Tooltip(self.node_button, "Network-layer virtual nodes")
self.node_enum = ImageEnum.ROUTER
def draw_network_picker(self):
"""
@ -320,12 +366,17 @@ class Toolbar(ttk.Frame):
self.hide_pickers()
self.network_picker = ttk.Frame(self.master)
for node_draw in NodeUtils.NETWORK_NODES:
toolbar_image = icon(node_draw.image_enum)
image = icon(node_draw.image_enum, PICKER_SIZE)
toolbar_image = self.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
image = self.get_icon(node_draw.image_enum, PICKER_SIZE)
self.create_picker_button(
image,
partial(
self.update_button, self.network_button, toolbar_image, node_draw
self.update_button,
self.network_button,
toolbar_image,
node_draw,
NodeTypeEnum.NETWORK,
node_draw.image_enum,
),
self.network_picker,
node_draw.label,
@ -340,13 +391,14 @@ class Toolbar(ttk.Frame):
Create link-layer node button and the options that represent different
link-layer node types.
"""
image = icon(ImageEnum.HUB)
image = self.get_icon(ImageEnum.HUB, TOOLBAR_SIZE)
self.network_button = ttk.Button(
self.design_frame, image=image, command=self.draw_network_picker
)
self.network_button.image = image
self.network_button.grid(sticky="ew")
Tooltip(self.network_button, "link-layer nodes")
self.network_enum = ImageEnum.HUB
def draw_annotation_picker(self):
"""
@ -361,11 +413,11 @@ class Toolbar(ttk.Frame):
(ImageEnum.TEXT, ShapeType.TEXT),
]
for image_enum, shape_type in nodes:
toolbar_image = icon(image_enum)
image = icon(image_enum, PICKER_SIZE)
toolbar_image = self.get_icon(image_enum, TOOLBAR_SIZE)
image = self.get_icon(image_enum, PICKER_SIZE)
self.create_picker_button(
image,
partial(self.update_annotation, toolbar_image, shape_type),
partial(self.update_annotation, toolbar_image, shape_type, image_enum),
self.annotation_picker,
shape_type.value,
)
@ -378,13 +430,14 @@ class Toolbar(ttk.Frame):
"""
Create marker button and options that represent different marker types
"""
image = icon(ImageEnum.MARKER)
image = self.get_icon(ImageEnum.MARKER, TOOLBAR_SIZE)
self.annotation_button = ttk.Button(
self.design_frame, image=image, command=self.draw_annotation_picker
)
self.annotation_button.image = image
self.annotation_button.grid(sticky="ew")
Tooltip(self.annotation_button, "background annotation tools")
self.annotation_enum = ImageEnum.MARKER
def create_observe_button(self):
menu_button = ttk.Menubutton(
@ -429,13 +482,16 @@ class Toolbar(ttk.Frame):
self.app.statusbar.set_status(message)
self.app.canvas.stopped_session()
def update_annotation(self, image: "ImageTk.PhotoImage", shape_type: ShapeType):
def update_annotation(
self, image: "ImageTk.PhotoImage", shape_type: ShapeType, image_enum
):
logging.debug("clicked annotation: ")
self.hide_pickers()
self.annotation_button.configure(image=image)
self.annotation_button.image = image
self.app.canvas.mode = GraphMode.ANNOTATION
self.app.canvas.annotation_type = shape_type
self.annotation_enum = image_enum
if is_marker(shape_type):
if self.marker_tool:
self.marker_tool.destroy()
@ -460,3 +516,24 @@ class Toolbar(ttk.Frame):
def click_two_node_button(self):
logging.debug("Click TWONODE button")
# def scale_button(cls, button, image_enum, scale):
def scale_button(self, button, image_enum):
image = icon(image_enum, int(TOOLBAR_SIZE * self.app.app_scale))
button.config(image=image)
button.image = image
def scale(self):
self.scale_button(self.play_button, ImageEnum.START)
self.scale_button(self.select_button, ImageEnum.SELECT)
self.scale_button(self.link_button, ImageEnum.LINK)
self.scale_button(self.node_button, self.node_enum)
self.scale_button(self.network_button, self.network_enum)
self.scale_button(self.annotation_button, self.annotation_enum)
self.scale_button(self.runtime_select_button, ImageEnum.SELECT)
self.scale_button(self.stop_button, ImageEnum.STOP)
self.scale_button(self.plot_button, ImageEnum.PLOT)
self.scale_button(self.runtime_marker_button, ImageEnum.MARKER)
self.scale_button(self.node_command_button, ImageEnum.TWONODE)
self.scale_button(self.run_command_button, ImageEnum.RUN)