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:
Blake Harnden 2019-12-30 16:34:44 -08:00
parent dd43fae62a
commit 3e87737ee6
7 changed files with 127 additions and 110 deletions

View file

@ -93,5 +93,12 @@ class Application(tk.Frame):
def save_config(self): def save_config(self):
appconfig.save(self.guiconfig) 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): def close(self):
self.master.destroy() self.master.destroy()

View file

@ -4,9 +4,7 @@ Incorporate grpc into python tkinter GUI
import json import json
import logging import logging
import os import os
import time
from pathlib import Path from pathlib import Path
from tkinter import messagebox
import grpc import grpc
@ -294,16 +292,10 @@ class CoreClient:
response = self.client.get_session_metadata(self.session_id) response = self.client.get_session_metadata(self.session_id)
self.parse_metadata(response.config) self.parse_metadata(response.config)
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e) self.app.after(0, show_grpc_error, e)
# update ui to represent current state # update ui to represent current state
if self.is_runtime(): self.app.after(0, self.app.joined_session_update)
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()
def is_runtime(self): def is_runtime(self):
return self.state == core_pb2.SessionState.RUNTIME return self.state == core_pb2.SessionState.RUNTIME
@ -390,7 +382,7 @@ class CoreClient:
) )
self.join_session(response.session_id, query_location=False) self.join_session(response.session_id, query_location=False)
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e) self.app.after(0, show_grpc_error, e)
def delete_session(self, session_id=None): def delete_session(self, session_id=None):
if session_id is None: if session_id is None:
@ -399,7 +391,7 @@ class CoreClient:
response = self.client.delete_session(session_id) response = self.client.delete_session(session_id)
logging.info("deleted session result: %s", response) logging.info("deleted session result: %s", response)
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e) self.app.after(0, show_grpc_error, e)
def set_up(self): def set_up(self):
""" """
@ -431,7 +423,7 @@ class CoreClient:
x.node_type: set(x.services) for x in response.defaults x.node_type: set(x.services) for x in response.defaults
} }
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e) self.app.after(0, show_grpc_error, e)
self.app.close() self.app.close()
def edit_node(self, core_node): def edit_node(self, core_node):
@ -440,7 +432,7 @@ class CoreClient:
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
) )
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e) self.app.after(0, show_grpc_error, e)
def start_session(self): def start_session(self):
nodes = [x.core_node for x in self.canvas_nodes.values()] nodes = [x.core_node for x in self.canvas_nodes.values()]
@ -459,7 +451,7 @@ class CoreClient:
else: else:
emane_config = None emane_config = None
start = time.perf_counter() response = core_pb2.StartSessionResponse(result=False)
try: try:
response = self.client.start_session( response = self.client.start_session(
self.session_id, self.session_id,
@ -478,45 +470,30 @@ class CoreClient:
logging.debug( logging.debug(
"start session(%s), result: %s", self.session_id, response.result "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: if response.result:
self.set_metadata() 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: 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): def stop_session(self, session_id=None):
if not session_id: if not session_id:
session_id = self.session_id session_id = self.session_id
start = time.perf_counter() response = core_pb2.StopSessionResponse(result=False)
try: try:
response = self.client.stop_session(session_id) response = self.client.stop_session(session_id)
self.app.canvas.stopped_session() logging.debug("stopped session(%s), result: %s", session_id, response)
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)
except grpc.RpcError as e: 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): def set_metadata(self):
# create canvas data # create canvas data
@ -549,7 +526,7 @@ class CoreClient:
logging.info("get terminal %s", response.terminal) logging.info("get terminal %s", response.terminal)
os.system(f"{terminal} {response.terminal} &") os.system(f"{terminal} {response.terminal} &")
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e) self.app.after(0, show_grpc_error, e)
def save_xml(self, file_path): def save_xml(self, file_path):
""" """
@ -567,7 +544,7 @@ class CoreClient:
response = self.client.save_xml(self.session_id, file_path) response = self.client.save_xml(self.session_id, file_path)
logging.info("saved xml(%s): %s", file_path, response) logging.info("saved xml(%s): %s", file_path, response)
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e) self.app.after(0, show_grpc_error, e)
def open_xml(self, file_path): def open_xml(self, file_path):
""" """
@ -581,7 +558,7 @@ class CoreClient:
logging.debug("open xml: %s", response) logging.debug("open xml: %s", response)
self.join_session(response.session_id) self.join_session(response.session_id)
except grpc.RpcError as e: 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): def get_node_service(self, node_id, service_name):
response = self.client.get_node_service(self.session_id, node_id, service_name) response = self.client.get_node_service(self.session_id, node_id, service_name)

