updates to use tk after for backgrounded tasks, also added background task convenience class for running something in the background and running a callback using tk.after when done
This commit is contained in:
parent
dd43fae62a
commit
3e87737ee6
7 changed files with 127 additions and 110 deletions
|
@ -93,5 +93,12 @@ class Application(tk.Frame):
|
|||
def save_config(self):
|
||||
appconfig.save(self.guiconfig)
|
||||
|
||||
def joined_session_update(self):
|
||||
self.statusbar.progress_bar.stop()
|
||||
if self.core.is_runtime():
|
||||
self.toolbar.set_runtime()
|
||||
else:
|
||||
self.toolbar.set_design()
|
||||
|
||||
def close(self):
|
||||
self.master.destroy()
|
||||
|
|
|
@ -4,9 +4,7 @@ Incorporate grpc into python tkinter GUI
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from tkinter import messagebox
|
||||
|
||||
import grpc
|
||||
|
||||
|
@ -294,16 +292,10 @@ class CoreClient:
|
|||
response = self.client.get_session_metadata(self.session_id)
|
||||
self.parse_metadata(response.config)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
|
||||
# update ui to represent current state
|
||||
if self.is_runtime():
|
||||
self.app.toolbar.runtime_frame.tkraise()
|
||||
self.app.toolbar.click_runtime_selection()
|
||||
else:
|
||||
self.app.toolbar.design_frame.tkraise()
|
||||
self.app.toolbar.click_selection()
|
||||
self.app.statusbar.progress_bar.stop()
|
||||
self.app.after(0, self.app.joined_session_update)
|
||||
|
||||
def is_runtime(self):
|
||||
return self.state == core_pb2.SessionState.RUNTIME
|
||||
|
@ -390,7 +382,7 @@ class CoreClient:
|
|||
)
|
||||
self.join_session(response.session_id, query_location=False)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
|
||||
def delete_session(self, session_id=None):
|
||||
if session_id is None:
|
||||
|
@ -399,7 +391,7 @@ class CoreClient:
|
|||
response = self.client.delete_session(session_id)
|
||||
logging.info("deleted session result: %s", response)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
|
||||
def set_up(self):
|
||||
"""
|
||||
|
@ -431,7 +423,7 @@ class CoreClient:
|
|||
x.node_type: set(x.services) for x in response.defaults
|
||||
}
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
self.app.close()
|
||||
|
||||
def edit_node(self, core_node):
|
||||
|
@ -440,7 +432,7 @@ class CoreClient:
|
|||
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
|
||||
)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
|
||||
def start_session(self):
|
||||
nodes = [x.core_node for x in self.canvas_nodes.values()]
|
||||
|
@ -459,7 +451,7 @@ class CoreClient:
|
|||
else:
|
||||
emane_config = None
|
||||
|
||||
start = time.perf_counter()
|
||||
response = core_pb2.StartSessionResponse(result=False)
|
||||
try:
|
||||
response = self.client.start_session(
|
||||
self.session_id,
|
||||
|
@ -478,45 +470,30 @@ class CoreClient:
|
|||
logging.debug(
|
||||
"start session(%s), result: %s", self.session_id, response.result
|
||||
)
|
||||
process_time = time.perf_counter() - start
|
||||
|
||||
# stop progress bar and update status
|
||||
self.app.statusbar.progress_bar.stop()
|
||||
message = f"Start ran for {process_time:.3f} seconds"
|
||||
self.app.statusbar.set_status(message)
|
||||
|
||||
if response.result:
|
||||
self.set_metadata()
|
||||
self.app.toolbar.set_runtime()
|
||||
|
||||
# display mobility players
|
||||
for node_id, config in self.mobility_configs.items():
|
||||
canvas_node = self.canvas_nodes[node_id]
|
||||
mobility_player = MobilityPlayer(
|
||||
self.app, self.app, canvas_node, config
|
||||
)
|
||||
mobility_player.show()
|
||||
self.mobility_players[node_id] = mobility_player
|
||||
else:
|
||||
message = "\n".join(response.exceptions)
|
||||
messagebox.showerror("Start Error", message)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
return response
|
||||
|
||||
def stop_session(self, session_id=None):
|
||||
if not session_id:
|
||||
session_id = self.session_id
|
||||
start = time.perf_counter()
|
||||
response = core_pb2.StopSessionResponse(result=False)
|
||||
try:
|
||||
response = self.client.stop_session(session_id)
|
||||
self.app.canvas.stopped_session()
|
||||
logging.debug(
|
||||
"stopped session(%s), result: %s", session_id, response.result
|
||||
)
|
||||
process_time = time.perf_counter() - start
|
||||
self.app.statusbar.stop_session_callback(process_time)
|
||||
logging.debug("stopped session(%s), result: %s", session_id, response)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
return response
|
||||
|
||||
def show_mobility_players(self):
|
||||
for node_id, config in self.mobility_configs.items():
|
||||
canvas_node = self.canvas_nodes[node_id]
|
||||
mobility_player = MobilityPlayer(self.app, self.app, canvas_node, config)
|
||||
mobility_player.show()
|
||||
self.mobility_players[node_id] = mobility_player
|
||||
|
||||
def set_metadata(self):
|
||||
# create canvas data
|
||||
|
@ -549,7 +526,7 @@ class CoreClient:
|
|||
logging.info("get terminal %s", response.terminal)
|
||||
os.system(f"{terminal} {response.terminal} &")
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
|
||||
def save_xml(self, file_path):
|
||||
"""
|
||||
|
@ -567,7 +544,7 @@ class CoreClient:
|
|||
response = self.client.save_xml(self.session_id, file_path)
|
||||
logging.info("saved xml(%s): %s", file_path, response)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
|
||||
def open_xml(self, file_path):
|
||||
"""
|
||||
|
@ -581,7 +558,7 @@ class CoreClient:
|
|||
logging.debug("open xml: %s", response)
|
||||
self.join_session(response.session_id)
|
||||
except grpc.RpcError as e:
|
||||
show_grpc_error(e)
|
||||
self.app.after(0, show_grpc_error, e)
|
||||
|
||||
def get_node_service(self, node_id, service_name):
|
||||
response = self.client.get_node_service(self.session_id, node_id, service_name)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
marker dialog
|
||||
"""
|
||||
|
||||
import logging
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
|
@ -20,7 +19,6 @@ class MarkerDialog(Dialog):
|
|||
self.radius = MARKER_THICKNESS[0]
|
||||
self.marker_thickness = tk.IntVar(value=MARKER_THICKNESS[0])
|
||||
self.draw()
|
||||
self.top.bind("<Destroy>", self.close_marker)
|
||||
|
||||
def draw(self):
|
||||
button = ttk.Button(self.top, text="clear", command=self.clear_marker)
|
||||
|
@ -64,16 +62,8 @@ class MarkerDialog(Dialog):
|
|||
def change_thickness(self, event):
|
||||
self.radius = self.marker_thickness.get()
|
||||
|
||||
def close_marker(self, event):
|
||||
logging.debug("destroy marker dialog")
|
||||
self.app.toolbar.marker_tool = None
|
||||
|
||||
def position(self):
|
||||
print(self.winfo_width(), self.winfo_height())
|
||||
# print(self.app.master.winfo_x(), self.app.master.winfo_y())
|
||||
print(self.app.canvas.winfo_rootx())
|
||||
def show(self):
|
||||
super().show()
|
||||
self.geometry(
|
||||
"+{}+{}".format(
|
||||
self.app.canvas.winfo_rootx(), self.app.canvas.master.winfo_rooty()
|
||||
)
|
||||
f"+{self.app.canvas.winfo_rootx()}+{self.app.canvas.master.winfo_rooty()}"
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import logging
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
|
@ -9,6 +8,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.themes import PADX, PADY
|
||||
|
||||
|
||||
|
@ -164,10 +164,8 @@ class SessionsDialog(Dialog):
|
|||
|
||||
def join_session(self, session_id):
|
||||
self.app.statusbar.progress_bar.start(5)
|
||||
thread = threading.Thread(
|
||||
target=self.app.core.join_session, args=([session_id])
|
||||
)
|
||||
thread.start()
|
||||
task = BackgroundTask(self.app, self.app.core.join_session, args=(session_id,))
|
||||
task.start()
|
||||
self.destroy()
|
||||
|
||||
def on_selected(self, event):
|
||||
|
|
|
@ -3,13 +3,9 @@ The actions taken when each menubar option is clicked
|
|||
"""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
from tkinter import filedialog, messagebox
|
||||
|
||||
import grpc
|
||||
|
||||
from core.gui.appconfig import XMLS_PATH
|
||||
from core.gui.dialogs.about import AboutDialog
|
||||
from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog
|
||||
|
@ -21,6 +17,7 @@ from core.gui.dialogs.servers import ServersDialog
|
|||
from core.gui.dialogs.sessionoptions import SessionOptionsDialog
|
||||
from core.gui.dialogs.sessions import SessionsDialog
|
||||
from core.gui.dialogs.throughput import ThroughputDialog
|
||||
from core.gui.task import BackgroundTask
|
||||
|
||||
|
||||
class MenuAction:
|
||||
|
@ -35,13 +32,10 @@ class MenuAction:
|
|||
|
||||
def cleanup_old_session(self, quitapp=False):
|
||||
logging.info("cleaning up old session")
|
||||
start = time.perf_counter()
|
||||
self.app.core.stop_session()
|
||||
self.app.core.delete_session()
|
||||
process_time = time.perf_counter() - start
|
||||
self.app.statusbar.stop_session_callback(process_time)
|
||||
if quitapp:
|
||||
self.app.quit()
|
||||
# if quitapp:
|
||||
# self.app.quit()
|
||||
|
||||
def prompt_save_running_session(self, quitapp=False):
|
||||
"""
|
||||
|
@ -49,26 +43,18 @@ class MenuAction:
|
|||
|
||||
:return: nothing
|
||||
"""
|
||||
try:
|
||||
if not self.app.core.is_runtime():
|
||||
self.app.core.delete_session()
|
||||
if quitapp:
|
||||
self.app.quit()
|
||||
else:
|
||||
result = messagebox.askyesnocancel("Exit", "Stop the running session?")
|
||||
if result is True:
|
||||
self.app.statusbar.progress_bar.start(5)
|
||||
thread = threading.Thread(
|
||||
target=self.cleanup_old_session, args=([quitapp])
|
||||
)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
elif result is False and quitapp:
|
||||
self.app.quit()
|
||||
except grpc.RpcError:
|
||||
logging.exception("error deleting session")
|
||||
result = True
|
||||
if self.app.core.is_runtime():
|
||||
result = messagebox.askyesnocancel("Exit", "Stop the running session?")
|
||||
|
||||
if result:
|
||||
callback = None
|
||||
if quitapp:
|
||||
self.app.quit()
|
||||
callback = self.app.quit
|
||||
task = BackgroundTask(self.app, self.cleanup_old_session, callback)
|
||||
task.start()
|
||||
elif quitapp:
|
||||
self.app.quit()
|
||||
|
||||
def on_quit(self, event=None):
|
||||
"""
|
||||
|
@ -100,8 +86,8 @@ class MenuAction:
|
|||
logging.info("opening xml: %s", file_path)
|
||||
self.prompt_save_running_session()
|
||||
self.app.statusbar.progress_bar.start(5)
|
||||
thread = threading.Thread(target=self.app.core.open_xml, args=([file_path]))
|
||||
thread.start()
|
||||
task = BackgroundTask(self.app, self.app.core.open_xml, args=(file_path,))
|
||||
task.start()
|
||||
|
||||
def gui_preferences(self):
|
||||
dialog = PreferencesDialog(self.app, self.app)
|
||||
|
|
29
daemon/core/gui/task.py
Normal file
29
daemon/core/gui/task.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
import logging
|
||||
import threading
|
||||
|
||||
|
||||
class BackgroundTask:
|
||||
def __init__(self, master, task, callback=None, args=()):
|
||||
self.master = master
|
||||
self.args = args
|
||||
self.task = task
|
||||
self.callback = callback
|
||||
self.thread = None
|
||||
|
||||
def start(self):
|
||||
logging.info("starting task")
|
||||
self.thread = threading.Thread(target=self.run, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def run(self):
|
||||
result = self.task(*self.args)
|
||||
logging.info("task completed")
|
||||
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)
|
|
@ -1,8 +1,8 @@
|
|||
import logging
|
||||
import threading
|
||||
import time
|
||||
import tkinter as tk
|
||||
from functools import partial
|
||||
from tkinter import ttk
|
||||
from tkinter import messagebox, ttk
|
||||
from tkinter.font import Font
|
||||
|
||||
from core.gui.dialogs.customnodes import CustomNodesDialog
|
||||
|
@ -11,6 +11,7 @@ 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 NodeUtils
|
||||
from core.gui.task import BackgroundTask
|
||||
from core.gui.themes import Styles
|
||||
from core.gui.tooltip import Tooltip
|
||||
|
||||
|
@ -36,6 +37,7 @@ class Toolbar(ttk.Frame):
|
|||
super().__init__(master, **kwargs)
|
||||
self.app = app
|
||||
self.master = app.master
|
||||
self.time = None
|
||||
|
||||
# picker data
|
||||
self.picker_font = Font(size=8)
|
||||
|
@ -237,13 +239,32 @@ class Toolbar(ttk.Frame):
|
|||
self.app.canvas.hide_context()
|
||||
self.app.statusbar.progress_bar.start(5)
|
||||
self.app.canvas.mode = GraphMode.SELECT
|
||||
thread = threading.Thread(target=self.app.core.start_session)
|
||||
thread.start()
|
||||
self.time = time.perf_counter()
|
||||
task = BackgroundTask(self, self.app.core.start_session, self.start_callback)
|
||||
task.start()
|
||||
|
||||
def start_callback(self, response):
|
||||
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)
|
||||
messagebox.showerror("Start Error", message)
|
||||
|
||||
def set_runtime(self):
|
||||
self.runtime_frame.tkraise()
|
||||
self.click_runtime_selection()
|
||||
|
||||
def set_design(self):
|
||||
self.design_frame.tkraise()
|
||||
self.click_selection()
|
||||
|
||||
def click_link(self):
|
||||
logging.debug("Click LINK button")
|
||||
self.design_select(self.link_button)
|
||||
|
@ -401,10 +422,19 @@ class Toolbar(ttk.Frame):
|
|||
"""
|
||||
self.app.canvas.hide_context()
|
||||
self.app.statusbar.progress_bar.start(5)
|
||||
thread = threading.Thread(target=self.app.core.stop_session)
|
||||
thread.start()
|
||||
self.design_frame.tkraise()
|
||||
self.click_selection()
|
||||
self.time = time.perf_counter()
|
||||
task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback)
|
||||
task.start()
|
||||
|
||||
def stop_callback(self, response):
|
||||
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()
|
||||
if not response.result:
|
||||
messagebox.showerror("Stop Error", "Errors stopping session")
|
||||
|
||||
def update_annotation(self, image, shape_type):
|
||||
logging.info("clicked annotation: ")
|
||||
|
@ -414,10 +444,10 @@ class Toolbar(ttk.Frame):
|
|||
self.app.canvas.mode = GraphMode.ANNOTATION
|
||||
self.app.canvas.annotation_type = shape_type
|
||||
if is_marker(shape_type):
|
||||
if not self.marker_tool:
|
||||
self.marker_tool = MarkerDialog(self.master, self.app)
|
||||
self.marker_tool.show()
|
||||
self.marker_tool.position()
|
||||
if self.marker_tool:
|
||||
self.marker_tool.destroy()
|
||||
self.marker_tool = MarkerDialog(self.master, self.app)
|
||||
self.marker_tool.show()
|
||||
|
||||
def click_run_button(self):
|
||||
logging.debug("Click on RUN button")
|
||||
|
@ -430,10 +460,10 @@ class Toolbar(ttk.Frame):
|
|||
self.runtime_select(self.runtime_marker_button)
|
||||
self.app.canvas.mode = GraphMode.ANNOTATION
|
||||
self.app.canvas.annotation_type = ShapeType.MARKER
|
||||
if not self.marker_tool:
|
||||
self.marker_tool = MarkerDialog(self.master, self.app)
|
||||
self.marker_tool.show()
|
||||
self.marker_tool.position()
|
||||
if self.marker_tool:
|
||||
self.marker_tool.destroy()
|
||||
self.marker_tool = MarkerDialog(self.master, self.app)
|
||||
self.marker_tool.show()
|
||||
|
||||
def click_two_node_button(self):
|
||||
logging.debug("Click TWONODE button")
|
||||
|
|
Loading…
Reference in a new issue