1111"""
1212
1313import sys
14- import time
1514import tkinter as tk
1615from argparse import ArgumentParser , Namespace
1716
2120from logging import getLevelName as logging_getLevelName
2221from logging import warning as logging_warning
2322from tkinter import filedialog , messagebox , ttk
24- from typing import Literal , Optional , Union
23+ from typing import Optional , Union
2524
2625# from logging import critical as logging_critical
2726from webbrowser import open as webbrowser_open # to open the blog post documentation
3130from ardupilot_methodic_configurator .backend_filesystem_program_settings import ProgramSettings
3231from ardupilot_methodic_configurator .backend_flightcontroller import FlightController
3332from ardupilot_methodic_configurator .common_arguments import add_common_arguments
34- from ardupilot_methodic_configurator .configuration_manager import ConfigurationManager
33+ from ardupilot_methodic_configurator .configuration_manager import ConfigurationManager , ExperimentChoice
3534from ardupilot_methodic_configurator .frontend_tkinter_autoresize_combobox import AutoResizeCombobox
3635from ardupilot_methodic_configurator .frontend_tkinter_base_window import (
3736 BaseWindow ,
@@ -166,7 +165,6 @@ def __init__(self, configuration_manager: ConfigurationManager) -> None:
166165 self .tempcal_imu_progress_window : ProgressWindow
167166 self .file_upload_progress_window : ProgressWindow
168167 self .skip_button : ttk .Button
169- self .last_time_asked_to_save : float = 0
170168 self .gui_complexity = str (ProgramSettings .get_setting ("gui_complexity" ))
171169
172170 self .root .title (
@@ -509,36 +507,24 @@ def select_file(title: str, filetypes: list[str]) -> Optional[str]:
509507 finally :
510508 self .tempcal_imu_progress_window .destroy ()
511509
512- def __handle_dialog_choice (self , result : list , dialog : tk .Toplevel , choice : Optional [ bool ] ) -> None :
510+ def __handle_dialog_choice (self , result : list , dialog : tk .Toplevel , choice : ExperimentChoice ) -> None :
513511 result .append (choice )
514512 dialog .destroy ()
515513
516- def __should_copy_fc_values_to_file (self , selected_file : str ) -> None : # pylint: disable=too-many-locals
517- should_copy , relevant_fc_params , auto_changed_by = self .configuration_manager .should_copy_fc_values_to_file (
518- selected_file
519- )
520- if should_copy and relevant_fc_params and auto_changed_by :
521- msg = _ (
522- "This configuration step requires external changes by: {auto_changed_by}\n \n "
523- "The external tool experiment procedure is described in the tuning guide.\n \n "
524- "Choose an option:\n "
525- "* CLOSE - Close the application and go perform the experiment\n "
526- "* YES - Copy current FC values to {selected_file} (if you've already completed the experiment)\n "
527- "* NO - Continue without copying values (if you haven't performed the experiment yet,"
528- " but know what you are doing)"
529- ).format (auto_changed_by = auto_changed_by , selected_file = selected_file )
530-
514+ def __should_copy_fc_values_to_file (self , selected_file : str ) -> None :
515+ def ask_user_choice (title : str , message : str , options : list [str ]) -> ExperimentChoice : # pylint: disable=too-many-locals
516+ """GUI callback for asking user choice with custom dialog."""
531517 # Create custom dialog with Close, Yes, No buttons
532518 dialog = tk .Toplevel (self .root )
533519 # Hide dialog initially to prevent flickering
534520 dialog .withdraw ()
535521 dialog .transient (self .root )
536- dialog .title (_ ( "Update file with values from FC?" ) )
522+ dialog .title (title )
537523 dialog .resizable (width = False , height = False )
538524 dialog .protocol ("WM_DELETE_WINDOW" , dialog .destroy )
539525
540526 # Message text
541- message_label = tk .Label (dialog , text = msg , justify = tk .LEFT , padx = 20 , pady = 10 )
527+ message_label = tk .Label (dialog , text = message , justify = tk .LEFT , padx = 20 , pady = 10 )
542528 message_label .pack (padx = 10 , pady = 10 )
543529
544530 # Clickable link to tuning guide
@@ -554,7 +540,7 @@ def __should_copy_fc_values_to_file(self, selected_file: str) -> None: # pylint
554540 link_label .bind ("<Button-1>" , lambda _e : self .configuration_manager .open_documentation_in_browser (selected_file ))
555541
556542 # Result variable
557- result : list [Optional [ Literal [ True , False ]]] = [None ]
543+ result : list [ExperimentChoice ] = []
558544
559545 # Button frame
560546 button_frame = tk .Frame (dialog )
@@ -563,25 +549,31 @@ def __should_copy_fc_values_to_file(self, selected_file: str) -> None: # pylint
563549 # Close button (default)
564550 close_button = tk .Button (
565551 button_frame ,
566- text = _ ( "Close" ),
552+ text = options [ 0 ], # "Close"
567553 width = 10 ,
568- command = lambda : self .__handle_dialog_choice (result , dialog , choice = None ),
554+ command = lambda : self .__handle_dialog_choice (result , dialog , choice = "close" ),
569555 )
570556 close_button .pack (side = tk .LEFT , padx = 5 )
571557
572558 # Yes button
573559 yes_button = tk .Button (
574- button_frame , text = _ ("Yes" ), width = 10 , command = lambda : self .__handle_dialog_choice (result , dialog , choice = True )
560+ button_frame ,
561+ text = options [1 ],
562+ width = 10 , # "Yes"
563+ command = lambda : self .__handle_dialog_choice (result , dialog , choice = True ),
575564 )
576565 yes_button .pack (side = tk .LEFT , padx = 5 )
577566
578567 # No button
579568 no_button = tk .Button (
580- button_frame , text = _ ("No" ), width = 10 , command = lambda : self .__handle_dialog_choice (result , dialog , choice = False )
569+ button_frame ,
570+ text = options [2 ],
571+ width = 10 , # "No"
572+ command = lambda : self .__handle_dialog_choice (result , dialog , choice = False ),
581573 )
582574 no_button .pack (side = tk .LEFT , padx = 5 )
583575
584- dialog .bind ("<Return>" , lambda _event : self .__handle_dialog_choice (result , dialog , None ))
576+ dialog .bind ("<Return>" , lambda _event : self .__handle_dialog_choice (result , dialog , choice = "close" ))
585577
586578 # Center the dialog on the parent window
587579 dialog .deiconify ()
@@ -604,23 +596,27 @@ def __should_copy_fc_values_to_file(self, selected_file: str) -> None: # pylint
604596
605597 # Wait until dialog is closed
606598 self .root .wait_window (dialog )
607- response = result [- 1 ] if len ( result ) > 1 else None
599+ return result [- 1 ] if result else "close"
608600
609- if response is True : # Yes option
610- _params_copied = self .configuration_manager .copy_fc_values_to_file (selected_file , relevant_fc_params )
611- elif response is None : # Close option
612- sys .exit (0 )
613- # If response is False (No option), do nothing and continue
601+ result = self .configuration_manager .handle_copy_fc_values_workflow (
602+ selected_file ,
603+ ask_user_choice ,
604+ show_info_popup ,
605+ )
606+
607+ if result == "close" :
608+ # User chose to close the application
609+ sys .exit (0 )
614610
615611 def __should_jump_to_file (self , selected_file : str ) -> str :
616- jump_options = self .configuration_manager .get_file_jump_options ( selected_file )
617- for dest_file , msg in jump_options . items ():
618- if self .gui_complexity == "simple" or messagebox . askyesno (
619- _ ( "Skip some steps?" ), _ ( msg ) if msg else _ ( "Skip to {dest_file}?" ). format ( ** locals ())
620- ):
621- self . file_selection_combobox . set ( dest_file )
622- return dest_file
623- return selected_file
612+ dest_file = self .configuration_manager .handle_file_jump_workflow (
613+ selected_file ,
614+ self .gui_complexity ,
615+ ask_yesno_popup ,
616+ )
617+ if dest_file != selected_file :
618+ self . file_selection_combobox . set ( dest_file )
619+ return dest_file
624620
625621 def __should_download_file_from_url (self , selected_file : str ) -> None :
626622 self .configuration_manager .should_download_file_from_url_workflow (
@@ -699,7 +695,7 @@ def on_show_only_changed_checkbox_change(self) -> None:
699695
700696 def on_upload_selected_click (self ) -> None :
701697 self .write_changes_to_intermediate_parameter_file ()
702- selected_params = self .parameter_editor_table .get_upload_selected_params (str ( self .gui_complexity ) )
698+ selected_params = self .parameter_editor_table .get_upload_selected_params (self .gui_complexity )
703699 if selected_params :
704700 if self .configuration_manager .fc_parameters :
705701 self .upload_selected_params (selected_params )
@@ -804,20 +800,10 @@ def on_skip_click(self, _event: Union[None, tk.Event] = None) -> None:
804800 self .on_param_file_combobox_change (None )
805801
806802 def write_changes_to_intermediate_parameter_file (self ) -> None :
807- elapsed_since_last_ask = time .time () - self .last_time_asked_to_save
808- # if annotate parameters into files is true, we always need to write to file, because
809- # the parameter metadata might have changed, or not be present in the file.
810- # In that situation, avoid asking multiple times to write the file, by checking the time last asked
811- # But only if self.annotate_params_into_files.get()
812- if self .configuration_manager .has_unsaved_changes () or (
813- self .annotate_params_into_files .get () and elapsed_since_last_ask > 1.0
814- ):
815- msg = _ ("Do you want to write the changes to the {current_filename} file?" ).format (
816- current_filename = self .configuration_manager .current_file
817- )
818- if messagebox .askyesno (_ ("One or more parameters have been edited" ), msg .format (** locals ())):
819- self .configuration_manager .export_current_file (annotate_doc = self .annotate_params_into_files .get ())
820- self .last_time_asked_to_save = time .time ()
803+ self .configuration_manager .handle_write_changes_workflow (
804+ self .annotate_params_into_files .get (),
805+ ask_yesno_popup ,
806+ )
821807
822808 def close_connection_and_quit (self ) -> None :
823809 focused_widget = self .parameter_editor_table .view_port .focus_get ()
0 commit comments