View file

@ -2,7 +2,6 @@
marker dialog marker dialog
""" """
import logging
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
@ -20,7 +19,6 @@ class MarkerDialog(Dialog):
self.radius = MARKER_THICKNESS[0] self.radius = MARKER_THICKNESS[0]
self.marker_thickness = tk.IntVar(value=MARKER_THICKNESS[0]) self.marker_thickness = tk.IntVar(value=MARKER_THICKNESS[0])
self.draw() self.draw()
self.top.bind("<Destroy>", self.close_marker)
def draw(self): def draw(self):
button = ttk.Button(self.top, text="clear", command=self.clear_marker) button = ttk.Button(self.top, text="clear", command=self.clear_marker)
@ -64,16 +62,8 @@ class MarkerDialog(Dialog):
def change_thickness(self, event): def change_thickness(self, event):
self.radius = self.marker_thickness.get() self.radius = self.marker_thickness.get()
def close_marker(self, event): def show(self):
logging.debug("destroy marker dialog") super().show()
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())
self.geometry( self.geometry(
"+{}+{}".format( f"+{self.app.canvas.winfo_rootx()}+{self.app.canvas.master.winfo_rooty()}"
self.app.canvas.winfo_rootx(), self.app.canvas.master.winfo_rooty()
)
) )

View file

@ -1,5 +1,4 @@
import logging import logging
import threading
import tkinter as tk import tkinter as tk
from tkinter import ttk 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.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.themes import PADX, PADY from core.gui.themes import PADX, PADY
@ -164,10 +164,8 @@ class SessionsDialog(Dialog):
def join_session(self, session_id): def join_session(self, session_id):
self.app.statusbar.progress_bar.start(5) self.app.statusbar.progress_bar.start(5)
thread = threading.Thread( task = BackgroundTask(self.app, self.app.core.join_session, args=(session_id,))
target=self.app.core.join_session, args=([session_id]) task.start()
)
thread.start()
self.destroy() self.destroy()
def on_selected(self, event): def on_selected(self, event):

View file

@ -3,13 +3,9 @@ The actions taken when each menubar option is clicked
""" """
import logging import logging
import threading
import time
import webbrowser import webbrowser
from tkinter import filedialog, messagebox from tkinter import filedialog, messagebox
import grpc
from core.gui.appconfig import XMLS_PATH from core.gui.appconfig import XMLS_PATH
from core.gui.dialogs.about import AboutDialog from core.gui.dialogs.about import AboutDialog
from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog 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.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.task import BackgroundTask
class MenuAction: class MenuAction:
@ -35,13 +32,10 @@ class MenuAction:
def cleanup_old_session(self, quitapp=False): def cleanup_old_session(self, quitapp=False):
logging.info("cleaning up old session") logging.info("cleaning up old session")
start = time.perf_counter()
self.app.core.stop_session() self.app.core.stop_session()
self.app.core.delete_session() self.app.core.delete_session()
process_time = time.perf_counter() - start # if quitapp:
self.app.statusbar.stop_session_callback(process_time) # self.app.quit()
if quitapp:
self.app.quit()
def prompt_save_running_session(self, quitapp=False): def prompt_save_running_session(self, quitapp=False):
""" """
@ -49,26 +43,18 @@ class MenuAction:
:return: nothing :return: nothing
""" """
try: result = True
if not self.app.core.is_runtime(): if self.app.core.is_runtime():
self.app.core.delete_session() result = messagebox.askyesnocancel("Exit", "Stop the running session?")
if quitapp:
self.app.quit() if result:
else: callback = None
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")
if quitapp: 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): def on_quit(self, event=None):
""" """
@ -100,8 +86,8 @@ class MenuAction:
logging.info("opening xml: %s", file_path) logging.info("opening xml: %s", file_path)
self.prompt_save_running_session() self.prompt_save_running_session()
self.app.statusbar.progress_bar.start(5) self.app.statusbar.progress_bar.start(5)
thread = threading.Thread(target=self.app.core.open_xml, args=([file_path])) task = BackgroundTask(self.app, self.app.core.open_xml, args=(file_path,))
thread.start() task.start()
def gui_preferences(self): def gui_preferences(self):
dialog = PreferencesDialog(self.app, self.app) dialog = PreferencesDialog(self.app, self.app)

