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:
|
for service_exception in boot_exception.args:
|
||||||
exceptions.append(str(service_exception))
|
exceptions.append(str(service_exception))
|
||||||
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
|
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
|
||||||
|
|
||||||
return core_pb2.StartSessionResponse(result=True)
|
return core_pb2.StartSessionResponse(result=True)
|
||||||
|
|
||||||
def StopSession(
|
def StopSession(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import math
|
import math
|
||||||
|
import time
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import font, ttk
|
from tkinter import font, ttk
|
||||||
|
from tkinter.ttk import Progressbar
|
||||||
|
|
||||||
from core.gui import appconfig, themes
|
from core.gui import appconfig, themes
|
||||||
from core.gui.coreclient import CoreClient
|
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.menubar import Menubar
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
from core.gui.statusbar import StatusBar
|
from core.gui.statusbar import StatusBar
|
||||||
|
from core.gui.task import ProgressTask
|
||||||
from core.gui.toolbar import Toolbar
|
from core.gui.toolbar import Toolbar
|
||||||
from core.gui.validation import InputValidation
|
from core.gui.validation import InputValidation
|
||||||
|
|
||||||
|
@ -29,6 +32,8 @@ class Application(ttk.Frame):
|
||||||
self.canvas = None
|
self.canvas = None
|
||||||
self.statusbar = None
|
self.statusbar = None
|
||||||
self.validation = None
|
self.validation = None
|
||||||
|
self.progress = None
|
||||||
|
self.time = None
|
||||||
|
|
||||||
# fonts
|
# fonts
|
||||||
self.fonts_size = None
|
self.fonts_size = None
|
||||||
|
@ -93,6 +98,7 @@ class Application(ttk.Frame):
|
||||||
self.right_frame.grid(row=0, column=1, sticky="nsew")
|
self.right_frame.grid(row=0, column=1, sticky="nsew")
|
||||||
self.draw_canvas()
|
self.draw_canvas()
|
||||||
self.draw_status()
|
self.draw_status()
|
||||||
|
self.progress = Progressbar(self.right_frame, mode="indeterminate")
|
||||||
self.menubar = Menubar(self.master, self)
|
self.menubar = Menubar(self.master, self)
|
||||||
|
|
||||||
def draw_canvas(self):
|
def draw_canvas(self):
|
||||||
|
@ -117,6 +123,21 @@ class Application(ttk.Frame):
|
||||||
self.statusbar = StatusBar(self.right_frame, self)
|
self.statusbar = StatusBar(self.right_frame, self)
|
||||||
self.statusbar.grid(sticky="ew")
|
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):
|
def on_closing(self):
|
||||||
self.menubar.prompt_save_running_session(True)
|
self.menubar.prompt_save_running_session(True)
|
||||||
|
|
||||||
|
@ -124,7 +145,6 @@ class Application(ttk.Frame):
|
||||||
appconfig.save(self.guiconfig)
|
appconfig.save(self.guiconfig)
|
||||||
|
|
||||||
def joined_session_update(self):
|
def joined_session_update(self):
|
||||||
self.statusbar.progress_bar.stop()
|
|
||||||
if self.core.is_runtime():
|
if self.core.is_runtime():
|
||||||
self.toolbar.set_runtime()
|
self.toolbar.set_runtime()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -9,7 +9,7 @@ from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.errors import show_grpc_error
|
from core.gui.errors import show_grpc_error
|
||||||
from core.gui.images import ImageEnum, Images
|
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
|
from core.gui.themes import PADX, PADY
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -183,12 +183,11 @@ class SessionsDialog(Dialog):
|
||||||
self.join_session(self.selected_session)
|
self.join_session(self.selected_session)
|
||||||
|
|
||||||
def join_session(self, session_id: int) -> None:
|
def join_session(self, session_id: int) -> None:
|
||||||
|
self.destroy()
|
||||||
if self.app.core.xml_file:
|
if self.app.core.xml_file:
|
||||||
self.app.core.xml_file = None
|
self.app.core.xml_file = None
|
||||||
self.app.statusbar.progress_bar.start(5)
|
task = ProgressTask(self.app.core.join_session, args=(session_id,))
|
||||||
task = BackgroundTask(self.app, self.app.core.join_session, args=(session_id,))
|
self.app.progress_task(task)
|
||||||
task.start()
|
|
||||||
self.destroy()
|
|
||||||
|
|
||||||
def double_click_join(self, _event: tk.Event) -> None:
|
def double_click_join(self, _event: tk.Event) -> None:
|
||||||
item = self.tree.selection()
|
item = self.tree.selection()
|
||||||
|
@ -201,7 +200,6 @@ class SessionsDialog(Dialog):
|
||||||
if not self.selected_session:
|
if not self.selected_session:
|
||||||
return
|
return
|
||||||
logging.debug("delete session: %s", self.selected_session)
|
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.tree.delete(self.selected_id)
|
||||||
self.app.core.delete_session(self.selected_session)
|
self.app.core.delete_session(self.selected_session)
|
||||||
if self.selected_session == self.app.core.session_id:
|
if self.selected_session == self.app.core.session_id:
|
||||||
|
|
|
@ -43,7 +43,12 @@ class ErrorDialog(Dialog):
|
||||||
button.grid(sticky="ew")
|
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 = [x.capitalize() for x in e.code().name.lower().split("_")]
|
||||||
title = " ".join(title)
|
title = " ".join(title)
|
||||||
title = f"GRPC {title}"
|
title = f"GRPC {title}"
|
||||||
|
@ -51,8 +56,6 @@ def show_grpc_error(e: grpc.RpcError, master, app: "Application"):
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
|
|
||||||
def show_grpc_response_exceptions(class_name, exceptions, master, app: "Application"):
|
def show_error(app: "Application", title: str, message: str) -> None:
|
||||||
title = f"Exceptions from {class_name}"
|
dialog = ErrorDialog(app, app, title, message)
|
||||||
detail = "\n".join([str(x) for x in exceptions])
|
|
||||||
dialog = ErrorDialog(master, app, title, detail)
|
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
|
@ -22,7 +22,7 @@ from core.gui.dialogs.sessionoptions import SessionOptionsDialog
|
||||||
from core.gui.dialogs.sessions import SessionsDialog
|
from core.gui.dialogs.sessions import SessionsDialog
|
||||||
from core.gui.dialogs.throughput import ThroughputDialog
|
from core.gui.dialogs.throughput import ThroughputDialog
|
||||||
from core.gui.nodeutils import ICON_SIZE
|
from core.gui.nodeutils import ICON_SIZE
|
||||||
from core.gui.task import BackgroundTask
|
from core.gui.task import ProgressTask
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.gui.app import Application
|
from core.gui.app import Application
|
||||||
|
@ -340,9 +340,8 @@ class Menubar(tk.Menu):
|
||||||
self.core.xml_file = filename
|
self.core.xml_file = filename
|
||||||
self.core.xml_dir = str(os.path.dirname(filename))
|
self.core.xml_dir = str(os.path.dirname(filename))
|
||||||
self.prompt_save_running_session()
|
self.prompt_save_running_session()
|
||||||
self.app.statusbar.progress_bar.start(5)
|
task = ProgressTask(self.core.open_xml, args=(filename,))
|
||||||
task = BackgroundTask(self.app, self.core.open_xml, args=(filename,))
|
self.app.progress_task(task)
|
||||||
task.start()
|
|
||||||
|
|
||||||
def execute_python(self):
|
def execute_python(self):
|
||||||
dialog = ExecutePythonDialog(self.app, self.app)
|
dialog = ExecutePythonDialog(self.app, self.app)
|
||||||
|
|
|
@ -18,7 +18,6 @@ class StatusBar(ttk.Frame):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.status = None
|
self.status = None
|
||||||
self.statusvar = tk.StringVar()
|
self.statusvar = tk.StringVar()
|
||||||
self.progress_bar = None
|
|
||||||
self.zoom = None
|
self.zoom = None
|
||||||
self.cpu_usage = None
|
self.cpu_usage = None
|
||||||
self.memory = None
|
self.memory = None
|
||||||
|
@ -28,19 +27,14 @@ class StatusBar(ttk.Frame):
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
self.columnconfigure(0, weight=1)
|
self.columnconfigure(0, weight=7)
|
||||||
self.columnconfigure(1, weight=5)
|
self.columnconfigure(1, weight=1)
|
||||||
self.columnconfigure(2, weight=1)
|
self.columnconfigure(2, weight=1)
|
||||||
self.columnconfigure(3, weight=1)
|
self.columnconfigure(3, weight=1)
|
||||||
self.columnconfigure(4, weight=1)
|
|
||||||
|
|
||||||
frame = ttk.Frame(self, borderwidth=1, relief=tk.RIDGE)
|
frame = ttk.Frame(self, borderwidth=1, relief=tk.RIDGE)
|
||||||
frame.grid(row=0, column=0, sticky="ew")
|
frame.grid(row=0, column=0, sticky="ew")
|
||||||
frame.columnconfigure(0, weight=1)
|
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.status = ttk.Label(
|
||||||
self,
|
self,
|
||||||
|
@ -49,7 +43,7 @@ class StatusBar(ttk.Frame):
|
||||||
borderwidth=1,
|
borderwidth=1,
|
||||||
relief=tk.RIDGE,
|
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.zoom = ttk.Label(
|
||||||
self,
|
self,
|
||||||
|
@ -58,17 +52,17 @@ class StatusBar(ttk.Frame):
|
||||||
borderwidth=1,
|
borderwidth=1,
|
||||||
relief=tk.RIDGE,
|
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.cpu_usage = ttk.Label(
|
||||||
self, text="CPU TBD", anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE
|
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.alerts_button = ttk.Button(
|
||||||
self, text="Alerts", command=self.click_alerts, style=Styles.green_alert
|
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):
|
def click_alerts(self):
|
||||||
dialog = AlertsDialog(self.app, self.app)
|
dialog = AlertsDialog(self.app, self.app)
|
||||||
|
|
|
@ -1,46 +1,39 @@
|
||||||
import logging
|
import logging
|
||||||
import threading
|
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:
|
class ProgressTask:
|
||||||
def __init__(self, master: Any, task: Callable, callback: Callable = None, args=()):
|
def __init__(
|
||||||
self.master = master
|
self, task: Callable, callback: Callable = None, args: Tuple[Any] = None
|
||||||
self.args = args
|
):
|
||||||
|
self.app = None
|
||||||
self.task = task
|
self.task = task
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.thread = None
|
self.args = args
|
||||||
|
if self.args is None:
|
||||||
|
self.args = ()
|
||||||
|
|
||||||
def start(self):
|
def start(self) -> None:
|
||||||
logging.info("starting task")
|
thread = threading.Thread(target=self.run, daemon=True)
|
||||||
self.thread = threading.Thread(target=self.run, daemon=True)
|
thread.start()
|
||||||
self.thread.start()
|
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
result = self.task(*self.args)
|
logging.info("running task")
|
||||||
logging.info("task completed")
|
try:
|
||||||
# if start session fails, a response with Result: False and a list of
|
values = self.task(*self.args)
|
||||||
# exceptions is returned
|
if values is None:
|
||||||
if not getattr(result, "result", True):
|
values = ()
|
||||||
if len(getattr(result, "exceptions", [])) > 0:
|
elif values and not isinstance(values, tuple):
|
||||||
self.master.after(
|
values = (values,)
|
||||||
0,
|
|
||||||
show_grpc_response_exceptions,
|
|
||||||
*(
|
|
||||||
result.__class__.__name__,
|
|
||||||
result.exceptions,
|
|
||||||
self.master,
|
|
||||||
self.master,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if self.callback:
|
if self.callback:
|
||||||
if result is None:
|
logging.info("calling callback")
|
||||||
args = ()
|
self.app.after(0, self.callback, *values)
|
||||||
elif isinstance(result, (list, tuple)):
|
except Exception as e:
|
||||||
args = result
|
logging.exception("progress task exception")
|
||||||
else:
|
args = (self.app, "Task Error", e)
|
||||||
args = (result,)
|
self.app.after(0, show_exception, *args)
|
||||||
logging.info("calling callback: %s", args)
|
finally:
|
||||||
self.master.after(0, self.callback, *args)
|
self.app.after(0, self.app.progress_task_complete)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import partial
|
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.customnodes import CustomNodesDialog
|
||||||
from core.gui.dialogs.marker import MarkerDialog
|
from core.gui.dialogs.marker import MarkerDialog
|
||||||
from core.gui.dialogs.runtool import RunToolDialog
|
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.enums import GraphMode
|
||||||
from core.gui.graph.shapeutils import ShapeType, is_marker
|
from core.gui.graph.shapeutils import ShapeType, is_marker
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
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.themes import Styles
|
||||||
from core.gui.tooltip import Tooltip
|
from core.gui.tooltip import Tooltip
|
||||||
|
|
||||||
|
@ -47,7 +47,6 @@ class Toolbar(ttk.Frame):
|
||||||
"""
|
"""
|
||||||
super().__init__(master, **kwargs)
|
super().__init__(master, **kwargs)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.time = None
|
|
||||||
|
|
||||||
# design buttons
|
# design buttons
|
||||||
self.play_button = None
|
self.play_button = None
|
||||||
|
@ -263,22 +262,18 @@ class Toolbar(ttk.Frame):
|
||||||
server.
|
server.
|
||||||
"""
|
"""
|
||||||
self.app.menubar.change_menubar_item_state(is_runtime=True)
|
self.app.menubar.change_menubar_item_state(is_runtime=True)
|
||||||
self.app.statusbar.progress_bar.start(5)
|
|
||||||
self.app.canvas.mode = GraphMode.SELECT
|
self.app.canvas.mode = GraphMode.SELECT
|
||||||
self.time = time.perf_counter()
|
task = ProgressTask(self.app.core.start_session, self.start_callback)
|
||||||
task = BackgroundTask(self, self.app.core.start_session, self.start_callback)
|
self.app.progress_task(task)
|
||||||
task.start()
|
|
||||||
|
|
||||||
def start_callback(self, response: core_pb2.StartSessionResponse):
|
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:
|
if response.result:
|
||||||
self.set_runtime()
|
self.set_runtime()
|
||||||
self.app.core.set_metadata()
|
self.app.core.set_metadata()
|
||||||
self.app.core.show_mobility_players()
|
self.app.core.show_mobility_players()
|
||||||
|
else:
|
||||||
|
message = "\n".join(response.exceptions)
|
||||||
|
show_error(self.app, "Start Session Error", message)
|
||||||
|
|
||||||
def set_runtime(self):
|
def set_runtime(self):
|
||||||
self.runtime_frame.tkraise()
|
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
|
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.menubar.change_menubar_item_state(is_runtime=False)
|
||||||
self.app.statusbar.progress_bar.start(5)
|
task = ProgressTask(self.app.core.stop_session, self.stop_callback)
|
||||||
self.time = time.perf_counter()
|
self.app.progress_task(task)
|
||||||
task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback)
|
|
||||||
task.start()
|
|
||||||
|
|
||||||
def stop_callback(self, response: core_pb2.StopSessionResponse):
|
def stop_callback(self, response: core_pb2.StopSessionResponse):
|
||||||
self.app.statusbar.progress_bar.stop()
|
|
||||||
self.set_design()
|
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()
|
self.app.canvas.stopped_session()
|
||||||
|
|
||||||
def update_annotation(
|
def update_annotation(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue