3737# Import centralized defaults - handle both KiCad plugin and standalone context
3838try :
3939 from ...core .defaultsConfig import (
40- WINDOW_DEFAULTS , PERFORMANCE_DEFAULTS , DEFAULTS , BETA_DEFAULTS , TIME_TRACKER_DEFAULTS , debug_print
40+ WINDOW_DEFAULTS , PERFORMANCE_DEFAULTS , DEFAULTS , BETA_DEFAULTS ,
41+ TIME_TRACKER_DEFAULTS , KICAD_SYNC_DEFAULTS , debug_print
4142 )
43+ from ...core .kicad_extractor import get_kicad_sync
4244except ImportError :
4345 from core .defaultsConfig import (
44- WINDOW_DEFAULTS , PERFORMANCE_DEFAULTS , DEFAULTS , BETA_DEFAULTS , TIME_TRACKER_DEFAULTS , debug_print
46+ WINDOW_DEFAULTS , PERFORMANCE_DEFAULTS , DEFAULTS , BETA_DEFAULTS ,
47+ TIME_TRACKER_DEFAULTS , KICAD_SYNC_DEFAULTS , debug_print
4548 )
49+ from core .kicad_extractor import get_kicad_sync
4650
4751
4852# ------------------------------ Helpers ---------------------------------
@@ -521,7 +525,7 @@ def _build_panel_size_section(self, parent, sizer):
521525 self ._build_pdf_format_section (self ._scroll_panel , sizer )
522526
523527 def _build_performance_section (self , parent , sizer ):
524- """Build performance settings section (timer interval)."""
528+ """Build performance settings section (autosave mode + timer interval)."""
525529 perf_header = wx .StaticText (parent , label = "⚡ Performance" )
526530 set_label_style (perf_header , self ._theme , bold = True , size = 10 )
527531 sizer .Add (perf_header , 0 , wx .LEFT | wx .BOTTOM , SECTION_MARGIN )
@@ -535,34 +539,121 @@ def _build_performance_section(self, parent, sizer):
535539 current_settings = notes_manager .load_settings () if notes_manager else {}
536540 current_interval_ms = current_settings .get ('timer_interval_ms' , PERFORMANCE_DEFAULTS ['timer_interval_ms' ])
537541 current_interval_sec = current_interval_ms // 1000 # Convert to seconds for UI
538-
539- # Timer interval row
540- timer_row = wx .BoxSizer (wx .HORIZONTAL )
541-
542- timer_label = wx .StaticText (perf_panel , label = "Auto-save interval:" )
543- timer_label .SetForegroundColour (hex_to_colour (self ._theme ["text_primary" ]))
544- timer_row .Add (timer_label , 0 , wx .ALIGN_CENTER_VERTICAL | wx .RIGHT , 8 )
545-
546- # SpinCtrl for interval (3-60 seconds)
542+ current_autosave_mode = current_settings .get ('autosave_mode' , KICAD_SYNC_DEFAULTS ['autosave_mode' ])
543+
544+ # ========== Autosave Mode Radio Buttons ==========
545+ mode_label = wx .StaticText (perf_panel , label = "Auto-save mode:" )
546+ mode_label .SetForegroundColour (hex_to_colour (self ._theme ["text_primary" ]))
547+ perf_sizer .Add (mode_label , 0 , wx .LEFT | wx .TOP , 10 )
548+
549+ # Check KiCad sync availability and get all backup settings
550+ kicad_sync = get_kicad_sync ()
551+ kicad_available = kicad_sync .is_available ()
552+ kicad_interval_sec = None
553+ self ._kicad_backup_settings = {} # Store for info panel
554+ if kicad_available :
555+ kicad_interval_ms = kicad_sync .get_autosave_interval_ms ()
556+ if kicad_interval_ms :
557+ kicad_interval_sec = kicad_interval_ms // 1000
558+ # Get all backup settings for info display
559+ self ._kicad_backup_settings = kicad_sync .get_all_backup_settings ()
560+
561+ # ========== Row 1: KiNotes mode with inline spinner ==========
562+ kinotes_row = wx .BoxSizer (wx .HORIZONTAL )
563+
564+ self ._autosave_mode_kinotes = wx .RadioButton (perf_panel , label = " KiNotes" , style = wx .RB_GROUP )
565+ self ._autosave_mode_kinotes .SetForegroundColour (hex_to_colour (self ._theme ["text_primary" ]))
566+ self ._autosave_mode_kinotes .SetValue (current_autosave_mode == 'kinotes' )
567+ self ._autosave_mode_kinotes .Bind (wx .EVT_RADIOBUTTON , self ._on_autosave_mode_change )
568+ kinotes_row .Add (self ._autosave_mode_kinotes , 0 , wx .ALIGN_CENTER_VERTICAL | wx .RIGHT , 8 )
569+
570+ # SpinCtrl inline with radio (3-300 seconds = 5 min max)
547571 min_sec = PERFORMANCE_DEFAULTS ['timer_min_ms' ] // 1000
548572 max_sec = PERFORMANCE_DEFAULTS ['timer_max_ms' ] // 1000
549573 self ._timer_interval_spin = wx .SpinCtrl (perf_panel , min = min_sec , max = max_sec ,
550- initial = max (min_sec , min (current_interval_sec , max_sec )))
551- block_scroll_wheel (self ._timer_interval_spin ) # Prevent accidental value changes while scrolling
574+ initial = max (min_sec , min (current_interval_sec , max_sec )),
575+ size = (70 , - 1 ))
576+ block_scroll_wheel (self ._timer_interval_spin )
552577 self ._timer_interval_spin .SetForegroundColour (hex_to_colour (self ._theme ["text_primary" ]))
553578 self ._timer_interval_spin .SetBackgroundColour (hex_to_colour (self ._theme ["bg_editor" ]))
554- timer_row .Add (self ._timer_interval_spin , 0 , wx .ALIGN_CENTER_VERTICAL | wx .RIGHT , 4 )
579+ kinotes_row .Add (self ._timer_interval_spin , 0 , wx .ALIGN_CENTER_VERTICAL | wx .RIGHT , 4 )
580+
581+ self ._sec_label = wx .StaticText (perf_panel , label = "seconds" )
582+ self ._sec_label .SetForegroundColour (hex_to_colour (self ._theme ["text_secondary" ]))
583+ kinotes_row .Add (self ._sec_label , 0 , wx .ALIGN_CENTER_VERTICAL )
584+
585+ perf_sizer .Add (kinotes_row , 0 , wx .LEFT | wx .TOP , 15 )
586+
587+ # ========== Row 2: Sync with KiCad ==========
588+ if kicad_available and kicad_interval_sec :
589+ # Format interval for display (e.g., "5 min" or "30s")
590+ if kicad_interval_sec >= 60 :
591+ mins = kicad_interval_sec // 60
592+ secs = kicad_interval_sec % 60
593+ if secs :
594+ interval_display = f"{ mins } m { secs } s"
595+ else :
596+ interval_display = f"{ mins } min"
597+ else :
598+ interval_display = f"{ kicad_interval_sec } s"
599+ kicad_label = f" Sync with KiCad ({ interval_display } )"
600+ else :
601+ kicad_label = " Sync with KiCad (unavailable)"
602+
603+ self ._autosave_mode_kicad = wx .RadioButton (perf_panel , label = kicad_label )
604+ self ._autosave_mode_kicad .SetForegroundColour (hex_to_colour (self ._theme ["text_primary" ]))
605+ self ._autosave_mode_kicad .SetValue (current_autosave_mode == 'kicad' )
606+ self ._autosave_mode_kicad .Enable (kicad_available and kicad_interval_sec is not None )
607+ self ._autosave_mode_kicad .Bind (wx .EVT_RADIOBUTTON , self ._on_autosave_mode_change )
608+ perf_sizer .Add (self ._autosave_mode_kicad , 0 , wx .LEFT | wx .TOP , 15 )
609+
610+ # ========== KiCad Info Panel (shown when KiCad sync selected) ==========
611+ self ._kicad_info_panel = wx .Panel (perf_panel )
612+ self ._kicad_info_panel .SetBackgroundColour (hex_to_colour (self ._theme .get ("bg_toolbar" , "#F5F5F5" )))
613+ kicad_info_sizer = wx .BoxSizer (wx .VERTICAL )
614+
615+ # Build info text from KiCad Session settings
616+ if self ._kicad_backup_settings :
617+ session_display = self ._kicad_backup_settings .get ('session_autosave_display' , 'N/A' )
618+ backup_enabled = self ._kicad_backup_settings .get ('enabled' , False )
619+ backup_on_autosave = self ._kicad_backup_settings .get ('backup_on_autosave' , False )
620+
621+ info_lines = [
622+ f"KiCad → Preferences → Common → Session:" ,
623+ f" • Auto save: { session_display } " ,
624+ f"" ,
625+ f"Project Backup: { '✓ Enabled' if backup_enabled else '✗ Disabled' } " ,
626+ f" • Backup on autosave: { '✓' if backup_on_autosave else '✗' } " ,
627+ ]
628+ info_text = "\n " .join (info_lines )
629+ else :
630+ info_text = "KiCad settings not available"
631+
632+ kicad_info_label = wx .StaticText (self ._kicad_info_panel , label = info_text )
633+ kicad_info_label .SetForegroundColour (hex_to_colour (self ._theme .get ("text_secondary" , "#666666" )))
634+ kicad_info_label .SetFont (wx .Font (9 , wx .FONTFAMILY_DEFAULT , wx .FONTSTYLE_NORMAL , wx .FONTWEIGHT_NORMAL ))
635+ kicad_info_sizer .Add (kicad_info_label , 0 , wx .ALL , 8 )
555636
556- sec_label = wx .StaticText (perf_panel , label = "seconds" )
557- sec_label .SetForegroundColour (hex_to_colour (self ._theme ["text_secondary" ]))
558- timer_row .Add (sec_label , 0 , wx .ALIGN_CENTER_VERTICAL )
637+ self ._kicad_info_panel .SetSizer (kicad_info_sizer )
638+ perf_sizer .Add (self ._kicad_info_panel , 0 , wx .LEFT | wx .RIGHT | wx .TOP , 25 )
559639
560- perf_sizer .Add (timer_row , 0 , wx .ALL , 10 )
640+ # Show/hide based on current mode
641+ self ._kicad_info_panel .Show (current_autosave_mode == 'kicad' )
642+
643+ # ========== Row 3: Disabled ==========
644+ self ._autosave_mode_disabled = wx .RadioButton (perf_panel , label = " Disabled (manual save only)" )
645+ self ._autosave_mode_disabled .SetForegroundColour (hex_to_colour (self ._theme ["text_primary" ]))
646+ self ._autosave_mode_disabled .SetValue (current_autosave_mode == 'disabled' )
647+ self ._autosave_mode_disabled .Bind (wx .EVT_RADIOBUTTON , self ._on_autosave_mode_change )
648+ perf_sizer .Add (self ._autosave_mode_disabled , 0 , wx .LEFT | wx .TOP | wx .BOTTOM , 15 )
649+
650+ # Enable/disable custom interval based on mode
651+ self ._update_timer_controls_state ()
561652
562653 perf_hint = wx .StaticText (perf_panel ,
563- label = "Higher values = better performance, lower = faster saves (Min: 3s) " )
654+ label = "Higher values = better performance, lower = faster saves" )
564655 perf_hint .SetForegroundColour (hex_to_colour (self ._theme ["text_secondary" ]))
565- perf_sizer .Add (perf_hint , 0 , wx .LEFT | wx .BOTTOM , 10 )
656+ perf_sizer .Add (perf_hint , 0 , wx .LEFT | wx .TOP | wx . BOTTOM , 10 )
566657
567658 perf_panel .SetSizer (perf_sizer )
568659 sizer .Add (perf_panel , 0 , wx .EXPAND | wx .LEFT | wx .RIGHT , SECTION_MARGIN )
@@ -571,6 +662,25 @@ def _build_performance_section(self, parent, sizer):
571662 self ._add_separator (parent , sizer )
572663 sizer .AddSpacer (SECTION_SPACING )
573664
665+ def _on_autosave_mode_change (self , event ):
666+ """Handle autosave mode radio button change."""
667+ self ._update_timer_controls_state ()
668+
669+ # Show/hide KiCad info panel based on mode
670+ is_kicad_mode = self ._autosave_mode_kicad .GetValue ()
671+ if hasattr (self , '_kicad_info_panel' ):
672+ self ._kicad_info_panel .Show (is_kicad_mode )
673+ # Relayout the scroll panel
674+ self ._scroll_panel .Layout ()
675+ self ._scroll_panel .FitInside ()
676+
677+ def _update_timer_controls_state (self ):
678+ """Enable/disable custom interval controls based on autosave mode."""
679+ # Custom interval only enabled for 'kinotes' mode
680+ enabled = self ._autosave_mode_kinotes .GetValue ()
681+ self ._timer_interval_spin .Enable (enabled )
682+ self ._sec_label .Enable (enabled )
683+
574684 def _build_pdf_format_section (self , parent , sizer ):
575685 """Build PDF export format settings section."""
576686 pdf_header = wx .StaticText (parent , label = "💾 PDF Export Format" )
@@ -950,6 +1060,14 @@ def get_result(self):
9501060 bg_color_name = bg_choices [self ._bg_choice .GetSelection ()]
9511061 text_color_name = txt_choices [self ._txt_choice .GetSelection ()]
9521062
1063+ # Determine autosave mode from radio buttons
1064+ if self ._autosave_mode_kicad .GetValue ():
1065+ autosave_mode = 'kicad'
1066+ elif self ._autosave_mode_disabled .GetValue ():
1067+ autosave_mode = 'disabled'
1068+ else :
1069+ autosave_mode = 'kinotes'
1070+
9531071 return {
9541072 'dark_mode' : self ._selected_theme_dark ,
9551073 'bg_color_name' : bg_color_name if not self ._selected_theme_dark else self ._config .get ('bg_color_name' , 'Ivory Paper' ),
@@ -967,6 +1085,7 @@ def get_result(self):
9671085 'scale_factor' : None if self ._scale_auto_checkbox .GetValue () else self ._scale_slider .GetValue () / 100.0 ,
9681086 'panel_width' : self ._panel_width_spin .GetValue (),
9691087 'panel_height' : self ._panel_height_spin .GetValue (),
1088+ 'autosave_mode' : autosave_mode ,
9701089 'timer_interval_ms' : self ._timer_interval_spin .GetValue () * 1000 , # Convert seconds to ms
9711090 'beta_markdown' : self ._beta_markdown_cb .GetValue (),
9721091 'beta_bom' : self ._beta_bom_cb .GetValue (),
0 commit comments