pygui: further refactoring around retrieving icon images

This commit is contained in:
Blake Harnden 2021-02-18 23:07:55 -08:00
parent a6fadb76cc
commit 93813358b5
9 changed files with 67 additions and 81 deletions

View file

@ -196,10 +196,10 @@ class Application(ttk.Frame):
self.menubar.set_state(is_runtime=False) self.menubar.set_state(is_runtime=False)
self.toolbar.set_design() 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) 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) return images.from_name(image_file, width=width, scale=self.app_scale)
def close(self) -> None: def close(self) -> None:

View file

@ -84,17 +84,17 @@ class MobilityPlayerDialog(Dialog):
for i in range(3): for i in range(3):
frame.columnconfigure(i, weight=1) 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 = ttk.Button(frame, image=image, command=self.click_play)
self.play_button.image = image self.play_button.image = image
self.play_button.grid(row=0, column=0, sticky=tk.EW, padx=PADX) 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 = ttk.Button(frame, image=image, command=self.click_pause)
self.pause_button.image = image self.pause_button.image = image
self.pause_button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) 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 = ttk.Button(frame, image=image, command=self.click_stop)
self.stop_button.image = image self.stop_button.image = image
self.stop_button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) self.stop_button.grid(row=0, column=2, sticky=tk.EW, padx=PADX)

View file

@ -49,10 +49,10 @@ class ServiceConfigDialog(Dialog):
self.default_directories: List[str] = [] self.default_directories: List[str] = []
self.temp_directories: List[str] = [] self.temp_directories: List[str] = []
self.documentnew_img: PhotoImage = self.app.get_icon( 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( self.editdelete_img: PhotoImage = self.app.get_icon(
ImageEnum.EDITDELETE, ICON_SIZE ImageEnum.EDITDELETE, width=ICON_SIZE
) )
self.notebook: Optional[ttk.Notebook] = None self.notebook: Optional[ttk.Notebook] = None
self.metadata_entry: Optional[ttk.Entry] = None self.metadata_entry: Optional[ttk.Entry] = None

View file

@ -17,7 +17,6 @@ from core.gui.graph.enums import GraphMode, ScaleOption
from core.gui.graph.node import CanvasNode, ShadowNode from core.gui.graph.node import CanvasNode, ShadowNode
from core.gui.graph.shape import Shape from core.gui.graph.shape import Shape
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
from core.gui.images import TypeToImage
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -26,7 +25,6 @@ if TYPE_CHECKING:
ZOOM_IN: float = 1.1 ZOOM_IN: float = 1.1
ZOOM_OUT: float = 0.9 ZOOM_OUT: float = 0.9
ICON_SIZE: int = 48
MOVE_NODE_MODES: Set[GraphMode] = {GraphMode.NODE, GraphMode.SELECT} MOVE_NODE_MODES: Set[GraphMode] = {GraphMode.NODE, GraphMode.SELECT}
MOVE_SHAPE_MODES: Set[GraphMode] = {GraphMode.ANNOTATION, GraphMode.SELECT} MOVE_SHAPE_MODES: Set[GraphMode] = {GraphMode.ANNOTATION, GraphMode.SELECT}
BACKGROUND_COLOR: str = "#cccccc" BACKGROUND_COLOR: str = "#cccccc"
@ -518,14 +516,6 @@ class CanvasGraph(tk.Canvas):
if not core_node: if not core_node:
return return
core_node.canvas = self.id 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) node = CanvasNode(self.app, self, x, y, core_node, self.manager.node_draw.image)
self.nodes[node.id] = node self.nodes[node.id] = node
self.core.set_canvas_node(core_node, node) self.core.set_canvas_node(core_node, node)
@ -801,23 +791,12 @@ class CanvasGraph(tk.Canvas):
self.tag_raise(tags.NODE) self.tag_raise(tags.NODE)
def scale_graph(self) -> None: def scale_graph(self) -> None:
for nid, canvas_node in self.nodes.items(): for node_id, canvas_node in self.nodes.items():
img = None image = nutils.get_icon(canvas_node.core_node, self.app)
if nutils.is_custom(canvas_node.core_node): self.itemconfig(node_id, image=image)
for custom_node in self.app.guiconfig.nodes: canvas_node.image = image
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
canvas_node.scale_text() canvas_node.scale_text()
canvas_node.scale_antennas() canvas_node.scale_antennas()
for edge_id in self.find_withtag(tags.EDGE): for edge_id in self.find_withtag(tags.EDGE):
self.itemconfig(edge_id, width=int(EDGE_WIDTH * self.app.app_scale)) self.itemconfig(edge_id, width=int(EDGE_WIDTH * self.app.app_scale))

