pygui revamped progress bar functionality into app task calls to simplify and commonize the functionality, handle and display task exceptions

This commit is contained in:
Blake Harnden 2020-05-03 10:41:36 -07:00
parent 835675480b
commit 0999fabb14
8 changed files with 82 additions and 87 deletions

View file

@ -297,7 +297,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
for service_exception in boot_exception.args:
exceptions.append(str(service_exception))
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
return core_pb2.StartSessionResponse(result=True)
def StopSession(

View file

@ -1,6 +1,8 @@
import math
import time
import tkinter as tk
from tkinter import font, ttk
from tkinter.ttk import Progressbar
from core.gui import appconfig, themes
from core.gui.coreclient import CoreClient
@ -9,6 +11,7 @@ from core.gui.images import ImageEnum, Images
from core.gui.menubar import Menubar
from core.gui.nodeutils import NodeUtils
from core.gui.statusbar import StatusBar
from core.gui.task import ProgressTask
from core.gui.toolbar import Toolbar
from core.gui.validation import InputValidation
@ -29,6 +32,8 @@ class Application(ttk.Frame):
self.canvas = None
self.statusbar = None
self.validation = None
self.progress = None
self.time = None
# fonts
self.fonts_size = None
@ -93,6 +98,7 @@ class Application(ttk.Frame):
self.right_frame.grid(row=0, column=1, sticky="nsew")
self.draw_canvas()
self.draw_status()
self.progress = Progressbar(self.right_frame, mode="indeterminate")
self.menubar = Menubar(self.master, self)
def draw_canvas(self):
@ -117,6 +123,21 @@ class Application(ttk.Frame):
self.statusbar = StatusBar(self.right_frame, self)
self.statusbar.grid(sticky="ew")
def progress_task(self, task: ProgressTask) -> None:
self.progress.grid(sticky="ew")
self.progress.start()
self.time = time.perf_counter()
task.app = self
task.start()
def progress_task_complete(self) -> None:
self.progress.stop()
self.progress.grid_forget()
total = time.perf_counter() - self.time
self.time = None
message = f"Task ran for {total:.3f} seconds"
self.statusbar.set_status(message)
def on_closing(self):
self.menubar.prompt_save_running_session(True)
@ -124,7 +145,6 @@ class Application(ttk.Frame):
appconfig.save(self.guiconfig)
def joined_session_update(self):
self.statusbar.progress_bar.stop()
if self.core.is_runtime():
self.toolbar.set_runtime()
else:

View file

@ -9,7 +9,7 @@ from core.api.grpc import core_pb2
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images
from core.gui.task import BackgroundTask
from core.gui.task import ProgressTask
from core.gui.themes import PADX, PADY
if TYPE_CHECKING:
@ -183,12 +183,11 @@ class SessionsDialog(Dialog):
self.join_session(self.selected_session)
def join_session(self, session_id: int) -> None:
self.destroy()
if self.app.core.xml_file:
self.app.core.xml_file = None
self.app.statusbar.progress_bar.start(5)
task = BackgroundTask(self.app, self.app.core.join_session, args=(session_id,))
task.start()
self.destroy()
task = ProgressTask(self.app.core.join_session, args=(session_id,))
self.app.progress_task(task)
def double_click_join(self, _event: tk.Event) -> None:
item = self.tree.selection()
@ -201,7 +200,6 @@ class SessionsDialog(Dialog):
if not self.selected_session:
return
logging.debug("delete session: %s", self.selected_session)
# self.app.core.delete_session(self.selected_id, self.top)
self.tree.delete(self.selected_id)
self.app.core.delete_session(self.selected_session)
if self.selected_session == self.app.core.session_id:

View file

@ -43,7 +43,12 @@ class ErrorDialog(Dialog):
button.grid(sticky="ew")
def show_grpc_error(e: grpc.RpcError, master, app: "Application"):
def show_exception(app: "Application", title: str, exception: Exception) -> None:
dialog = ErrorDialog(app, app, title, str(exception))
dialog.show()
def show_grpc_error(e: grpc.RpcError, master, app: "Application") -> None:
title = [x.capitalize() for x in e.code().name.lower().split("_")]
title = " ".join(title)
title = f"GRPC {title}"
@ -51,8 +56,6 @@ def show_grpc_error(e: grpc.RpcError, master, app: "Application"):
dialog.show()
def show_grpc_response_exceptions(class_name, exceptions, master, app: "Application"):
title = f"Exceptions from {class_name}"
detail = "\n".join([str(x) for x in exceptions])
dialog = ErrorDialog(master, app, title, detail)
def show_error(app: "Application", title: str, message: str) -> None:
dialog = ErrorDialog(app, app, title, message)
dialog.show()

View file

@ -22,7 +22,7 @@ from core.gui.dialogs.sessionoptions import SessionOptionsDialog
from core.gui.dialogs.sessions import SessionsDialog
from core.gui.dialogs.throughput import ThroughputDialog
from core.gui.nodeutils import ICON_SIZE
from core.gui.task import BackgroundTask
from core.gui.task import ProgressTask
if TYPE_CHECKING:
from core.gui.app import Application
@ -340,9 +340,8 @@ class Menubar(tk.Menu):
self.core.xml_file = filename
self.core.xml_dir = str(os.path.dirname(filename))
self.prompt_save_running_session()
self.app.statusbar.progress_bar.start(5)
task = BackgroundTask(self.app, self.core.open_xml, args=(filename,))
task.start()
task = ProgressTask(self.core.open_xml, args=(filename,))
self.app.progress_task(task)
def execute_python(self):
dialog = ExecutePythonDialog(self.app, self.app)

View file

@ -18,7 +18,6 @@ class StatusBar(ttk.Frame):
self.app = app
self.status = None
self.statusvar = tk.StringVar()
self.progress_bar = None
self.zoom = None
self.cpu_usage = None
self.memory = None
@ -28,19 +27,14 @@ class StatusBar(ttk.Frame):
self.draw()
def draw(self):
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=5)
self.columnconfigure(0, weight=7)
self.columnconfigure(1, weight=1)
self.columnconfigure(2, weight=1)
self.columnconfigure(3, weight=1)
self.columnconfigure(4, weight=1)
frame = ttk.Frame(self, borderwidth=1, relief=tk.RIDGE)
frame.grid(row=0, column=0, sticky="ew")
frame.columnconfigure(0, weight=1)
self.progress_bar = ttk.Progressbar(
frame, orient="horizontal", mode="indeterminate"
)
self.progress_bar.grid(sticky="ew")
self.status = ttk.Label(
self,
@ -49,7 +43,7 @@ class StatusBar(ttk.Frame):
borderwidth=1,
relief=tk.RIDGE,
)
self.status.grid(row=0, column=1, sticky="ew")
self.status.grid(row=0, column=0, sticky="ew")
self.zoom = ttk.Label(
self,
@ -58,17 +52,17 @@ class StatusBar(ttk.Frame):
borderwidth=1,
relief=tk.RIDGE,
)
self.zoom.grid(row=0, column=2, sticky="ew")
self.zoom.grid(row=0, column=1, sticky="ew")
self.cpu_usage = ttk.Label(
self, text="CPU TBD", anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE
)
self.cpu_usage.grid(row=0, column=3, sticky="ew")
self.cpu_usage.grid(row=0, column=2, sticky="ew")
self.alerts_button = ttk.Button(
self, text="Alerts", command=self.click_alerts, style=Styles.green_alert
)
self.alerts_button.grid(row=0, column=4, sticky="ew")
self.alerts_button.grid(row=0, column=3, sticky="ew")
def click_alerts(self):
dialog = AlertsDialog(self.app, self.app)

