diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4b6a79f..22004e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,6 +15,7 @@ jobs: - run: pip install ttkthemes - run: pip install pandas - run: pip install matplotlib + - run: pip install typing-extensions - run: python3 -m unittest tests/test_gui_window_basic.py -v test_documentation: diff --git a/tugui/gui_configuration.py b/tugui/gui_configuration.py index 6cbdb6a..8f9fcc2 100644 --- a/tugui/gui_configuration.py +++ b/tugui/gui_configuration.py @@ -6,6 +6,7 @@ from dataclasses import dataclass, field from typing import Dict, Tuple, List +from typing_extensions import Self from plot_settings import GroupType from support import IDGA @@ -22,7 +23,7 @@ class DiagramCharacteristics(): idga: str = '' @staticmethod - def init_tuplot_DiagramCharacteristics(number: str, idga: str): + def init_tuplot_DiagramCharacteristics(number: str, idga: str) -> Self: """ Method that, given the plot number and idga values, builds an instance of the 'DiagramCharacteristics' dataclass and evaluates the group value @@ -50,9 +51,9 @@ def init_tuplot_DiagramCharacteristics(number: str, idga: str): @dataclass class GuiPlotFieldsConfigurator(): """ - Dataclass providing a record storing all the needed information for filling up - the plot configuration fields into the GUI, as well as for enabling the plotting - functionalities. + Dataclass providing a record storing all the needed information for filling + up the plot configuration fields into the GUI, as well as for enabling the + plotting functionalities. To do so, some support dictionaries are stored as well. """ diagr_path: str = '' @@ -71,7 +72,7 @@ class GuiPlotFieldsConfigurator(): tustat_path: str = '' @staticmethod - def init_GuiPlotFieldsConfigurator_attrs(): + def init_GuiPlotFieldsConfigurator_attrs() -> Self: """ Method that builds and configure the attributes of an instance of the 'GuiPlotFieldsConfigurator' class. @@ -209,7 +210,7 @@ def init_GuiPlotFieldsConfigurator_attrs(): # Return the configured 'GuiPlotFieldsConfigurator' dataclass return gui_config - def __check_config_file_existence(self, path2check: str, filename: str): + def __check_config_file_existence(self, path2check: str, filename: str) -> None: """ Function that raises an exception if the given path does not correspond to an existing file. @@ -218,7 +219,7 @@ def __check_config_file_existence(self, path2check: str, filename: str): # Raise an exception raise FileNotFoundError("Error: missing \"" + filename + "\" configuration file") - def __check_exe_file_existence(self, path2check: str, filename: str): + def __check_exe_file_existence(self, path2check: str, filename: str) -> None: """ Function that raises an exception if the given path does not correspond to an existing executable file with right permission. @@ -230,7 +231,7 @@ def __check_exe_file_existence(self, path2check: str, filename: str): # Raise an exception raise PermissionError("Error: the \"" + filename + "\" does not have execution permission") - def __build_nVsKn(self, group_file: str, group_dict: dict, search_num: str): + def __build_nVsKn(self, group_file: str, group_dict: Dict[str, List[str]], search_num: str) -> None: """ Function that, given the 'Group' file to read (its path), it fills up the input dictionary with: . keys, being the line indicating the plot 'Number' @@ -262,7 +263,7 @@ def __build_nVsKn(self, group_file: str, group_dict: dict, search_num: str): if __name__ == "__main__": # Instantiate and configure the dataclass storing the GUI configuration - gui_config = GuiPlotFieldsConfigurator.init_GuiPlotFieldsConfigurator_attrs() + gui_config: GuiPlotFieldsConfigurator = GuiPlotFieldsConfigurator.init_GuiPlotFieldsConfigurator_attrs() # Print the built dictionaries print(gui_config.groupVSnumVsKn) diff --git a/tugui/gui_widgets.py b/tugui/gui_widgets.py index 2a48744..3ff31fd 100644 --- a/tugui/gui_widgets.py +++ b/tugui/gui_widgets.py @@ -3,6 +3,7 @@ from tkinter import ttk from tkinter import messagebox from PIL import Image, ImageTk +from typing import Callable, List, Union class EntryVariable: @@ -17,9 +18,9 @@ def __init__(self, frame: tk.Frame, width: int, col: int, row: int, end: str) -> configure the Entry object within the frame. """ # Instantiate the string variable - self.var = tk.StringVar() + self.var: tk.StringVar = tk.StringVar() # Instantiate the entry field - self.entry = ttk.Entry(frame, width = width, textvariable=self.var) + self.entry: ttk.Entry = ttk.Entry(frame, width = width, textvariable=self.var) # Place the entry in the frame grid self.entry.grid(column = col, row = row, sticky = 'ew') @@ -30,9 +31,9 @@ def __init__(self, frame: tk.Frame, width: int, col: int, row: int, end: str) -> self.entry.configure(validate='focusout', validatecommand=valid_entry) # Entry file extension - self.entry_extension = end + self.entry_extension: str = end - def validate(self, event=None, newval: str = ""): + def validate(self, event: Union[tk.Event, None] = None, newval: str = "") -> bool: """ Method that checks if the entry is valid. The "end" parameter indicates the extension to check against. @@ -49,7 +50,7 @@ def validate(self, event=None, newval: str = ""): self.on_invalid() return False - def on_invalid(self): + def on_invalid(self) -> None: """ Show the error message if the data is not valid. """ @@ -66,9 +67,9 @@ class StatusBar(ttk.Frame): Class describing a Frame where a label is shown. This represents a status bar that provides useful log to the user. """ - def __init__(self, container, color: str = 'light gray'): + def __init__(self, container: tk.Misc, color: str = 'light gray') -> None: # Initialize the Style object - s = ttk.Style() + s: ttk.Style = ttk.Style() # Configure the style for the status bar frame s.configure('self.TFrame', background=color, border=1, borderwidth=1, relief=tk.GROOVE) # Configure the style for the status bar label @@ -78,20 +79,20 @@ def __init__(self, container, color: str = 'light gray'): super().__init__(container, style='self.TFrame') # Declare an empty label providing the status messages - self.label = ttk.Label(self, text="", style='self.TLabel') + self.label: ttk.Label = ttk.Label(self, text="", style='self.TLabel') # Align the label on the left self.label.pack(side=tk.LEFT, padx=3, pady=3) # Configure the status bar in order to fill all the space in the horizontal direction self.grid(sticky='ew') - def set_text(self, new_text): + def set_text(self, new_text: str) -> None: """ Method that allow the modification of the status bar text. """ self.label.configure(text=new_text) - def clear_label(self): + def clear_label(self) -> None: """ Method that clears any text already present in the label status bar. """ @@ -104,9 +105,9 @@ class CustomNotebook(ttk.Notebook): to build a notebook with tabs presenting a button for closing each one of them. """ - __initialized = False + __initialized: bool = False - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # Initialize the custom style of the notebook, if not yet done if not self.__initialized: self.__initialize_custom_style() @@ -116,12 +117,12 @@ def __init__(self, *args, **kwargs): kwargs["style"] = "CustomNotebook" # Call the superclass constructor ttk.Notebook.__init__(self, *args, **kwargs) - self._active = None + self._active: Union[int, None] = None # Bind the mouse events to the execution of corresponding methods self.bind("", self.on_close_press, True) self.bind("", self.on_close_release) - def on_close_press(self, event): + def on_close_press(self, event: tk.Event) -> Union[str, None]: """ Method that is called when the mouse button is pressed over the close button. @@ -130,15 +131,13 @@ def on_close_press(self, event): element = self.identify(event.x, event.y) # Handle the case when the event happens on the close button of a tab if "close" in element: - # Get the index of the tab where the event happened - index = self.index("@%d,%d" % (event.x, event.y)) + # Get and store the index of the tab where the event happened + self._active = self.index("@%d,%d" % (event.x, event.y)) # Set the state of the widget self.state(['pressed']) - # Store the index of the tab where the event happened - self._active = index return "break" - def on_close_release(self, event): + def on_close_release(self, event: tk.Event) -> None: """ Method that is called when the mouse button is released after having pressed over the close button. @@ -174,7 +173,7 @@ def on_close_release(self, event): self.state(["!pressed"]) self._active = None - def __initialize_custom_style(self): + def __initialize_custom_style(self) -> None: # Declare a new Style instance style = ttk.Style() # Store the images representing the different states of the close button @@ -231,18 +230,22 @@ class SquareButton(ttk.Frame): This class includes the possibility to add an image to the button, whose path is passed to the constructor. """ - def __init__(self, parent, size=None, text="", command=None, style=None, image=None): + def __init__(self, parent: Union[tk.Misc, None], + size: Union[int, None] = None, text: str = "", + command: Union[Callable, None] = None, + style: Union[str, None] = None, + image: Union[str, None] = None) -> None: # Call the frame constructor by specifying its height and width ttk.Frame.__init__(self, parent, height=size, width=size, style="SquareButton.TFrame") # Indicate that the frame must not adapt to its widgets self.pack_propagate(0) # Instantiate the button as a private attribute - self._btn = ttk.Button(self, text=text, command=command, style=style, padding=0) + self._btn: ttk.Button = ttk.Button(self, text=text, command=command, style=style, padding=0) if image: size = self._btn.winfo_pixels('18p') print("Size:", size) - self.image = Image.open(image) + self.image: Union[Image.Image, ImageTk.PhotoImage] = Image.open(image) # assure a RGBA image as foreground color is RGB self.image = self.image.convert("RGBA") self.image = ImageTk.PhotoImage(self.image.resize((24, 24))) @@ -252,14 +255,14 @@ def __init__(self, parent, size=None, text="", command=None, style=None, image=N # Place the button in the frame self._btn.pack(fill=tk.BOTH, expand=0, ipady=0, ipadx=0) - def set_text(self, new_text: str): + def set_text(self, new_text: str) -> None: """ Method for setting the text of the button. """ self._btn.configure(text=new_text) -def provide_label_image(container, img_path: str) -> ttk.Label: +def provide_label_image(container: tk.Misc, img_path: str) -> ttk.Label: """ Function that provides a label filled with an image. It builds an instance of the 'ttk.Label' class by receiving the contaniner to put the label @@ -295,26 +298,31 @@ class OnOffClickableLabel(ttk.Frame): A function object can be passed to the constructor too; this is called whenever a click event on the label happens. """ - def __init__(self, parent, size=None, command=None, style=None, image: list=None, rotation: int=0): + def __init__(self, parent: Union[tk.Misc, None], + size: Union[int, None] = None, + command: Union[Callable, None] = None, + style: Union[str, None] = None, + image: Union[List[str], None] = None, + rotation: int = 0) -> None: # Call the frame constructor by specifying its height and width ttk.Frame.__init__(self, parent, height=size, width=size, style="ClickableLabelWithImage.TFrame") # Indicate that the frame must adapt to its widgets self.pack_propagate(1) # Instantiate the label as a private attribute - self._lbl = ttk.Label(self, style=style, padding=2, anchor=tk.CENTER) + self._lbl: ttk.Label = ttk.Label(self, style=style, padding=2, anchor=tk.CENTER) # Place the label in the frame self._lbl.pack(fill=tk.BOTH, expand=0, ipady=0, ipadx=0) # Set label default state - self.on_state = True + self.on_state: bool = True # Store the command to run when the label is clicked - self.command = command + self.command: Union[Callable, None] = command # Store the toggle images if two of them are provided if len(image) == 2: # Rotate the images, if a rotation value has been provided - self.on_image = Image.open(image[0]).rotate(rotation) - self.off_image = Image.open(image[1]).rotate(rotation) + self.on_image: Union[Image.Image, ImageTk.PhotoImage] = Image.open(image[0]).rotate(rotation) + self.off_image: Union[Image.Image, ImageTk.PhotoImage] = Image.open(image[1]).rotate(rotation) # Assure a RGBA image as foreground color is RGB self.on_image = self.on_image.convert("RGBA") self.off_image = self.off_image.convert("RGBA") @@ -326,9 +334,10 @@ def __init__(self, parent, size=None, command=None, style=None, image: list=None self._lbl.configure(image=self.on_image) # Bind mouse click event to image toggle and input function object call - self.click_event = self._lbl.bind('', lambda event: self.on_click(event, command)) + self.click_event: str = self._lbl.bind('', lambda event: self.on_click(event, command)) - def on_click(self, event=None, command=None): + def on_click(self, event: Union[tk.Event, None] = None, + command: Union[Callable, None] = None) -> None: """ Method that is called whenever a mouse click event on the label happens. Given the current label 'on_state' attribute value, the method switches @@ -351,7 +360,7 @@ def on_click(self, event=None, command=None): if command: command() - def activate_label(self): + def activate_label(self) -> None: """ Method that allows to activate the clickable label by binding the click event to the associated function and setting the label state @@ -362,7 +371,7 @@ def activate_label(self): # Enable the binding to the label click self.click_event = self._lbl.bind('', lambda event: self.on_click(event, self.command)) - def deactivate_label(self): + def deactivate_label(self) -> None: """ Method that allows to de-activate the clickable label by unbinding the click event to the associated function and setting the label state as @@ -382,15 +391,15 @@ class WidgetTooltip(): enters the borders of the widget and remains active for a specified time interval or when the mouse leave the widget area. """ - def __init__(self, widget, text='widget info'): + def __init__(self, widget: tk.Widget, text: str = 'widget info') -> None: # Specify the waiting time (in ms) for the tooltip to show - self.wait_time = 500 + self.wait_time: int = 500 # Specify the dimension (in px) of the tooltip area - self.wraplength = 180 + self.wraplength: int = 180 # Store the widget the tooltip has to be shown for - self.widget = widget + self.widget: tk.Widget = widget # Store the tooltip text - self.text = text + self.text: str = text # Bind the mouse enter event, for the given widget, to the method call showing the tooltip self.widget.bind("", self.enter) # Bind the mouse leave event, for the given widget, to the method call removing the tooltip @@ -398,18 +407,18 @@ def __init__(self, widget, text='widget info'): # Bind the mouse click event, for the given widget, to the method call removing the tooltip self.widget.bind("", self.leave) # Initialize the tooltip ID - self.id = None + self.id: Union[str, None] = None # Initialize the tooltip window - self.tooltip_window = None + self.tooltip_window: Union[tk.Toplevel, None] = None - def enter(self, event=None): + def enter(self, event: Union[tk.Event, None] = None) -> None: """ Method that is run whenever the mouse enters the widget area for showing its tooltip. """ self.schedule() - def leave(self, event=None): + def leave(self, event: Union[tk.Event, None] = None) -> None: """ Method that is run whenever the mouse leaves the widget area for hiding its tooltip. @@ -417,7 +426,7 @@ def leave(self, event=None): self.unschedule() self.hidetip() - def schedule(self): + def schedule(self) -> None: """ Method that clears any previous tooltip and shows the one for the widget after a specific waiting time, i.e. the corresponding method @@ -427,7 +436,7 @@ def schedule(self): # Run the method after a waiting time and store its ID self.id = self.widget.after(self.wait_time, self.showtip) - def unschedule(self): + def unschedule(self) -> None: """ Method that stops the job that shows the tooltip. The ID of the job to stop is retrieved by the corresponding instance attribute. @@ -438,14 +447,14 @@ def unschedule(self): # Cancel the job, given its ID, that shows the tooltip self.widget.after_cancel(id) - def showtip(self, event=None): + def showtip(self, event: Union[tk.Event, None] = None) -> None: """ Method that shows the tooltip as a window with a label object inside. """ # Initialize both X-Y coordinates to '0' x = y = 0 # Get the size of the widget - x, y, cx, cy = self.widget.bbox("insert") + x, y, _, _ = self.widget.bbox("insert") # Calculate the X-Y coordinates of the tooltip window, so that it is shown # below and to the right of the widget x += self.widget.winfo_rootx() + 25 @@ -467,7 +476,7 @@ def showtip(self, event=None): wraplength = self.wraplength) label.pack(ipadx=1) - def hidetip(self): + def hidetip(self) -> None: """ Method that hides the tooltip by destroying the Toplevel window that contains it. @@ -479,22 +488,22 @@ def hidetip(self): # testing ... if __name__ == '__main__': - root = tk.Tk() + root: tk.Tk = tk.Tk() #---------------------------- # Testing the tooltip feature #---------------------------- - btn1 = tk.Button(root, text="button 1") + btn1: tk.Button = tk.Button(root, text = "button 1") btn1.pack(padx=10, pady=5) - button1_ttp = WidgetTooltip(btn1, \ + button1_ttp: WidgetTooltip = WidgetTooltip(btn1, \ 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, ' 'consectetur, adipisci velit. Neque porro quisquam est qui dolorem ipsum ' 'quia dolor sit amet, consectetur, adipisci velit. Neque porro quisquam ' 'est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.') - btn2 = tk.Button(root, text="button 2") + btn2: tk.Button = tk.Button(root, text="button 2") btn2.pack(padx=10, pady=5) - button2_ttp = WidgetTooltip(btn2, \ + button2_ttp: WidgetTooltip = WidgetTooltip(btn2, \ "First thing's first, I'm the realest. Drop this and let the whole world " "feel it. And I'm still in the Murda Bizness. I could hold you down, like " "I'm givin' lessons in physics. You should want a bad Vic like this.") @@ -502,7 +511,7 @@ def hidetip(self): #------------------------------------ # Testing the clickable label feature #------------------------------------ - clickable_lbl = OnOffClickableLabel( + clickable_lbl: OnOffClickableLabel = OnOffClickableLabel( parent=root, command=lambda: print("Execute"), size=30, @@ -514,7 +523,7 @@ def hidetip(self): #-------------------------------------------------- # Testing the notebook with tabs that can be closed #-------------------------------------------------- - notebook = CustomNotebook(width=200, height=200) + notebook: CustomNotebook = CustomNotebook(width=200, height=200) notebook.pack(side="top", fill="both", expand=True) for color in ("red", "orange", "green", "blue", "violet"): diff --git a/tugui/main.py b/tugui/main.py index 5327aac..2c3394f 100644 --- a/tugui/main.py +++ b/tugui/main.py @@ -15,6 +15,7 @@ from gui_widgets import CustomNotebook, EntryVariable, StatusBar, provide_label_image from support import IANT from shutil import copyfile +from typing import Union ERROR_LEVEL: bool = 0 @@ -33,7 +34,7 @@ class TuPostProcessingGui(ThemedTk): . the plot area (right side) where the selected curves are shown . a status bar (bottom window) showing log messages. """ - def __init__(self, window_title, width, height): + def __init__(self, window_title: str, width: int, height: int) -> None: """ App windows's constructor """ @@ -58,7 +59,7 @@ def __init__(self, window_title, width, height): # Instantiate and configure the 'GuiPlotFieldsConfigurator' class in a try-except try: - self.guiconfig = GuiPlotFieldsConfigurator.init_GuiPlotFieldsConfigurator_attrs() + self.guiconfig: GuiPlotFieldsConfigurator = GuiPlotFieldsConfigurator.init_GuiPlotFieldsConfigurator_attrs() except Exception as e: # Intercept any exception produced by running the configuration logic according to the selected error level if ERROR_LEVEL: messagebox.showerror("Error", type(e).__name__ + "–" + str(e)) @@ -71,7 +72,7 @@ def __init__(self, window_title, width, height): self.create_menu() # Set the initial directory to the current working directory - self.initial_dir = os.getcwd() + self.initial_dir: str = os.getcwd() # Set the initial number of rows and columns and how much they are resized self.grid_rowconfigure((0,2), weight=0) @@ -108,7 +109,7 @@ def __init__(self, window_title, width, height): # Instantiate a label for the .pli file entry ttk.Label(mainframe, text="Path to the .pli input file").grid(column=0, row=0, sticky='ew') # Instantiate the field holding the path to the .pli input file - self.pli_entry = EntryVariable(mainframe, 50, col=1, row=0, end="pli") + self.pli_entry: EntryVariable = EntryVariable(mainframe, 50, col=1, row=0, end="pli") # Put a button next the the entry for allowing the selection of the .pli file to open ttk.Button(mainframe, # width = 80, text="Choose file", @@ -128,7 +129,7 @@ def __init__(self, window_title, width, height): self.build_tabs_area() # Add a status bar to the bottom of the window - self.status_bar = StatusBar(self) + self.status_bar: StatusBar = StatusBar(self) self.status_bar.grid(column=0, row=2, columnspan=2) # Add a padding for each widget in the top section frame @@ -164,7 +165,7 @@ def __init__(self, window_title, width, height): # Bind the <> virtual event to the plot creation self.bind('<>', func=lambda event: self.display_plot()) - def __initialize_gui_window(self, title, width, height): + def __initialize_gui_window(self, title: str, width: int, height: int) -> None: """ Method that sets the GUI window title, as well as its dimensions, in terms of width and height. Given the screen size, the window is placed in the center @@ -180,7 +181,7 @@ def __initialize_gui_window(self, title, width, height): # Set the window geometry self.geometry(f"{width}x{height}+{left}+{top}") - def display_plot(self): + def display_plot(self) -> None: """ Method that enables the display-only mode for plots provided by reading the input .dat and .plt files. @@ -222,7 +223,7 @@ def display_plot(self): # Plot the i-th diagram self.plot_curves(plot_figure, self.loaded_dat_files[i], self.loaded_plt_files[i], "") - def display_inp_plots(self): + def display_inp_plots(self) -> None: """ Method that enables the display-only mode for plots provided by reading an input .inp file. @@ -298,7 +299,7 @@ def display_inp_plots(self): # Plot the i-th diagram self.plot_curves(plot_figure, inp_to_dat.dat_paths[i], inp_to_dat.plt_paths[i], inp_to_dat.out_paths[i]) - def build_tabs_area(self): + def build_tabs_area(self) -> None: """ Method that builds the tabs containing the plot configuration area and the plot display one for both the TuPlot and the TuStat cases. @@ -324,7 +325,7 @@ def build_tabs_area(self): self.tustat_tab = TuStatTabContentBuilder( self.tabControl, tab_name="TU Stat", state=tk.DISABLED, guiConfig=self.guiconfig) - def retrieve_simulation_info(self): + def retrieve_simulation_info(self) -> None: """ Method that extract all the needed information from the .pli input file. Given the paths to the .mac, .mic, .sta, .sti files therein present, the method provides the @@ -409,7 +410,7 @@ def retrieve_simulation_info(self): # pop-up box is produced showing the error message messagebox.showerror("Error", type(e).__name__ + "–" + str(e)) - def activate_all_tabs(self): + def activate_all_tabs(self) -> None: """ Method that activates both the TuPlot and the TuStat tabs by calling the corresponding methods. @@ -417,7 +418,7 @@ def activate_all_tabs(self): self.activate_tuplot_area() self.activate_tustat_area() - def activate_tustat_area(self): + def activate_tustat_area(self) -> None: """ Method that activates the TuStat tab by changing the state attribute of the corresponding tab object. @@ -438,7 +439,7 @@ def activate_tustat_area(self): # Pass the herein-defined method to call whenever the "Run" button of the TuStat tab is pressed self.tustat_tab.run_plot(self.run_tuStat) - def activate_tuplot_area(self): + def activate_tuplot_area(self) -> None: """ Method that activates the TuPlot tab by changing the state attribute of the corresponding tab object. @@ -462,7 +463,7 @@ def activate_tuplot_area(self): # Pass the herein-defined method to call whenever the "Run" button of the TuPlot object is pressed self.tuplot_tab.run_plot(self.run_tuPlot) - def run_tuPlot(self): + def run_tuPlot(self) -> None: """ Method that runs the TuPlot executable by passing the required .inp file. This file is first built up based on the user's choices made in the plot @@ -572,7 +573,7 @@ def run_tuPlot(self): messagebox.showerror("Error", type(e).__name__ + "–" + str(e)) raise Exception(e) - def run_tuStat(self): + def run_tuStat(self) -> None: """ Method that runs the TuStat executable by passing the required .inp file. This file is first built up based on the user's choices made in the plot @@ -624,7 +625,8 @@ def run_tuStat(self): # show a pop-up error message messagebox.showerror("Error", type(e).__name__ + "–" + str(e)) - def handle_plot_production(self, tuinp: TuInp, output_files_name: str, executable_path: str, active_plotFigure: PlotFigure): + def handle_plot_production(self, tuinp: TuInp, output_files_name: str, + executable_path: str, active_plotFigure: PlotFigure) -> None: """ Method that handles all the operations for displaying the diagrams on the plotting area of the main window. In particular, we have that: @@ -664,7 +666,8 @@ def handle_plot_production(self, tuinp: TuInp, output_files_name: str, executabl # onto the currently active tab figure --> only 1 .dat and .plt file is considered self.plot_curves(active_plotFigure, self.active_dat_file, self.active_plt_file, inp_to_dat.out_paths[0]) - def plot_curves(self, plotFigure: PlotFigure, dat_file: str, plt_file: str, out_file: str): + def plot_curves(self, plotFigure: PlotFigure, dat_file: str, plt_file: str, + out_file: str) -> None: """ Method that instantiate the class handling the plot functionalities:\n . the output .dat and .plt files are read in order to extract:\n @@ -684,13 +687,13 @@ def plot_curves(self, plotFigure: PlotFigure, dat_file: str, plt_file: str, out_ messagebox.showerror("Error", type(e).__name__ + "–" + str(e)) raise Exception(e) - def open_pli_file(self, event=None): + def open_pli_file(self, event: Union[tk.Event, None] = None) -> None: """ Method for asking the user to open an input .pli file. """ self.select_file_and_fill_entry(self.pli_entry.entry, "Input file", "pli") - def select_file(self, fileToSearch: str, format: str): + def select_file(self, fileToSearch: str, format: str) -> str: """ Method for asking the user to select a file by opening a file selection window. A string representing the format of the target file is provided in order @@ -709,7 +712,7 @@ def select_file(self, fileToSearch: str, format: str): initialdir = self.initial_dir, filetypes = filetypes) - def select_file_and_fill_entry(self, entry: ttk.Entry, fileToSerch: str, format: str): + def select_file_and_fill_entry(self, entry: ttk.Entry, fileToSerch: str, format: str) -> None: """ Method for asking the user to select a file by opening a file selection window. An Entry object is provided as input so that the path to the selected file is @@ -748,7 +751,7 @@ def select_file_and_fill_entry(self, entry: ttk.Entry, fileToSerch: str, format: # content validation self.focus() - def select_output_folder(self, event=None): + def select_output_folder(self, event: Union[tk.Event, None] = None) -> None: """ Method for asking the user to select the output folder by opening a selection window. """ @@ -772,7 +775,7 @@ def select_output_folder(self, event=None): print("Selected output folder:", self.output_dir) - def load_inp_file(self, event=None): + def load_inp_file(self, event: Union[tk.Event, None] = None) -> None: """ Method for asking the user to select a plot configuration .inp file to load by opening a file selection window. @@ -797,7 +800,7 @@ def load_inp_file(self, event=None): # Generate the '<>' virtual event self.event_generate('<>') - def load_output_files(self, event=None): + def load_output_files(self, event: Union[tk.Event, None] = None) -> None: """ Method for asking the user to select the plot configuration files provided as a couple of .dat and .plt files, with the first containing the X-Y data of the curves and the second @@ -844,7 +847,7 @@ def load_output_files(self, event=None): # Generate the '<>' virtual event self.event_generate('<>') - def save_file(self, fileToSave: str, format: str): + def save_file(self, fileToSave: str, format: str) -> str: """ """ # Declare a tuple of tuples having the file type and its extension for filtering files in folders. @@ -860,7 +863,7 @@ def save_file(self, fileToSave: str, format: str): initialdir = self.initial_dir, filetypes = filetypes) - def save_inp_file(self, event=None): + def save_inp_file(self, event: Union[tk.Event, None] = None) -> None: """ Method for asking the user to save the currently active plot configuration .inp file by opening a file save selection window. @@ -882,7 +885,7 @@ def save_inp_file(self, event=None): # Provide a message to the status bar self.status_bar.set_text("Saved .inp file: " + dest) - def save_output_files(self, event=None): + def save_output_files(self, event: Union[tk.Event, None] = None) -> None: """ Method for asking the user to save the currently active plot configuration .inp file by opening a file save selection window. @@ -909,7 +912,7 @@ def save_output_files(self, event=None): # Provide a message to the status bar self.status_bar.set_text("Saved .inp file: " + filename) - def create_menu(self): + def create_menu(self) -> None: """ Method that creates and adds a menu bar to the main window. It also builds the menu commands for each menu category. @@ -952,14 +955,14 @@ def create_menu(self): # Add the menu bar to the main window self.configure(menu=menubar) - def quit_app(self, event=None): + def quit_app(self, event: Union[tk.Event, None] = None) -> None: """ Method that quit the application. """ # Destroy the main window and all its widgets, thus closing the application self.destroy() - def reset_main_window(self, event=None): + def reset_main_window(self, event: Union[tk.Event, None] = None) -> None: """ Method that resets the main window by clearing the content of the entry widget for setting the path to the input .pli file, as well as the message provided by the @@ -975,7 +978,7 @@ def reset_main_window(self, event=None): self.build_tabs_area() -def new_postprocessing(event=None): +def new_postprocessing(event: Union[tk.Event, None] = None) -> None: """ Function that opens a new window for performing post-processing of results from a TU simulation. """ diff --git a/tugui/plot_builder.py b/tugui/plot_builder.py index 4dedfb6..6046d77 100644 --- a/tugui/plot_builder.py +++ b/tugui/plot_builder.py @@ -14,8 +14,10 @@ from matplotlib.figure import Figure from matplotlib.lines import Line2D from matplotlib.offsetbox import AnnotationBbox, TextArea, VPacker +from numpy.typing import ArrayLike from tkinter import ttk from tkinter.filedialog import asksaveasfilename +from typing import Callable, Dict, List, Union, Tuple class PlotFigure(ttk.Frame): @@ -29,7 +31,7 @@ class PlotFigure(ttk.Frame): CustomToolbar instance; . report: a Frame object providing a Text object for the report to display. """ - def __init__(self, container): + def __init__(self, container: tk.Misc) -> None: super().__init__(container) # Build a paned window @@ -43,7 +45,7 @@ def __init__(self, container): plot_frame.grid_rowconfigure(1, weight=0) plot_frame.grid_columnconfigure(0, weight=3) # Build a frame holding the plot report text - self.report_frame = ttk.Frame(container) + self.report_frame: ttk.Frame = ttk.Frame(container) self.report_frame.grid(column=0, row=0, sticky='nsew') # Build the content of the report frame self._build_report_area(self.report_frame) @@ -54,7 +56,7 @@ def __init__(self, container): panedwindow.add(self.report_frame, weight=1) # Instantiate the figure that will contain the plot; it is treated as an instance attribute - self.fig = Figure(figsize = (10, 5), dpi = 100) + self.fig: Figure = Figure(figsize = (10, 5), dpi = 100) self.fig.add_subplot(111) # Add a Tkinter canvas within the plot frame and that holds the matplotlib Figure object canvas = FigureCanvasTkAgg(self.fig, master = plot_frame) @@ -63,7 +65,7 @@ def __init__(self, container): canvas.get_tk_widget().grid(column=0, row=0, sticky='nsew') # Instantiate the toolbar - self.toolbar = CustomToolbar(canvas, plot_frame, False) + self.toolbar: CustomToolbar = CustomToolbar(canvas, plot_frame, False) # Reset the axes self.toolbar.update() # Place the toolbar into the Frame grid @@ -77,7 +79,7 @@ def __init__(self, container): # Bind the deselection of the toolbar buttons to the "DeselectButtons" event self.bind('<>', func=lambda event: self.toolbar.reset_toolbar_buttons()) - def _build_report_area(self, report_frame: ttk.Frame): + def _build_report_area(self, report_frame: ttk.Frame) -> None: """ Method that builds the report area (as a Text widget) where the content of the .out file is displayed. @@ -109,7 +111,7 @@ def _build_report_area(self, report_frame: ttk.Frame): self.text_widget.bind("", self._copy_report_selection) self.text_widget.bind("", self._copy_report_selection) # In case caps lock is on - def _select_all_report(self, event): + def _select_all_report(self, event: tk.Event) -> str: """ Method that selects all the content of the text widget showing the plot report. """ @@ -122,7 +124,7 @@ def _select_all_report(self, event): # Stops other bindings on this event from being invoked return 'break' - def _copy_report_selection(self, event): + def _copy_report_selection(self, event: tk.Event) -> None: """ Method that copies the current report selection to the clipboard. """ @@ -147,7 +149,10 @@ class CustomToolbar(NavigationToolbar2Tk): . save as CSV: functionality for saving the X-Y data of the currently active curves to a file in the CSV format. """ - def __init__(self, canvas, frame, pack_toolbar=False, axes=None, x=None, ys=None): + def __init__(self, canvas: tk.Canvas, frame: tk.Frame, + pack_toolbar: bool = False, axes: Union[Axes, None] =None, + x: List[ArrayLike] = None, + ys: List[ArrayLike] = None) -> None: super().__init__(canvas, window=frame, pack_toolbar=pack_toolbar) # Add the cursor button to the toolbar @@ -178,12 +183,12 @@ def __init__(self, canvas, frame, pack_toolbar=False, axes=None, x=None, ys=None self.get_toolbar_button('savecsv').configure(state='disabled') # Set the initial directory - self.initial_dir = os.getcwd() + self.initial_dir: str = os.getcwd() # Instantiate the PlotCursor class - self.cursor = PlotCursor(axes, x, ys) + self.cursor: PlotCursor = PlotCursor(axes, x, ys) - def get_toolbar_button(self, button_name: str) -> tk.Button | tk.Checkbutton: + def get_toolbar_button(self, button_name: str) -> Union[tk.Button, tk.Checkbutton]: """ Method that, given a string representing the toolbar button name, it returns the corresponding button, either as an instance of the 'tk.Button' or the @@ -191,7 +196,7 @@ def get_toolbar_button(self, button_name: str) -> tk.Button | tk.Checkbutton: """ return self._buttons[button_name] - def need_cursor_activation(self): + def need_cursor_activation(self) -> None: """ Method that is called when the cursor button is pressed. Depending on the PlotCursor object 'button_state' attribute, the corresponding method of the PlotCursor instance @@ -216,7 +221,7 @@ def need_cursor_activation(self): # Call the cursor object method for handling the cursor activation self.cursor.activate_cursor() - def need_legend_activation(self): + def need_legend_activation(self) -> None: """ Method that is called when the legend button is pressed. Depending on the legend current visibility, the opposite state is assigned. @@ -241,7 +246,7 @@ def need_legend_activation(self): # Re-draw the figure self.axes.figure.canvas.draw_idle() - def reset_toolbar_buttons(self): + def reset_toolbar_buttons(self) -> None: """ Method that resets the additional buttons of the toolbar. In particular, both the cursor and the legend buttons are toggled off, while the PlotCursor related instance @@ -254,7 +259,7 @@ def reset_toolbar_buttons(self): self.cursor.state = False self.cursor.set_button_state(False) - def save_csv(self): + def save_csv(self) -> None: """ Method that enables the functionality for saving the X-Y data of the currently active curves on a file in CSV format. In case the plot presents curves with @@ -279,7 +284,7 @@ def save_csv(self): curves_to_save.update({h: l for h, l in zip(handles, legends) for c in self.active_curves if c == h}) # Get the list of X-data for each of the active curves - curves: list[Line2D] = list(curves_to_save.keys()) + curves: List[Line2D] = list(curves_to_save.keys()) # Define the header list with elements being the legend labels headers = list(curves_to_save.values()) # Add the plot X-axis label in the first position of the header list @@ -334,7 +339,7 @@ def save_csv(self): # Update the initial directory to the path of the saved file folder self.initial_dir = os.path.dirname(filename) - def set_active_curves(self, active_curves: list[Line2D]): + def set_active_curves(self, active_curves: List[Line2D]) -> None: """ Method that sets the instance attribute referring to the currently active curves, provided as a list of 'Line2D' objects. @@ -343,7 +348,7 @@ def set_active_curves(self, active_curves: list[Line2D]): # Activate the 'SaveCSV' toolbar button as there are curves available self._buttons['savecsv'].configure(state='active') - def set_axes(self, axes: Axes): + def set_axes(self, axes: Axes) -> None: """ Method that sets the instance attribute referring to the current plot Axes object. @@ -353,7 +358,8 @@ def set_axes(self, axes: Axes): self._buttons['legend'].configure(state='active') self._buttons['cursor'].configure(state='active') - def _add_new_button(self, name: str, text: str, img_relpath: str, toggle: bool, command, tooltip: str): + def _add_new_button(self, name: str, text: str, img_relpath: str, + toggle: bool, command: Callable, tooltip: str) -> None: """ Method that allows to add a new button for the plot toolbar by providing its name, a descriptive text, the path to its image, relative to this module, if it has to be treated as an on/off button, @@ -383,7 +389,7 @@ class PlotCursor(): The cursor can be dragged by the mouse and can assume positions given by the X-Y values of the active curves stored within the present instance. """ - def __init__(self, ax: Axes, x, y: list): + def __init__(self, ax: Axes, x: List[ArrayLike], y: List[ArrayLike]) -> None: """ Class constructor. It needs the plot Axes object, the X values and a list of Y-values for each curve. @@ -395,29 +401,29 @@ def __init__(self, ax: Axes, x, y: list): # curves are plotted yet. if ax != None: # Store the current plot Axes object - self.ax = ax + self.ax: Axes = ax # Store the X-Y values into the corresponding instance variables by extracting # them from the corresponding curves. - self.xs = x - self.ys = y + self.xs: List[ArrayLike] = x + self.ys: List[ArrayLike] = y # Connect events on the plot area to the execution of the corresponding methods self._connect_events_to_methods() # Initialize a list holding the curves markers and labels - self.marker: list[Line2D] = list() + self.marker: List[Line2D] = list() # Initialize flags for defining the cursor events - self.moved = None - self.pressed = False - self.start = False + self.moved: Union[bool, None] = None + self.pressed: bool = False + self.start: bool = False # Initialize to False the flag providing the cursor activation - self.state = False - self.button_state = False + self.state: bool = False + self.button_state: bool = False # Initialize a variable identifying the index for the curves X-Y lists - self.indx = 0 + self.indx: int = 0 - def activate_cursor(self): + def activate_cursor(self) -> None: """ Method that deals with the cursor activation. When called, it builds the cursor vertical line, the annotation box showing the curves Y-values at the cursor @@ -454,7 +460,7 @@ def activate_cursor(self): # Declare a list of empty strings with same dimension of the colors one texts = ['' for e in self.line_colors] # Declare a list of TextArea objects - self.text_areas: list[TextArea] = list() + self.text_areas: List[TextArea] = list() # Loop over all the elements of both text and colors lists for t,c in zip(texts, self.line_colors): # Add a TextArea object, each with a different color (for Y-values) @@ -559,7 +565,7 @@ def activate_cursor(self): # Re-draw the figure self.figcanvas.draw_idle() - def deactivate_cursor(self): + def deactivate_cursor(self) -> None: """ Method that deals with the cursor deactivation. When called, it removes the cursor vertical line, deleting the corresponding instance attribute, the @@ -587,7 +593,7 @@ def deactivate_cursor(self): # Re-draw the figure self.figcanvas.draw_idle() - def set_attributes(self, ax: Axes, lines: list[Line2D]): + def set_attributes(self, ax: Axes, lines: List[Line2D]) -> None: """ Method for setting the Axes object of the plot, the X-Y values lists, and the list of colors used by each plot. @@ -621,7 +627,7 @@ def set_attributes(self, ax: Axes, lines: list[Line2D]): # Connect events on the plot area to the execution of the corresponding methods self._connect_events_to_methods() - def _evaluate_display_mode(self): + def _evaluate_display_mode(self) -> str: """ Method that evaluates the cursor display mode according to the X-coordinates of the input curves: if they all have the same X-values, the cursor shows an annotation box @@ -647,14 +653,14 @@ def _evaluate_display_mode(self): # If here, return the 'YYX' mode (same X-values for all curves) return 'YYX' - def set_button_state(self, button_state: bool): + def set_button_state(self, button_state: bool) -> None: """ Method that allows to set the instance variable storing the corresponding button state in the plot toolbar. """ self.button_state = button_state - def show_activeonly_curves_info(self, active_curves: list[Line2D]): + def show_activeonly_curves_info(self, active_curves: List[Line2D]) -> None: """ Method that, given a list of Line2D objects, set the instance attribute storing the currently active curves, i.e. those shown in the plot and @@ -689,7 +695,7 @@ def show_activeonly_curves_info(self, active_curves: list[Line2D]): # the cursor given the new X-Y values. self.activate_cursor() - def _connect_events_to_methods(self): + def _connect_events_to_methods(self) -> None: """ Method that handles the binding of the instance methods ot specific events happening within the plot area. In particular, we have: @@ -715,7 +721,7 @@ def _connect_events_to_methods(self): self.ax.callbacks.connect('xlim_changed', self._handle_xylims_change) self.ax.callbacks.connect('ylim_changed', self._handle_xylims_change) - def _handle_mouse_move(self, event): + def _handle_mouse_move(self, event: tk.Event) -> None: """ Method that is called whenever the mouse move event happens. It allows to move the cursor, the annotation box showing the X-Y values, and the corresponding markers @@ -755,7 +761,7 @@ def _handle_mouse_move(self, event): # Re-draw the figure self.ax.figure.canvas.draw_idle() - def _handle_mouse_press(self, event): + def _handle_mouse_press(self, event: tk.Event) -> None: """ Method that is called whenever the mouse button press event happens to set the point corresponding to the X-coordinate of @@ -772,7 +778,7 @@ def _handle_mouse_press(self, event): # Having pressed the mouse button, set the corresponding flag to True self.pressed = True - def _handle_mouse_release(self, event): + def _handle_mouse_release(self, event: tk.Event) -> None: """ Method that is called whenever the mouse button release event happens to set the instance flags. @@ -789,7 +795,7 @@ def _handle_mouse_release(self, event): self.start = False self.obj = self.moved - def _handle_resize(self, event): + def _handle_resize(self, event: tk.Event) -> None: """ Method that is called whenever an event related to a resize of the plot figure happens. Given the new figure size, it calls the method for @@ -799,7 +805,7 @@ def _handle_resize(self, event): if hasattr(self, 'ann'): self._update_box_coordinates() - def _handle_xylims_change(self, event_ax): + def _handle_xylims_change(self, event_ax: Axes) -> None: """ Method that is called whenever an event related to a change in the X-axis range happens. Given the new limits, it calls the method for updating the @@ -820,7 +826,7 @@ def _handle_xylims_change(self, event_ax): if self.xs[self.indx] < self.ax.get_xlim()[0]: self.indx += 1 - def _update_box_coordinates(self): + def _update_box_coordinates(self) -> None: """ Method that updates the position of the annotation box showing the X-Y values of the curves at the cursor position, if any has been defined. @@ -857,7 +863,7 @@ def _update_box_coordinates(self): else: self.ann.set(visible = True) - def _update_curves_info(self, x): + def _update_curves_info(self, x: List[ArrayLike]) -> None: """ Method that updates the text shown by the annotation box. Given the current X-position of the cursor, provided as argument, it extract the Y-values for the plotted curves and @@ -924,12 +930,12 @@ class PlotManager(): Class that handles the plot creation by extracting the data provided by the output files produced by the TuPlot and TuStat executables. """ - def __init__(self, dat_file: str, plt_file: str, out_file: str=""): + def __init__(self, dat_file: str, plt_file: str, out_file: str="") -> None: # Set the instance attributes - self.dat_file = dat_file - self.plt_file = plt_file + self.dat_file: str = dat_file + self.plt_file: str = plt_file if out_file != "": - self.out_file = out_file + self.out_file: str = out_file # Extract the plot information from the .plt file self._read_plt_file() @@ -939,7 +945,7 @@ def __init__(self, dat_file: str, plt_file: str, out_file: str=""): if hasattr(self, 'out_file'): self._read_out_file() - def plot(self, plotFigure: PlotFigure, plot_index: int = 111): + def plot(self, plotFigure: PlotFigure, plot_index: int = 111) -> None: """ Method that, given the input PlotFigure object, configures the plots and shows the curves. The plot legend is configured so that each curve visibility can be @@ -948,7 +954,7 @@ def plot(self, plotFigure: PlotFigure, plot_index: int = 111): # Get the Figure from PlotFigure instance fig = plotFigure.fig # Store a reference to the toolbar for the given PlotFigure object - self.toolbar = plotFigure.toolbar + self.toolbar: CustomToolbar = plotFigure.toolbar # Reset the cursor state of the toolbar self.toolbar.cursor.deactivate_cursor() @@ -966,7 +972,7 @@ def plot(self, plotFigure: PlotFigure, plot_index: int = 111): axes.set_ylabel(self.y_axis_name) # Declare an array holding the X-Y data for each curve to plot - lines: list[Line2D] = list() + lines: List[Line2D] = list() # Loop over all the curves stored in the dictionary extracted by reading the .dat file # and plot each of them @@ -1029,7 +1035,8 @@ def plot(self, plotFigure: PlotFigure, plot_index: int = 111): # Disable the editability of the report text area plotFigure.text_widget.configure(state=tk.DISABLED) - def _handle_legend_pick(self, event, fig: Figure, map_legend_to_ax: dict[Axes.legend, Line2D]): + def _handle_legend_pick(self, event: tk.Event, fig: Figure, + map_legend_to_ax: Dict[Axes.legend, Line2D]) -> None: """ Method that, given the pick event, changes the visibility of the curve selected from the plot legend. @@ -1063,7 +1070,7 @@ def _handle_legend_pick(self, event, fig: Figure, map_legend_to_ax: dict[Axes.le # Pass the active curves to the toolbar object self.toolbar.set_active_curves(active_curves) - def _read_dat_file(self): + def _read_dat_file(self) -> None: """ Method for extracting the X-Y data for the curves to be plotted. A different reading method is used that is given by the presence of the '/td' string at the beginning of the file. This @@ -1089,7 +1096,7 @@ def _read_dat_file(self): # List containing the X-Y values for every curve curve = list() # Dictionary containing the curves values with key the legend and value a list of X-Y values - self.curves2plot: dict[(list | str), list] = dict() + self.curves2plot: Dict[Union[List[str], str], List[Tuple[float]]] = dict() # Handle the .dat content reading differently on the basis of the first line of the .dat file: # . if it starts with '/td', the curves X-Y data are provided separately as in the 'Radius' @@ -1158,9 +1165,9 @@ def _read_dat_file(self): # information is present, which has already been retrieved from the already read .plt file. ############################################################################################## # Declare a list holding the X-data - x: list[str] = list() + x: List[str] = list() # Declare a list of lists holding the Y-data for each curve - ys: list[list[str]] = list() + ys: List[List[str]] = list() print("CURVE LEGEND 2: ", self.legend) @@ -1202,7 +1209,7 @@ def _read_dat_file(self): self.curves2plot[l].append((float(x[i].replace("D", "E")), float(ys[i][j].replace("D", "E")))) j += 1 - def _read_out_file(self): + def _read_out_file(self) -> None: """ Method for extracting the content of the report .out file and saving it into a string instance attribute. @@ -1212,7 +1219,7 @@ def _read_out_file(self): # Save all the file content into a string self.report = file.read() - def _read_plt_file(self): + def _read_plt_file(self) -> None: """ Method for extracting the data for setting up the plot display information, i.e. the axes names, the plot title, the plot legend, if present. @@ -1260,7 +1267,7 @@ def _read_plt_file(self): if not self.legend[i]: self.legend[i] = self.y_axis_name - def _render_mathtext(self, text: str): + def _render_mathtext(self, text: str) -> str: """ Method that, given the input string, allows its rendering as a mathematical expression. The string can contain some special characters that need to be interpreted as a @@ -1293,7 +1300,7 @@ def _render_mathtext(self, text: str): # Return the string with any performed modification return text - def _interpret_matplotlib_symbol(self, letter: str): + def _interpret_matplotlib_symbol(self, letter: str) -> str: """ Method that, given the input letter, returns a new one that corresponds to how matplotlib interprets the corresponding symbol. @@ -1346,7 +1353,7 @@ def _interpret_matplotlib_symbol(self, letter: str): # Default case case _: return letter - def _search_and_replace(self, text: str, search_char: str): + def _search_and_replace(self, text: str, search_char: str) -> str: """ Method that, given the input string 'text', searches for any match of substrings that start with the input character. @@ -1372,12 +1379,12 @@ def _search_and_replace(self, text: str, search_char: str): """ if __name__ == "__main__": # Instantiate the root window - root = tk.Tk() + root: tk.Tk = tk.Tk() # Instantiate the PlotManager class - plt_manager = PlotManager(dat_file="../Input/TuPlot01.dat", + plt_manager: PlotManager = PlotManager(dat_file="../Input/TuPlot01.dat", plt_file="../Input/TuPlot01.plt") # Instantiate a PlotFigure object - plt_figure = PlotFigure(root) + plt_figure: PlotFigure = PlotFigure(root) plt_figure.pack(fill='both', expand=True) # Remove the report area plt_figure.report_frame.destroy() diff --git a/tugui/plot_settings.py b/tugui/plot_settings.py index f9b31d0..f49e245 100644 --- a/tugui/plot_settings.py +++ b/tugui/plot_settings.py @@ -1,6 +1,7 @@ from enum import Enum import tkinter as tk from tkinter import ttk +from typing import Callable, List, Union class FieldType(Enum): @@ -10,9 +11,9 @@ class FieldType(Enum): . type2 --> Time . type3 --> Slice """ - type1 = "Kn" - type2 = "Time" - type3 = "Slice" + type1: str = "Kn" + type2: str = "Time" + type3: str = "Slice" class GroupType(Enum): """ @@ -22,171 +23,10 @@ class GroupType(Enum): . group2A --> function of time (integral quantities) . group3 --> function of axial coordinate """ - group1 = "Radius" - group2 = "Time" - group2A = "TimeIntegral" - group3 = "Axial" - - -class PlotSettingsConfigurator(): - """ - Class that build the structure of the additional plot settings area as several rows - containing a label and a combobox each, provided as instances of the 'LabelledCombobox' - class, with a listbox at the end, as instance of the 'PlotSettingsListBox' class. - The plot group choice influences the structure of the built widgets. - """ - def __init__(self, container: ttk.Frame, group: GroupType, row_index: int): - """ - Build an instance of the 'PlotSettingsConfigurator' class that provides the structure - of the additional plot settings area in terms of three field with the first two being - instances of the 'LabelledCombobox' class, whereas the last one of the - 'PlotSettingsListBox' class. Depending on the plot group, the first field can be absent, - that is for groups '2A' and '3'. It receives as parameters: - . container: a frame object to which this instance is added - . group: a value of the enumeration 'GroupType' indicating the plot group - . row_index: an integer indicating the row index of the container grid where the - built widgets are added - """ - # Build the structure of the additional plot settings area: it is made of - # several rows containing a label and a combobox. A listbox handles the choice - # of the plots to produce. - # Build fields 1 and 2 depending on the plot group: field 1 can be absent for - # groups 2A and 3. - - match group: - case GroupType.group1: - # Instantiate a label followed by a combobox for both field 1 and 2 - self.field1 = LabelledCombobox(container, row_index, "", []) - self.field2 = LabelledCombobox(container, self.field1.row_next, "", []) - - # Bind each field selection to the execution of a method that checks if all fields have been set - self.field1.cbx.bind('<>', - lambda event: self.handle_selection(container, self.field1.store_selected)) - self.field2.cbx.bind('<>', - lambda event: self.handle_selection(container, self.field2.store_selected)) - case GroupType.group2: - # Instantiate a label followed by a combobox for field 1 - self.field1 = LabelledCombobox(container, row_index, "", []) - # Instantiate a label followed by two label + combobox groups - self.field2 = PlotSettingsField_2(container, self.field1.row_next, []) - - # Bind each field selection to the execution of a method that checks if all fields have been set - self.field1.cbx.bind('<>', - lambda event: self.handle_selection(container, self.field1.store_selected)) - self.field2.start_time.cbx.bind('<>', - lambda event: self.handle_selection(container, self.field2.check_time_consistency)) - self.field2.end_time.cbx.bind('<>', - lambda event: self.handle_selection(container, self.field2.check_time_consistency)) - case GroupType.group2A: - # Instantiate a label followed by two label + combobox groups - self.field2 = PlotSettingsField_2(container, row_index, []) - # Bind each field selection to the execution of a method that checks if all fields have been set - self.field2.start_time.cbx.bind('<>', - lambda event: self.handle_selection(container, self.field2.check_time_consistency)) - self.field2.end_time.cbx.bind('<>', - lambda event: self.handle_selection(container, self.field2.check_time_consistency)) - case GroupType.group3: - # Instantiate a label followed by a label + combobox group - self.field2 = LabelledCombobox(container, row_index, "Time (h s ms)", []) - # Bind each field selection to the execution of a method that checks if all fields have been set - self.field2.cbx.bind('<>', - lambda event: self.handle_selection(container, self.field2.store_selected)) - # Instantiate a label followed by a listbox - self.field3 = PlotSettingsListBox(container, "", [], self.field2.row_next) - # Bind field3 selection to the execution of a method that checks if all fields have been set - self.field3.choice_lb.bind('<>', - lambda event: self.handle_selection(container, self.field3.store_selected)) - - # Store the group value - self.group = group - - # Get from the last field the index indicating the row where to add additional widgets - self.row_next = self.field3.row_next - print("BUTTON ROW", self.row_next) - - def configure_fields(self, - field1_lbl: str = None, field1_cbx: list = None, - field2_lbl: str = "", field2_cbx: list = list(), - field3_lbl: str = "", field3_lbx: list = list()): - """ - Method for configuring all the widgets declared within this class instance. - """ - # Configure field 1 only if the group is not 2A or 3 - if not (self.group == GroupType.group2A or self.group == GroupType.group3): - self.field1.label.configure(text=field1_lbl) - self.field1.cbx.configure(values=field1_cbx) - # Configure field 2 label only if the group is not 2 or 2A - if not (self.group == GroupType.group2 or self.group == GroupType.group2A): - self.field2.label.configure(text=field2_lbl) - self.field2.cbx.configure(values=field2_cbx) - else: - # Case 2/2A: set the two combobox lists - self.field2.start_time.cbx.configure(values=field2_cbx) - self.field2.end_time.cbx.configure(values=field2_cbx[1:]) - # Configure the remaining field - self.field3.label.configure(text=field3_lbl) - self.field3.choicesvar.set(field3_lbx) - - def is_everything_set(self, container: ttk.Frame): - """ - Method that checks if every field has been set up. If so, it generates a - virtual event, in the container frame, representing this condition. - """ - # Check if this instance has declared field1 - if hasattr(self, 'field1'): - # Check if all the fields are set - if self.field1.is_set and self.field2.is_set and self.field3.is_set: - # Generate the virtual event in the container frame - container.event_generate('<>') - else: - # Check if the available fields are set - if self.field2.is_set and self.field3.is_set: - # Generate the virtual event in the container frame - container.event_generate('<>') - - def handle_selection(self, container: ttk.Frame, set_field): - """ - Method called whenever an event involving the set up of the value either of a combobox - or of a listbox happens. - It receives a generic function for setting the field value of the widget that generate - the event. It is called in order to set a widget flag stating the field has been set. - Afterwards, a check if all fields have been set is performed. - """ - # Call the input function for setting the field attribute - set_field() - # Check if this instance has declared field1 - if hasattr(self, 'field1'): - # Check if every field have been set ony if all have declared the corresponding flag - if hasattr(self.field1, 'is_set') and hasattr(self.field2, 'is_set') and hasattr(self.field3, 'is_set'): - # Call the function that checks the flags value and generates an event - self.is_everything_set(container) - else: - # Check if every field have been set ony if all have declared the corresponding flag - if hasattr(self.field2, 'is_set') and hasattr(self.field3, 'is_set'): - # Call the function that checks the flags value and generates an event - self.is_everything_set(container) - - def destroy_fields(self): - """ - Method for destroying all the widgets declared within this class instance. - """ - # Destroy field 1 only if present. - if hasattr(self, 'field1'): - self.field1.destroy_fields() - self.field2.destroy_fields() - self.field3.destroy_fields() - - def set_fields_type(self, field1_type: FieldType, field2_type: FieldType, field3_type: FieldType): - """ - Method for setting the type of all the widgets declared within this class instance. - The available types are given by the values of the 'FieldType' enumeration. - It helps the interpretation of the fields choices when building the .inp file. - """ - # Set the field1 type only if present - if hasattr(self, 'field1'): - self.field1_type = field1_type.value - self.field2_type = field2_type.value - self.field3_type = field3_type.value + group1: str = "Radius" + group2: str = "Time" + group2A: str = "TimeIntegral" + group3: str = "Axial" class LabelledCombobox(): @@ -195,7 +35,8 @@ class LabelledCombobox(): . a label: providing a description of the option . a combobox: providing a mean for choosing among different alternative values """ - def __init__(self, container: ttk.Frame, row_index: int, label_text: str, cbx_list: list, state: str='readonly'): + def __init__(self, container: ttk.Frame, row_index: int, label_text: str, + cbx_list: List[str], state: str = 'readonly') -> None: """ Build an instance of the 'LabelledCombobox' class that provides the content of a configuration option. It receives as parameters: @@ -207,20 +48,20 @@ def __init__(self, container: ttk.Frame, row_index: int, label_text: str, cbx_li . state: a string describing the combobox state, which defaults to 'readonly' """ # Put a descriptive label - self.label = ttk.Label(container, text=label_text) + self.label: ttk.Label = ttk.Label(container, text=label_text) self.label.grid(column=0, row=row_index, sticky='w') # Declare a variable holding the choosen value from the combobox field - self.var = tk.StringVar() + self.var: tk.StringVar = tk.StringVar() # Instantiate the combobox - self.cbx = ttk.Combobox(container, textvariable=self.var, state=state, values=tuple(cbx_list)) + self.cbx: ttk.Combobox = ttk.Combobox(container, textvariable=self.var, state=state, values=tuple(cbx_list)) # Put the combobox field into the frame self.cbx.grid(column=1, row=row_index, sticky='ew') # Declare a variable stating if the combobox has been set - self.is_set = False + self.is_set: bool = False # Update the index indicating the row where to add additional widgets - self.row_next = row_index + 1 + self.row_next: int = row_index + 1 # Bind the selection of a value of the combobox to the execution of a method storing its value self.cbx.bind('<>', self.store_selected) @@ -256,7 +97,8 @@ class PlotSettingsField_2(): . a label: providing a description of the option . a combobox: providing a mean for choosing among different alternative values """ - def __init__(self, container, row_index: int, cbx_list: list): + def __init__(self, container: ttk.Frame, row_index: int, + cbx_list: List[str]) -> None: """ Build an instance of the 'PlotSettingsField_2' class that provides the means for configuring the 'start' and 'end' time values options in the GUI for plots as @@ -267,15 +109,15 @@ def __init__(self, container, row_index: int, cbx_list: list): . cbx_list: a list of values for the comboboxes to choose among """ # Put a descriptive label - self.label = ttk.Label(container, text="Time axis (h s ms): ") + self.label: ttk.Label = ttk.Label(container, text="Time axis (h s ms): ") self.label.grid(column=0, row=row_index, sticky='w') # Add the configuration field for the start time - self.start_time = LabelledCombobox(container, row_index+1, "Start: ", cbx_list) + self.start_time: LabelledCombobox = LabelledCombobox(container, row_index+1, "Start: ", cbx_list) # Add the configuration field for the end time (same values except for the first) - self.end_time = LabelledCombobox(container, row_index+2, "End: ", cbx_list[1:]) + self.end_time: LabelledCombobox = LabelledCombobox(container, row_index+2, "End: ", cbx_list[1:]) # Update the index indicating the row where to add additional widgets - self.row_next = row_index + 3 + self.row_next: int = row_index + 3 # Bind the selection of the time values to the check for their consistency self.start_time.cbx.bind('<>', func=lambda event: self.check_time_consistency()) self.end_time.cbx.bind('<>', func=lambda event: self.check_time_consistency()) @@ -283,7 +125,7 @@ def __init__(self, container, row_index: int, cbx_list: list): # Add an X-Y padding to the label widget herein defined self.label.grid_configure(padx=5, pady=2) - def check_time_consistency(self): + def check_time_consistency(self) -> None: """ Method called for checking that the value of the end time is greater than the one of the start time. @@ -303,7 +145,7 @@ def check_time_consistency(self): # Store a flag stating the comboboxes have been set self.is_set = True - def destroy_fields(self): + def destroy_fields(self) -> None: """ Method for destroying all the widgets declared with this class instance. """ @@ -321,7 +163,8 @@ class PlotSettingsListBox(): alternative values. This widget also presents a vertical scrollbar to help the inspection of the available values. """ - def __init__(self, container: ttk.Frame, label_text: str, choices: list, row_index: int): + def __init__(self, container: ttk.Frame, label_text: str, + choices: Union[List[str], None], row_index: int) -> None: """ Build an instance of the 'PlotSettingsListBox' class that provides the means for selecting one or more values from a given list. It receives as parameters: @@ -332,13 +175,13 @@ def __init__(self, container: ttk.Frame, label_text: str, choices: list, row_ind from which the built widgets are added """ # Put a descriptive label - self.label = ttk.Label(container, text=label_text) + self.label: ttk.Label = ttk.Label(container, text=label_text) self.label.grid(column=0, row=row_index, sticky='w') # Declare a variable holding the listbox possible choices, provided as input - self.choicesvar = tk.StringVar(value=choices) + self.choicesvar: tk.StringVar = tk.StringVar(value=choices) # Instantiate the listbox - self.choice_lb = tk.Listbox( + self.choice_lb: tk.Listbox = tk.Listbox( container, listvariable=self.choicesvar, selectmode=tk.EXTENDED, exportselection=False) # Put the listbox field into the frame self.choice_lb.grid(column=0, row=row_index+1, sticky='ew') @@ -361,16 +204,16 @@ def __init__(self, container: ttk.Frame, label_text: str, choices: list, row_ind self.choice_lb.grid_configure(padx=5, pady=2) # Update the index indicating the row where to add additional widgets - self.row_next = row_index + 2 + self.row_next: int = row_index + 2 - def destroy_fields(self): + def destroy_fields(self) -> None: """ Method for destroying all the widgets declared with this class instance. """ self.label.destroy() self.choice_lb.destroy() - def store_selected(self, event=None): + def store_selected(self, event: tk.Event = None) -> None: """ Method called whenever the user select one or more values from the listbox. It stores the values as a list in an instance variable. @@ -389,16 +232,183 @@ def store_selected(self, event=None): print("Selected values:", self.lb_selected_values) + +class PlotSettingsConfigurator(): + """ + Class that build the structure of the additional plot settings area as several rows + containing a label and a combobox each, provided as instances of the 'LabelledCombobox' + class, with a listbox at the end, as instance of the 'PlotSettingsListBox' class. + The plot group choice influences the structure of the built widgets. + """ + field1: LabelledCombobox + field2: Union[LabelledCombobox, PlotSettingsField_2] + field3: PlotSettingsListBox + + def __init__(self, container: ttk.Frame, group: GroupType, row_index: int) -> None: + """ + Build an instance of the 'PlotSettingsConfigurator' class that provides the structure + of the additional plot settings area in terms of three field with the first two being + instances of the 'LabelledCombobox' class, whereas the last one of the + 'PlotSettingsListBox' class. Depending on the plot group, the first field can be absent, + that is for groups '2A' and '3'. It receives as parameters: + . container: a frame object to which this instance is added + . group: a value of the enumeration 'GroupType' indicating the plot group + . row_index: an integer indicating the row index of the container grid where the + built widgets are added + """ + # Build the structure of the additional plot settings area: it is made of + # several rows containing a label and a combobox. A listbox handles the choice + # of the plots to produce. + # Build fields 1 and 2 depending on the plot group: field 1 can be absent for + # groups 2A and 3. + + match group: + case GroupType.group1: + # Instantiate a label followed by a combobox for both field 1 and 2 + self.field1 = LabelledCombobox(container, row_index, "", []) + self.field2 = LabelledCombobox(container, self.field1.row_next, "", []) + + # Bind each field selection to the execution of a method that checks if all fields have been set + self.field1.cbx.bind('<>', + lambda event: self.handle_selection(container, self.field1.store_selected)) + self.field2.cbx.bind('<>', + lambda event: self.handle_selection(container, self.field2.store_selected)) + case GroupType.group2: + # Instantiate a label followed by a combobox for field 1 + self.field1 = LabelledCombobox(container, row_index, "", []) + # Instantiate a label followed by two label + combobox groups + self.field2 = PlotSettingsField_2(container, self.field1.row_next, []) + + # Bind each field selection to the execution of a method that checks if all fields have been set + self.field1.cbx.bind('<>', + lambda event: self.handle_selection(container, self.field1.store_selected)) + self.field2.start_time.cbx.bind('<>', + lambda event: self.handle_selection(container, self.field2.check_time_consistency)) + self.field2.end_time.cbx.bind('<>', + lambda event: self.handle_selection(container, self.field2.check_time_consistency)) + case GroupType.group2A: + # Instantiate a label followed by two label + combobox groups + self.field2 = PlotSettingsField_2(container, row_index, []) + # Bind each field selection to the execution of a method that checks if all fields have been set + self.field2.start_time.cbx.bind('<>', + lambda event: self.handle_selection(container, self.field2.check_time_consistency)) + self.field2.end_time.cbx.bind('<>', + lambda event: self.handle_selection(container, self.field2.check_time_consistency)) + case GroupType.group3: + # Instantiate a label followed by a label + combobox group + self.field2 = LabelledCombobox(container, row_index, "Time (h s ms)", []) + # Bind each field selection to the execution of a method that checks if all fields have been set + self.field2.cbx.bind('<>', + lambda event: self.handle_selection(container, self.field2.store_selected)) + # Instantiate a label followed by a listbox + self.field3 = PlotSettingsListBox(container, "", [], self.field2.row_next) + # Bind field3 selection to the execution of a method that checks if all fields have been set + self.field3.choice_lb.bind('<>', + lambda event: self.handle_selection(container, self.field3.store_selected)) + + # Store the group value + self.group: GroupType = group + + # Get from the last field the index indicating the row where to add additional widgets + self.row_next: int = self.field3.row_next + print("BUTTON ROW", self.row_next) + + def configure_fields(self, + field1_lbl: str = None, field1_cbx: List[str] = None, + field2_lbl: str = "", field2_cbx: List[str] = list(), + field3_lbl: str = "", field3_lbx: List[str] = list()) -> None: + """ + Method for configuring all the widgets declared within this class instance. + """ + # Configure field 1 only if the group is not 2A or 3 + if not (self.group == GroupType.group2A or self.group == GroupType.group3): + self.field1.label.configure(text=field1_lbl) + self.field1.cbx.configure(values=field1_cbx) + # Configure field 2 label only if the group is not 2 or 2A + if not (self.group == GroupType.group2 or self.group == GroupType.group2A): + self.field2.label.configure(text=field2_lbl) + self.field2.cbx.configure(values=field2_cbx) + else: + # Case 2/2A: set the two combobox lists + self.field2.start_time.cbx.configure(values=field2_cbx) + self.field2.end_time.cbx.configure(values=field2_cbx[1:]) + # Configure the remaining field + self.field3.label.configure(text=field3_lbl) + self.field3.choicesvar.set(field3_lbx) + + def is_everything_set(self, container: ttk.Frame) -> None: + """ + Method that checks if every field has been set up. If so, it generates a + virtual event, in the container frame, representing this condition. + """ + # Check if this instance has declared field1 + if hasattr(self, 'field1'): + # Check if all the fields are set + if self.field1.is_set and self.field2.is_set and self.field3.is_set: + # Generate the virtual event in the container frame + container.event_generate('<>') + else: + # Check if the available fields are set + if self.field2.is_set and self.field3.is_set: + # Generate the virtual event in the container frame + container.event_generate('<>') + + def handle_selection(self, container: ttk.Frame, set_field: Callable) -> None: + """ + Method called whenever an event involving the set up of the value either of a combobox + or of a listbox happens. + It receives a generic function for setting the field value of the widget that generate + the event. It is called in order to set a widget flag stating the field has been set. + Afterwards, a check if all fields have been set is performed. + """ + # Call the input function for setting the field attribute + set_field() + # Check if this instance has declared field1 + if hasattr(self, 'field1'): + # Check if every field have been set ony if all have declared the corresponding flag + if hasattr(self.field1, 'is_set') and hasattr(self.field2, 'is_set') and hasattr(self.field3, 'is_set'): + # Call the function that checks the flags value and generates an event + self.is_everything_set(container) + else: + # Check if every field have been set ony if all have declared the corresponding flag + if hasattr(self.field2, 'is_set') and hasattr(self.field3, 'is_set'): + # Call the function that checks the flags value and generates an event + self.is_everything_set(container) + + def destroy_fields(self) -> None: + """ + Method for destroying all the widgets declared within this class instance. + """ + # Destroy field 1 only if present. + if hasattr(self, 'field1'): + self.field1.destroy_fields() + self.field2.destroy_fields() + self.field3.destroy_fields() + + def set_fields_type(self, field1_type: FieldType, field2_type: FieldType, + field3_type: FieldType) -> None: + """ + Method for setting the type of all the widgets declared within this class instance. + The available types are given by the values of the 'FieldType' enumeration. + It helps the interpretation of the fields choices when building the .inp file. + """ + # Set the field1 type only if present + if hasattr(self, 'field1'): + self.field1_type = field1_type.value + self.field2_type = field2_type.value + self.field3_type = field3_type.value + + if __name__ == "__main__": # Build a window - root = tk.Tk() + root: tk.Tk = tk.Tk() # Build the main frame - mainframe = ttk.Frame(root) + mainframe: ttk.Frame = ttk.Frame(root) # Place the frame into the window grid mainframe.grid(column=0, row=0) # Instantiate the class - plot_config = PlotSettingsConfigurator(mainframe, GroupType.group2, 0) + plot_config: PlotSettingsConfigurator = PlotSettingsConfigurator(mainframe, GroupType.group2, 0) plot_config.configure_fields( "Pippo", ["ciao", "bello"], "Pippo1: ", ["ciao1", "bello1"], @@ -408,7 +418,7 @@ def store_selected(self, event=None): ttk.Button(mainframe, command=plot_config.destroy_fields, text="DO NOT PRESS").grid(column=1, row=4) # plot_lb = PlotSettingsListBox(mainframe, ['ehi', 'come', 'va', 'in', 'città'], 3) - plot_config2 = PlotSettingsField_2(mainframe, 6, ['0 0 0', '1 30 100', '2 0 0', '2 25 0']) + plot_config2: PlotSettingsField_2 = PlotSettingsField_2(mainframe, 6, ['0 0 0', '1 30 100', '2 0 0', '2 25 0']) print(plot_config.field1_type, plot_config.field2_type, plot_config.field3_type) diff --git a/tugui/support.py b/tugui/support.py index c932965..bc7dd96 100644 --- a/tugui/support.py +++ b/tugui/support.py @@ -1,23 +1,23 @@ from enum import Enum - +from typing import Tuple class IDGA(Enum): """ Enumeration storing the different types of plots (field "Type"). """ - IDGA_1 = (1, "1 - Different Curve Numbers") - IDGA_2 = (2, "2 - Different Times") - IDGA_3 = (3, "3 - Different Slices") + IDGA_1: Tuple[int, str] = (1, "1 - Different Curve Numbers") + IDGA_2: Tuple[int, str] = (2, "2 - Different Times") + IDGA_3: Tuple[int, str] = (3, "3 - Different Slices") @property - def index(self): + def index(self) -> int: """ Index of the element of the enumeration """ return self.value[0] @property - def description(self): + def description(self) -> str: """ Descriptive string of the element of the enumeration """ @@ -28,19 +28,19 @@ class IANT(Enum): """ Enumeration storing the different types of IANT field. """ - IANT_1 = (1, "IANT 1") - IANT_2 = (2, "IANT 2") - IANT_3 = (3, "IANT 3") + IANT_1: Tuple[int, str] = (1, "IANT 1") + IANT_2: Tuple[int, str] = (2, "IANT 2") + IANT_3: Tuple[int, str] = (3, "IANT 3") @property - def index(self): + def index(self) -> int: """ Index of the element of the enumeration """ return self.value[0] @property - def description(self): + def description(self) -> str: """ Descriptive string of the element of the enumeration """ diff --git a/tugui/tab_builder.py b/tugui/tab_builder.py index 8ecfc72..3fc3a36 100644 --- a/tugui/tab_builder.py +++ b/tugui/tab_builder.py @@ -5,6 +5,7 @@ from ttkthemes import ThemedTk from abc import ABC, abstractmethod +from typing import Callable, List, Union from gui_configuration import GuiPlotFieldsConfigurator from plot_settings import FieldType, GroupType, PlotSettingsConfigurator, LabelledCombobox from plot_builder import PlotFigure @@ -22,7 +23,9 @@ class TabContentBuilder(ttk.Frame, ABC): left, while a text area on the right. The latter is where the report produced by the TU plot executables is shown. """ - def __init__(self, container: ttk.Notebook, guiConfig: GuiPlotFieldsConfigurator, tab_name: str, state: str): + def __init__(self, container: ttk.Notebook, + guiConfig: GuiPlotFieldsConfigurator, + tab_name: str, state: str) -> None: """ Build an instance of the 'TabContentBuilder' class that provides the content of a notebook tab as a frame. It receives as parameters: @@ -36,12 +39,12 @@ def __init__(self, container: ttk.Notebook, guiConfig: GuiPlotFieldsConfigurator super().__init__(container) # Store the GUI configuration - self.gui_config = guiConfig + self.gui_config: GuiPlotFieldsConfigurator = guiConfig # Build the tab content self._build_tab_content(container, tab_name, state) - def get_active_plotFigure(self): + def get_active_plotFigure(self) -> List[PlotFigure]: """ Method that returns the 'PlotFigure' instance of the currently active tab of the notebook displaying the plots. @@ -66,7 +69,7 @@ def get_active_plot_tab_index(self) -> int: self.run_button.configure(state=tk.DISABLED) raise Exception("No plot figures are currently present. Please, create a new one first.") - def run_plot(self, func=None): + def run_plot(self, func: Union[Callable, None] = None) -> None: """ Method for storing the input function as an instance attribute, if any is provided. @@ -75,7 +78,7 @@ def run_plot(self, func=None): if func != None: self.run_func = func - def set_slice_list(self, slices: list): + def set_slice_list(self, slices: List[str]) -> None: """ Method for setting the list of strings used for populating the slice field in the plot configuration area. @@ -83,7 +86,7 @@ def set_slice_list(self, slices: list): self.slice_settings = slices @abstractmethod - def set_times(self, **kwargs): + def set_times(self, **kwargs) -> None: """ Abstract method that allows to set the instance attributes that corresponds to the simulation step times. @@ -91,7 +94,7 @@ def set_times(self, **kwargs): their specific implementation. """ - def _add_new_plot_figure(self, plot_name: str): + def _add_new_plot_figure(self, plot_name: str) -> None: """ Method that adds a new 'PlotFigure' object to this instance notebook. Given the prefix input string, this method assigns a name to the @@ -133,7 +136,7 @@ def _add_new_plot_figure(self, plot_name: str): self.plotTabControl.select(self.plotTabControl.index('end')-1) @abstractmethod - def _build_configuration_fields(self, config_area: ttk.LabelFrame): + def _build_configuration_fields(self, config_area: ttk.LabelFrame) -> None: """ Abstract method for building the plot configuration fields area, provided as input. This method, being an interface, will be overridden by subclasses @@ -141,7 +144,7 @@ def _build_configuration_fields(self, config_area: ttk.LabelFrame): """ return - def _build_plot_config_area(self, paned_window: ttk.PanedWindow): + def _build_plot_config_area(self, paned_window: ttk.PanedWindow) -> ttk.Frame: """ Method for building the plot configuration area as a frame holding a 'LabelFrame' object within which the configuration fields and a button object for running the @@ -172,7 +175,7 @@ def _build_plot_config_area(self, paned_window: ttk.PanedWindow): # Return the built frame return frame - def _build_plot_figure(self, container): + def _build_plot_figure(self, container: tk.Misc) -> ttk.Frame: """ Method that builds the content of the area where the selected TU quantities are plotted. A Frame object, placed within the input container, holds the content of the plot area, @@ -222,7 +225,7 @@ def _build_plot_figure(self, container): # Return the just built frame object return frame - def _build_quick_access_buttons(self, panedwindow: ttk.PanedWindow): + def _build_quick_access_buttons(self, panedwindow: ttk.PanedWindow) -> None: """ Method that builds a quick-access area with utility buttons to collapse the lateral panes of the window. @@ -268,7 +271,7 @@ def _build_quick_access_buttons(self, panedwindow: ttk.PanedWindow): self.bind("<>", lambda event: hide_show_report_button.deactivate_label()) self.bind("<>", lambda event: hide_show_report_button.activate_label()) - def _build_tab_content(self, container: ttk.Notebook, tab_name: str, state=tk.NORMAL): + def _build_tab_content(self, container: ttk.Notebook, tab_name: str, state=tk.NORMAL) -> None: """ Method that builds the content of a tab held by the input Notebook object. Its name and state (default value to NORMAL, meaning the tab is active) are provided as input as well. @@ -307,7 +310,7 @@ def _build_tab_content(self, container: ttk.Notebook, tab_name: str, state=tk.NO # Add the just built frame to the notebook container.add(self, text=tab_name, state=state) - def expand_collapse_config_area(self, panedwindow: ttk.PanedWindow): + def expand_collapse_config_area(self, panedwindow: ttk.PanedWindow) -> None: """ Method that, given the input 'PanedWindow' object, collapses or expands the plot configuration section. This object is associated to an instance attribute that @@ -330,7 +333,7 @@ def expand_collapse_config_area(self, panedwindow: ttk.PanedWindow): # Hide the pane object from the given PanedWindow panedwindow.remove(self.config_area) - def expand_collapse_report_area(self): + def expand_collapse_report_area(self) -> None: """ Method that, given the input 'PanedWindow' object, collapses or expands the plot report section. This object is associated to an instance attribute that is saved @@ -360,7 +363,8 @@ def expand_collapse_report_area(self): # Hide the pane object from the given PanedWindow active_pw.remove(active_pw.report_area) - def handle_tab_change(self, event, hide_show_report_button: OnOffClickableLabel): + def handle_tab_change(self, event: tk.Event, + hide_show_report_button: OnOffClickableLabel) -> None: """ Method that is called whenever a plot tab is changed. It retrieves the 'PanedWindow' object of the 'PlotFigure' for the currently active tab. @@ -404,7 +408,9 @@ class TuPlotTabContentBuilder(TabContentBuilder): While keeping the same structure, this class implements the widgets contained in the configuration area, as they are specific of the 'TuPlot' case. """ - def __init__(self, container: ttk.Notebook, guiConfig: GuiPlotFieldsConfigurator, tab_name: str, state: str): + def __init__(self, container: ttk.Notebook, + guiConfig: GuiPlotFieldsConfigurator, + tab_name: str, state: str) -> None: """ Build an instance of the 'TuPlotTabContentBuilder' class that provides the content of a notebook tab. It receives as parameters: @@ -421,7 +427,7 @@ def __init__(self, container: ttk.Notebook, guiConfig: GuiPlotFieldsConfigurator # Specify the text of the button for running the plot executable self.run_button.configure(text='Run TuPlot') - def set_times(self, **kwargs): + def set_times(self, **kwargs) -> None: """ Method that allows to set the instance attributes for the simulation macro and micro step times. @@ -437,7 +443,8 @@ def set_times(self, **kwargs): self.macro_time = kwargs['macro_time'] self.micro_time = kwargs['micro_time'] - def _activate_additional_settings(self, box_to_check: ttk.Combobox, container: ttk.Frame, row: int): + def _activate_additional_settings(self, box_to_check: ttk.Combobox, + container: ttk.Frame, row: int) -> None: """ Method that is called whenever the "Number" or the "Type" combobox values are selected. It checks if the other combobox is selected and, if so, it activates the frame that @@ -497,7 +504,9 @@ def _activate_additional_settings(self, box_to_check: ttk.Combobox, container: t # Change the run button state when elements are selected in every additional field self.additional_frame.bind('<>', func=lambda event: self.run_button.configure(state=tk.ACTIVE)) - def _activate_fields(self, event=None, number: ttk.Combobox=None, type: ttk.Combobox=None): + def _activate_fields(self, event: Union[tk.Event, None] = None, + number: Union[ttk.Combobox, None] = None, + type: Union[ttk.Combobox, None] = None) -> None: """ Method that activates the "Number" and "Type" fields whenever the user chooses a value for the "Group" field in the plot settings area. @@ -539,7 +548,8 @@ def _activate_fields(self, event=None, number: ttk.Combobox=None, type: ttk.Comb # Hide the button for running the plot executable self.run_button.grid_remove() - def _build_iant_field(self, container, row, label: str, values: list, row_index: int, iant: IANT): + def _build_iant_field(self, container: tk.Misc, row, label: str, + values: List[str], row_index: int, iant: IANT) -> int: """ Method that builds a field for setting the IANT value in case the plot number is any in the range 102-108 (for radiation stress related cases) or the 113 one (for @@ -564,7 +574,7 @@ def _build_iant_field(self, container, row, label: str, values: list, row_index: # Update the row index return row_index + 1 - def _build_configuration_fields(self, config_area: ttk.LabelFrame): + def _build_configuration_fields(self, config_area: ttk.LabelFrame) -> None: """ Method that builds the fields, in terms of the widgets, enabling the user to configure the quantities to plot for a 'TuPlot' case. This method overrides @@ -593,7 +603,7 @@ def _build_configuration_fields(self, config_area: ttk.LabelFrame): number.cbx.bind('<>', func=lambda event: self._activate_additional_settings(type.cbx, config_area, 3)) type.cbx.bind('<>', func=lambda event: self._activate_additional_settings(number.cbx, config_area, 3)) - def _build_frame(self, container, row): + def _build_frame(self, container: tk.Misc, row: int) -> ttk.Frame: """ Method that builds and returns a frame within the given container object. It also places the frame within the container at the specified row index @@ -610,7 +620,7 @@ def _build_frame(self, container, row): # Return the just built frame return frame - def _configure_additional_fields(self, group: GroupType): + def _configure_additional_fields(self, group: GroupType) -> None: """ Method for configuring the values of the additional plot settings fields, provided by the 'PlotSettingsConfigurator' instance, according to the choices made for the "Type" field. @@ -680,7 +690,9 @@ class TuStatTabContentBuilder(TabContentBuilder): While keeping the same structure, this class implements the widgets contained in the configuration area, as they are specific of the 'TuStat' case. """ - def __init__(self, container: ttk.Notebook, guiConfig: GuiPlotFieldsConfigurator, tab_name: str, state: str): + def __init__(self, container: ttk.Notebook, + guiConfig: GuiPlotFieldsConfigurator, + tab_name: str, state: str) -> None: """ Build an instance of the 'TuStatTabContentBuilder' class that provides the content of a notebook tab. It receives as parameters: @@ -697,7 +709,7 @@ def __init__(self, container: ttk.Notebook, guiConfig: GuiPlotFieldsConfigurator # Specify the text of the button for running the plot executable self.run_button.configure(text='Run TuStat') - def set_times(self, **kwargs): + def set_times(self, **kwargs) -> None: """ Method that allows to set the instance attribute for the step times of the statistical simulation. @@ -709,7 +721,7 @@ def set_times(self, **kwargs): # Store the times in the corresponding instance attributes self.sta_time = kwargs['sta_times'] - def _activate_fields(self, event=None): + def _activate_fields(self, event: Union[tk.Event, None] = None) -> None: """ Method that activates the 'TuStat' tab plot configuration fields whenever the user chooses a value for the "Diagram Nr." field. @@ -738,7 +750,7 @@ def _activate_fields(self, event=None): # Show the button for running the plot executable self.run_button.grid(column=1, row=8, sticky='e') - def _are_all_fields_set(self, event=None): + def _are_all_fields_set(self, event: Union[tk.Event, None] = None) -> None: """ Method called whenever a combobox field is set for checking if all the other fields have been set. If so, the button for running the TuStat executable is enabled. @@ -751,7 +763,7 @@ def _are_all_fields_set(self, event=None): # Disable the button self.run_button.configure(state=tk.DISABLED) - def _build_configuration_fields(self, config_area: ttk.LabelFrame): + def _build_configuration_fields(self, config_area: ttk.LabelFrame) -> None: """ Method that builds the fields, in terms of the widgets, enabling the user to configure the quantities to plot for a 'TuStat' case. This method overrides @@ -783,20 +795,20 @@ def _build_configuration_fields(self, config_area: ttk.LabelFrame): if __name__ == "__main__": # Intantiate the root window - root = ThemedTk() + root: ThemedTk = ThemedTk() root.configure(theme='radiance') # Instantiate a notebook - tabControl = ttk.Notebook(root) + tabControl: ttk.Notebook = ttk.Notebook(root) tabControl.pack(fill='both', expand=True) # Instantiate the GuiPlotFieldsConfigurator class providing the values for filling the fields - guiConfig = GuiPlotFieldsConfigurator.init_GuiPlotFieldsConfigurator_attrs() + guiConfig: GuiPlotFieldsConfigurator = GuiPlotFieldsConfigurator.init_GuiPlotFieldsConfigurator_attrs() # Instatiate a TabBuilder object holding the tabs - tab1 = TuPlotTabContentBuilder( + tab1: TuPlotTabContentBuilder = TuPlotTabContentBuilder( container=tabControl, tab_name="Tab 1", state='normal', guiConfig=guiConfig) tab1.set_slice_list(["Slice 1", "Slice 2", "Slice 3"]) - tab2 = TuStatTabContentBuilder( + tab2: TuStatTabContentBuilder = TuStatTabContentBuilder( container=tabControl, tab_name="Tab 2", state='normal', guiConfig=guiConfig) tab2.set_slice_list(["Slice 1", "Slice 2"]) diff --git a/tugui/tu_interface.py b/tugui/tu_interface.py index 926e48c..3ad5481 100644 --- a/tugui/tu_interface.py +++ b/tugui/tu_interface.py @@ -1,8 +1,10 @@ import os import platform import shutil -from typing import Dict, List +from typing import Dict, List, Tuple +from typing_extensions import Self import numpy as np +from numpy.typing import NDArray import re from abc import ABC, abstractmethod @@ -29,7 +31,7 @@ class TuInp: diagr_type: DiagramCharacteristics = None @staticmethod - def configure_tuplot_inp_fields(info: dict): + def configure_tuplot_inp_fields(info: Dict[str, str]) -> Self: """ Method that builds and configures the fields of the 'TuInp' dataclass for the 'TuPlot' case. This is done by getting the needed information @@ -78,7 +80,7 @@ def configure_tuplot_inp_fields(info: dict): return tuinp @staticmethod - def configure_tustat_inp_fields(info: dict): + def configure_tustat_inp_fields(info: Dict[str, str]) -> Self: """ Method that builds and configures the fields of the 'TuInp' dataclass for the 'TuStat' case. This is done by getting the needed information @@ -123,7 +125,7 @@ class InpHandler(): Class for handling the functionalities devoted to reading and writing a plot configuration .inp file. """ - def __init__(self, inp_path: str): + def __init__(self, inp_path: str) -> None: """ Construct an instance of this class by receiving the path to the .inp file. """ @@ -131,14 +133,14 @@ def __init__(self, inp_path: str): self.inp_path = inp_path self.inp_dir = os.path.dirname(self.inp_path) - def read_inp_file(self): + def read_inp_file(self) -> None: """ Method that reads an input .inp file and interprets its content according to the specific 'TuPlot' or 'TuStat' subroutine it was created from. """ # Declare a list of dataclasses holding the configuration values for # each diagram declared in the loaded input file - self.diagrams_list: list[TuInp] = list() + self.diagrams_list: List[TuInp] = list() # Declare an index representing the plot index number plot_index = 0 @@ -159,7 +161,7 @@ def read_inp_file(self): # Extract and store all the information describing the plot configuration of a single diagram self._extract_diagram_info(plot_index, inp) - def save_inp_file(self, diagrams: list[TuInp]): + def save_inp_file(self, diagrams: List[TuInp]) -> None: """ Method that saves 1 or more plot configuration diagrams on a single .inp file. The plots configuration are provided as a list of 'TuInp' dataclasses storing @@ -176,7 +178,7 @@ def save_inp_file(self, diagrams: list[TuInp]): f.write(diagr.diagram_config) f.write(diagr.ikon + '\n') - def save_loaded_inp(self): + def save_loaded_inp(self) -> str: """ Method that saves the loaded .inp file in the current working directory if its name is different from 'TuPlot.inp' or 'TuStat.inp', as the post- @@ -223,7 +225,8 @@ def save_loaded_inp(self): # Return the path to the saved .inp file return os.path.join(self.inp_dir, filename) - def _extract_diagram_info(self, plot_index: int, inp_file_handle: TextIOWrapper): + def _extract_diagram_info(self, plot_index: int, + inp_file_handle: TextIOWrapper) -> None: """ Method that extract from the given 'TextIOWrapper' instance, given by opening the .inp file for reading, all the information about the plot configuration of a single @@ -276,7 +279,7 @@ def _extract_diagram_info(self, plot_index: int, inp_file_handle: TextIOWrapper) inp_config.diagram_config += line -def check_file_existence(file_path: str, file_extension: str): +def check_file_existence(file_path: str, file_extension: str) -> None: """ Function that can be accessed globally for checking if the given file path exists and is a file. If not, the function raises an @@ -302,7 +305,9 @@ class DatGenerator(): out_paths: List[str] = field(default_factory=list) @staticmethod - def init_DatGenerator_and_run_exec(plotexec_path: str, inp_path: str, plots_num: int, cwd: str, output_files_name: str): + def init_DatGenerator_and_run_exec(plotexec_path: str, inp_path: str, + plots_num: int, cwd: str, + output_files_name: str) -> Self: """ Static method that initialize the 'DatGenerator' dataclass by providing all the needed information received as input to this function. @@ -368,7 +373,7 @@ def init_DatGenerator_and_run_exec(plotexec_path: str, inp_path: str, plots_num: return dat_gen -def run_plot_files_generation(datGen: DatGenerator): +def run_plot_files_generation(datGen: DatGenerator) -> Self: """ Function that runs the plotting executable by feeding it with the .inp file. Since the run needs to be in the folder of the .inp input file, the current @@ -445,7 +450,7 @@ class PliReader(): axial_steps: str = '' @staticmethod - def init_PliReader(pli_path: str): + def init_PliReader(pli_path: str) -> Self: """ Method that builds and configures the 'PliReader' dataclass by providing all the needed information that come by interpreting the content of the .pli file @@ -519,7 +524,7 @@ class DaReader(ABC): by the TU simulation. It provides methods to be overridden by its subclasses. """ - def __init__(self, da_path: str, extension: str): + def __init__(self, da_path: str, extension: str) -> None: """ Build an instance of the 'DaReader' class. It receives as parameter the path to the direct-access file to read and checks its actual existence. @@ -527,16 +532,16 @@ def __init__(self, da_path: str, extension: str): # Check the direct-access file existence check_file_existence(da_path, extension) # Store the direct-access file path - self.da_path = da_path + self.da_path: str = da_path # Initialize the time values read from the direct-access file - self.time_h = list() - self.time_s = list() - self.time_ms : list[int] = list() + self.time_h: List[int] = list() + self.time_s: List[int] = list() + self.time_ms : List[int] = list() # Call the ABC constructor super().__init__() @abstractmethod - def extract_time_hsms(self, record_length: str) -> tuple[list[int], list[int], list[int]]: + def extract_time_hsms(self, record_length: int) -> Tuple[List[int], List[int], List[int]]: """ Method that extracts from the direct-access file the simulation time instants as arrays for hours, seconds and milliseconds respectively. These arrays are @@ -544,7 +549,7 @@ def extract_time_hsms(self, record_length: str) -> tuple[list[int], list[int], l """ @abstractmethod - def read_tu_data(self, record_length): + def read_tu_data(self, record_length: int) -> NDArray[np.float32]: """ Method that opens the direct-access file and re-elaborate its content, originally stored as a one-line array. Given the record length, the array is reshaped as a 2D array having: @@ -561,7 +566,7 @@ class MicReader(DaReader): For every micro-step (TU internal step) several quantities are stored, that are section/slice dependent variables and special quantities. """ - def __init__(self, mic_path: str): + def __init__(self, mic_path: str) -> None: """ Build an instance of the 'MicReader' class that interprets the content of the .mic file produced by the TU simulation. @@ -573,7 +578,7 @@ def __init__(self, mic_path: str): # Call the superclass constructor super().__init__(mic_path, self.extension) - def extract_time_hsms(self, record_length): + def extract_time_hsms(self, record_length: int) -> Tuple[List[int], List[int], List[int]]: """ Method that extracts from the direct-access file the simulation time instants as arrays for hours, seconds and milliseconds respectively. These arrays are @@ -588,7 +593,7 @@ def extract_time_hsms(self, record_length): # Return the tuple of times return (self.time_h, self.time_s, self.time_ms) - def read_tu_data(self, record_length): + def read_tu_data(self, record_length: int) -> NDArray[np.float32]: """ Method that opens the direct-access file and re-elaborate its content, originally stored as a one-line array. Given the record length, the array is reshaped as a 2D array having: @@ -616,7 +621,7 @@ class MacReader(MicReader): the radially dependent quantities at every simulation time for every (i, j)-th element of the domain. """ - def __init__(self, mac_path: str, n_slices: int): + def __init__(self, mac_path: str, n_slices: int) -> None: """ Build an instance of the 'MacReader' class that interprets the content of the .mac file produced by the TU simulation. @@ -624,14 +629,14 @@ def __init__(self, mac_path: str, n_slices: int): existence of the file. """ # Store the file extension - self.extension = 'mac' + self.extension: str = 'mac' # Call the superclass constructor super().__init__(mac_path) # Store the number of slices - self.n_slices = n_slices + self.n_slices: int = n_slices - def extract_xtime_hsms(self, record_length): + def extract_xtime_hsms(self, record_length: int) -> Tuple[List[int], List[int], List[int]]: """ Method that extracts from the .mac file the simulation time instants as arrays for hours, seconds and milliseconds respectively. These values are provided @@ -654,7 +659,7 @@ class StaReader(DaReader): Class that interprets the content of the .sta file produced by the TU simulation. For every time step (choosen by users) several quantities are stored. """ - def __init__(self, sta_path: str, ibyte): + def __init__(self, sta_path: str, ibyte: int) -> None: """ Build an instance of the 'MicReader' class that interprets the content of the .mic file produced by the TU simulation. @@ -665,9 +670,10 @@ def __init__(self, sta_path: str, ibyte): super().__init__(sta_path, 'sta') # Store the ibyte value - self.ibyte = ibyte + self.ibyte: int = ibyte - def extract_time_hsms(self, record_length: int, axial_steps: int, sta_dataset_length: int): + def extract_time_hsms(self, record_length: int, axial_steps: int, + sta_dataset_length: int) -> Tuple[List[int], List[int], List[int]]: """ Method that overrides the superclass method for extracting the time steps from a generic direct-access file. @@ -693,7 +699,8 @@ def extract_time_hsms(self, record_length: int, axial_steps: int, sta_dataset_le # Return the tuple of times return (self.time_h, self.time_s, self.time_ms) - def read_tu_data(self, record_length: int, axial_steps: int, sta_dataset_length: int): + def read_tu_data(self, record_length: int, axial_steps: int, + sta_dataset_length: int) -> NDArray[np.float32]: """ Method that overrides the superclass method for extracting the content of a generic direct-access file. @@ -732,22 +739,22 @@ def read_tu_data(self, record_length: int, axial_steps: int, sta_dataset_length: # 4-MacReader # 5-StaReader # 6-InpHandler (loading .inp) - test = 1 + test: int = 1 match test: case 1: print("Testing the interface to Tuplotgui executable") # Get the absolute path of the current file - abspath = os.path.abspath(__file__) + abspath: str = os.path.abspath(__file__) # Get the file directory path - dname = os.path.dirname(abspath) + dname: str = os.path.dirname(abspath) # Run the method that deals with instantiating the dataclass storing the needed # information for the plotting executable to be run. The corresponding executable # is run afterwards and the paths to the output .dat and .plt files, stored in the # returned object, are updated. - inp_to_dat = DatGenerator.init_DatGenerator_and_run_exec( + inp_to_dat: DatGenerator = DatGenerator.init_DatGenerator_and_run_exec( plotexec_path=os.path.join(dname, "bin/tuplotgui_nc"), inp_path="../tests/input/TuPlot.inp", plots_num=1, @@ -756,10 +763,10 @@ def read_tu_data(self, record_length: int, axial_steps: int, sta_dataset_length: case 2: print("Testing the interface to .mic file") # Extract the information from the .pli file and instantiate the 'PliReader' class - plireader = PliReader.init_PliReader("../Input/rodcd.pli") + plireader: PliReader = PliReader.init_PliReader("../Input/rodcd.pli") # Instantiate the MicReader class - micreader = MicReader(os.path.dirname(plireader.pli_path) + os.sep + plireader.mic_path) + micreader: MicReader = MicReader(os.path.dirname(plireader.pli_path) + os.sep + plireader.mic_path) # Extract the time values (h, s, ms) = micreader.extract_time_hsms(int(plireader.mic_recordLength)) print(h, s, ms) @@ -767,7 +774,7 @@ def read_tu_data(self, record_length: int, axial_steps: int, sta_dataset_length: # PliReader case print("Testing the interface to the .pli file") # Extract the information from the .pli file and instantiate the 'PliReader' class - plireader = PliReader.init_PliReader("../Input/rodcd.pli") + plireader: PliReader = PliReader.init_PliReader("../Input/rodcd.pli") print("Path to the .pli file: " + plireader.pli_path) print(plireader.opt_dict) @@ -778,10 +785,10 @@ def read_tu_data(self, record_length: int, axial_steps: int, sta_dataset_length: case 4: print("Testing the interface to .mac file") # Extract the information from the .pli file and instantiate the 'PliReader' class - plireader = PliReader.init_PliReader("../Input/rodcd.pli") + plireader: PliReader = PliReader.init_PliReader("../Input/rodcd.pli") # Instantiate the MacReader class - macreader = MacReader(os.path.dirname(plireader.pli_path) + os.sep + plireader.mac_path, + macreader: MacReader = MacReader(os.path.dirname(plireader.pli_path) + os.sep + plireader.mac_path, int(plireader.opt_dict['M3'])) # Extract the time values (h, s, ms) = macreader.extract_xtime_hsms(int(plireader.mac_recordLength)) @@ -790,10 +797,10 @@ def read_tu_data(self, record_length: int, axial_steps: int, sta_dataset_length: case 5: print("Testing the interface to .sta file") # Extract the information from the .pli file and instantiate the 'PliReader' class - plireader = PliReader.init_PliReader("../Input/rodcd.pli") + plireader: PliReader = PliReader.init_PliReader("../Input/rodcd.pli") # Instantiate the StaReader class - stareader = StaReader(os.path.dirname(plireader.pli_path) + os.sep + plireader.sta_path, + stareader: StaReader = StaReader(os.path.dirname(plireader.pli_path) + os.sep + plireader.sta_path, int(plireader.opt_dict['IBYTE'])) # stareader.read_tu_data(int(plireader.sta_recordLength), int(plireader.opt_dict['M3']), int(plireader.sta_dataset)) # Extract the time values @@ -806,7 +813,7 @@ def read_tu_data(self, record_length: int, axial_steps: int, sta_dataset_length: case 6: print("Testing the interface to .inp file") # Instantiate the 'InpHandler' class - inpreader = InpHandler("../Input/TuPlot_2diagrams.inp") + inpreader: InpHandler = InpHandler("../Input/TuPlot_2diagrams.inp") # Read the loaded .inp file and extract its content inpreader.read_inp_file() # Save the content of the read .inp file into a file whose name complies with