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:
parent
835675480b
commit
0999fabb14
8 changed files with 82 additions and 87 deletions
|
@ -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(
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue