From adadd4a20c05a10279690fb8fe99e731afde6381 Mon Sep 17 00:00:00 2001 From: edouardbruelhart Date: Thu, 11 Apr 2024 18:28:52 +0200 Subject: [PATCH] completely changed application structure --- ms_sample_list_creator/csv_batch.py | 38 ++-- ms_sample_list_creator/home_page.py | 176 +++--------------- .../ms_sample_list_creator.py | 65 +++++++ ms_sample_list_creator/new_batch.py | 46 ++--- ms_sample_list_creator/pieces.py | 25 +++ ms_sample_list_creator/prefix_window.py | 47 +++++ 6 files changed, 211 insertions(+), 186 deletions(-) create mode 100644 ms_sample_list_creator/ms_sample_list_creator.py create mode 100644 ms_sample_list_creator/pieces.py create mode 100644 ms_sample_list_creator/prefix_window.py diff --git a/ms_sample_list_creator/csv_batch.py b/ms_sample_list_creator/csv_batch.py index 8cb3cc8..ca47db2 100644 --- a/ms_sample_list_creator/csv_batch.py +++ b/ms_sample_list_creator/csv_batch.py @@ -3,11 +3,8 @@ from datetime import datetime from tkinter import filedialog -import home_page - - class csvBatch(tk.Frame): - def __init__(self, csv_path, parent, *args, **kwargs): + def __init__(self, csv_batch_window, root): """ Initializes an instance of the class. @@ -18,11 +15,16 @@ def __init__(self, csv_path, parent, *args, **kwargs): Returns: None """ - print("csv batch") + self.csv_batch_window = csv_batch_window + self.root = root + + # Make CsvWindow wait for AskBoxPrefixWindow result + self.root.withdraw() - tk.Frame.__init__(self, csv_path, parent, *args, **kwargs) + self.csv_batch_window.protocol("WM_DELETE_WINDOW", self.on_exit) self.operator = str(os.environ.get("OPERATOR")) + self.output_folder = os.environ.get("OUTPUT_FOLDER") self.ms_id = str(os.environ.get("MS_ID")) self.col_rack_size = int(str(os.environ.get("COL_RACK_NUMBER"))) self.row_rack_size = int(str(os.environ.get("ROW_RACK_NUMBER"))) @@ -35,31 +37,33 @@ def __init__(self, csv_path, parent, *args, **kwargs): self.method_file = str(os.environ.get("METHOD_FILE")) self.data_path = str(os.environ.get("DATA_FOLDER")) self.standby_file = str(os.environ.get("STANDBY_FILE")) - self.csv_path = csv_path + self.file = str(os.environ.get("FILE")) self.current_position = 1 self.current_row = 1 self.timestamp = datetime.now().strftime("%Y%m%d%H%M") + self.csv_path = f"{self.output_folder}/{datetime.now().strftime('%Y%m%d')}_{self.operator}_dbgi_{self.file}.csv" - # Create widgets for the main page - label = tk.Label(self, text="Search for your CSV:") + warning_label = tk.Label(self.csv_batch_window, text="Warning, this mode is exclusively made to submit sample lists that have already been made using this tool.") + warning_label.pack() + + label = tk.Label(self.csv_batch_window, text="Search for your CSV:", pady=10) label.pack() - import_button = tk.Button(self, text="Import your CSV", width=17, command=self.import_csv) + import_button = tk.Button(self.csv_batch_window, text="Import your CSV", width=17, command=self.import_csv, pady=10) import_button.pack() - button_submit = tk.Button(self, text="Submit", command=self.show_values) + button_submit = tk.Button(self.csv_batch_window, text="Submit", width=17, command=self.show_values, pady=10) button_submit.pack() - button_back = tk.Button(self, text="Back to Main Page", command=self.back_to_main) + button_back = tk.Button(self.csv_batch_window, text="Go back to home", width=17, command=self.on_exit, pady=10) button_back.pack() + def on_exit(self): + self.csv_batch_window.destroy() + self.root.deiconify() + def import_csv(self): os.environ["FILE_PATH"] = filedialog.askopenfilename(filetypes=[("CSV Files", "*.csv")]) def show_values(self): print("correctly written") - - def back_to_main(self): - # Destroy Window 2 and show the main page - self.destroy() - home_page.HomeWindow.pack() diff --git a/ms_sample_list_creator/home_page.py b/ms_sample_list_creator/home_page.py index ba242f8..e22108b 100644 --- a/ms_sample_list_creator/home_page.py +++ b/ms_sample_list_creator/home_page.py @@ -1,29 +1,18 @@ -# To generate binaries for this script, install pyinstaller (pip install pyinstaller) and run "pyinstaller --onefile main.py" -# Generated binaries are made for the native system where the pyinstaller command is run. - -# You can generate windows executable from linux using wine, by previously installing wine, python 3.8.19, pyinstaller and -# other non-built-in packages (here requests) inside wine. Then run: wine pyinstaller --onefile main.py - import os import tkinter as tk -from datetime import datetime from tkinter import filedialog -from typing import Any -import csv_batch -import new_batch import requests class HomeWindow(tk.Frame): - def __init__(self, parent: tk.Widget, *args: Any, **kwargs: Any): + def __init__(self, parent, *args, **kwargs): """ Initializes an instance of the class. Args: - parent (tk.Widget): The parent widget or window where this frame will be placed. - *args: Additional positional arguments that may be passed to the parent class constructor (optional). - **kwargs: Additional keyword arguments that may be passed to the parent class constructor (optional). + root(tk.Tk): The parent widget or window where this frame will be placed. + csv_path(str): CSV path and name. Returns: None @@ -31,9 +20,6 @@ def __init__(self, parent: tk.Widget, *args: Any, **kwargs: Any): tk.Frame.__init__(self, parent, *args, **kwargs) - # Bind the destroy event to the callback function - # window.protocol("WM_DELETE_WINDOW", self.destroy_window) - # Create a variable to store the entered text self.username = tk.StringVar(None) self.password = tk.StringVar(None) @@ -194,22 +180,9 @@ def __init__(self, parent: tk.Widget, *args: Any, **kwargs: Any): self.output_path_button = tk.Button(frame_entry_output, text="output", width=17, command=self.output_folder) self.output_path_button.pack(side="right", padx=(0, 1), anchor="center") - frame_submit = tk.Frame(self) - frame_submit.pack(pady=(50, 0)) - - button_new_batch = tk.Button( - frame_submit, text="New sample list", width=20, command=lambda: self.show_values(clicked_button="new") - ) - button_new_batch.pack(side="left") - - button_submit_csv = tk.Button( - frame_submit, text="Sample list from CSV", width=20, command=lambda: self.show_values(clicked_button="csv") - ) - button_submit_csv.pack(side="right") - - def destroy_window(self) -> None: + def data_folder(self) -> None: """ - Destroys the window when close button is pressed. + Asks the user to choose the data folder where MS data will be stored. Args: None @@ -217,7 +190,13 @@ def destroy_window(self) -> None: Returns: None """ - window.destroy() + + data_folder = filedialog.askdirectory() + if data_folder: + os.environ["DATA_FOLDER"] = data_folder + parts = data_folder.split("/") + folder = parts[-1] + self.data_path_button.config(text=folder) def method_file(self) -> None: """ @@ -234,11 +213,12 @@ def method_file(self) -> None: os.environ["METHOD_FILE"] = method_file parts = method_file.split("/") self.file = parts[-1] + os.environ["FILE"] = self.file self.method_path_button.config(text=self.file) - def data_folder(self) -> None: + def standby_file(self) -> None: """ - Asks the user to choose the data folder where MS data will be stored. + Asks the user to choose the Standby method file he wants to use. Args: None @@ -246,13 +226,12 @@ def data_folder(self) -> None: Returns: None """ - - data_folder = filedialog.askdirectory() - if data_folder: - os.environ["DATA_FOLDER"] = data_folder - parts = data_folder.split("/") - folder = parts[-1] - self.data_path_button.config(text=folder) + standby_file = filedialog.askopenfilename(filetypes=[("methods", "*.meth")]).split(".")[0] + if standby_file: + os.environ["STANDBY_FILE"] = standby_file + parts = standby_file.split("/") + file = parts[-1] + self.standby_path_button.config(text=file) def output_folder(self) -> None: """ @@ -271,23 +250,6 @@ def output_folder(self) -> None: folder = parts[-1] self.output_path_button.config(text=folder) - def standby_file(self) -> None: - """ - Asks the user to choose the Standby method file he wants to use. - - Args: - None - - Returns: - None - """ - standby_file = filedialog.askopenfilename(filetypes=[("methods", "*.meth")]).split(".")[0] - if standby_file: - os.environ["STANDBY_FILE"] = standby_file - parts = standby_file.split("/") - file = parts[-1] - self.standby_path_button.config(text=file) - def show_values(self, clicked_button) -> None: """ Stores all the parameters to the environment when user confirms his choice. @@ -410,7 +372,7 @@ def testConnection(self) -> None: def manage_choice(self) -> None: """ - Redirects the user to the correct next window (new batch or csv batch). + Returns to main script which option did the user choose. Args: None @@ -419,93 +381,11 @@ def manage_choice(self) -> None: None """ if self.clicked_button == "new": - self.open_new_batch() + self.label.config(text="Connect to directus and adjust the parameters", foreground="black") + return "new" elif self.clicked_button == "csv": - self.open_csv_batch() + self.label.config(text="Connect to directus and adjust the parameters", foreground="black") + return "csv" else: - print("unknown error, please try again.") - - def open_new_batch(self) -> None: - """ - Hides the main window and initializes the new batch sample list window. - - Args: - None - - Returns: - None - """ - # Hide the main page - window.withdraw() - - operator = os.environ.get("OPERATOR") - - newWindow = tk.Tk() - newWindow.title("New batch") - - output_folder = os.environ.get("OUTPUT_FOLDER") - new_batch.newBatch( - root=newWindow, - csv_path=f"{output_folder}/{datetime.now().strftime('%Y%m%d')}_{operator}_dbgi_{self.file}.csv", - ) - - def open_csv_batch(self) -> None: - """ - Hides the main window and initializes the csv sample list window. - - Args: - None - - Returns: - None - """ - # Hide the main page - self.pack_forget() - # window.destroy() - - operator = os.environ.get("OPERATOR") - output_folder = os.environ.get("OUTPUT_FOLDER") - - # window3 = tk.Tk() - - # csvBatch( - # root=window3, - # csv_path=f"{output_folder}/{datetime.now().strftime('%Y%m%d')}_{operator}_dbgi_{self.file}.csv", - # ) - - # Hide the main page and open Window 1 - # self.pack_forget() - csvWindow = csv_batch.csvBatch( - parent=self.master, - csv_path=f"{output_folder}/{datetime.now().strftime('%Y%m%d')}_{operator}_dbgi_{self.file}.csv", - ) - csvWindow.title("CSV import") - csvWindow.pack() - - def deiconify(self) -> None: - """ - make the home page visible again. Called by external scripts. - - Args: - None - - Returns: - None - """ - window.deiconify() - - -# Create the main window -window = tk.Tk() -window.title("Home") -window.minsize(600, 600) - -# Create a Frame within the Tk window -window_frame = tk.Frame(window) -# Pack the Frame to occupy the whole window -window_frame.pack() -# Pass window_frame as the parent -main_page = HomeWindow(window_frame) -# Pack the HomeWindow within the Frame -main_page.pack() -window.mainloop() + # If user didn't enter all necessary values, shows this message + self.label.config(text="Unknow error, please try again with other parameters", foreground="red") diff --git a/ms_sample_list_creator/ms_sample_list_creator.py b/ms_sample_list_creator/ms_sample_list_creator.py new file mode 100644 index 0000000..9790017 --- /dev/null +++ b/ms_sample_list_creator/ms_sample_list_creator.py @@ -0,0 +1,65 @@ +# To generate binaries for this script, install pyinstaller (pip install pyinstaller) and run "pyinstaller --onefile main.py" +# Generated binaries are made for the native system where the pyinstaller command is run. + +# You can generate windows executable from linux using wine, by previously installing wine, python 3.8.19, pyinstaller and +# other non-built-in packages (here requests) inside wine. Then run: wine pyinstaller --onefile main.py + +import tkinter as tk +from home_page import HomeWindow +from new_batch import newBatch +from csv_batch import csvBatch + +def submit_results(clicked_button:str): + home_page.show_values(clicked_button) + handle_user_choice() + +# Function to handle the user choice +def handle_user_choice(): + # Call the manage_choice method to get the user choice + user_choice = home_page.manage_choice() + # Show the corresponding window based on the user's choice + show_selected_window(user_choice) + +def show_selected_window(choice): + if choice == "new": + # Create a new Toplevel window for the new batch + new_batch_window = tk.Toplevel(root) + new_batch_window.title("Create new batch") + # Show the window for a new batch + newBatch(new_batch_window, root) + elif choice == "csv": + # Create a new Toplevel window for the new batch + csv_batch_window = tk.Toplevel(root) + csv_batch_window.minsize(300, 200) + csv_batch_window.title("Import csv batch") + # Show the window for a new batch + csvBatch(csv_batch_window, root) + else: + # Handle the case of an unknown choice + print("Unknown error, please try again with other parameters.") + +# Create an instance of the main window +root = tk.Tk() +root.title("Home") +root.minsize(600, 600) + +# Create an instance of the HomeWindow class +home_page = HomeWindow(root) + +# Display the HomeWindow +home_page.pack() + +frame_submit = tk.Frame(root) +frame_submit.pack(pady=(50, 0)) + +button_new_batch = tk.Button( +frame_submit, text="New sample list", width=20, command=lambda: submit_results("new")) +button_new_batch.pack(side="left") + +button_submit_csv = tk.Button( +frame_submit, text="Sample list from CSV", width=20, command=lambda: submit_results("csv")) +button_submit_csv.pack(side="right") + +# Start the tkinter event loop +root.mainloop() + diff --git a/ms_sample_list_creator/new_batch.py b/ms_sample_list_creator/new_batch.py index bc891e4..0707e84 100644 --- a/ms_sample_list_creator/new_batch.py +++ b/ms_sample_list_creator/new_batch.py @@ -9,7 +9,7 @@ class newBatch: - def __init__(self, root: tk.Tk, csv_path: str): + def __init__(self, new_batch_window, root): """ Initializes an instance of the class. @@ -20,12 +20,14 @@ def __init__(self, root: tk.Tk, csv_path: str): Returns: None """ - print("new batch") + self.new_batch_window = new_batch_window self.root = root - # Bind the destroy event to the callback function - self.root.protocol("WM_DELETE_WINDOW", self.back_to_main) + # Make CsvWindow wait for AskBoxPrefixWindow result + self.root.withdraw() + + self.new_batch_window.protocol("WM_DELETE_WINDOW", self.on_exit) self.operator = str(os.environ.get("OPERATOR")) self.ms_id = str(os.environ.get("MS_ID")) @@ -40,14 +42,16 @@ def __init__(self, root: tk.Tk, csv_path: str): self.method_file = str(os.environ.get("METHOD_FILE")) self.data_path = str(os.environ.get("DATA_FOLDER")) self.standby_file = str(os.environ.get("STANDBY_FILE")) - self.csv_path = csv_path + self.output_folder = str(os.environ.get("OUTPUT_FOLDER")) + self.file = str(os.environ.get("FILE")) + self.csv_path = f"{self.output_folder}/{datetime.now().strftime('%Y%m%d')}_{self.operator}_dbgi_{self.file}.csv" self.current_position = 1 self.current_row = 1 self.timestamp = datetime.now().strftime("%Y%m%d%H%M") # Create Treeview widget self.tree = ttk.Treeview( - root, + self.new_batch_window, columns=( "aliquot_id", "operator", @@ -71,19 +75,20 @@ def __init__(self, root: tk.Tk, csv_path: str): self.tree.heading("Inj Vol", text="Inj Vol") # Bind Enter key to add row - self.root.bind("", self.add_row) + self.new_batch_window.bind("", self.add_row) # Entry widgets for data input - self.aliquot_id_entry = ttk.Entry(root) + self.aliquot_id_entry = ttk.Entry(self.new_batch_window) # Error text hidden: - self.label = ttk.Label(root, text="") + self.label = ttk.Label(self.new_batch_window, text="") self.label.grid(row=2, column=0, columnspan=2, pady=10) # Submit button - submit_button = ttk.Button(root, text="Generate sample list", width=17, command=self.submit_table) + submit_button = ttk.Button(self.new_batch_window, text="Generate sample list", width=20, command=self.submit_table) - button_back = tk.Button(root, text="Back to Home", width=17, command=self.back_to_main) + # Back button + button_back = tk.Button(self.new_batch_window, text="Back to Home", width=20, command=self.on_exit) # Grid layout for widgets self.tree.grid(row=0, column=0, padx=10, pady=10, columnspan=2) @@ -92,14 +97,12 @@ def __init__(self, root: tk.Tk, csv_path: str): button_back.grid(row=4, column=1, columnspan=2, pady=10) # Start the Tkinter event loop - self.root.mainloop() - - def back_to_main(self): - import home_page + self.new_batch_window.mainloop() + self.root.withdraw() - # Destroy Window 2 and show the main page - home_page.HomeWindow.deiconify(self) - self.root.destroy() + def on_exit(self): + self.new_batch_window.destroy() + self.root.deiconify() def add_row(self, event: Optional[tk.Event] = None) -> None: """ @@ -157,13 +160,13 @@ def add_row(self, event: Optional[tk.Event] = None) -> None: self.current_position == 1 and self.current_row == 1 ): # Open window to ask prefix - ask_prefix_window = tk.Toplevel(self.root) + ask_prefix_window = tk.Toplevel(self.new_batch_window) ask_prefix_window.title("Add Prefix") self.ask_box = AskBoxPrefixWindow(ask_prefix_window) self.ask_box.pack() # Make CsvWindow wait for AskBoxPrefixWindow result - ask_prefix_window.transient(self.root) + ask_prefix_window.transient(self.new_batch_window) ask_prefix_window.wait_window(self.ask_box) prefix = os.environ.get("PREFIX") @@ -275,6 +278,7 @@ def submit_table(self) -> None: csv_writer.writerow([filename, path, standby, position, inj_volume]) # Close the Tkinter window + self.new_batch_window.destroy() self.root.destroy() def directus_reconnect(self) -> None: @@ -376,4 +380,4 @@ def store_prefix(self) -> None: os.environ["PREFIX"] = self.prefix.get() # Close the AskBoxPrefixWindow - self.master.destroy() + self.master.destroy() \ No newline at end of file diff --git a/ms_sample_list_creator/pieces.py b/ms_sample_list_creator/pieces.py new file mode 100644 index 0000000..7f29b75 --- /dev/null +++ b/ms_sample_list_creator/pieces.py @@ -0,0 +1,25 @@ +from tkinter import * + +class MainWindow: + def __init__(self, master): + mainframe = Frame(master, width = 300, height = 200) + button = Button(mainframe, text="Open Window", + command=self.openWindow) + button.place(x = 100, y = 80) + mainframe.pack() + + def openWindow(self): + win = ExtraWindow() + +class ExtraWindow: + def __init__(self): + top = Toplevel() + + subframe = Frame(top, width = 200, height = 150) + button = Button(top, text="Destroy Window", command=top.destroy) + button.place(x = 50, y = 50) + subframe.pack() + +root = Tk() +window = MainWindow(root) +root.mainloop() \ No newline at end of file diff --git a/ms_sample_list_creator/prefix_window.py b/ms_sample_list_creator/prefix_window.py new file mode 100644 index 0000000..30b2b56 --- /dev/null +++ b/ms_sample_list_creator/prefix_window.py @@ -0,0 +1,47 @@ +import tkinter as tk +import os + +class AskBoxPrefixWindow(tk.Frame): + def __init__(self, root: tk.Toplevel): + """ + Initializes an instance of the class. + + Args: + root(tk.Toplevel): The parent widget or window where this frame will be placed. + csv_path(str): CSV path and name. + + Returns: + None + """ + tk.Frame.__init__(self, root) + + self.prefix = tk.StringVar() + + # Adjust the window size + root.geometry("300x150") + + # Label + textbox to enter prefix + label_prefix = tk.Label(self, text="Box's prefix:") + label_prefix.pack() + + entry_prefix = tk.Entry(self, textvariable=self.prefix) + entry_prefix.pack() + + # Submit button + button_submit = tk.Button(self, text="Submit", command=self.store_prefix) + button_submit.pack() + + def store_prefix(self) -> None: + """ + Puts the asked prefix to the environment. + + Args: + None + + Returns: + None + """ + os.environ["PREFIX"] = self.prefix.get() + + # Close the AskBoxPrefixWindow + self.master.destroy() \ No newline at end of file