From 25ed0e97ef5947b1f1d67e125ee540a9dca11012 Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:05:40 -0500 Subject: [PATCH 01/12] Fixed the indicate of each field --- daq/chart-observe.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index 8984bd3..011b867 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -195,6 +195,7 @@ def default_parameters(): int_time_in.configure(state=tkinter.DISABLED) nint_in.configure(state=tkinter.DISABLED) +# Why these defaults? default_freq_i = '1415' default_freq_f = '1425' default_int_time = '5' @@ -534,6 +535,8 @@ def mode(): curr_time.place(relx=0.7, rely=0.5, anchor=tkinter.W) + + combobox = customtkinter.CTkComboBox(master=app, values=["am", "pm"], height = 25, width = 55, variable = "") @@ -541,9 +544,48 @@ def mode(): combobox.set("am") # set initial value combobox.place(relx=0.81, rely=0.5, anchor=tkinter.W) +# This function is to add hint labels to entry widgets when the user starts typing +# the goal is to have the hints under the entry or to the side so that the user knows what format to use + +def add_hint_label(entry_widget, hint_text, position="below"): + """Adds a small gray hint label that appears when the user starts typing.""" + hint_label = customtkinter.CTkLabel(master=app, + text=hint_text, + text_color="gray50", + font=("Arial", 10)) + hint_label.place_forget() # Hidden until user types + + def on_type(event): + content = entry_widget.get() + if content.strip(): + x, y = entry_widget.winfo_x(), entry_widget.winfo_y() + if position == "below": + hint_label.place(x=x, y=y + entry_widget.winfo_height() + 2) + else: # show beside + hint_label.place(x=x + entry_widget.winfo_width() + 5, y=y) + else: + hint_label.place_forget() + + # Bind typing events + entry_widget.bind("", on_type) + + #here are the two switches at the bottom of each side. You can view the location with relx and rely system_date_time_switch = customtkinter.CTkSwitch(master=app, text="Use System Date and Time", command=current_date_time, onvalue="on", offvalue="off") system_date_time_switch.pack(padx=20, pady=10) system_date_time_switch.place(relx=0.7, rely=.6, anchor=tkinter.CENTER) +# Add hint labels for all entries with appropriate positions +add_hint_label(freq_i_in, "Start frequency in MHz", position="below") +add_hint_label(freq_f_in, "End frequency in MHz", position="below") +add_hint_label(int_time_in, "Integration time in seconds", position="below") +add_hint_label(nint_in, "Number of integrations", position="below") +add_hint_label(user_name, "Your WSU username or observer name", position="below") +add_hint_label(location_name, "e.g., Winona, MN or Observatory", position="below") +add_hint_label(time_name, "Trial number (1, 2, 3…)", position="below") +add_hint_label(date_name, "Format: MM.DD.YYYY", position="below") +add_hint_label(curr_time, "Format: HH:MM with am/pm toggle", position="below") +add_hint_label(description, "Short description of observation", position="below") + + app.mainloop() From 2c6007698a3f2b34433778a2e56d9b38e1d46ba6 Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Sun, 12 Oct 2025 16:42:29 -0500 Subject: [PATCH 02/12] Update chart-observe.py --- daq/chart-observe.py | 160 +++++++++++++++++++++++++++++++++---------- 1 file changed, 125 insertions(+), 35 deletions(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index 011b867..2aa4a41 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -59,8 +59,10 @@ def start(): #the variables taken from the entry inputs sUser = customtkinter.CTkEntry.get(user_name) - sLocation = customtkinter.CTkEntry.get(location_name) - trial = customtkinter.CTkEntry.get(time_name) + # sLocation = customtkinter.CTkEntry.get(location_name) + sLongitude = customtkinter.CTkEntry.get(longitude_name) #getting the longitude from the entry + sLatitude = customtkinter.CTkEntry.get(latitude_name) #getting the latitude from the entry + # trial = customtkinter.CTkEntry.get(time_name) date_name.configure(state=tkinter.NORMAL) #checking if date was empty so that it knows to use the input from entry or the one from the system time and date @@ -75,7 +77,7 @@ def start(): tDay = combobox.get() #make sure the location does not have spaces or slashes that many people accidentally do - location = sLocation.replace(" ", "-") + # location = sLocation.replace(" ", "-") date = date.replace("/", ".") user = sUser.replace("_", ".") @@ -87,7 +89,10 @@ def start(): date_y_m_d = year+"."+month+"."+day #changed the date to be the correct formal - directory = user+"_"+location+"_"+date_y_m_d+"_"+trial+"_"+time.replace(":", ".")+"_"+tDay + # directory = user+"_"+location+"_"+date_y_m_d+"_"+trial+"_"+time.replace(":", ".")+"_"+tDay + # New format without location and with latitude and longitude + # I used f strings here for clarity + directory = f"{user}_lon{sLongitude}_lat{sLatitude}_{date_y_m_d}_{time.replace(':', '.')}_{tDay}" print(directory) if(len(month) == 1): @@ -455,45 +460,86 @@ def mode(): user_name.place(relx=0.7, rely=0.1, anchor=tkinter.W) -label = customtkinter.CTkLabel(master=app, - text="Location:", +# Getting rid of location to add latitude and longitude +# I still have the code here if we want to go back to location + +# label = customtkinter.CTkLabel(master=app, +# text="Location:", +# width=100, +# height=25, +# fg_color=("white", "gray"), +# corner_radius=5 +# ) + +# label.place(relx=0.5, rely=0.2, anchor=tkinter.W) + +# location_name = customtkinter.CTkEntry(master=app, +# placeholder_text="Enter Here", +# width=210, +# height=25, +# border_width=2, +# corner_radius=10 +# ) + +# location_name.place(relx=0.7, rely=0.2, anchor=tkinter.W) + +# Adding Latitude and Longitude instead of general location +label_long = customtkinter.CTkLabel(master=app, + text="Longitude:", width=100, height=25, fg_color=("white", "gray"), corner_radius=5 ) +label_long.place(relx=0.5, rely=0.2, anchor=tkinter.W) -label.place(relx=0.5, rely=0.2, anchor=tkinter.W) - -location_name = customtkinter.CTkEntry(master=app, - placeholder_text="Enter Here", +longitude_name = customtkinter.CTkEntry(master=app, + placeholder_text="e.g., -91.64", width=210, height=25, border_width=2, corner_radius=10 ) +longitude_name.place(relx=0.7, rely=0.2, anchor=tkinter.W) -location_name.place(relx=0.7, rely=0.2, anchor=tkinter.W) - -label = customtkinter.CTkLabel(master=app, - text="Trial:", +label_lat = customtkinter.CTkLabel(master=app, + text="Latitude:", width=100, height=25, fg_color=("white", "gray"), - corner_radius= 5 + corner_radius=5 ) +label_lat.place(relx=0.5, rely=0.3, anchor=tkinter.W) -label.place(relx=0.5, rely=0.3, anchor=tkinter.W) - -time_name = customtkinter.CTkEntry(master=app, - placeholder_text="00", - width=120, +latitude_name = customtkinter.CTkEntry(master=app, + placeholder_text="e.g., 44.05", + width=210, height=25, border_width=2, corner_radius=10 ) +latitude_name.place(relx=0.7, rely=0.3, anchor=tkinter.W) + +# Getting rid of trial number to simplify user input +# label = customtkinter.CTkLabel(master=app, +# text="Trial:", +# width=100, +# height=25, +# fg_color=("white", "gray"), +# corner_radius= 5 +# ) + +# label.place(relx=0.5, rely=0.3, anchor=tkinter.W) -time_name.place(relx=0.7, rely=0.3, anchor=tkinter.W) +# time_name = customtkinter.CTkEntry(master=app, +# placeholder_text="00", +# width=120, +# height=25, +# border_width=2, +# corner_radius=10 +# ) + +# time_name.place(relx=0.7, rely=0.3, anchor=tkinter.W) label = customtkinter.CTkLabel(master=app, text="Date:", @@ -547,27 +593,68 @@ def mode(): # This function is to add hint labels to entry widgets when the user starts typing # the goal is to have the hints under the entry or to the side so that the user knows what format to use +# def add_hint_label(entry_widget, hint_text, position="below"): +# #Adds a small gray hint label that appears when the user starts typing. +# hint_label = customtkinter.CTkLabel(master=app, +# text=hint_text, +# text_color="gray50", +# font=("Arial", 10)) +# hint_label.place_forget() # Hidden until user types + +# def on_type(event): +# content = entry_widget.get() +# if content.strip(): +# x, y = entry_widget.winfo_x(), entry_widget.winfo_y() +# if position == "below": +# hint_label.place(x=x, y=y + entry_widget.winfo_height() + 2) +# else: # show beside +# hint_label.place(x=x + entry_widget.winfo_width() + 5, y=y) +# else: +# hint_label.place_forget() + +# # Bind typing events +# entry_widget.bind("", on_type) + +# Enhanced hint label +# It will only shows for active entry +active_hint_label = None # global tracker + def add_hint_label(entry_widget, hint_text, position="below"): - """Adds a small gray hint label that appears when the user starts typing.""" +# Adds a small gray hint label that appears only while typing in this entry.""" + global active_hint_label + hint_label = customtkinter.CTkLabel(master=app, text=hint_text, text_color="gray50", font=("Arial", 10)) - hint_label.place_forget() # Hidden until user types + hint_label.place_forget() # start hidden - def on_type(event): + def show_hint(event): + """Show this hint and hide others.""" + global active_hint_label + + # Hide any previously active hint + if active_hint_label is not None and active_hint_label != hint_label: + active_hint_label.place_forget() + + # Show this hint content = entry_widget.get() - if content.strip(): - x, y = entry_widget.winfo_x(), entry_widget.winfo_y() - if position == "below": - hint_label.place(x=x, y=y + entry_widget.winfo_height() + 2) - else: # show beside - hint_label.place(x=x + entry_widget.winfo_width() + 5, y=y) + x, y = entry_widget.winfo_x(), entry_widget.winfo_y() + if position == "below": + hint_label.place(x=x, y=y + entry_widget.winfo_height() + 2) else: - hint_label.place_forget() + hint_label.place(x=x + entry_widget.winfo_width() + 5, y=y) + + active_hint_label = hint_label + + def hide_hint(event): + """Hide hint when leaving this box.""" + hint_label.place_forget() + + # Bind focus events + entry_widget.bind("", show_hint) + entry_widget.bind("", hide_hint) - # Bind typing events - entry_widget.bind("", on_type) #here are the two switches at the bottom of each side. You can view the location with relx and rely @@ -576,13 +663,16 @@ def on_type(event): system_date_time_switch.place(relx=0.7, rely=.6, anchor=tkinter.CENTER) # Add hint labels for all entries with appropriate positions +# Position can be "below" or "beside" as needed add_hint_label(freq_i_in, "Start frequency in MHz", position="below") add_hint_label(freq_f_in, "End frequency in MHz", position="below") add_hint_label(int_time_in, "Integration time in seconds", position="below") add_hint_label(nint_in, "Number of integrations", position="below") add_hint_label(user_name, "Your WSU username or observer name", position="below") -add_hint_label(location_name, "e.g., Winona, MN or Observatory", position="below") -add_hint_label(time_name, "Trial number (1, 2, 3…)", position="below") +# add_hint_label(location_name, "e.g., Winona, MN or Observatory", position="below") +add_hint_label(longitude_name, "Longitude in decimal degrees (East/West)", position="below") +add_hint_label(latitude_name, "Latitude in decimal degrees (North/South)", position="below") +# add_hint_label(time_name, "Trial number (1, 2, 3…)", position="below") add_hint_label(date_name, "Format: MM.DD.YYYY", position="below") add_hint_label(curr_time, "Format: HH:MM with am/pm toggle", position="below") add_hint_label(description, "Short description of observation", position="below") From 2ea3932d0633b1893789362146a797cabfb44650 Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:36:21 -0500 Subject: [PATCH 03/12] Update daq/chart-observe.py Co-authored-by: Adam Beardsley --- daq/chart-observe.py | 1 - 1 file changed, 1 deletion(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index 2aa4a41..f9c007d 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -59,7 +59,6 @@ def start(): #the variables taken from the entry inputs sUser = customtkinter.CTkEntry.get(user_name) - # sLocation = customtkinter.CTkEntry.get(location_name) sLongitude = customtkinter.CTkEntry.get(longitude_name) #getting the longitude from the entry sLatitude = customtkinter.CTkEntry.get(latitude_name) #getting the latitude from the entry # trial = customtkinter.CTkEntry.get(time_name) From 33193df061edd24ffdba674d7d184ed5639c81d9 Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Sun, 9 Nov 2025 16:28:41 -0600 Subject: [PATCH 04/12] Update chart-observe.py --- daq/chart-observe.py | 256 +++++++++++++++++++++---------------------- 1 file changed, 124 insertions(+), 132 deletions(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index 2aa4a41..ff8cf37 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -88,8 +88,6 @@ def start(): date_y_m_d = year+"."+month+"."+day - #changed the date to be the correct formal - # directory = user+"_"+location+"_"+date_y_m_d+"_"+trial+"_"+time.replace(":", ".")+"_"+tDay # New format without location and with latitude and longitude # I used f strings here for clarity directory = f"{user}_lon{sLongitude}_lat{sLatitude}_{date_y_m_d}_{time.replace(':', '.')}_{tDay}" @@ -324,11 +322,11 @@ def mode(): #this is the start of the gui design where everything is layed out label = customtkinter.CTkLabel(master=app, - text="Initial Frequency:", + text="Initial Frequency(in MHz): ", width=100, height=25, fg_color=("white", "gray"), - corner_radius=8) + corner_radius=9) label.place(relx=0.1, rely=0.1, anchor=tkinter.W) @@ -340,10 +338,10 @@ def mode(): corner_radius=10 ) -freq_i_in.place(relx=0.3, rely=0.1, anchor=tkinter.W) +freq_i_in.place(relx=0.35, rely=0.1, anchor=tkinter.W) label = customtkinter.CTkLabel(master=app, - text="Final Frequency:", + text="Final Frequency: (in MHz)", width=100, height=25, fg_color=("white", "gray"), @@ -440,40 +438,80 @@ def mode(): local_jupyter_button.place(relx=0.8, rely=.9, anchor=tkinter.CENTER) local_jupyter_button.configure(state=tkinter.NORMAL) -label = customtkinter.CTkLabel(master=app, - text="Username:", - width=100, - height=25, - fg_color=("white", "gray"), - corner_radius=5 - ) -label.place(relx=0.5, rely=0.1, anchor=tkinter.W) - -user_name = customtkinter.CTkEntry(master=app, - placeholder_text="Enter Here", - width=210, - height=25, - border_width=2, - corner_radius=10 - ) +# Create a main frame to hold all widgets +main_frame = customtkinter.CTkFrame(master=app) +main_frame.pack(fill="both", expand=True, padx=20, pady=20) + +# Configure the grid weights for flexibility +for i in range(6): # 6 rows for now, you can adjust + main_frame.rowconfigure(i, weight=1) +for j in range(2): # two columns: label + entry + main_frame.columnconfigure(j, weight=1) + + +# Row 0 — Username +label_user = customtkinter.CTkLabel(main_frame, text="Username:") +label_user.grid(row=0, column=0, sticky="e", padx=10, pady=10) +user_name = customtkinter.CTkEntry(main_frame, placeholder_text="Enter Here") +user_name.grid(row=0, column=1, sticky="we", padx=10, pady=10) + +# Row 1 — Longitude +label_long = customtkinter.CTkLabel(main_frame, text="Longitude:") +label_long.grid(row=1, column=0, sticky="e", padx=10, pady=10) +longitude_name = customtkinter.CTkEntry(main_frame, placeholder_text="e.g., -91.64") +longitude_name.grid(row=1, column=1, sticky="we", padx=10, pady=10) + +# Row 2 — Latitude +label_lat = customtkinter.CTkLabel(main_frame, text="Latitude:") +label_lat.grid(row=2, column=0, sticky="e", padx=10, pady=10) +latitude_name = customtkinter.CTkEntry(main_frame, placeholder_text="e.g., 44.05") +latitude_name.grid(row=2, column=1, sticky="we", padx=10, pady=10) + +# Row 3 — Date +label_date = customtkinter.CTkLabel(main_frame, text="Date:") +label_date.grid(row=3, column=0, sticky="e", padx=10, pady=10) +date_name = customtkinter.CTkEntry(main_frame, placeholder_text="MM.DD.YYYY") +date_name.grid(row=3, column=1, sticky="we", padx=10, pady=10) + +# Row 4 — Time and AM/PM combo +label_time = customtkinter.CTkLabel(main_frame, text="Time:") +label_time.grid(row=4, column=0, sticky="e", padx=10, pady=10) + +time_frame = customtkinter.CTkFrame(main_frame) +time_frame.grid(row=4, column=1, sticky="we", padx=10, pady=10) +time_frame.columnconfigure(0, weight=1) +time_frame.columnconfigure(1, weight=0) + +curr_time = customtkinter.CTkEntry(time_frame, placeholder_text="00:00") +curr_time.grid(row=0, column=0, sticky="we", padx=(0, 5)) +combobox = customtkinter.CTkComboBox(time_frame, values=["am", "pm"], width=55) +combobox.grid(row=0, column=1) +combobox.set("am") + +# Row 5 — Frequency Inputs +label_freq_i = customtkinter.CTkLabel(main_frame, text="Initial Frequency (MHz):") +label_freq_i.grid(row=5, column=0, sticky="e", padx=10, pady=10) +freq_i_in = customtkinter.CTkEntry(main_frame, placeholder_text="1415") +freq_i_in.grid(row=5, column=1, sticky="we", padx=10, pady=10) + +label_freq_f = customtkinter.CTkLabel(main_frame, text="Final Frequency (MHz):") +label_freq_f.grid(row=6, column=0, sticky="e", padx=10, pady=10) +freq_f_in = customtkinter.CTkEntry(main_frame, placeholder_text="1425") +freq_f_in.grid(row=6, column=1, sticky="we", padx=10, pady=10) -user_name.place(relx=0.7, rely=0.1, anchor=tkinter.W) - -# Getting rid of location to add latitude and longitude -# I still have the code here if we want to go back to location # label = customtkinter.CTkLabel(master=app, -# text="Location:", +# text="Username:", # width=100, # height=25, # fg_color=("white", "gray"), # corner_radius=5 # ) -# label.place(relx=0.5, rely=0.2, anchor=tkinter.W) +# label.place(relx=0.5, rely=0.1, anchor=tkinter.W) -# location_name = customtkinter.CTkEntry(master=app, +# user_name = customtkinter.CTkEntry(master=app, # placeholder_text="Enter Here", # width=210, # height=25, @@ -481,105 +519,84 @@ def mode(): # corner_radius=10 # ) -# location_name.place(relx=0.7, rely=0.2, anchor=tkinter.W) +# user_name.place(relx=0.7, rely=0.1, anchor=tkinter.W) -# Adding Latitude and Longitude instead of general location -label_long = customtkinter.CTkLabel(master=app, - text="Longitude:", - width=100, - height=25, - fg_color=("white", "gray"), - corner_radius=5 - ) -label_long.place(relx=0.5, rely=0.2, anchor=tkinter.W) +# # Adding Latitude and Longitude instead of general location +# label_long = customtkinter.CTkLabel(master=app, +# text="Longitude:", +# width=100, +# height=25, +# fg_color=("white", "gray"), +# corner_radius=5 +# ) +# label_long.place(relx=0.5, rely=0.2, anchor=tkinter.W) -longitude_name = customtkinter.CTkEntry(master=app, - placeholder_text="e.g., -91.64", - width=210, - height=25, - border_width=2, - corner_radius=10 - ) -longitude_name.place(relx=0.7, rely=0.2, anchor=tkinter.W) +# longitude_name = customtkinter.CTkEntry(master=app, +# placeholder_text="e.g., -91.64", +# width=210, +# height=25, +# border_width=2, +# corner_radius=10 +# ) +# longitude_name.place(relx=0.7, rely=0.2, anchor=tkinter.W) -label_lat = customtkinter.CTkLabel(master=app, - text="Latitude:", - width=100, - height=25, - fg_color=("white", "gray"), - corner_radius=5 - ) -label_lat.place(relx=0.5, rely=0.3, anchor=tkinter.W) +# label_lat = customtkinter.CTkLabel(master=app, +# text="Latitude:", +# width=100, +# height=25, +# fg_color=("white", "gray"), +# corner_radius=5 +# ) +# label_lat.place(relx=0.5, rely=0.3, anchor=tkinter.W) -latitude_name = customtkinter.CTkEntry(master=app, - placeholder_text="e.g., 44.05", - width=210, - height=25, - border_width=2, - corner_radius=10 - ) -latitude_name.place(relx=0.7, rely=0.3, anchor=tkinter.W) +# latitude_name = customtkinter.CTkEntry(master=app, +# placeholder_text="e.g., 44.05", +# width=210, +# height=25, +# border_width=2, +# corner_radius=10 +# ) +# latitude_name.place(relx=0.7, rely=0.3, anchor=tkinter.W) -# Getting rid of trial number to simplify user input # label = customtkinter.CTkLabel(master=app, -# text="Trial:", +# text="Date:", # width=100, # height=25, # fg_color=("white", "gray"), -# corner_radius= 5 +# corner_radius=5 # ) -# label.place(relx=0.5, rely=0.3, anchor=tkinter.W) +# label.place(relx=0.5, rely=0.4, anchor=tkinter.W) -# time_name = customtkinter.CTkEntry(master=app, -# placeholder_text="00", -# width=120, +# date_name = customtkinter.CTkEntry(master=app, +# placeholder_text="MM.DD.YYYY", +# width=150, # height=25, # border_width=2, # corner_radius=10 # ) -# time_name.place(relx=0.7, rely=0.3, anchor=tkinter.W) - -label = customtkinter.CTkLabel(master=app, - text="Date:", - width=100, - height=25, - fg_color=("white", "gray"), - corner_radius=5 - ) - -label.place(relx=0.5, rely=0.4, anchor=tkinter.W) - -date_name = customtkinter.CTkEntry(master=app, - placeholder_text="MM.DD.YYYY", - width=150, - height=25, - border_width=2, - corner_radius=10 - ) - -date_name.place(relx=0.7, rely=0.4, anchor=tkinter.W) +# date_name.place(relx=0.7, rely=0.4, anchor=tkinter.W) -label = customtkinter.CTkLabel(master=app, - text="Time:", - width=100, - height=25, - fg_color=("white", "gray"), - corner_radius=5 - ) +# label = customtkinter.CTkLabel(master=app, +# text="Time:", +# width=100, +# height=25, +# fg_color=("white", "gray"), +# corner_radius=5 +# ) -label.place(relx=0.5, rely=0.5, anchor=tkinter.W) +# label.place(relx=0.5, rely=0.5, anchor=tkinter.W) -curr_time = customtkinter.CTkEntry(master=app, - placeholder_text="00:00", - width=70, - height=25, - border_width=2, - corner_radius=10 - ) +# curr_time = customtkinter.CTkEntry(master=app, +# placeholder_text="00:00", +# width=70, +# height=25, +# border_width=2, +# corner_radius=10 +# ) -curr_time.place(relx=0.7, rely=0.5, anchor=tkinter.W) +# curr_time.place(relx=0.7, rely=0.5, anchor=tkinter.W) @@ -590,31 +607,6 @@ def mode(): combobox.set("am") # set initial value combobox.place(relx=0.81, rely=0.5, anchor=tkinter.W) -# This function is to add hint labels to entry widgets when the user starts typing -# the goal is to have the hints under the entry or to the side so that the user knows what format to use - -# def add_hint_label(entry_widget, hint_text, position="below"): -# #Adds a small gray hint label that appears when the user starts typing. -# hint_label = customtkinter.CTkLabel(master=app, -# text=hint_text, -# text_color="gray50", -# font=("Arial", 10)) -# hint_label.place_forget() # Hidden until user types - -# def on_type(event): -# content = entry_widget.get() -# if content.strip(): -# x, y = entry_widget.winfo_x(), entry_widget.winfo_y() -# if position == "below": -# hint_label.place(x=x, y=y + entry_widget.winfo_height() + 2) -# else: # show beside -# hint_label.place(x=x + entry_widget.winfo_width() + 5, y=y) -# else: -# hint_label.place_forget() - -# # Bind typing events -# entry_widget.bind("", on_type) - # Enhanced hint label # It will only shows for active entry active_hint_label = None # global tracker From 761660fa6a589ca47a0d2346262371647346ca4d Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Sun, 9 Nov 2025 21:23:15 -0600 Subject: [PATCH 05/12] Little better. But I am not fully satisfied with this one --- daq/chart-observe.py | 366 +++++++++++-------------------------------- 1 file changed, 90 insertions(+), 276 deletions(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index eb047cf..7d8d453 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -314,297 +314,114 @@ def mode(): customtkinter.set_appearance_mode("Light") - +# below is the layout in the GUI mode_switch = customtkinter.CTkSwitch(master=app, text="Dark Mode", command=mode, onvalue="on", offvalue="off") -mode_switch.pack(padx=20, pady=10) -mode_switch.place(relx=0.1, rely=.03, anchor=tkinter.CENTER) - -#this is the start of the gui design where everything is layed out -label = customtkinter.CTkLabel(master=app, - text="Initial Frequency(in MHz): ", - width=100, - height=25, - fg_color=("white", "gray"), - corner_radius=9) - -label.place(relx=0.1, rely=0.1, anchor=tkinter.W) - -freq_i_in = customtkinter.CTkEntry(master=app, - placeholder_text= default_freq_i, - width=80, - height=25, - border_width=2, - corner_radius=10 - ) - -freq_i_in.place(relx=0.35, rely=0.1, anchor=tkinter.W) - -label = customtkinter.CTkLabel(master=app, - text="Final Frequency: (in MHz)", - width=100, - height=25, - fg_color=("white", "gray"), - corner_radius=5 - ) - -label.place(relx=0.1, rely=0.2, anchor=tkinter.W) - -freq_f_in = customtkinter.CTkEntry(master=app, - placeholder_text= default_freq_f, - width=80, - height=25, - border_width=2, - corner_radius=10 - ) - -freq_f_in.place(relx=0.3, rely=0.2, anchor=tkinter.W) - -label = customtkinter.CTkLabel(master=app, - text="Integration Time:", - width=100, - height=25, - fg_color=("white", "gray"), - corner_radius=5 - ) - -label.place(relx=0.1, rely=0.3, anchor=tkinter.W) - -int_time_in = customtkinter.CTkEntry(master=app, - placeholder_text=default_int_time, - width=80, - height=25, - border_width=2, - corner_radius=10 - ) - -int_time_in.place(relx=0.3, rely=0.3, anchor=tkinter.W) - -label = customtkinter.CTkLabel(master=app, - text="Number of Integrations:", - width=100, - height=25, - fg_color=("white", "gray"), - corner_radius=5 - ) - -label.place(relx=0.07, rely=0.4, anchor=tkinter.W) - -nint_in = customtkinter.CTkEntry(master=app, - placeholder_text=default_nint, - width=80, - height=25, - border_width=2, - corner_radius=10 - ) - -nint_in.place(relx=0.34, rely=0.4, anchor=tkinter.W) - -default_parameters_switch = customtkinter.CTkSwitch(master=app, text="Use Default Parameters", command=default_parameters, onvalue="on", offvalue="off") -default_parameters_switch.pack(padx=20, pady=10) -default_parameters_switch.place(relx=0.25, rely=.5, anchor=tkinter.CENTER) - -biasT_switch = customtkinter.CTkSwitch(master=app, text="Enable Bias-T", command=biasT_switch, onvalue="on", offvalue="off") -biasT_switch.pack(padx=20, pady=10) -biasT_switch.place(relx=0.25, rely=.57, anchor=tkinter.CENTER) - -description = customtkinter.CTkEntry(master=app, - placeholder_text="Describe what you are looking at.", - width=310, - height=30, - border_width=2, - corner_radius=10 - ) - -description.place(relx=0.05, rely=0.67, anchor=tkinter.W) - - -#below is the right side layout in the GUI -start_button = customtkinter.CTkButton(master=app, text="Start", command=start) -start_button.place(relx=0.5, rely=.8, anchor=tkinter.CENTER) -start_button.configure(state=tkinter.NORMAL) - - -stop_button = customtkinter.CTkButton(master=app, text="Stop", command=stop) -stop_button.place(relx=0.5, rely=.9, anchor=tkinter.CENTER) -stop_button.configure(state=tkinter.DISABLED) -#start with stop disabled so you cannot click stop before start - -jupyter_button = customtkinter.CTkButton(master=app, text="Open Jupyter Hub to Upload", command=open_jupyter) -jupyter_button.place(relx=0.8, rely=.8, anchor=tkinter.CENTER) -jupyter_button.configure(state=tkinter.NORMAL) - -local_jupyter_button = customtkinter.CTkButton(master=app, text="Open LOCAL Jupyter Notebook", command=open_local_jupyter) -local_jupyter_button.place(relx=0.8, rely=.9, anchor=tkinter.CENTER) -local_jupyter_button.configure(state=tkinter.NORMAL) +mode_switch.pack(padx=20, pady=(10, 0), anchor="w") + +# Scrollable Frame for all widgets +scroll_frame = customtkinter.CTkScrollableFrame(master=app, width=760, height=440) +scroll_frame.pack(fill="both", expand=True, padx=10, pady=10) +# Configure flexible grid layout +for i in range(12): + scroll_frame.rowconfigure(i, weight=1) +for j in range(2): + scroll_frame.columnconfigure(j, weight=1) -# Create a main frame to hold all widgets -main_frame = customtkinter.CTkFrame(master=app) -main_frame.pack(fill="both", expand=True, padx=20, pady=20) - -# Configure the grid weights for flexibility -for i in range(6): # 6 rows for now, you can adjust - main_frame.rowconfigure(i, weight=1) -for j in range(2): # two columns: label + entry - main_frame.columnconfigure(j, weight=1) - +# Configure flexible grid layout +for i in range(12): + scroll_frame.rowconfigure(i, weight=1) +for j in range(2): + scroll_frame.columnconfigure(j, weight=1) # Row 0 — Username -label_user = customtkinter.CTkLabel(main_frame, text="Username:") +label_user = customtkinter.CTkLabel(scroll_frame, text="Username:") label_user.grid(row=0, column=0, sticky="e", padx=10, pady=10) -user_name = customtkinter.CTkEntry(main_frame, placeholder_text="Enter Here") +user_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="Enter Here") user_name.grid(row=0, column=1, sticky="we", padx=10, pady=10) # Row 1 — Longitude -label_long = customtkinter.CTkLabel(main_frame, text="Longitude:") +label_long = customtkinter.CTkLabel(scroll_frame, text="Longitude:") label_long.grid(row=1, column=0, sticky="e", padx=10, pady=10) -longitude_name = customtkinter.CTkEntry(main_frame, placeholder_text="e.g., -91.64") +longitude_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="e.g., -91.64") longitude_name.grid(row=1, column=1, sticky="we", padx=10, pady=10) # Row 2 — Latitude -label_lat = customtkinter.CTkLabel(main_frame, text="Latitude:") +label_lat = customtkinter.CTkLabel(scroll_frame, text="Latitude:") label_lat.grid(row=2, column=0, sticky="e", padx=10, pady=10) -latitude_name = customtkinter.CTkEntry(main_frame, placeholder_text="e.g., 44.05") +latitude_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="e.g., 44.05") latitude_name.grid(row=2, column=1, sticky="we", padx=10, pady=10) # Row 3 — Date -label_date = customtkinter.CTkLabel(main_frame, text="Date:") +label_date = customtkinter.CTkLabel(scroll_frame, text="Date:") label_date.grid(row=3, column=0, sticky="e", padx=10, pady=10) -date_name = customtkinter.CTkEntry(main_frame, placeholder_text="MM.DD.YYYY") +date_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="MM.DD.YYYY") date_name.grid(row=3, column=1, sticky="we", padx=10, pady=10) # Row 4 — Time and AM/PM combo -label_time = customtkinter.CTkLabel(main_frame, text="Time:") +label_time = customtkinter.CTkLabel(scroll_frame, text="Time:") label_time.grid(row=4, column=0, sticky="e", padx=10, pady=10) - -time_frame = customtkinter.CTkFrame(main_frame) +time_frame = customtkinter.CTkFrame(scroll_frame) time_frame.grid(row=4, column=1, sticky="we", padx=10, pady=10) time_frame.columnconfigure(0, weight=1) -time_frame.columnconfigure(1, weight=0) - curr_time = customtkinter.CTkEntry(time_frame, placeholder_text="00:00") curr_time.grid(row=0, column=0, sticky="we", padx=(0, 5)) combobox = customtkinter.CTkComboBox(time_frame, values=["am", "pm"], width=55) combobox.grid(row=0, column=1) combobox.set("am") -# Row 5 — Frequency Inputs -label_freq_i = customtkinter.CTkLabel(main_frame, text="Initial Frequency (MHz):") +# Row 5 — Initial Frequency +label_freq_i = customtkinter.CTkLabel(scroll_frame, text="Initial Frequency (MHz):") label_freq_i.grid(row=5, column=0, sticky="e", padx=10, pady=10) -freq_i_in = customtkinter.CTkEntry(main_frame, placeholder_text="1415") +freq_i_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="1415") freq_i_in.grid(row=5, column=1, sticky="we", padx=10, pady=10) -label_freq_f = customtkinter.CTkLabel(main_frame, text="Final Frequency (MHz):") +# Row 6 — Final Frequency +label_freq_f = customtkinter.CTkLabel(scroll_frame, text="Final Frequency (MHz):") label_freq_f.grid(row=6, column=0, sticky="e", padx=10, pady=10) -freq_f_in = customtkinter.CTkEntry(main_frame, placeholder_text="1425") +freq_f_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="1425") freq_f_in.grid(row=6, column=1, sticky="we", padx=10, pady=10) - -# label = customtkinter.CTkLabel(master=app, -# text="Username:", -# width=100, -# height=25, -# fg_color=("white", "gray"), -# corner_radius=5 -# ) - -# label.place(relx=0.5, rely=0.1, anchor=tkinter.W) - -# user_name = customtkinter.CTkEntry(master=app, -# placeholder_text="Enter Here", -# width=210, -# height=25, -# border_width=2, -# corner_radius=10 -# ) - -# user_name.place(relx=0.7, rely=0.1, anchor=tkinter.W) - -# # Adding Latitude and Longitude instead of general location -# label_long = customtkinter.CTkLabel(master=app, -# text="Longitude:", -# width=100, -# height=25, -# fg_color=("white", "gray"), -# corner_radius=5 -# ) -# label_long.place(relx=0.5, rely=0.2, anchor=tkinter.W) - -# longitude_name = customtkinter.CTkEntry(master=app, -# placeholder_text="e.g., -91.64", -# width=210, -# height=25, -# border_width=2, -# corner_radius=10 -# ) -# longitude_name.place(relx=0.7, rely=0.2, anchor=tkinter.W) - -# label_lat = customtkinter.CTkLabel(master=app, -# text="Latitude:", -# width=100, -# height=25, -# fg_color=("white", "gray"), -# corner_radius=5 -# ) -# label_lat.place(relx=0.5, rely=0.3, anchor=tkinter.W) - -# latitude_name = customtkinter.CTkEntry(master=app, -# placeholder_text="e.g., 44.05", -# width=210, -# height=25, -# border_width=2, -# corner_radius=10 -# ) -# latitude_name.place(relx=0.7, rely=0.3, anchor=tkinter.W) - -# label = customtkinter.CTkLabel(master=app, -# text="Date:", -# width=100, -# height=25, -# fg_color=("white", "gray"), -# corner_radius=5 -# ) - -# label.place(relx=0.5, rely=0.4, anchor=tkinter.W) - -# date_name = customtkinter.CTkEntry(master=app, -# placeholder_text="MM.DD.YYYY", -# width=150, -# height=25, -# border_width=2, -# corner_radius=10 -# ) - -# date_name.place(relx=0.7, rely=0.4, anchor=tkinter.W) - -# label = customtkinter.CTkLabel(master=app, -# text="Time:", -# width=100, -# height=25, -# fg_color=("white", "gray"), -# corner_radius=5 -# ) - -# label.place(relx=0.5, rely=0.5, anchor=tkinter.W) - -# curr_time = customtkinter.CTkEntry(master=app, -# placeholder_text="00:00", -# width=70, -# height=25, -# border_width=2, -# corner_radius=10 -# ) - -# curr_time.place(relx=0.7, rely=0.5, anchor=tkinter.W) - - - -combobox = customtkinter.CTkComboBox(master=app, - values=["am", "pm"], - height = 25, width = 55, variable = "") -combobox.pack(padx=5, pady=5) -combobox.set("am") # set initial value -combobox.place(relx=0.81, rely=0.5, anchor=tkinter.W) +# Row 7 — Integration Time +label_int_time = customtkinter.CTkLabel(scroll_frame, text="Integration Time (s):") +label_int_time.grid(row=7, column=0, sticky="e", padx=10, pady=10) +int_time_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="5") +int_time_in.grid(row=7, column=1, sticky="we", padx=10, pady=10) + +# Row 8 — Number of Integrations +label_nint = customtkinter.CTkLabel(scroll_frame, text="Number of Integrations:") +label_nint.grid(row=8, column=0, sticky="e", padx=10, pady=10) +nint_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="10") +nint_in.grid(row=8, column=1, sticky="we", padx=10, pady=10) + +# Row 9 — Description +label_desc = customtkinter.CTkLabel(scroll_frame, text="Description:") +label_desc.grid(row=9, column=0, sticky="ne", padx=10, pady=10) +description = customtkinter.CTkEntry(scroll_frame, + placeholder_text="Describe what you are looking at.") +description.grid(row=9, column=1, sticky="we", padx=10, pady=10) + +# Row 10 — Switches +switch_frame = customtkinter.CTkFrame(scroll_frame) +switch_frame.grid(row=10, column=0, columnspan=2, pady=10) +default_parameters_switch = customtkinter.CTkSwitch(switch_frame, text="Use Default Parameters", command=default_parameters) +biasT_switch = customtkinter.CTkSwitch(switch_frame, text="Enable Bias-T", command=biasT_switch) +system_date_time_switch = customtkinter.CTkSwitch(switch_frame, text="Use System Date and Time", command=current_date_time) +default_parameters_switch.grid(row=0, column=0, padx=10) +biasT_switch.grid(row=0, column=1, padx=10) +system_date_time_switch.grid(row=0, column=2, padx=10) + +# Row 11 — Buttons +button_frame = customtkinter.CTkFrame(scroll_frame) +button_frame.grid(row=11, column=0, columnspan=2, pady=(15, 5)) +start_button = customtkinter.CTkButton(button_frame, text="Start", command=start) +stop_button = customtkinter.CTkButton(button_frame, text="Stop", command=stop) +jupyter_button = customtkinter.CTkButton(button_frame, text="Open Jupyter Hub", command=open_jupyter) +local_jupyter_button = customtkinter.CTkButton(button_frame, text="Open Local Jupyter", command=open_local_jupyter) +start_button.grid(row=0, column=0, padx=10) +stop_button.grid(row=0, column=1, padx=10) +jupyter_button.grid(row=0, column=2, padx=10) +local_jupyter_button.grid(row=0, column=3, padx=10) # Enhanced hint label # It will only shows for active entry @@ -629,13 +446,8 @@ def show_hint(event): active_hint_label.place_forget() # Show this hint - content = entry_widget.get() x, y = entry_widget.winfo_x(), entry_widget.winfo_y() - if position == "below": - hint_label.place(x=x, y=y + entry_widget.winfo_height() + 2) - else: - hint_label.place(x=x + entry_widget.winfo_width() + 5, y=y) - + hint_label.place(x=x, y=y + entry_widget.winfo_height() + 2) active_hint_label = hint_label def hide_hint(event): @@ -649,24 +461,26 @@ def hide_hint(event): #here are the two switches at the bottom of each side. You can view the location with relx and rely -system_date_time_switch = customtkinter.CTkSwitch(master=app, text="Use System Date and Time", command=current_date_time, onvalue="on", offvalue="off") -system_date_time_switch.pack(padx=20, pady=10) -system_date_time_switch.place(relx=0.7, rely=.6, anchor=tkinter.CENTER) +# system_date_time_switch = customtkinter.CTkSwitch(master=app, text="Use System Date and Time", command=current_date_time, onvalue="on", offvalue="off") +# system_date_time_switch.pack(padx=20, pady=10) +# system_date_time_switch.place(relx=0.7, rely=.6, anchor=tkinter.CENTER) # Add hint labels for all entries with appropriate positions # Position can be "below" or "beside" as needed -add_hint_label(freq_i_in, "Start frequency in MHz", position="below") -add_hint_label(freq_f_in, "End frequency in MHz", position="below") -add_hint_label(int_time_in, "Integration time in seconds", position="below") -add_hint_label(nint_in, "Number of integrations", position="below") -add_hint_label(user_name, "Your WSU username or observer name", position="below") -# add_hint_label(location_name, "e.g., Winona, MN or Observatory", position="below") -add_hint_label(longitude_name, "Longitude in decimal degrees (East/West)", position="below") -add_hint_label(latitude_name, "Latitude in decimal degrees (North/South)", position="below") -# add_hint_label(time_name, "Trial number (1, 2, 3…)", position="below") -add_hint_label(date_name, "Format: MM.DD.YYYY", position="below") -add_hint_label(curr_time, "Format: HH:MM with am/pm toggle", position="below") -add_hint_label(description, "Short description of observation", position="below") +add_hint_label(user_name, "Your WSU username or observer name") +add_hint_label(longitude_name, "Longitude in decimal degrees (East/West)") +add_hint_label(latitude_name, "Latitude in decimal degrees (North/South)") +add_hint_label(date_name, "Format: MM.DD.YYYY") +add_hint_label(curr_time, "Format: HH:MM with am/pm toggle") +add_hint_label(freq_i_in, "Start frequency in MHz") +add_hint_label(freq_f_in, "End frequency in MHz") +add_hint_label(int_time_in, "Integration time in seconds") +add_hint_label(nint_in, "Number of integrations") +add_hint_label(description, "Short description of observation") + +# Make window responsive +app.rowconfigure(0, weight=1) +app.columnconfigure(0, weight=1) app.mainloop() From e4edad756b26522a96b7ff58ad8c6d3adca197de Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:26:35 -0600 Subject: [PATCH 06/12] Update chart-observe.py --- daq/chart-observe.py | 98 +++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index 7d8d453..f1ca183 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -334,76 +334,79 @@ def mode(): for j in range(2): scroll_frame.columnconfigure(j, weight=1) +# Store references to all fields for responsive layout +responsive_widgets = [] + +# === Input Fields (Adaptive Grid) === + # Row 0 — Username label_user = customtkinter.CTkLabel(scroll_frame, text="Username:") -label_user.grid(row=0, column=0, sticky="e", padx=10, pady=10) -user_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="Enter Here") -user_name.grid(row=0, column=1, sticky="we", padx=10, pady=10) +label_user.grid(row=0, column=0, sticky="w", padx=10, pady=10) +user_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="Enter Here", width=180) +user_name.grid(row=0, column=1, sticky="w", padx=10, pady=10) -# Row 1 — Longitude +# Row 0 (right side) — Longitude label_long = customtkinter.CTkLabel(scroll_frame, text="Longitude:") -label_long.grid(row=1, column=0, sticky="e", padx=10, pady=10) -longitude_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="e.g., -91.64") -longitude_name.grid(row=1, column=1, sticky="we", padx=10, pady=10) +label_long.grid(row=0, column=2, sticky="w", padx=10, pady=10) +longitude_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="e.g., -91.64", width=120) +longitude_name.grid(row=0, column=3, sticky="w", padx=10, pady=10) -# Row 2 — Latitude +# Row 1 — Latitude label_lat = customtkinter.CTkLabel(scroll_frame, text="Latitude:") -label_lat.grid(row=2, column=0, sticky="e", padx=10, pady=10) -latitude_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="e.g., 44.05") -latitude_name.grid(row=2, column=1, sticky="we", padx=10, pady=10) +label_lat.grid(row=1, column=0, sticky="w", padx=10, pady=10) +latitude_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="e.g., 44.05", width=120) +latitude_name.grid(row=1, column=1, sticky="w", padx=10, pady=10) -# Row 3 — Date +# Row 1 (right side) — Date label_date = customtkinter.CTkLabel(scroll_frame, text="Date:") -label_date.grid(row=3, column=0, sticky="e", padx=10, pady=10) -date_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="MM.DD.YYYY") -date_name.grid(row=3, column=1, sticky="we", padx=10, pady=10) +label_date.grid(row=1, column=2, sticky="w", padx=10, pady=10) +date_name = customtkinter.CTkEntry(scroll_frame, placeholder_text="MM.DD.YYYY", width=120) +date_name.grid(row=1, column=3, sticky="w", padx=10, pady=10) -# Row 4 — Time and AM/PM combo +# Row 2 — Time + AM/PM combo label_time = customtkinter.CTkLabel(scroll_frame, text="Time:") -label_time.grid(row=4, column=0, sticky="e", padx=10, pady=10) +label_time.grid(row=2, column=0, sticky="w", padx=10, pady=10) time_frame = customtkinter.CTkFrame(scroll_frame) -time_frame.grid(row=4, column=1, sticky="we", padx=10, pady=10) -time_frame.columnconfigure(0, weight=1) -curr_time = customtkinter.CTkEntry(time_frame, placeholder_text="00:00") -curr_time.grid(row=0, column=0, sticky="we", padx=(0, 5)) -combobox = customtkinter.CTkComboBox(time_frame, values=["am", "pm"], width=55) +time_frame.grid(row=2, column=1, sticky="w", padx=10, pady=10) +curr_time = customtkinter.CTkEntry(time_frame, placeholder_text="00:00", width=80) +curr_time.grid(row=0, column=0, sticky="w", padx=(0, 5)) +combobox = customtkinter.CTkComboBox(time_frame, values=["am", "pm"], width=60) combobox.grid(row=0, column=1) combobox.set("am") -# Row 5 — Initial Frequency +# Row 2 (right side) — Initial Frequency label_freq_i = customtkinter.CTkLabel(scroll_frame, text="Initial Frequency (MHz):") -label_freq_i.grid(row=5, column=0, sticky="e", padx=10, pady=10) -freq_i_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="1415") -freq_i_in.grid(row=5, column=1, sticky="we", padx=10, pady=10) +label_freq_i.grid(row=2, column=2, sticky="w", padx=10, pady=10) +freq_i_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="1415", width=100) +freq_i_in.grid(row=2, column=3, sticky="w", padx=10, pady=10) -# Row 6 — Final Frequency +# Row 3 — Final Frequency label_freq_f = customtkinter.CTkLabel(scroll_frame, text="Final Frequency (MHz):") -label_freq_f.grid(row=6, column=0, sticky="e", padx=10, pady=10) -freq_f_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="1425") -freq_f_in.grid(row=6, column=1, sticky="we", padx=10, pady=10) +label_freq_f.grid(row=3, column=0, sticky="w", padx=10, pady=10) +freq_f_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="1425", width=100) +freq_f_in.grid(row=3, column=1, sticky="w", padx=10, pady=10) -# Row 7 — Integration Time +# Row 3 (right side) — Integration Time label_int_time = customtkinter.CTkLabel(scroll_frame, text="Integration Time (s):") -label_int_time.grid(row=7, column=0, sticky="e", padx=10, pady=10) -int_time_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="5") -int_time_in.grid(row=7, column=1, sticky="we", padx=10, pady=10) +label_int_time.grid(row=3, column=2, sticky="w", padx=10, pady=10) +int_time_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="5", width=100) +int_time_in.grid(row=3, column=3, sticky="w", padx=10, pady=10) -# Row 8 — Number of Integrations +# Row 4 — Number of Integrations label_nint = customtkinter.CTkLabel(scroll_frame, text="Number of Integrations:") -label_nint.grid(row=8, column=0, sticky="e", padx=10, pady=10) -nint_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="10") -nint_in.grid(row=8, column=1, sticky="we", padx=10, pady=10) +label_nint.grid(row=4, column=0, sticky="w", padx=10, pady=10) +nint_in = customtkinter.CTkEntry(scroll_frame, placeholder_text="10", width=100) +nint_in.grid(row=4, column=1, sticky="w", padx=10, pady=10) -# Row 9 — Description +# Row 4 (right side) — Description label_desc = customtkinter.CTkLabel(scroll_frame, text="Description:") -label_desc.grid(row=9, column=0, sticky="ne", padx=10, pady=10) -description = customtkinter.CTkEntry(scroll_frame, - placeholder_text="Describe what you are looking at.") -description.grid(row=9, column=1, sticky="we", padx=10, pady=10) +label_desc.grid(row=4, column=2, sticky="w", padx=10, pady=10) +description = customtkinter.CTkEntry(scroll_frame, placeholder_text="Describe observation", width=280) +description.grid(row=4, column=3, sticky="w", padx=10, pady=10) -# Row 10 — Switches +# Row 5 — Switches switch_frame = customtkinter.CTkFrame(scroll_frame) -switch_frame.grid(row=10, column=0, columnspan=2, pady=10) +switch_frame.grid(row=5, column=0, columnspan=4, pady=10, sticky="w") default_parameters_switch = customtkinter.CTkSwitch(switch_frame, text="Use Default Parameters", command=default_parameters) biasT_switch = customtkinter.CTkSwitch(switch_frame, text="Enable Bias-T", command=biasT_switch) system_date_time_switch = customtkinter.CTkSwitch(switch_frame, text="Use System Date and Time", command=current_date_time) @@ -411,9 +414,9 @@ def mode(): biasT_switch.grid(row=0, column=1, padx=10) system_date_time_switch.grid(row=0, column=2, padx=10) -# Row 11 — Buttons +# Row 6 — Buttons button_frame = customtkinter.CTkFrame(scroll_frame) -button_frame.grid(row=11, column=0, columnspan=2, pady=(15, 5)) +button_frame.grid(row=6, column=0, columnspan=4, pady=(15, 5), sticky="w") start_button = customtkinter.CTkButton(button_frame, text="Start", command=start) stop_button = customtkinter.CTkButton(button_frame, text="Stop", command=stop) jupyter_button = customtkinter.CTkButton(button_frame, text="Open Jupyter Hub", command=open_jupyter) @@ -423,6 +426,7 @@ def mode(): jupyter_button.grid(row=0, column=2, padx=10) local_jupyter_button.grid(row=0, column=3, padx=10) + # Enhanced hint label # It will only shows for active entry active_hint_label = None # global tracker From b816f7fa712aee5e6ce6f82367c1178b9799df6b Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Sun, 16 Nov 2025 22:08:25 -0600 Subject: [PATCH 07/12] Update chart-observe.py --- daq/chart-observe.py | 147 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 128 insertions(+), 19 deletions(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index f1ca183..2760e69 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -69,11 +69,6 @@ def start(): date = customtkinter.CTkEntry.get(date_name) time = customtkinter.CTkEntry.get(curr_time) - #checking for empty trials - if not trial: - trial = '1' - - tDay = combobox.get() #make sure the location does not have spaces or slashes that many people accidentally do # location = sLocation.replace(" ", "-") @@ -322,12 +317,6 @@ def mode(): scroll_frame = customtkinter.CTkScrollableFrame(master=app, width=760, height=440) scroll_frame.pack(fill="both", expand=True, padx=10, pady=10) -# Configure flexible grid layout -for i in range(12): - scroll_frame.rowconfigure(i, weight=1) -for j in range(2): - scroll_frame.columnconfigure(j, weight=1) - # Configure flexible grid layout for i in range(12): scroll_frame.rowconfigure(i, weight=1) @@ -404,16 +393,51 @@ def mode(): description = customtkinter.CTkEntry(scroll_frame, placeholder_text="Describe observation", width=280) description.grid(row=4, column=3, sticky="w", padx=10, pady=10) +# Fix minimum label widths so text never disappears +MIN_LABEL_WIDTH = 150 + +all_labels = [ + label_user, label_long, label_lat, label_date, label_time, + label_freq_i, label_freq_f, label_int_time, label_nint, label_desc +] + +for lbl in all_labels: + lbl.configure(width=MIN_LABEL_WIDTH) + + # Row 5 — Switches switch_frame = customtkinter.CTkFrame(scroll_frame) switch_frame.grid(row=5, column=0, columnspan=4, pady=10, sticky="w") -default_parameters_switch = customtkinter.CTkSwitch(switch_frame, text="Use Default Parameters", command=default_parameters) -biasT_switch = customtkinter.CTkSwitch(switch_frame, text="Enable Bias-T", command=biasT_switch) -system_date_time_switch = customtkinter.CTkSwitch(switch_frame, text="Use System Date and Time", command=current_date_time) + +default_parameters_switch = customtkinter.CTkSwitch( + switch_frame, + text="Use Default Parameters", + command=default_parameters, + onvalue="on", + offvalue="off" +) + +biasT_switch = customtkinter.CTkSwitch( + switch_frame, + text="Enable Bias-T", + command=biasT_switch, + onvalue="on", + offvalue="off" +) + +system_date_time_switch = customtkinter.CTkSwitch( + switch_frame, + text="Use System Date and Time", + command=current_date_time, + onvalue="on", + offvalue="off" +) + default_parameters_switch.grid(row=0, column=0, padx=10) biasT_switch.grid(row=0, column=1, padx=10) system_date_time_switch.grid(row=0, column=2, padx=10) + # Row 6 — Buttons button_frame = customtkinter.CTkFrame(scroll_frame) button_frame.grid(row=6, column=0, columnspan=4, pady=(15, 5), sticky="w") @@ -432,13 +456,16 @@ def mode(): active_hint_label = None # global tracker def add_hint_label(entry_widget, hint_text, position="below"): -# Adds a small gray hint label that appears only while typing in this entry.""" + # Adds a small gray hint label that appears only while typing in this entry. global active_hint_label - hint_label = customtkinter.CTkLabel(master=app, - text=hint_text, - text_color="gray50", - font=("Arial", 10)) + hint_label = customtkinter.CTkLabel( + master=scroll_frame, + text=hint_text, + text_color="gray50", + font=("Arial", 10) + ) + hint_label.place_forget() # start hidden def show_hint(event): @@ -483,6 +510,88 @@ def hide_hint(event): add_hint_label(description, "Short description of observation") # Make window responsive +# Responsive layout: switch between 2-column and 1-column when resizing +def on_resize(event): + width = app.winfo_width() + + # Threshold for two-column layout +def on_resize(event): + width = app.winfo_width() + if width < 900: + + # Reflow input fields (already working) + label_user.grid_configure(row=0, column=0) + user_name.grid_configure(row=0, column=1) + + label_long.grid_configure(row=1, column=0) + longitude_name.grid_configure(row=1, column=1) + + label_lat.grid_configure(row=2, column=0) + latitude_name.grid_configure(row=2, column=1) + + label_date.grid_configure(row=3, column=0) + date_name.grid_configure(row=3, column=1) + + label_time.grid_configure(row=4, column=0) + time_frame.grid_configure(row=4, column=1) + + label_freq_i.grid_configure(row=5, column=0) + freq_i_in.grid_configure(row=5, column=1) + + label_freq_f.grid_configure(row=6, column=0) + freq_f_in.grid_configure(row=6, column=1) + + label_int_time.grid_configure(row=7, column=0) + int_time_in.grid_configure(row=7, column=1) + + label_nint.grid_configure(row=8, column=0) + nint_in.grid_configure(row=8, column=1) + + label_desc.grid_configure(row=9, column=0) + description.grid_configure(row=9, column=1) + + switch_frame.grid_configure(row=10, column=0, columnspan=2, sticky="w", padx=10, pady=(20, 10)) + + button_frame.grid_configure(row=11, column=0, columnspan=2, sticky="w", padx=10, pady=(10, 20)) + else: + + label_user.grid_configure(row=0, column=0) + user_name.grid_configure(row=0, column=1) + + label_long.grid_configure(row=0, column=2) + longitude_name.grid_configure(row=0, column=3) + + label_lat.grid_configure(row=1, column=0) + latitude_name.grid_configure(row=1, column=1) + + label_date.grid_configure(row=1, column=2) + date_name.grid_configure(row=1, column=3) + + label_time.grid_configure(row=2, column=0) + time_frame.grid_configure(row=2, column=1) + + label_freq_i.grid_configure(row=2, column=2) + freq_i_in.grid_configure(row=2, column=3) + + label_freq_f.grid_configure(row=3, column=0) + freq_f_in.grid_configure(row=3, column=1) + + label_int_time.grid_configure(row=3, column=2) + int_time_in.grid_configure(row=3, column=3) + + label_nint.grid_configure(row=4, column=0) + nint_in.grid_configure(row=4, column=1) + + label_desc.grid_configure(row=4, column=2) + description.grid_configure(row=4, column=3) + + switch_frame.grid_configure(row=5, column=0, columnspan=4, sticky="w", padx=10, pady=10) + + button_frame.grid_configure(row=6, column=0, columnspan=4, sticky="w", padx=10, pady=10) + +# Bind the handler +app.bind("", on_resize) + app.rowconfigure(0, weight=1) app.columnconfigure(0, weight=1) From 403b5a9027544134a46ea348bb9f4fc42bdcb86f Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Sun, 30 Nov 2025 22:30:14 -0600 Subject: [PATCH 08/12] Update daq/chart-observe.py Co-authored-by: Adam Beardsley --- daq/chart-observe.py | 1 - 1 file changed, 1 deletion(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index 2760e69..a2d5751 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -61,7 +61,6 @@ def start(): sUser = customtkinter.CTkEntry.get(user_name) sLongitude = customtkinter.CTkEntry.get(longitude_name) #getting the longitude from the entry sLatitude = customtkinter.CTkEntry.get(latitude_name) #getting the latitude from the entry - # trial = customtkinter.CTkEntry.get(time_name) date_name.configure(state=tkinter.NORMAL) #checking if date was empty so that it knows to use the input from entry or the one from the system time and date From 4421fe7a1a7a94d6caa2c0dda89f21997ca2eed4 Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Sun, 30 Nov 2025 22:30:21 -0600 Subject: [PATCH 09/12] Update daq/chart-observe.py Co-authored-by: Adam Beardsley --- daq/chart-observe.py | 1 - 1 file changed, 1 deletion(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index a2d5751..a269ce7 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -70,7 +70,6 @@ def start(): tDay = combobox.get() #make sure the location does not have spaces or slashes that many people accidentally do - # location = sLocation.replace(" ", "-") date = date.replace("/", ".") user = sUser.replace("_", ".") From 3baa96c95713eec952bfb92a956905a3530bc123 Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Sun, 30 Nov 2025 22:30:44 -0600 Subject: [PATCH 10/12] Update daq/chart-observe.py Co-authored-by: Adam Beardsley --- daq/chart-observe.py | 1 - 1 file changed, 1 deletion(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index a269ce7..319001e 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -190,7 +190,6 @@ def default_parameters(): int_time_in.configure(state=tkinter.DISABLED) nint_in.configure(state=tkinter.DISABLED) -# Why these defaults? default_freq_i = '1415' default_freq_f = '1425' default_int_time = '5' From 751b9774872ede6a3a453160215d856a20e5c270 Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:05:02 -0600 Subject: [PATCH 11/12] Update chart-observe.py --- daq/chart-observe.py | 50 +++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/daq/chart-observe.py b/daq/chart-observe.py index 319001e..02403c2 100755 --- a/daq/chart-observe.py +++ b/daq/chart-observe.py @@ -452,37 +452,50 @@ def mode(): # It will only shows for active entry active_hint_label = None # global tracker -def add_hint_label(entry_widget, hint_text, position="below"): - # Adds a small gray hint label that appears only while typing in this entry. - global active_hint_label +# Enhanced responsive hint labels +active_hint_label = None +HINT_MODE = "below" # this will dynamically switch based on window width + +def add_hint_label(entry_widget, hint_text): + global active_hint_label hint_label = customtkinter.CTkLabel( master=scroll_frame, text=hint_text, text_color="gray50", - font=("Arial", 10) + font=("Arial", 9) ) - - hint_label.place_forget() # start hidden + hint_label.place_forget() def show_hint(event): - """Show this hint and hide others.""" - global active_hint_label + global active_hint_label, HINT_MODE - # Hide any previously active hint - if active_hint_label is not None and active_hint_label != hint_label: + # Hide previous hint + if active_hint_label and active_hint_label != hint_label: active_hint_label.place_forget() - # Show this hint - x, y = entry_widget.winfo_x(), entry_widget.winfo_y() - hint_label.place(x=x, y=y + entry_widget.winfo_height() + 2) + # Get widget coords relative to scroll_frame + entry_x = entry_widget.winfo_x() + entry_y = entry_widget.winfo_y() + + if HINT_MODE == "side": + # Side placement + hint_label.place( + x=entry_x + entry_x/2, + y=entry_y + ) + else: + # Below placement + hint_label.place( + x=entry_x, + y=entry_y + entry_widget.winfo_height()/2 + ) + active_hint_label = hint_label def hide_hint(event): - """Hide hint when leaving this box.""" hint_label.place_forget() - # Bind focus events entry_widget.bind("", show_hint) entry_widget.bind("", hide_hint) @@ -514,6 +527,13 @@ def on_resize(event): # Threshold for two-column layout def on_resize(event): width = app.winfo_width() + + # Making the hint global to adjust its position + global HINT_MODE + if width < 900: + HINT_MODE = "side" + else: + HINT_MODE = "below" if width < 900: # Reflow input fields (already working) From 449dbb613fb14a6cfe6f324363537c14efda22c2 Mon Sep 17 00:00:00 2001 From: Pronob Kumar <115051133+Maxman0001@users.noreply.github.com> Date: Sat, 20 Dec 2025 16:44:39 -0600 Subject: [PATCH 12/12] New responsive layout --- daq/chart_observe_flet.py | 502 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 502 insertions(+) create mode 100644 daq/chart_observe_flet.py diff --git a/daq/chart_observe_flet.py b/daq/chart_observe_flet.py new file mode 100644 index 0000000..6ca4b50 --- /dev/null +++ b/daq/chart_observe_flet.py @@ -0,0 +1,502 @@ +#!/usr/bin/python3 +import os +import subprocess +import datetime +import shutil +import threading +import webbrowser +import time as time_mod + +import flet as ft # Flet for GUI to become modern and responsive + +# Defaults (same as our old Tk app) +DEFAULT_FREQ_I = "1415" +DEFAULT_FREQ_F = "1425" +DEFAULT_INT_TIME = "5" +DEFAULT_NINT = "10" + + + +# Backend helpers (preserve behavior) +def _normalize_date(date_str: str) -> str: + # Your original code replaces "/" with "." and expects MM.DD.YYYY + return (date_str or "").replace("/", ".") + + +def _normalize_user(user: str) -> str: + # Your original code replaced "_" with "." + return (user or "").replace("_", ".") + + +def _ensure_data_dir() -> str: + home = os.path.expanduser("~") + data_dir = os.path.join(home, "data") + if not os.path.isdir(data_dir): + os.mkdir(data_dir, mode=0o1777) + print(f"Directory '{data_dir}' is built!") + else: + print("directory data already exists") + return data_dir + + +def _make_observation_dir_name(user: str, lon: str, lat: str, date_str: str, time_str: str, ampm: str) -> str: + # Matches our existing naming pattern (no "location", no "trial") + # directory = f"{user}_lon{lon}_lat{lat}_{year}.{month}.{day}_{time.replace(':','.')}_{ampm}" + month, day, year = date_str.split(".") + date_y_m_d = f"{year}.{month}.{day}" + return f"{user}_lon{lon}_lat{lat}_{date_y_m_d}_{time_str.replace(':', '.')}_{ampm}" + + +def _apply_system_date_time(date_str: str, time_str: str, ampm: str) -> None: + """ + Preserve your behavior: + - Convert date MM.DD.YYYY and time HH:MM with am/pm into sudo date -s "YYYY-MM-DDTHH:MM:SS" + - Add seconds ":00" + """ + month, day, year = date_str.split(".") + if len(month) == 1: + month = "0" + month + if len(day) == 1: + day = "0" + day + if len(year) == 2: + year = "20" + year # Assuming 21st century for 2-digit years + + hour, minute = time_str.split(":") + if ampm == "pm" and hour != "12": + hour = str(int(hour) + 12) + if len(hour) == 1: + hour = "0" + hour + + # Add seconds + hhmmss = f"{hour}:{minute}:00" + cmd = f'sudo date -s "{year}-{month}-{day}T{hhmmss}"' + os.system(cmd) + + +# Flet App +def main(page: ft.Page): + page.title = "CHART Data Collection" + page.theme_mode = ft.ThemeMode.LIGHT + page.padding = 16 + + # State + state = { + "biasT": False, + "proc": None, + "data_directory": None, + "directory": None, + "stop_requested": False, + } + + # UI Controls + # Inputs (structure matches our old app) + user_tf = ft.TextField( + label="Username", + hint_text="Enter Here", + helper_text="Your WSU username or observer name", + width=320, + ) + lon_tf = ft.TextField( + label="Longitude", + hint_text="e.g., -91.64", + helper_text="Longitude in decimal degrees (East/West)", + width=220, + ) + lat_tf = ft.TextField( + label="Latitude", + hint_text="e.g., 44.05", + helper_text="Latitude in decimal degrees (North/South)", + width=220, + ) + + date_tf = ft.TextField( + label="Date", + hint_text="MM.DD.YYYY", + helper_text="Format: MM.DD.YYYY", + width=220, + ) + time_tf = ft.TextField( + label="Time", + hint_text="HH:MM", + helper_text="Format: HH:MM (use am/pm selector)", + width=140, + ) + ampm_dd = ft.Dropdown( + label="AM/PM", + options=[ft.dropdown.Option("am"), ft.dropdown.Option("pm")], + value="am", + width=120, + ) + + freq_i_tf = ft.TextField( + label="Initial Frequency (MHz)", + hint_text=DEFAULT_FREQ_I, + helper_text="Start frequency in MHz", + width=220, + ) + freq_f_tf = ft.TextField( + label="Final Frequency (MHz)", + hint_text=DEFAULT_FREQ_F, + helper_text="End frequency in MHz", + width=220, + ) + int_time_tf = ft.TextField( + label="Integration Time (s)", + hint_text=DEFAULT_INT_TIME, + helper_text="Integration time in seconds", + width=220, + ) + nint_tf = ft.TextField( + label="Number of Integrations", + hint_text=DEFAULT_NINT, + helper_text="Number of integrations", + width=240, + ) + + desc_tf = ft.TextField( + label="Description", + hint_text="Describe observation", + helper_text="Short description of observation", + width=520, + ) + + # Switches (keep same behaviors) + use_defaults_sw = ft.Switch(label="Use Default Parameters", value=False) + biasT_sw = ft.Switch(label="Enable Bias-T", value=False) + use_system_dt_sw = ft.Switch(label="Use System Date and Time", value=False) + dark_mode_sw = ft.Switch(label="Dark Mode", value=False) + + # Status / logging + status_text = ft.Text(value="Ready.", selectable=True) + snack = ft.SnackBar(content=ft.Text("")) + + page.snack_bar = snack + + # Buttons + start_btn = ft.ElevatedButton(text="Start") + stop_btn = ft.ElevatedButton(text="Stop", disabled=True) + open_jupyter_btn = ft.OutlinedButton(text="Open documentation") + + # Helpers + def _toast(msg: str): + page.snack_bar.content = ft.Text(msg) + page.snack_bar.open = True + page.update() + + def _set_status(msg: str): + status_text.value = msg + page.update() + + def _set_running(running: bool): + start_btn.disabled = running + stop_btn.disabled = not running + page.update() + + def _refresh_system_datetime_fields(): + now = datetime.datetime.now() + date_entry = f"{now.month}.{now.day}.{now.year}" + hour = now.hour + ampm = "am" + if hour >= 12: + ampm = "pm" + if hour > 12: + hour -= 12 + minute = f"{now.minute:02d}" + time_entry = f"{hour}:{minute}" + + # Fill hints/values similarly to your placeholder approach + date_tf.value = date_entry + time_tf.value = time_entry + ampm_dd.value = ampm + + # Disable/enable fields to match the switch behavior + locked = use_system_dt_sw.value is True + date_tf.disabled = locked + time_tf.disabled = locked + ampm_dd.disabled = locked + + page.update() + + # If system datetime is enabled, keep updating every 10 seconds + def _system_dt_loop(): + while True: + time_mod.sleep(10) #this can be increased or decreased + if use_system_dt_sw.value: + _refresh_system_datetime_fields() + + threading.Thread(target=_system_dt_loop, daemon=True).start() + + def _create_zip_watcher(): + """ + Here the Tk version calls create_zip() periodically and if proc ends successfully: + - writes description.txt + - zips the directory + - stops + """ + while True: + time_mod.sleep(10) + proc = state.get("proc") + if not proc: + continue + # If process ended with code 0 + if proc.poll() is not None and proc.poll() == 0: + try: + data_dir = state["data_directory"] + directory = state["directory"] + if not data_dir or not directory: + continue + + _set_status("Creating description.txt and zip archive...") + + desc = desc_tf.value or "" + with open(os.path.join(data_dir, directory, "description.txt"), "w") as f: + f.write(desc) + + shutil.make_archive(os.path.join(data_dir, directory), "zip", data_dir, directory) + _set_status("Zip created. Data collection halted.") + # Auto-stop behavior matches your original + _do_stop(silent=True) + except Exception as e: + _toast(f"Zip creation error: {e}") + + threading.Thread(target=_create_zip_watcher, daemon=True).start() + + # Actions + def _do_stop(silent: bool = False): + proc = state.get("proc") + if proc: + try: + proc.terminate() + except Exception: + pass + state["proc"] = None + _set_running(False) + if not silent: + _set_status("Data collection halted!") + + def _do_start(e=None): + # Gather inputs + user = _normalize_user(user_tf.value.strip() if user_tf.value else "") + lon = (lon_tf.value or "").strip() + lat = (lat_tf.value or "").strip() + + # Use datetime depending on switch + if use_system_dt_sw.value: + # Ensure latest values are present + _refresh_system_datetime_fields() + + date_str = _normalize_date(date_tf.value.strip() if date_tf.value else "") + time_str = (time_tf.value or "").strip() + ampm = (ampm_dd.value or "am").strip() + + # Validate minimum required + if not user: + _toast("Username is required.") + return + if not lon or not lat: + _toast("Longitude and Latitude are required.") + return + if not date_str or not time_str: + _toast("Date and Time are required.") + return + + # Handle defaults switch + freq_i = (freq_i_tf.value or "").strip() + freq_f = (freq_f_tf.value or "").strip() + itime = (int_time_tf.value or "").strip() + nint = (nint_tf.value or "").strip() + + if use_defaults_sw.value: + freq_i, freq_f, itime, nint = DEFAULT_FREQ_I, DEFAULT_FREQ_F, DEFAULT_INT_TIME, DEFAULT_NINT + freq_i_tf.value = freq_i + freq_f_tf.value = freq_f + int_time_tf.value = itime + nint_tf.value = nint + + # Disable fields when using defaults (like the old Tk behavior) + freq_i_tf.disabled = True + freq_f_tf.disabled = True + int_time_tf.disabled = True + nint_tf.disabled = True + else: + # Enable fields + freq_i_tf.disabled = False + freq_f_tf.disabled = False + int_time_tf.disabled = False + nint_tf.disabled = False + + # Fill missing with defaults + if not freq_i: + freq_i = DEFAULT_FREQ_I + if not freq_f: + freq_f = DEFAULT_FREQ_F + if not itime: + itime = DEFAULT_INT_TIME + if not nint: + nint = DEFAULT_NINT + + page.update() + + # Apply system date/time to OS (same as the old script) + try: + _apply_system_date_time(date_str, time_str, ampm) + except Exception as ex: + _toast(f"Date/time apply failed: {ex}") + return + + # Create data directory and observation directory + data_dir = _ensure_data_dir() + directory = _make_observation_dir_name(user, lon, lat, date_str, time_str, ampm) + main_dir = os.path.join(data_dir, directory) + + if os.path.isdir(main_dir): + _toast("File already exists. Change the time before clicking Start.") + return + + os.mkdir(main_dir, mode=0o1777) + state["data_directory"] = data_dir + state["directory"] = directory + + use_directory = os.path.join(data_dir, directory) + print("directory being used:", use_directory) + + # Build command (Tried to have the exact behavior as the old one. But not sure everything is same) + biasT = bool(biasT_sw.value) + state["biasT"] = biasT + + if biasT: + cmd = [ + "freq_and_time_scan.py", + f"--freq_i={freq_i}", + f"--freq_f={freq_f}", + f"--int_time={itime}", + f"--nint={nint}", + f"--data_dir={use_directory}", + "--biasT=True", + ] + else: + cmd = [ + "freq_and_time_scan.py", + f"--freq_i={freq_i}", + f"--freq_f={freq_f}", + f"--int_time={itime}", + f"--nint={nint}", + f"--data_dir={use_directory}", + ] + + try: + proc = subprocess.Popen(cmd) + state["proc"] = proc + except Exception as ex: + _toast(f"Failed to start scan: {ex}") + return + + _set_running(True) + _set_status(f"Running. Output directory: {directory}") + + def _toggle_defaults(e): + # If turning on defaults, disable and fill. else enable + if use_defaults_sw.value: + freq_i_tf.value = DEFAULT_FREQ_I + freq_f_tf.value = DEFAULT_FREQ_F + int_time_tf.value = DEFAULT_INT_TIME + nint_tf.value = DEFAULT_NINT + freq_i_tf.disabled = True + freq_f_tf.disabled = True + int_time_tf.disabled = True + nint_tf.disabled = True + else: + freq_i_tf.disabled = False + freq_f_tf.disabled = False + int_time_tf.disabled = False + nint_tf.disabled = False + page.update() + + def _toggle_system_dt(e): + _refresh_system_datetime_fields() + + def _toggle_dark_mode(e): + page.theme_mode = ft.ThemeMode.DARK if dark_mode_sw.value else ft.ThemeMode.LIGHT + page.update() + + def _open_jupyter(e): + webbrowser.open_new("https://adampbeardsley.github.io/research.html#chart") # It is going to the documentation of the project + # We can change it to the Jupyter Hub link if needed + + # Wire handlers + start_btn.on_click = _do_start + stop_btn.on_click = lambda e: _do_stop() + open_jupyter_btn.on_click = _open_jupyter + use_defaults_sw.on_change = _toggle_defaults + use_system_dt_sw.on_change = _toggle_system_dt + dark_mode_sw.on_change = _toggle_dark_mode + + # Initial state of system datetime fields + _refresh_system_datetime_fields() + + # Responsive Layout + # ResponsiveRow wraps controls naturally (no manual resize code required). This part was my favorite. + # We keep the structure similar to our existing GUI: observer info + time/date + parameters + description + switches + buttons. + form = ft.ResponsiveRow( + columns=12, + spacing=12, + run_spacing=12, + controls=[ + ft.Container(user_tf, col={"sm": 12, "md": 6, "lg": 6}), + ft.Container(lon_tf, col={"sm": 12, "md": 3, "lg": 3}), + ft.Container(lat_tf, col={"sm": 12, "md": 3, "lg": 3}), + + ft.Container(date_tf, col={"sm": 12, "md": 4, "lg": 4}), + ft.Container(time_tf, col={"sm": 6, "md": 4, "lg": 4}), + ft.Container(ampm_dd, col={"sm": 6, "md": 4, "lg": 4}), + + ft.Container(freq_i_tf, col={"sm": 12, "md": 3, "lg": 3}), + ft.Container(freq_f_tf, col={"sm": 12, "md": 3, "lg": 3}), + ft.Container(int_time_tf, col={"sm": 12, "md": 3, "lg": 3}), + ft.Container(nint_tf, col={"sm": 12, "md": 3, "lg": 3}), + + ft.Container(desc_tf, col={"sm": 12, "md": 12, "lg": 12}), + ], + ) + + switches = ft.ResponsiveRow( + columns=12, + spacing=12, + controls=[ + ft.Container(dark_mode_sw, col={"sm": 12, "md": 3, "lg": 3}), + ft.Container(use_defaults_sw, col={"sm": 12, "md": 3, "lg": 3}), + ft.Container(biasT_sw, col={"sm": 12, "md": 3, "lg": 3}), + ft.Container(use_system_dt_sw, col={"sm": 12, "md": 3, "lg": 3}), + ], + ) + + buttons = ft.ResponsiveRow( + columns=12, + spacing=12, + controls=[ + ft.Container(start_btn, col={"sm": 6, "md": 3, "lg": 2}), + ft.Container(stop_btn, col={"sm": 6, "md": 3, "lg": 2}), + ft.Container(open_jupyter_btn, col={"sm": 12, "md": 6, "lg": 8}), + ], + ) + + # Scrollable page content + page.add( + ft.Column( + expand=True, + scroll=ft.ScrollMode.AUTO, + controls=[ + ft.Text("CHART Data Collection", size=20, weight=ft.FontWeight.BOLD), + form, + switches, + buttons, + ft.Divider(), + ft.Text("Status", weight=ft.FontWeight.BOLD), + status_text, + ], + ) + ) + + +if __name__ == "__main__": + ft.app(target=main)