29
daemon/core/gui/task.py Normal file
View 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)

View file

@ -1,8 +1,8 @@
import logging import logging
import threading import time
import tkinter as tk import tkinter as tk
from functools import partial from functools import partial
from tkinter import ttk from tkinter import messagebox, ttk
from tkinter.font import Font from tkinter.font import Font
from core.gui.dialogs.customnodes import CustomNodesDialog 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.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 NodeUtils from core.gui.nodeutils import NodeUtils
from core.gui.task import BackgroundTask
from core.gui.themes import Styles from core.gui.themes import Styles
from core.gui.tooltip import Tooltip from core.gui.tooltip import Tooltip
@ -36,6 +37,7 @@ class Toolbar(ttk.Frame):
super().__init__(master, **kwargs) super().__init__(master, **kwargs)
self.app = app self.app = app
self.master = app.master self.master = app.master
self.time = None
# picker data # picker data
self.picker_font = Font(size=8) self.picker_font = Font(size=8)
@ -237,13 +239,32 @@ class Toolbar(ttk.Frame):
self.app.canvas.hide_context() self.app.canvas.hide_context()
self.app.statusbar.progress_bar.start(5) self.app.statusbar.progress_bar.start(5)
self.app.canvas.mode = GraphMode.SELECT self.app.canvas.mode = GraphMode.SELECT
thread = threading.Thread(target=self.app.core.start_session) self.time = time.perf_counter()
thread.start() 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): def set_runtime(self):
self.runtime_frame.tkraise() self.runtime_frame.tkraise()
self.click_runtime_selection() self.click_runtime_selection()
def set_design(self):
self.design_frame.tkraise()
self.click_selection()
def click_link(self): def click_link(self):
logging.debug("Click LINK button") logging.debug("Click LINK button")
self.design_select(self.link_button) self.design_select(self.link_button)
@ -401,10 +422,19 @@ class Toolbar(ttk.Frame):
""" """
self.app.canvas.hide_context() self.app.canvas.hide_context()
self.app.statusbar.progress_bar.start(5) self.app.statusbar.progress_bar.start(5)
thread = threading.Thread(target=self.app.core.stop_session) self.time = time.perf_counter()
thread.start() task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback)
self.design_frame.tkraise() task.start()
self.click_selection()
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): def update_annotation(self, image, shape_type):
logging.info("clicked annotation: ") logging.info("clicked annotation: ")
@ -414,10 +444,10 @@ class Toolbar(ttk.Frame):
self.app.canvas.mode = GraphMode.ANNOTATION self.app.canvas.mode = GraphMode.ANNOTATION
self.app.canvas.annotation_type = shape_type self.app.canvas.annotation_type = shape_type
if is_marker(shape_type): if is_marker(shape_type):
if not self.marker_tool: if self.marker_tool:
self.marker_tool = MarkerDialog(self.master, self.app) self.marker_tool.destroy()
self.marker_tool.show() self.marker_tool = MarkerDialog(self.master, self.app)
self.marker_tool.position() self.marker_tool.show()
def click_run_button(self): def click_run_button(self):
logging.debug("Click on RUN button") logging.debug("Click on RUN button")
@ -430,10 +460,10 @@ class Toolbar(ttk.Frame):
self.runtime_select(self.runtime_marker_button) self.runtime_select(self.runtime_marker_button)
self.app.canvas.mode = GraphMode.ANNOTATION self.app.canvas.mode = GraphMode.ANNOTATION
self.app.canvas.annotation_type = ShapeType.MARKER self.app.canvas.annotation_type = ShapeType.MARKER
if not self.marker_tool: if self.marker_tool:
self.marker_tool = MarkerDialog(self.master, self.app) self.marker_tool.destroy()
self.marker_tool.show() self.marker_tool = MarkerDialog(self.master, self.app)
self.marker_tool.position() self.marker_tool.show()
def click_two_node_button(self): def click_two_node_button(self):
logging.debug("Click TWONODE button") logging.debug("Click TWONODE button")