pygui: changes to support saving and loading canvas backgrounds to xml, canvas dimensions will apply globally

This commit is contained in:
Blake Harnden 2020-12-17 12:25:11 -08:00
parent 5d436dd94d
commit 4a8f8557a6
7 changed files with 123 additions and 108 deletions

View file

@ -40,17 +40,13 @@ from core.api.grpc.wrappers import (
SessionState,
ThroughputsEvent,
)
from core.gui import appconfig
from core.gui.appconfig import BACKGROUNDS_PATH, XMLS_PATH, CoreServer, Observer
from core.gui.dialogs.emaneinstall import EmaneInstallDialog
from core.gui.dialogs.error import ErrorDialog
from core.gui.dialogs.mobilityplayer import MobilityPlayer
from core.gui.dialogs.sessions import SessionsDialog
from core.gui.graph.edges import CanvasEdge
from core.gui.graph.graph import CanvasGraph
from core.gui.graph.node import CanvasNode
from core.gui.graph.shape import AnnotationData, Shape
from core.gui.graph.shapeutils import ShapeType
from core.gui.interface import InterfaceManager
from core.gui.nodeutils import NodeDraw, NodeUtils
@ -326,59 +322,65 @@ class CoreClient:
def is_runtime(self) -> bool:
return self.session and self.session.state == SessionState.RUNTIME
def parse_metadata(self, canvas: CanvasGraph) -> None:
def parse_metadata(self) -> None:
# canvas setting
config = self.session.metadata
canvas_config = config.get("canvas")
logging.debug("canvas metadata: %s", canvas_config)
if canvas_config:
canvas_config = json.loads(canvas_config)
# get configured dimensions and gridlines option
dimensions = self.app.manager.default_dimensions
dimensions = canvas_config.get("dimensions", dimensions)
gridlines = canvas_config.get("gridlines", True)
self.app.canvas.show_grid.set(gridlines)
fit_image = canvas_config.get("fit_image", False)
self.app.canvas.adjust_to_dim.set(fit_image)
wallpaper_style = canvas_config.get("wallpaper-style", 1)
self.app.canvas.scale_option.set(wallpaper_style)
width = self.app.guiconfig.preferences.width
height = self.app.guiconfig.preferences.height
dimensions = canvas_config.get("dimensions", [width, height])
self.app.canvas.redraw_canvas(dimensions)
wallpaper = canvas_config.get("wallpaper")
if wallpaper:
wallpaper = str(appconfig.BACKGROUNDS_PATH.joinpath(wallpaper))
canvas.set_wallpaper(wallpaper)
else:
canvas.redraw_canvas()
canvas.set_wallpaper(None)
self.app.manager.show_grid.set(gridlines)
self.app.manager.redraw_canvases(dimensions)
# get background configurations
for background_config in canvas_config.get("canvases", []):
canvas_id = background_config.get("id")
if canvas_id is None:
logging.error("canvas config id not provided")
continue
canvas = self.app.manager.get(canvas_id)
fit_image = background_config.get("fit_image", False)
wallpaper_style = background_config.get("wallpaper-style", 1)
wallpaper = background_config.get("wallpaper")
canvas.adjust_to_dim.set(fit_image)
canvas.scale_option.set(wallpaper_style)
logging.info("canvas config: %s", background_config)
if wallpaper:
wallpaper = str(BACKGROUNDS_PATH.joinpath(wallpaper))
canvas.set_wallpaper(wallpaper)
# load saved shapes
shapes_config = config.get("shapes")
if shapes_config:
shapes_config = json.loads(shapes_config)
for shape_config in shapes_config:
logging.debug("loading shape: %s", shape_config)
shape_type = shape_config["type"]
try:
shape_type = ShapeType(shape_type)
coords = shape_config["iconcoords"]
data = AnnotationData(
shape_config["label"],
shape_config["fontfamily"],
shape_config["fontsize"],
shape_config["labelcolor"],
shape_config["color"],
shape_config["border"],
shape_config["width"],
shape_config["bold"],
shape_config["italic"],
shape_config["underline"],
)
shape = Shape(
self.app, self.app.canvas, shape_type, *coords, data=data
)
canvas.shapes[shape.id] = shape
except ValueError:
logging.exception("unknown shape: %s", shape_type)
# shapes_config = config.get("shapes")
# if shapes_config:
# shapes_config = json.loads(shapes_config)
# for shape_config in shapes_config:
# logging.debug("loading shape: %s", shape_config)
# shape_type = shape_config["type"]
# try:
# shape_type = ShapeType(shape_type)
# coords = shape_config["iconcoords"]
# data = AnnotationData(
# shape_config["label"],
# shape_config["fontfamily"],
# shape_config["fontsize"],
# shape_config["labelcolor"],
# shape_config["color"],
# shape_config["border"],
# shape_config["width"],
# shape_config["bold"],
# shape_config["italic"],
# shape_config["underline"],
# )
# shape = Shape(
# self.app, self.app.canvas, shape_type, *coords, data=data
# )
# canvas.shapes[shape.id] = shape
# except ValueError:
# logging.exception("unknown shape: %s", shape_type)
# load edges config
edges_config = config.get("edges")
@ -555,29 +557,35 @@ class CoreClient:
mobility_player.show()
def set_metadata(self) -> None:
# TODO: handle metadata for multiple canvases
return
# create canvas data
wallpaper_path = None
if self.app.canvas.wallpaper_file:
wallpaper = Path(self.app.canvas.wallpaper_file)
if BACKGROUNDS_PATH == wallpaper.parent:
wallpaper_path = wallpaper.name
else:
wallpaper_path = str(wallpaper)
canvas_config = {
"wallpaper": wallpaper_path,
"wallpaper-style": self.app.canvas.scale_option.get(),
"gridlines": self.app.canvas.show_grid.get(),
"fit_image": self.app.canvas.adjust_to_dim.get(),
"dimensions": self.app.canvas.current_dimensions,
}
canvases = []
for canvas in self.app.manager.canvases.values():
wallpaper_path = None
if canvas.wallpaper_file:
wallpaper = Path(canvas.wallpaper_file)
if BACKGROUNDS_PATH == wallpaper.parent:
wallpaper_path = wallpaper.name
else:
wallpaper_path = str(wallpaper)
config = {
"id": canvas.id,
"wallpaper": wallpaper_path,
"wallpaper-style": canvas.scale_option.get(),
"fit_image": canvas.adjust_to_dim.get(),
}
canvases.append(config)
canvas_config = dict(
gridlines=self.app.manager.show_grid.get(),
dimensions=self.app.manager.current_dimensions,
canvases=canvases,
)
canvas_config = json.dumps(canvas_config)
# create shapes data
shapes = []
for shape in self.app.canvas.shapes.values():
shapes.append(shape.metadata())
# TODO: handle shapes being on multiple canvases
# for shape in self.app.canvas.shapes.values():
# shapes.append(shape.metadata())
shapes = json.dumps(shapes)
# create edges config

