Skip to content

Commit

Permalink
model running status indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
j-bryan committed Aug 2, 2024
1 parent 424661d commit 2f112a6
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 7 deletions.
2 changes: 2 additions & 0 deletions package/ui/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .file_selection import FileSelectionController
from .text_output import TextOutputController
from .model_status import ModelStatusController


class Controller:
Expand All @@ -12,6 +13,7 @@ def __init__(self, model, view):
# Initialize controllers
self.file_selection_controller = FileSelectionController(self.model, self.view.frames["file_selection"])
self.text_output_controller = TextOutputController(self.model, self.view.frames["text_output"])
self.model_status_controller = ModelStatusController(self.model, self.view.frames["text_output"].model_status)

# Bind the run button to the model
self.view.frames["run_abort"].run_button.config(command=self.run_model)
Expand Down
41 changes: 41 additions & 0 deletions package/ui/controllers/model_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import threading
import time
from ..models.main import ModelStatus


class ModelStatusController:
""" Tracks if the model is running and updates the view to reflect the status. """
def __init__(self, model, view):
"""
Constructor
@In, model, Model, the model
@In, view, View, the view
"""
self.model = model
self.view = view
self._model_has_run = False # Flag to indicate the model has already been run
self.check_model_status()

def check_model_status(self):
"""
Check the status of the model and update the view
@In, None
@Out, None
"""
def _check_status():
"""
Local helper function for checking the status of the model in a separate thread
@In, None
@Out, None
"""
current_status = self.model.status
while True:
if current_status != self.model.status:
current_status = self.model.status
self.view.set_status(self.model.status)
time.sleep(0.5)

thread = threading.Thread(target=_check_status)
thread.daemon = True
thread.name = "ModelStatusChecker"
thread.start()
10 changes: 6 additions & 4 deletions package/ui/controllers/text_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@ def __init__(self, model, view):

def toggle_show_text(self):
"""
Toggle the visibility of the output text widget
Toggle the visibility of the output text widget and resize the window to fit
@In, None
@Out, None
"""
if self.view.is_showing: # Hide output
self.view.text.grid_forget()
# self.view.text.grid_forget()
self.view.hide_text_output()
self.view.show_hide_button.config(text='Show Output')
else: # Show output
self.view.text.grid(row=1, column=0, sticky='nsew')
# self.view.text.grid(row=1, column=0, sticky='nsew')
self.view.show_text_output()
self.view.show_hide_button.config(text='Hide Output')
self.view.is_showing = not self.view.is_showing
# self.view.is_showing = not self.view.is_showing


class StdoutRedirector:
Expand Down
27 changes: 26 additions & 1 deletion package/ui/models/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import threading
from typing import Callable
import time
from enum import Enum


class ModelStatus(Enum):
""" Enum for model status """
NOT_STARTED = "Not yet run"
RUNNING = "Running"
FINISHED = "Finished"
ERROR = "Error"


class Model:
""" Runs a function in a separate thread """
Expand All @@ -15,15 +24,31 @@ def __init__(self, func: Callable, **kwargs):
self.func = func
self.thread = None
self.kwargs = kwargs
self.status = ModelStatus.NOT_STARTED

def start(self):
"""
Start the thread
@In, None
@Out, None
"""
self.thread = threading.Thread(target=self.func, kwargs=self.kwargs)
def func_wrapper():
"""
Wrapper for the function to run and set the status to FINISHED when done
@In, None
@Out, None
"""
self.status = ModelStatus.RUNNING
try:
self.func(**self.kwargs)
except:
self.status = ModelStatus.ERROR
else:
self.status = ModelStatus.FINISHED

self.thread = threading.Thread(target=func_wrapper)
self.thread.daemon = True
self.thread.name = self.get_package_name()
self.thread.start()

def get_package_name(self):
Expand Down
1 change: 0 additions & 1 deletion package/ui/views/file_selection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from typing import Optional, Callable
import os
import tkinter as tk


Expand Down
34 changes: 34 additions & 0 deletions package/ui/views/model_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import tkinter as tk
from ..models.main import ModelStatus as ModelStatusEnum


class ModelStatus(tk.Frame):
""" A widget for displaying the status of the model. """
def __init__(self, master: tk.Widget, **kwargs):
"""
Constructor
@In, master, tk.Widget, the parent widget
@In, kwargs, dict, keyword arguments
@Out, None
"""
super().__init__(master, **kwargs)
self.status = tk.StringVar()
self.status.set("Model not yet run")
self.status_label = tk.Label(self, textvariable=self.status, bg="white", anchor='w', padx=10, pady=3)
self.status_label.pack(side='left')
self.grid_columnconfigure(0, weight=1)

def set_status(self, new_status: ModelStatusEnum):
"""
Set the status label
@In, new_status, ModelStatusEnum, the new status
@Out, None
"""
self.status.set(new_status.value)
# Change the color of the label based on the status
if new_status == ModelStatusEnum.FINISHED:
self.status_label.config(fg='green')
elif new_status == ModelStatusEnum.ERROR:
self.status_label.config(fg='red')
else:
self.status_label.config(fg='black')
31 changes: 30 additions & 1 deletion package/ui/views/text_output.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from .model_status import ModelStatus


class TextOutput(tk.Frame):
Expand All @@ -14,9 +15,37 @@ def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.show_hide_button = tk.Button(self, text='Hide Ouptut', pady=5, width=15)
self.show_hide_button.grid(row=0, column=0, sticky='w')
self.model_status = ModelStatus(self)
self.model_status.grid(row=0, column=1, sticky='e')
self.text = ScrolledText(self, state=tk.DISABLED)
self.is_showing = True # To use with show/hide button
self.text.grid(row=1, column=0, sticky='nsew')
self.text.grid(row=1, column=0, sticky='nsew', columnspan=2)
self.grid_rowconfigure(0, minsize=50)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)

def show_text_output(self):
"""
Show the text output widget
@In, None
@Out, None
"""
self.text.grid(row=1, column=0, sticky='nsew', columnspan=2)
self.show_hide_button.config(text='Hide Output')
self.is_showing = True
# Set window to default size
self.master.update()
self.master.geometry("800x600")

def hide_text_output(self):
"""
Hide the text output widget
@In, None
@Out, None
"""
self.text.grid_forget()
self.show_hide_button.config(text='Show Output')
self.is_showing = False
# Reduce window size
self.master.update()
self.master.geometry("350x175")

0 comments on commit 2f112a6

Please sign in to comment.