View file

@ -1,46 +1,39 @@
import logging
import threading
from typing import Any, Callable
from typing import Any, Callable, Tuple
from core.gui.errors import show_grpc_response_exceptions
from core.gui.errors import show_exception
class BackgroundTask:
def __init__(self, master: Any, task: Callable, callback: Callable = None, args=()):
self.master = master
self.args = args
class ProgressTask:
def __init__(
self, task: Callable, callback: Callable = None, args: Tuple[Any] = None
):
self.app = None
self.task = task
self.callback = callback
self.thread = None
self.args = args
if self.args is None:
self.args = ()
def start(self):
logging.info("starting task")
self.thread = threading.Thread(target=self.run, daemon=True)
self.thread.start()
def start(self) -> None:
thread = threading.Thread(target=self.run, daemon=True)
thread.start()
def run(self):
result = self.task(*self.args)
logging.info("task completed")
# if start session fails, a response with Result: False and a list of
# exceptions is returned
if not getattr(result, "result", True):
if len(getattr(result, "exceptions", [])) > 0:
self.master.after(
0,
show_grpc_response_exceptions,
*(
result.__class__.__name__,
result.exceptions,
self.master,
self.master,
)
)
if self.callback:
if result is None:
args = ()
elif isinstance(result, (list, tuple)):
args = result
else:
args = (result,)
logging.info("calling callback: %s", args)
self.master.after(0, self.callback, *args)
def run(self) -> None:
logging.info("running task")
try:
values = self.task(*self.args)
if values is None:
values = ()
elif values and not isinstance(values, tuple):
values = (values,)
if self.callback:
logging.info("calling callback")
self.app.after(0, self.callback, *values)
except Exception as e:
logging.exception("progress task exception")
args = (self.app, "Task Error", e)
self.app.after(0, show_exception, *args)
finally:
self.app.after(0, self.app.progress_task_complete)

