From 29fc5acb996691f4fdf3d8f9553fa676deefa712 Mon Sep 17 00:00:00 2001
From: Blake Harnden <32446120+bharnden@users.noreply.github.com>
Date: Fri, 15 May 2020 23:23:07 -0700
Subject: [PATCH] pygui: toolbar cleanup for buttonbar frames

---
 daemon/core/gui/app.py     |   5 +-
 daemon/core/gui/toolbar.py | 198 +++++++++++++------------------------
 2 files changed, 74 insertions(+), 129 deletions(-)

diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py
index fe5c3659..2644a46d 100644
--- a/daemon/core/gui/app.py
+++ b/daemon/core/gui/app.py
@@ -1,7 +1,7 @@
 import logging
 import math
 import tkinter as tk
-from tkinter import font, ttk
+from tkinter import PhotoImage, font, ttk
 from tkinter.ttk import Progressbar
 
 import grpc
@@ -160,5 +160,8 @@ class Application(ttk.Frame):
         else:
             self.toolbar.set_design()
 
+    def get_icon(self, image_enum: ImageEnum, width: int) -> PhotoImage:
+        return Images.get(image_enum, int(width * self.app_scale))
+
     def close(self) -> None:
         self.master.destroy()
diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py
index 2bf4e63c..9fc81a74 100644
--- a/daemon/core/gui/toolbar.py
+++ b/daemon/core/gui/toolbar.py
@@ -37,6 +37,30 @@ def enable_buttons(frame: ttk.Frame, enabled: bool) -> None:
         child.configure(state=state)
 
 