View file

@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
from core.gui import validation
from core.gui.dialogs.dialog import Dialog
from core.gui.graph.graph import CanvasGraph
from core.gui.graph.manager import CanvasManager
from core.gui.themes import FRAME_PAD, PADX, PADY
if TYPE_CHECKING:
@ -22,9 +22,9 @@ class SizeAndScaleDialog(Dialog):
create an instance for size and scale object
"""
super().__init__(app, "Canvas Size and Scale")
self.canvas: CanvasGraph = self.app.canvas
self.manager: CanvasManager = self.app.manager
self.section_font: font.Font = font.Font(weight=font.BOLD)
width, height = self.canvas.current_dimensions
width, height = self.manager.current_dimensions
self.pixel_width: tk.IntVar = tk.IntVar(value=width)
self.pixel_height: tk.IntVar = tk.IntVar(value=height)
location = self.app.core.session.location
@ -189,9 +189,7 @@ class SizeAndScaleDialog(Dialog):
def click_apply(self) -> None:
width, height = self.pixel_width.get(), self.pixel_height.get()
self.canvas.redraw_canvas((width, height))
if self.canvas.wallpaper:
self.canvas.redraw_wallpaper()
self.manager.redraw_canvases((width, height))
location = self.app.core.session.location
location.x = self.x.get()
location.y = self.y.get()

View file

@ -23,7 +23,7 @@ class CanvasWallpaperDialog(Dialog):
create an instance of CanvasWallpaper object
"""
super().__init__(app, "Canvas Background")
self.canvas: CanvasGraph = self.app.canvas
self.canvas: CanvasGraph = self.app.manager.current()
self.scale_option: tk.IntVar = tk.IntVar(value=self.canvas.scale_option.get())
self.adjust_to_dim: tk.BooleanVar = tk.BooleanVar(
value=self.canvas.adjust_to_dim.get()
@ -161,7 +161,6 @@ class CanvasWallpaperDialog(Dialog):
def click_apply(self) -> None:
self.canvas.scale_option.set(self.scale_option.get())
self.canvas.adjust_to_dim.set(self.adjust_to_dim.get())
self.canvas.show_grid.click_handler()
filename = self.filename.get()
if not filename:
filename = None

View file

@ -37,11 +37,12 @@ if TYPE_CHECKING:
from core.gui.graph.manager import CanvasManager
from core.gui.coreclient import CoreClient
ZOOM_IN = 1.1
ZOOM_OUT = 0.9
ICON_SIZE = 48
MOVE_NODE_MODES = {GraphMode.NODE, GraphMode.SELECT}
MOVE_SHAPE_MODES = {GraphMode.ANNOTATION, GraphMode.SELECT}
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"
class CanvasGraph(tk.Canvas):
@ -49,14 +50,15 @@ class CanvasGraph(tk.Canvas):
self,
master: tk.BaseWidget,
app: "Application",
canvas_manager: "CanvasManager",
manager: "CanvasManager",
core: "CoreClient",
_id: int,
dimensions: Tuple[int, int],
) -> None:
super().__init__(master, highlightthickness=0, background="#cccccc")
super().__init__(master, highlightthickness=0, background=BACKGROUND_COLOR)
self.id: int = _id
self.app: "Application" = app
self.manager: "CanvasManager" = canvas_manager
self.manager: "CanvasManager" = manager
self.core: "CoreClient" = core
self.selection: Dict[int, int] = {}
self.select_box: Optional[Shape] = None
@ -72,10 +74,7 @@ class CanvasGraph(tk.Canvas):
self.drawing_edge: Optional[CanvasEdge] = None
self.rect: Optional[int] = None
self.shape_drawing: bool = False
width = self.app.guiconfig.preferences.width
height = self.app.guiconfig.preferences.height
self.default_dimensions: Tuple[int, int] = (width, height)
self.current_dimensions: Tuple[int, int] = self.default_dimensions
self.current_dimensions: Tuple[int, int] = dimensions
self.ratio: float = 1.0
self.offset: Tuple[int, int] = (0, 0)
self.cursor: Tuple[int, int] = (0, 0)
@ -105,7 +104,7 @@ class CanvasGraph(tk.Canvas):
if self.rect is not None:
self.delete(self.rect)
if not dimensions:
dimensions = self.default_dimensions
dimensions = self.manager.default_dimensions
self.current_dimensions = dimensions
self.rect = self.create_rectangle(
0,
@ -845,7 +844,7 @@ class CanvasGraph(tk.Canvas):
self.tag_raise(tag)
def set_wallpaper(self, filename: Optional[str]) -> None:
logging.debug("setting wallpaper: %s", filename)
logging.info("setting canvas(%s) background: %s", self.id, filename)
if filename:
img = Image.open(filename)
self.wallpaper = img

View file

@ -52,6 +52,7 @@ class CanvasManager:
self.app.guiconfig.preferences.width,
self.app.guiconfig.preferences.height,
)
self.current_dimensions: Tuple[int, int] = self.default_dimensions
self.show_node_labels: ShowVar = ShowVar(self, tags.NODE_LABEL, value=True)
self.show_link_labels: ShowVar = ShowVar(self, tags.LINK_LABEL, value=True)
self.show_links: ShowVar = ShowVar(self, tags.EDGE, value=True)
@ -92,6 +93,12 @@ class CanvasManager:
def all(self) -> ValuesView[CanvasGraph]:
return self.canvases.values()
def get(self, canvas_id: int) -> CanvasGraph:
canvas = self.canvases.get(canvas_id)
if not canvas:
canvas = self.add_canvas(canvas_id)
return canvas
def add_canvas(self, canvas_id: int = None) -> CanvasGraph:
# create tab frame
tab = ttk.Frame(self.notebook, padding=0)
@ -102,11 +109,12 @@ class CanvasManager:
canvas_id = self._next_id()
self.notebook.add(tab, text=f"Canvas {canvas_id}")
unique_id = self.notebook.tabs()[-1]
logging.info("tab(%s) is %s", unique_id, canvas_id)
self.unique_ids[unique_id] = canvas_id
# create canvas
canvas = CanvasGraph(tab, self.app, self, self.core, canvas_id)
canvas = CanvasGraph(
tab, self.app, self, self.core, canvas_id, self.default_dimensions
)
canvas.grid(sticky=tk.NSEW)
self.canvases[canvas_id] = canvas
@ -148,22 +156,15 @@ class CanvasManager:
self.annotation_type = None
self.node_draw = None
if session.nodes:
self.draw_session(session)
else:
self.add_canvas()
# draw session
self.draw_session(session)
def draw_session(self, session: Session) -> None:
# create session nodes
for core_node in session.nodes.values():
# get tab id for node
canvas_id = core_node.canvas if core_node.canvas > 0 else 1
# get or create canvas
canvas = self.canvases.get(canvas_id)
if not canvas:
canvas = self.add_canvas(canvas_id)
canvas = self.get(canvas_id)
# add node, avoiding ignored nodes
if NodeUtils.is_ignore_node(core_node.type):
continue
@ -185,8 +186,17 @@ class CanvasManager:
else:
logging.error("cant handle nodes linked between canvases")
# TODO: handle metadata
# self.core.parse_metadata(canvas)
# organize all canvases
# parse metadata and organize canvases
self.core.parse_metadata()
for canvas in self.canvases.values():
canvas.organize()
# create a default canvas if none were created prior
if not self.canvases:
self.add_canvas()
def redraw_canvases(self, dimensions: Tuple[int, int]) -> None:
for canvas in self.canvases.values():
canvas.redraw_canvas(dimensions)
if canvas.wallpaper:
canvas.redraw_wallpaper()

