From 93813358b5d619bd2d721334b13cda6037038e93 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Feb 2021 23:07:55 -0800 Subject: [PATCH] pygui: further refactoring around retrieving icon images --- daemon/core/gui/app.py | 4 +-- daemon/core/gui/dialogs/mobilityplayer.py | 6 ++-- daemon/core/gui/dialogs/serviceconfig.py | 4 +-- daemon/core/gui/graph/graph.py | 33 ++++------------- daemon/core/gui/graph/manager.py | 7 +--- daemon/core/gui/graph/node.py | 8 +++-- daemon/core/gui/images.py | 43 ++++++++++++----------- daemon/core/gui/nodeutils.py | 25 ++++++++----- daemon/core/gui/toolbar.py | 18 +++++----- 9 files changed, 67 insertions(+), 81 deletions(-) diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index db37e4f6..18337ed0 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -196,10 +196,10 @@ class Application(ttk.Frame): self.menubar.set_state(is_runtime=False) self.toolbar.set_design() - def get_icon(self, image_enum: ImageEnum, width: int) -> PhotoImage: + def get_icon(self, image_enum: ImageEnum, *, width: int) -> PhotoImage: return images.from_enum(image_enum, width=width, scale=self.app_scale) - def get_custom_icon(self, image_file: str, width: int) -> PhotoImage: + def get_custom_icon(self, image_file: str, *, width: int) -> PhotoImage: return images.from_name(image_file, width=width, scale=self.app_scale) def close(self) -> None: diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index f27a3635..12286a62 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -84,17 +84,17 @@ class MobilityPlayerDialog(Dialog): for i in range(3): frame.columnconfigure(i, weight=1) - image = self.app.get_icon(ImageEnum.START, ICON_SIZE) + image = self.app.get_icon(ImageEnum.START, width=ICON_SIZE) 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=tk.EW, padx=PADX) - image = self.app.get_icon(ImageEnum.PAUSE, ICON_SIZE) + image = self.app.get_icon(ImageEnum.PAUSE, width=ICON_SIZE) 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=tk.EW, padx=PADX) - image = self.app.get_icon(ImageEnum.STOP, ICON_SIZE) + image = self.app.get_icon(ImageEnum.STOP, width=ICON_SIZE) 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=tk.EW, padx=PADX) diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index d51d182b..0aaf0ca4 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -49,10 +49,10 @@ class ServiceConfigDialog(Dialog): self.default_directories: List[str] = [] self.temp_directories: List[str] = [] self.documentnew_img: PhotoImage = self.app.get_icon( - ImageEnum.DOCUMENTNEW, ICON_SIZE + ImageEnum.DOCUMENTNEW, width=ICON_SIZE ) self.editdelete_img: PhotoImage = self.app.get_icon( - ImageEnum.EDITDELETE, ICON_SIZE + ImageEnum.EDITDELETE, width=ICON_SIZE ) self.notebook: Optional[ttk.Notebook] = None self.metadata_entry: Optional[ttk.Entry] = None diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index d55ebdc1..7dd68215 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -17,7 +17,6 @@ from core.gui.graph.enums import GraphMode, ScaleOption from core.gui.graph.node import CanvasNode, ShadowNode from core.gui.graph.shape import Shape from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker -from core.gui.images import TypeToImage if TYPE_CHECKING: from core.gui.app import Application @@ -26,7 +25,6 @@ if TYPE_CHECKING: ZOOM_IN: float = 1.1 ZOOM_OUT: float = 0.9 -ICON_SIZE: int = 48 MOVE_NODE_MODES: Set[GraphMode] = {GraphMode.NODE, GraphMode.SELECT} MOVE_SHAPE_MODES: Set[GraphMode] = {GraphMode.ANNOTATION, GraphMode.SELECT} BACKGROUND_COLOR: str = "#cccccc" @@ -518,14 +516,6 @@ class CanvasGraph(tk.Canvas): if not core_node: return core_node.canvas = self.id - try: - image_enum = self.manager.node_draw.image_enum - self.manager.node_draw.image = self.app.get_icon(image_enum, ICON_SIZE) - except AttributeError: - image_file = self.manager.node_draw.image_file - self.manager.node_draw.image = self.app.get_custom_icon( - image_file, ICON_SIZE - ) node = CanvasNode(self.app, self, x, y, core_node, self.manager.node_draw.image) self.nodes[node.id] = node self.core.set_canvas_node(core_node, node) @@ -801,25 +791,14 @@ class CanvasGraph(tk.Canvas): self.tag_raise(tags.NODE) def scale_graph(self) -> None: - for nid, canvas_node in self.nodes.items(): - img = None - if nutils.is_custom(canvas_node.core_node): - for custom_node in self.app.guiconfig.nodes: - if custom_node.name == canvas_node.core_node.model: - img = self.app.get_custom_icon(custom_node.image, ICON_SIZE) - else: - image_enum = TypeToImage.get( - canvas_node.core_node.type, canvas_node.core_node.model - ) - img = self.app.get_icon(image_enum, ICON_SIZE) - - self.itemconfig(nid, image=img) - canvas_node.image = img + for node_id, canvas_node in self.nodes.items(): + image = nutils.get_icon(canvas_node.core_node, self.app) + self.itemconfig(node_id, image=image) + canvas_node.image = image 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)) + for edge_id in self.find_withtag(tags.EDGE): + self.itemconfig(edge_id, width=int(EDGE_WIDTH * self.app.app_scale)) def get_metadata(self) -> Dict[str, Any]: wallpaper_path = None diff --git a/daemon/core/gui/graph/manager.py b/daemon/core/gui/graph/manager.py index 5b6dd0be..5f4be2e2 100644 --- a/daemon/core/gui/graph/manager.py +++ b/daemon/core/gui/graph/manager.py @@ -5,7 +5,6 @@ from tkinter import BooleanVar, messagebox, ttk from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, ValuesView from core.api.grpc.wrappers import Link, LinkType, Node, Session, ThroughputsEvent -from core.gui import images from core.gui import nodeutils as nutils from core.gui.graph import tags from core.gui.graph.edges import ( @@ -18,7 +17,6 @@ from core.gui.graph.enums import GraphMode from core.gui.graph.graph import CanvasGraph from core.gui.graph.node import CanvasNode from core.gui.graph.shapeutils import ShapeType -from core.gui.images import ImageEnum from core.gui.nodeutils import NodeDraw if TYPE_CHECKING: @@ -301,10 +299,7 @@ class CanvasManager: canvas_id = core_node.canvas if core_node.canvas > 0 else 1 logging.info("adding core node canvas(%s): %s", core_node.name, canvas_id) canvas = self.get(canvas_id) - # if the gui can't find node's image, default to the "edit-node" image - image = nutils.get_icon(core_node, self.app.guiconfig, self.app.app_scale) - if not image: - image = self.app.get_icon(ImageEnum.EDITNODE, images.NODE_SIZE) + image = nutils.get_icon(core_node, self.app) x = core_node.position.x y = core_node.position.y node = CanvasNode(self.app, canvas, x, y, core_node, image) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 6a3fd74c..35269a1b 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -95,7 +95,7 @@ class CanvasNode: def add_antenna(self) -> None: x, y = self.position() offset = len(self.antennas) * 8 * self.app.app_scale - img = self.app.get_icon(ImageEnum.ANTENNA, images.ANTENNA_SIZE) + img = self.app.get_icon(ImageEnum.ANTENNA, width=images.ANTENNA_SIZE) antenna_id = self.canvas.create_image( x - 16 + offset, y - int(23 * self.app.app_scale), @@ -389,7 +389,7 @@ class CanvasNode: def scale_antennas(self) -> None: for i in range(len(self.antennas)): antenna_id = self.antennas[i] - image = self.app.get_icon(ImageEnum.ANTENNA, images.ANTENNA_SIZE) + image = self.app.get_icon(ImageEnum.ANTENNA, width=images.ANTENNA_SIZE) self.canvas.itemconfig(antenna_id, image=image) self.antenna_images[antenna_id] = image node_x, node_y = self.canvas.coords(self.id) @@ -492,7 +492,9 @@ class ShadowNode: self.node: "CanvasNode" = node self.id: Optional[int] = None self.text_id: Optional[int] = None - self.image: PhotoImage = self.app.get_icon(ImageEnum.SHADOW, images.NODE_SIZE) + self.image: PhotoImage = self.app.get_icon( + ImageEnum.SHADOW, width=images.NODE_SIZE + ) self.draw() self.setup_bindings() diff --git a/daemon/core/gui/images.py b/daemon/core/gui/images.py index 7c3edd53..4e1e42c0 100644 --- a/daemon/core/gui/images.py +++ b/daemon/core/gui/images.py @@ -4,7 +4,7 @@ from typing import Dict, Optional, Tuple from PIL import Image from PIL.ImageTk import PhotoImage -from core.api.grpc.wrappers import NodeType +from core.api.grpc.wrappers import Node, NodeType from core.gui.appconfig import LOCAL_ICONS_PATH NODE_SIZE: int = 48 @@ -89,23 +89,26 @@ class ImageEnum(Enum): SHADOW = "shadow" -class TypeToImage: - type_to_image: Dict[Tuple[NodeType, str], ImageEnum] = { - (NodeType.DEFAULT, "router"): ImageEnum.ROUTER, - (NodeType.DEFAULT, "PC"): ImageEnum.PC, - (NodeType.DEFAULT, "host"): ImageEnum.HOST, - (NodeType.DEFAULT, "mdr"): ImageEnum.MDR, - (NodeType.DEFAULT, "prouter"): ImageEnum.PROUTER, - (NodeType.HUB, ""): ImageEnum.HUB, - (NodeType.SWITCH, ""): ImageEnum.SWITCH, - (NodeType.WIRELESS_LAN, ""): ImageEnum.WLAN, - (NodeType.EMANE, ""): ImageEnum.EMANE, - (NodeType.RJ45, ""): ImageEnum.RJ45, - (NodeType.TUNNEL, ""): ImageEnum.TUNNEL, - (NodeType.DOCKER, ""): ImageEnum.DOCKER, - (NodeType.LXC, ""): ImageEnum.LXC, - } +TYPE_MAP: Dict[Tuple[NodeType, str], ImageEnum] = { + (NodeType.DEFAULT, "router"): ImageEnum.ROUTER, + (NodeType.DEFAULT, "PC"): ImageEnum.PC, + (NodeType.DEFAULT, "host"): ImageEnum.HOST, + (NodeType.DEFAULT, "mdr"): ImageEnum.MDR, + (NodeType.DEFAULT, "prouter"): ImageEnum.PROUTER, + (NodeType.HUB, ""): ImageEnum.HUB, + (NodeType.SWITCH, ""): ImageEnum.SWITCH, + (NodeType.WIRELESS_LAN, ""): ImageEnum.WLAN, + (NodeType.EMANE, ""): ImageEnum.EMANE, + (NodeType.RJ45, ""): ImageEnum.RJ45, + (NodeType.TUNNEL, ""): ImageEnum.TUNNEL, + (NodeType.DOCKER, ""): ImageEnum.DOCKER, + (NodeType.LXC, ""): ImageEnum.LXC, +} - @classmethod - def get(cls, node_type, model) -> Optional[ImageEnum]: - return cls.type_to_image.get((node_type, model)) + +def from_node(node: Node, *, scale: float) -> Optional[PhotoImage]: + image = None + image_enum = TYPE_MAP.get((node.type, node.model)) + if image_enum: + image = from_enum(image_enum, width=NODE_SIZE, scale=scale) + return image diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 62db60fc..f2f0fc19 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -1,12 +1,15 @@ import logging -from typing import List, Optional, Set +from typing import TYPE_CHECKING, List, Optional, Set from PIL.ImageTk import PhotoImage from core.api.grpc.wrappers import Node, NodeType from core.gui import images from core.gui.appconfig import CustomNode, GuiConfig -from core.gui.images import ImageEnum, TypeToImage +from core.gui.images import ImageEnum + +if TYPE_CHECKING: + from core.gui.app import Application NODES: List["NodeDraw"] = [] NETWORK_NODES: List["NodeDraw"] = [] @@ -100,14 +103,15 @@ def get_custom_services(gui_config: GuiConfig, name: str) -> List[str]: return [] -def _get_image_file(config: GuiConfig, name: str) -> Optional[str]: +def _get_custom_file(config: GuiConfig, name: str) -> Optional[str]: for custom_node in config.nodes: if custom_node.name == name: return custom_node.image return None -def get_icon(node: Node, config: GuiConfig, scale: float) -> Optional[PhotoImage]: +def get_icon(node: Node, app: "Application") -> PhotoImage: + scale = app.app_scale image = None # node has defined a custom icon if node.icon: @@ -117,16 +121,19 @@ def get_icon(node: Node, config: GuiConfig, scale: float) -> Optional[PhotoImage logging.error("invalid icon: %s", node.icon) else: # attempt to find default type/model image - image_enum = TypeToImage.get(node.type, node.model) - if image_enum: - image = images.from_enum(image_enum, width=images.NODE_SIZE, scale=scale) + image = images.from_node(node, scale=scale) # attempt to find custom image file - else: - image_file = _get_image_file(config, node.model) + if not image: + image_file = _get_custom_file(app.guiconfig, node.model) if image_file: image = images.from_name( image_file, width=images.NODE_SIZE, scale=scale ) + # default image, if everything above fails + if not image: + image = images.from_enum( + ImageEnum.EDITNODE, width=images.NODE_SIZE, scale=scale + ) return image diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 8c03e9e6..db98e6cf 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -58,11 +58,11 @@ class PickerFrame(ttk.Frame): image_file: str = None, ) -> None: if image_enum: - bar_image = self.app.get_icon(image_enum, TOOLBAR_SIZE) - image = self.app.get_icon(image_enum, PICKER_SIZE) + bar_image = self.app.get_icon(image_enum, width=TOOLBAR_SIZE) + image = self.app.get_icon(image_enum, width=PICKER_SIZE) else: - bar_image = self.app.get_custom_icon(image_file, TOOLBAR_SIZE) - image = self.app.get_custom_icon(image_file, PICKER_SIZE) + bar_image = self.app.get_custom_icon(image_file, width=TOOLBAR_SIZE) + image = self.app.get_custom_icon(image_file, width=PICKER_SIZE) button = ttk.Button( self, image=image, text=label, compound=tk.TOP, style=Styles.picker_button ) @@ -93,7 +93,7 @@ class ButtonBar(ttk.Frame): def create_button( self, image_enum: ImageEnum, func: Callable, tooltip: str, radio: bool = False ) -> ttk.Button: - image = self.app.get_icon(image_enum, TOOLBAR_SIZE) + image = self.app.get_icon(image_enum, width=TOOLBAR_SIZE) button = ttk.Button(self, image=image, command=func) button.image = image button.grid(sticky=tk.EW) @@ -122,7 +122,7 @@ class MarkerFrame(ttk.Frame): def draw(self) -> None: self.columnconfigure(0, weight=1) - image = self.app.get_icon(ImageEnum.DELETE, 16) + image = self.app.get_icon(ImageEnum.DELETE, width=16) button = ttk.Button(self, image=image, width=2, command=self.click_clear) button.image = image button.grid(sticky=tk.EW, pady=self.PAD) @@ -384,7 +384,7 @@ class Toolbar(ttk.Frame): self.picker.show() def create_observe_button(self) -> None: - image = self.app.get_icon(ImageEnum.OBSERVE, TOOLBAR_SIZE) + image = self.app.get_icon(ImageEnum.OBSERVE, width=TOOLBAR_SIZE) menu_button = ttk.Menubutton( self.runtime_frame, image=image, direction=tk.RIGHT ) @@ -446,9 +446,9 @@ class Toolbar(ttk.Frame): ) -> None: image = None if image_enum: - image = self.app.get_icon(image_enum, TOOLBAR_SIZE) + image = self.app.get_icon(image_enum, width=TOOLBAR_SIZE) elif image_file: - image = self.app.get_custom_icon(image_file, TOOLBAR_SIZE) + image = self.app.get_custom_icon(image_file, width=TOOLBAR_SIZE) if image: button.config(image=image) button.image = image