View file

@ -1,5 +1,4 @@
import logging
import time
import tkinter as tk
from enum import Enum
from functools import partial
@ -10,11 +9,12 @@ from core.api.grpc import core_pb2
from core.gui.dialogs.customnodes import CustomNodesDialog
from core.gui.dialogs.marker import MarkerDialog
from core.gui.dialogs.runtool import RunToolDialog
from core.gui.errors import show_error
from core.gui.graph.enums import GraphMode
from core.gui.graph.shapeutils import ShapeType, is_marker
from core.gui.images import ImageEnum, Images
from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.task import BackgroundTask
from core.gui.task import ProgressTask
from core.gui.themes import Styles
from core.gui.tooltip import Tooltip
@ -47,7 +47,6 @@ class Toolbar(ttk.Frame):
"""
super().__init__(master, **kwargs)
self.app = app
self.time = None
# design buttons
self.play_button = None
@ -263,22 +262,18 @@ class Toolbar(ttk.Frame):
server.
"""
self.app.menubar.change_menubar_item_state(is_runtime=True)
self.app.statusbar.progress_bar.start(5)
self.app.canvas.mode = GraphMode.SELECT
self.time = time.perf_counter()
task = BackgroundTask(self, self.app.core.start_session, self.start_callback)
task.start()
task = ProgressTask(self.app.core.start_session, self.start_callback)
self.app.progress_task(task)
def start_callback(self, response: core_pb2.StartSessionResponse):
self.app.statusbar.progress_bar.stop()
total = time.perf_counter() - self.time
message = f"Start ran for {total:.3f} seconds"
self.app.statusbar.set_status(message)
self.time = None
if response.result:
self.set_runtime()
self.app.core.set_metadata()
self.app.core.show_mobility_players()
else:
message = "\n".join(response.exceptions)
show_error(self.app, "Start Session Error", message)
def set_runtime(self):
self.runtime_frame.tkraise()
@ -450,19 +445,13 @@ class Toolbar(ttk.Frame):
"""
redraw buttons on the toolbar, send node and link messages to grpc server
"""
logging.info("Click stop button")
logging.info("clicked stop button")
self.app.menubar.change_menubar_item_state(is_runtime=False)
self.app.statusbar.progress_bar.start(5)
self.time = time.perf_counter()
task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback)
task.start()
task = ProgressTask(self.app.core.stop_session, self.stop_callback)
self.app.progress_task(task)
def stop_callback(self, response: core_pb2.StopSessionResponse):
self.app.statusbar.progress_bar.stop()
self.set_design()
total = time.perf_counter() - self.time
message = f"Stopped in {total:.3f} seconds"
self.app.statusbar.set_status(message)
self.app.canvas.stopped_session()
def update_annotation(