View file

@ -85,7 +85,7 @@ class Shape:
fill=self.shape_data.fill_color,
outline=self.shape_data.border_color,
width=self.shape_data.border_width,
state=self.canvas.show_annotations.state(),
state=self.app.manager.show_annotations.state(),
)
self.draw_shape_text()
elif self.shape_type == ShapeType.RECTANGLE:
@ -99,7 +99,7 @@ class Shape:
fill=self.shape_data.fill_color,
outline=self.shape_data.border_color,
width=self.shape_data.border_width,
state=self.canvas.show_annotations.state(),
state=self.app.manager.show_annotations.state(),
)
self.draw_shape_text()
elif self.shape_type == ShapeType.TEXT:
@ -111,7 +111,7 @@ class Shape:
text=self.shape_data.text,
fill=self.shape_data.text_color,
font=font,
state=self.canvas.show_annotations.state(),
state=self.app.manager.show_annotations.state(),
)
else:
logging.error("unknown shape type: %s", self.shape_type)
@ -139,7 +139,7 @@ class Shape:
text=self.shape_data.text,
fill=self.shape_data.text_color,
font=font,
state=self.canvas.show_annotations.state(),
state=self.app.manager.show_annotations.state(),
)
def shape_motion(self, x1: float, y1: float) -> None:

View file

@ -129,8 +129,9 @@ class Menubar(tk.Menu):
"""
menu = tk.Menu(self)
menu.add_command(label="New", command=self.click_canvas_add)
menu.add_command(label="Delete", command=self.click_canvas_delete)
menu.add_command(label="Size / Scale", command=self.click_canvas_size_and_scale)
menu.add_separator()
menu.add_command(label="Delete", command=self.click_canvas_delete)
menu.add_command(label="Wallpaper", command=self.click_canvas_wallpaper)
self.add_cascade(label="Canvas", menu=menu)