3434 ask_yesno_popup ,
3535 show_error_popup ,
3636 show_info_popup ,
37+ show_warning_popup ,
3738)
3839from ardupilot_methodic_configurator .frontend_tkinter_entry_dynamic import EntryWithDynamicalyFilteredListbox
3940from ardupilot_methodic_configurator .frontend_tkinter_pair_tuple_combobox import (
5455NEW_VALUE_WIDGET_WIDTH = 9
5556NEW_VALUE_DIFFERENT_STR = "\u2260 " if platform_system () == "Windows" else "!="
5657
58+ # Maximum number of suggestions for bulk add button to be enabled.
59+ # When more than these suggestions are found, no bulk parameter add is offered.
60+ # This threshold prevents users from being overwhelmed by accidentally adding
61+ # too many parameters at once, which could lead to configuration errors or
62+ # difficulty tracking changes. 15 is chosen as a balance between convenience
63+ # and safety, allowing batch operations while maintaining user control.
64+ MAX_BULK_ADD_SUGGESTIONS = 15
65+
5766
5867@dataclass
5968class ParameterEditorTableDialogs :
@@ -790,11 +799,125 @@ def _on_parameter_delete(self, param_name: str) -> None:
790799 # Restore the scroll position
791800 self .canvas .yview_moveto (current_scroll_position )
792801
802+ @staticmethod
803+ def _calculate_bulk_add_button_state (suggestion_count : int ) -> str :
804+ """
805+ Calculate the state for the bulk add button based on suggestion count.
806+
807+ Args:
808+ suggestion_count: Number of filtered parameter suggestions
809+
810+ Returns:
811+ "normal" if 0 < suggestion_count <= MAX_BULK_ADD_SUGGESTIONS, otherwise "disabled"
812+
813+ Business logic extracted for testability.
814+
815+ """
816+ if 0 < suggestion_count <= MAX_BULK_ADD_SUGGESTIONS :
817+ return "normal"
818+ return "disabled"
819+
820+ @staticmethod
821+ def _generate_bulk_add_feedback_message (added : list [str ], skipped : list [str ], failed : list [str ]) -> tuple [str , str , str ]:
822+ """
823+ Generate feedback message for bulk parameter addition.
824+
825+ Args:
826+ added: List of successfully added parameter names
827+ skipped: List of skipped parameter names (already exist)
828+ failed: List of failed parameter names (invalid or errors)
829+
830+ Returns:
831+ Tuple of (message_type, title, message) where:
832+ - message_type: "success", "warning", "error", or "info"
833+ - title: The popup title
834+ - message: The detailed message text
835+
836+ Business logic extracted for testability.
837+
838+ """
839+ # All parameters added successfully
840+ if added and not skipped and not failed :
841+ return "success" , _ ("Success" ), _ ("Successfully added %d parameter(s)." ) % len (added )
842+
843+ # Partial success - some added, some skipped or failed
844+ if added and (skipped or failed ):
845+ msg_parts = [_ ("Added %d parameter(s)." ) % len (added )]
846+ if skipped :
847+ msg_parts .append (_ ("Skipped %d parameter(s): %s" ) % (len (skipped ), ", " .join (skipped )))
848+ if failed :
849+ msg_parts .append (_ ("Failed %d parameter(s): %s" ) % (len (failed ), ", " .join (failed )))
850+ return "warning" , _ ("Partial Success" ), "\n " .join (msg_parts )
851+
852+ # All parameters already exist (skipped only)
853+ if skipped and not added and not failed :
854+ return "info" , _ ("No Changes" ), _ ("All %d parameter(s) already exist in the file." ) % len (skipped )
855+
856+ # All parameters failed (no adds or skips)
857+ if failed and not added and not skipped :
858+ return "error" , _ ("Error" ), _ ("Failed to add all %d parameter(s): %s" ) % (len (failed ), ", " .join (failed ))
859+
860+ # Mixed skipped and failed, but nothing added
861+ if (skipped or failed ) and not added :
862+ msg_parts = []
863+ if skipped :
864+ msg_parts .append (_ ("Skipped %d parameter(s): %s" ) % (len (skipped ), ", " .join (skipped )))
865+ if failed :
866+ msg_parts .append (_ ("Failed %d parameter(s): %s" ) % (len (failed ), ", " .join (failed )))
867+ return "error" , _ ("No Parameters Added" ), "\n " .join (msg_parts )
868+
869+ # Fallback for unexpected state
870+ logging_critical ("Unexpected bulk add result - added: %s, skipped: %s, failed: %s" , added , skipped , failed )
871+ return "error" , _ ("Error" ), _ ("Unexpected result during bulk parameter addition." )
872+
873+ def _bulk_add_parameters_and_show_feedback (self , param_names : list [str ], dialog_window : BaseWindow ) -> None :
874+ """
875+ Execute bulk parameter addition and display feedback to user.
876+
877+ Args:
878+ param_names: List of parameter names to add (should be uppercase)
879+ dialog_window: The add parameter dialog window to close after operation
880+
881+ Side Effects:
882+ - Adds parameters to the current configuration file
883+ - Updates the parameter editor table display
884+ - Scrolls to bottom if parameters were added
885+ - Displays feedback popup based on operation results
886+ - Closes the dialog window
887+
888+ Note:
889+ This method orchestrates the UI workflow. The business logic is in
890+ ParameterEditor.bulk_add_parameters() and message generation is in
891+ _generate_bulk_add_feedback_message() for better testability.
892+
893+ """
894+ # Call business logic method
895+ added , skipped , failed = self .parameter_editor .bulk_add_parameters (param_names )
896+
897+ # Update UI if parameters were added
898+ if added :
899+ self ._pending_scroll_to_bottom = True
900+ self .parameter_editor_window .repopulate_parameter_table ()
901+
902+ # Generate and show feedback message
903+ message_type , title , message = self ._generate_bulk_add_feedback_message (added , skipped , failed )
904+
905+ if message_type == "success" :
906+ pass # Silent success - no popup needed for clean operation
907+ elif message_type == "warning" :
908+ show_warning_popup (title , message )
909+ elif message_type == "info" :
910+ show_info_popup (title , message )
911+ elif message_type == "error" :
912+ show_error_popup (title , message )
913+
914+ dialog_window .root .destroy ()
915+
793916 def _on_parameter_add (self ) -> None :
794917 """Handle parameter addition."""
795918 add_parameter_window = BaseWindow (self ._get_parent_root ())
796919 add_parameter_window .root .title (_ ("Add Parameter to " ) + self .parameter_editor .current_file )
797- add_parameter_window .root .geometry ("450x300 " )
920+ add_parameter_window .root .geometry ("450x320 " )
798921
799922 # Label for instruction
800923 instruction_label = ttk .Label (add_parameter_window .main_frame , text = _ ("Enter the parameter name to add:" ))
@@ -819,6 +942,41 @@ def _on_parameter_add(self) -> None:
819942 BaseWindow .center_window (add_parameter_window .root , self ._get_parent_toplevel ())
820943 parameter_name_combobox .focus ()
821944
945+ # Add all suggested parameters button (disabled by default)
946+ add_suggested_button = ttk .Button (
947+ add_parameter_window .main_frame ,
948+ text = _ ("Add all suggested parameters" ),
949+ state = "disabled" ,
950+ )
951+ add_suggested_button .pack (pady = (10 , 0 ))
952+
953+ # --- Helper: get filtered suggestions
954+ def get_filtered_parameter_names () -> list [str ]:
955+ return [name .upper () for name in parameter_name_combobox .get_filtered_items ()]
956+
957+ # --- Enable button only when <= MAX_BULK_ADD_SUGGESTIONS suggestions
958+ def update_add_suggested_button_state () -> None :
959+ filtered = get_filtered_parameter_names ()
960+ button_state = self ._calculate_bulk_add_button_state (len (filtered ))
961+ add_suggested_button .configure (state = button_state )
962+
963+ # --- Bulk add handler
964+ def on_add_suggested_parameters () -> None :
965+ filtered = get_filtered_parameter_names ()
966+ self ._bulk_add_parameters_and_show_feedback (filtered , add_parameter_window )
967+
968+ add_suggested_button .configure (command = on_add_suggested_parameters )
969+
970+ # Update button state while typing
971+ parameter_name_combobox .bind (
972+ "<KeyRelease>" ,
973+ lambda _event : update_add_suggested_button_state (),
974+ )
975+
976+ # Initialize button state on dialog open
977+ update_add_suggested_button_state ()
978+
979+ # --- Single-add behavior
822980 def custom_selection_handler (event : tk .Event ) -> None :
823981 parameter_name_combobox .update_entry_from_listbox (event )
824982 param_name = parameter_name_combobox .get ().upper ()
@@ -827,7 +985,7 @@ def custom_selection_handler(event: tk.Event) -> None:
827985 else :
828986 add_parameter_window .root .focus ()
829987
830- # Bindings to handle Enter press and selection while respecting original functionalities
988+ # Bindings to handle Enter press and selection
831989 parameter_name_combobox .bind ("<Return>" , custom_selection_handler )
832990 parameter_name_combobox .bind ("<<ComboboxSelected>>" , custom_selection_handler )
833991
0 commit comments