View file

@ -5,7 +5,6 @@ from tkinter import BooleanVar, messagebox, ttk
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, ValuesView from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, ValuesView
from core.api.grpc.wrappers import Link, LinkType, Node, Session, ThroughputsEvent 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 import nodeutils as nutils
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.edges import ( 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.graph import CanvasGraph
from core.gui.graph.node import CanvasNode from core.gui.graph.node import CanvasNode
from core.gui.graph.shapeutils import ShapeType from core.gui.graph.shapeutils import ShapeType
from core.gui.images import ImageEnum
from core.gui.nodeutils import NodeDraw from core.gui.nodeutils import NodeDraw
if TYPE_CHECKING: if TYPE_CHECKING:
@ -301,10 +299,7 @@ class CanvasManager:
canvas_id = core_node.canvas if core_node.canvas > 0 else 1 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) logging.info("adding core node canvas(%s): %s", core_node.name, canvas_id)
canvas = self.get(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)
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)
x = core_node.position.x x = core_node.position.x
y = core_node.position.y y = core_node.position.y
node = CanvasNode(self.app, canvas, x, y, core_node, image) node = CanvasNode(self.app, canvas, x, y, core_node, image)

View file

@ -95,7 +95,7 @@ class CanvasNode:
def add_antenna(self) -> None: def add_antenna(self) -> None:
x, y = self.position() x, y = self.position()
offset = len(self.antennas) * 8 * self.app.app_scale 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( antenna_id = self.canvas.create_image(
x - 16 + offset, x - 16 + offset,
y - int(23 * self.app.app_scale), y - int(23 * self.app.app_scale),
@ -389,7 +389,7 @@ class CanvasNode:
def scale_antennas(self) -> None: def scale_antennas(self) -> None:
for i in range(len(self.antennas)): for i in range(len(self.antennas)):
antenna_id = self.antennas[i] 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.canvas.itemconfig(antenna_id, image=image)
self.antenna_images[antenna_id] = image self.antenna_images[antenna_id] = image
node_x, node_y = self.canvas.coords(self.id) node_x, node_y = self.canvas.coords(self.id)
@ -492,7 +492,9 @@ class ShadowNode:
self.node: "CanvasNode" = node self.node: "CanvasNode" = node
self.id: Optional[int] = None self.id: Optional[int] = None
self.text_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.draw()
self.setup_bindings() self.setup_bindings()

View file

@ -4,7 +4,7 @@ from typing import Dict, Optional, Tuple
from PIL import Image from PIL import Image
from PIL.ImageTk import PhotoImage 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 from core.gui.appconfig import LOCAL_ICONS_PATH
NODE_SIZE: int = 48 NODE_SIZE: int = 48
@ -89,8 +89,7 @@ class ImageEnum(Enum):
SHADOW = "shadow" SHADOW = "shadow"
class TypeToImage: TYPE_MAP: Dict[Tuple[NodeType, str], ImageEnum] = {
type_to_image: Dict[Tuple[NodeType, str], ImageEnum] = {
(NodeType.DEFAULT, "router"): ImageEnum.ROUTER, (NodeType.DEFAULT, "router"): ImageEnum.ROUTER,
(NodeType.DEFAULT, "PC"): ImageEnum.PC, (NodeType.DEFAULT, "PC"): ImageEnum.PC,
(NodeType.DEFAULT, "host"): ImageEnum.HOST, (NodeType.DEFAULT, "host"): ImageEnum.HOST,
@ -104,8 +103,12 @@ class TypeToImage:
(NodeType.TUNNEL, ""): ImageEnum.TUNNEL, (NodeType.TUNNEL, ""): ImageEnum.TUNNEL,
(NodeType.DOCKER, ""): ImageEnum.DOCKER, (NodeType.DOCKER, ""): ImageEnum.DOCKER,
(NodeType.LXC, ""): ImageEnum.LXC, (NodeType.LXC, ""): ImageEnum.LXC,
} }
@classmethod
def get(cls, node_type, model) -> Optional[ImageEnum]: def from_node(node: Node, *, scale: float) -> Optional[PhotoImage]:
return cls.type_to_image.get((node_type, model)) 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

View file

@ -1,12 +1,15 @@
import logging import logging
from typing import List, Optional, Set from typing import TYPE_CHECKING, List, Optional, Set
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Node, NodeType from core.api.grpc.wrappers import Node, NodeType
from core.gui import images from core.gui import images
from core.gui.appconfig import CustomNode, GuiConfig 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"] = [] NODES: List["NodeDraw"] = []
NETWORK_NODES: List["NodeDraw"] = [] NETWORK_NODES: List["NodeDraw"] = []
@ -100,14 +103,15 @@ def get_custom_services(gui_config: GuiConfig, name: str) -> List[str]:
return [] 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: for custom_node in config.nodes:
if custom_node.name == name: if custom_node.name == name:
return custom_node.image return custom_node.image
return None 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 image = None
# node has defined a custom icon # node has defined a custom icon
if node.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) logging.error("invalid icon: %s", node.icon)
else: else:
# attempt to find default type/model image # attempt to find default type/model image
image_enum = TypeToImage.get(node.type, node.model) image = images.from_node(node, scale=scale)
if image_enum:
image = images.from_enum(image_enum, width=images.NODE_SIZE, scale=scale)
# attempt to find custom image file # attempt to find custom image file
else: if not image:
image_file = _get_image_file(config, node.model) image_file = _get_custom_file(app.guiconfig, node.model)
if image_file: if image_file:
image = images.from_name( image = images.from_name(
image_file, width=images.NODE_SIZE, scale=scale 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 return image

View file

@ -58,11 +58,11 @@ class PickerFrame(ttk.Frame):
image_file: str = None, image_file: str = None,
) -> None: ) -> None:
if image_enum: if image_enum:
bar_image = self.app.get_icon(image_enum, TOOLBAR_SIZE) bar_image = self.app.get_icon(image_enum, width=TOOLBAR_SIZE)
image = self.app.get_icon(image_enum, PICKER_SIZE) image = self.app.get_icon(image_enum, width=PICKER_SIZE)
else: else:
bar_image = self.app.get_custom_icon(image_file, TOOLBAR_SIZE) bar_image = self.app.get_custom_icon(image_file, width=TOOLBAR_SIZE)
image = self.app.get_custom_icon(image_file, PICKER_SIZE) image = self.app.get_custom_icon(image_file, width=PICKER_SIZE)
button = ttk.Button( button = ttk.Button(
self, image=image, text=label, compound=tk.TOP, style=Styles.picker_button self, image=image, text=label, compound=tk.TOP, style=Styles.picker_button
) )
@ -93,7 +93,7 @@ class ButtonBar(ttk.Frame):
def create_button( def create_button(
self, image_enum: ImageEnum, func: Callable, tooltip: str, radio: bool = False self, image_enum: ImageEnum, func: Callable, tooltip: str, radio: bool = False
) -> ttk.Button: ) -> 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 = ttk.Button(self, image=image, command=func)
button.image = image button.image = image
button.grid(sticky=tk.EW) button.grid(sticky=tk.EW)
@ -122,7 +122,7 @@ class MarkerFrame(ttk.Frame):
def draw(self) -> None: def draw(self) -> None:
self.columnconfigure(0, weight=1) 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 = ttk.Button(self, image=image, width=2, command=self.click_clear)
button.image = image button.image = image
button.grid(sticky=tk.EW, pady=self.PAD) button.grid(sticky=tk.EW, pady=self.PAD)
@ -384,7 +384,7 @@ class Toolbar(ttk.Frame):
self.picker.show() self.picker.show()
def create_observe_button(self) -> None: 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( menu_button = ttk.Menubutton(
self.runtime_frame, image=image, direction=tk.RIGHT self.runtime_frame, image=image, direction=tk.RIGHT
) )
@ -446,9 +446,9 @@ class Toolbar(ttk.Frame):
) -> None: ) -> None:
image = None image = None
if image_enum: 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: 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: if image:
button.config(image=image) button.config(image=image)
button.image = image button.image = image