+class ButtonBar(ttk.Frame):
+    def __init__(self, master: tk.Widget, app: "Application"):
+        super().__init__(master)
+        self.app = app
+        self.radio_buttons = []
+
+    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)
+        button = ttk.Button(self, image=image, command=func)
+        button.image = image
+        button.grid(sticky="ew")
+        Tooltip(button, tooltip)
+        if radio:
+            self.radio_buttons.append(button)
+        return button
+
+    def select_radio(self, selected: ttk.Button) -> None:
+        for button in self.radio_buttons:
+            button.state(["!pressed"])
+        selected.state(["pressed"])
+
+
 class Toolbar(ttk.Frame):
     """
     Core toolbar class
@@ -82,9 +106,6 @@ class Toolbar(ttk.Frame):
         # draw components
         self.draw()
 
-    def get_icon(self, image_enum: ImageEnum, width: int) -> PhotoImage:
-        return Images.get(image_enum, int(width * self.app.app_scale))
-
     def draw(self) -> None:
         self.columnconfigure(0, weight=1)
         self.rowconfigure(0, weight=1)
@@ -93,84 +114,59 @@ class Toolbar(ttk.Frame):
         self.design_frame.tkraise()
 
     def draw_design_frame(self) -> None:
-        self.design_frame = ttk.Frame(self)
+        self.design_frame = ButtonBar(self, self.app)
         self.design_frame.grid(row=0, column=0, sticky="nsew")
         self.design_frame.columnconfigure(0, weight=1)
-        self.play_button = self.create_button(
-            self.design_frame,
-            self.get_icon(ImageEnum.START, TOOLBAR_SIZE),
-            self.click_start,
-            "start the session",
+        self.play_button = self.design_frame.create_button(
+            ImageEnum.START, self.click_start, "Start Session"
         )
-        self.select_button = self.create_button(
-            self.design_frame,
-            self.get_icon(ImageEnum.SELECT, TOOLBAR_SIZE),
-            self.click_selection,
-            "selection tool",
+        self.select_button = self.design_frame.create_button(
+            ImageEnum.SELECT, self.click_selection, "Selection Tool", radio=True
         )
-        self.link_button = self.create_button(
-            self.design_frame,
-            self.get_icon(ImageEnum.LINK, TOOLBAR_SIZE),
-            self.click_link,
-            "link tool",
+        self.link_button = self.design_frame.create_button(
+            ImageEnum.LINK, self.click_link, "Link Tool", radio=True
+        )
+        self.node_enum = ImageEnum.ROUTER
+        self.node_button = self.design_frame.create_button(
+            self.node_enum, self.draw_node_picker, "Container Nodes", radio=True
+        )
+        self.network_enum = ImageEnum.HUB
+        self.network_button = self.design_frame.create_button(
+            self.network_enum, self.draw_network_picker, "Link Layer Nodes", radio=True
+        )
+        self.annotation_enum = ImageEnum.MARKER
+        self.annotation_button = self.design_frame.create_button(
+            self.annotation_enum,
+            self.draw_annotation_picker,
+            "Annotation Tools",
+            radio=True,
         )
-        self.create_node_button()
-        self.create_network_button()
-        self.create_annotation_button()
-
-    def design_select(self, button: ttk.Button) -> None:
-        logging.debug("selecting design button: %s", button)
-        self.select_button.state(["!pressed"])
-        self.link_button.state(["!pressed"])
-        self.node_button.state(["!pressed"])
-        self.network_button.state(["!pressed"])
-        self.annotation_button.state(["!pressed"])
-        button.state(["pressed"])
-
-    def runtime_select(self, button: ttk.Button) -> None:
-        logging.debug("selecting runtime button: %s", button)
-        self.runtime_select_button.state(["!pressed"])
-        self.stop_button.state(["!pressed"])
-        self.runtime_marker_button.state(["!pressed"])
-        self.run_command_button.state(["!pressed"])
-        button.state(["pressed"])
 
     def draw_runtime_frame(self) -> None:
-        self.runtime_frame = ttk.Frame(self)
+        self.runtime_frame = ButtonBar(self, self.app)
         self.runtime_frame.grid(row=0, column=0, sticky="nsew")
         self.runtime_frame.columnconfigure(0, weight=1)
-        self.stop_button = self.create_button(
-            self.runtime_frame,
-            self.get_icon(ImageEnum.STOP, TOOLBAR_SIZE),
-            self.click_stop,
-            "stop the session",
+        self.stop_button = self.runtime_frame.create_button(
+            ImageEnum.STOP, self.click_stop, "Stop Session"
         )
-        self.runtime_select_button = self.create_button(
-            self.runtime_frame,
-            self.get_icon(ImageEnum.SELECT, TOOLBAR_SIZE),
-            self.click_runtime_selection,
-            "selection tool",
+        self.runtime_select_button = self.runtime_frame.create_button(
+            ImageEnum.SELECT, self.click_runtime_selection, "Selection Tool", radio=True
         )
-        self.runtime_marker_button = self.create_button(
-            self.runtime_frame,
-            self.get_icon(ImageEnum.MARKER, TOOLBAR_SIZE),
-            self.click_marker_button,
-            "marker",
+        self.runtime_marker_button = self.runtime_frame.create_button(
+            ImageEnum.MARKER, self.click_marker_button, "Marker Tool", radio=True
         )
-        self.run_command_button = self.create_button(
-            self.runtime_frame,
-            self.get_icon(ImageEnum.RUN, TOOLBAR_SIZE),
-            self.click_run_button,
-            "run",
+        self.run_command_button = self.runtime_frame.create_button(
+            ImageEnum.RUN, self.click_run_button, "Run Tool"
         )
 
     def draw_node_picker(self) -> None:
+        self.design_frame.select_radio(self.node_button)
         self.hide_pickers()
         self.node_picker = ttk.Frame(self.master)
         # draw default nodes
         for node_draw in NodeUtils.NODES:
-            toolbar_image = self.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
-            image = self.get_icon(node_draw.image_enum, PICKER_SIZE)
+            toolbar_image = self.app.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
+            image = self.app.get_icon(node_draw.image_enum, PICKER_SIZE)
             func = partial(
                 self.update_button,
                 self.node_button,
@@ -198,7 +194,6 @@ class Toolbar(ttk.Frame):
                 node_draw.image_file,
             )
             self.create_picker_button(image, func, self.node_picker, name)
-        self.design_select(self.node_button)
         self.node_button.after(
             0, lambda: self.show_picker(self.node_button, self.node_picker)
         )
@@ -231,23 +226,12 @@ class Toolbar(ttk.Frame):
         button.bind("<ButtonRelease-1>", lambda e: func())
         button.grid(pady=1)
 
-    def create_button(
-        self, frame: ttk.Frame, image: PhotoImage, func: Callable, tooltip: str
-    ) -> ttk.Button:
-        button = ttk.Button(frame, image=image, command=func)
-        button.image = image
-        button.grid(sticky="ew")
-        Tooltip(button, tooltip)
-        return button
-
     def click_selection(self) -> None:
-        logging.debug("clicked selection tool")
-        self.design_select(self.select_button)
+        self.design_frame.select_radio(self.select_button)
         self.app.canvas.mode = GraphMode.SELECT
 
     def click_runtime_selection(self) -> None:
-        logging.debug("clicked selection tool")
-        self.runtime_select(self.runtime_select_button)
+        self.runtime_frame.select_radio(self.runtime_select_button)
         self.app.canvas.mode = GraphMode.SELECT
 
     def click_start(self) -> None:
@@ -284,8 +268,7 @@ class Toolbar(ttk.Frame):
         self.click_selection()
 
     def click_link(self) -> None:
-        logging.debug("Click LINK button")
-        self.design_select(self.link_button)
+        self.design_frame.select_radio(self.link_button)
         self.app.canvas.mode = GraphMode.EDGE
 
     def update_button(
@@ -319,28 +302,16 @@ class Toolbar(ttk.Frame):
             self.annotation_picker.destroy()
             self.annotation_picker = None
 
-    def create_node_button(self) -> None:
-        """
-        Create network layer button
-        """
-        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) -> None:
         """
         Draw the options for link-layer button.
         """
+        self.design_frame.select_radio(self.network_button)
         self.hide_pickers()
         self.network_picker = ttk.Frame(self.master)
         for node_draw in NodeUtils.NETWORK_NODES:
-            toolbar_image = self.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
-            image = self.get_icon(node_draw.image_enum, PICKER_SIZE)
+            toolbar_image = self.app.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
+            image = self.app.get_icon(node_draw.image_enum, PICKER_SIZE)
             self.create_picker_button(
                 image,
                 partial(
@@ -354,29 +325,15 @@ class Toolbar(ttk.Frame):
                 self.network_picker,
                 node_draw.label,
             )
-        self.design_select(self.network_button)
         self.network_button.after(
             0, lambda: self.show_picker(self.network_button, self.network_picker)
         )
 
-    def create_network_button(self) -> None:
-        """
-        Create link-layer node button and the options that represent different
-        link-layer node types.
-        """
-        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) -> None:
         """
         Draw the options for marker button.
         """
+        self.design_frame.select_radio(self.annotation_button)
         self.hide_pickers()
         self.annotation_picker = ttk.Frame(self.master)
         nodes = [
@@ -386,34 +343,20 @@ class Toolbar(ttk.Frame):
             (ImageEnum.TEXT, ShapeType.TEXT),
         ]
         for image_enum, shape_type in nodes:
-            toolbar_image = self.get_icon(image_enum, TOOLBAR_SIZE)
-            image = self.get_icon(image_enum, PICKER_SIZE)
+            toolbar_image = self.app.get_icon(image_enum, TOOLBAR_SIZE)
+            image = self.app.get_icon(image_enum, PICKER_SIZE)
             self.create_picker_button(
                 image,
                 partial(self.update_annotation, toolbar_image, shape_type, image_enum),
                 self.annotation_picker,
                 shape_type.value,
             )
-        self.design_select(self.annotation_button)
         self.annotation_button.after(
             0, lambda: self.show_picker(self.annotation_button, self.annotation_picker)
         )
 
-    def create_annotation_button(self) -> None:
-        """
-        Create marker button and options that represent different marker types
-        """
-        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) -> None:
-        image = self.get_icon(ImageEnum.OBSERVE, TOOLBAR_SIZE)
+        image = self.app.get_icon(ImageEnum.OBSERVE, TOOLBAR_SIZE)
         menu_button = ttk.Menubutton(
             self.runtime_frame, image=image, direction=tk.RIGHT
         )
@@ -476,8 +419,7 @@ class Toolbar(ttk.Frame):
         dialog.show()
 
     def click_marker_button(self) -> None:
-        logging.debug("Click on marker button")
-        self.runtime_select(self.runtime_marker_button)
+        self.runtime_frame.select_radio(self.runtime_marker_button)
         self.app.canvas.mode = GraphMode.ANNOTATION
         self.app.canvas.annotation_type = ShapeType.MARKER
         if self.marker_tool:
@@ -486,7 +428,7 @@ class Toolbar(ttk.Frame):
         self.marker_tool.show()
 
     def scale_button(self, button, image_enum) -> None:
-        image = self.get_icon(image_enum, TOOLBAR_SIZE)
+        image = self.app.get_icon(image_enum, TOOLBAR_SIZE)
         button.config(image=image)
         button.image = image