@@ -61,18 +61,6 @@ def check(self, *args):
6161 else :
6262 self .set (self .old_value )
6363
64- # Reference: https://code.activestate.com/recipes/580726-tkinter-notebook-that-fits-to-the-height-of-every-/
65- class AutoresizableNotebook (_tkinter_ttk .Notebook ):
66- def __init__ (self , master = None , ** kw ):
67- _tkinter_ttk .Notebook .__init__ (self , master , ** kw )
68- self .bind ("<<NotebookTabChanged>>" , self ._on_tab_changed )
69-
70- def _on_tab_changed (self , event ):
71- event .widget .update_idletasks ()
72-
73- tab = event .widget .nametowidget (event .widget .select ())
74- event .widget .configure (height = tab .winfo_reqheight ())
75-
7664 try :
7765 window = _tkinter .Tk ()
7866 except Exception as ex :
@@ -81,11 +69,41 @@ def _on_tab_changed(self, event):
8169
8270 window .title (VERSION_STRING )
8371
84- # Reference: https://www.holadevs.com/pregunta/64750/change-selected-tab-color-in-ttknotebook
72+ # Set theme and colors
73+ bg_color = "#f5f5f5"
74+ fg_color = "#333333"
75+ accent_color = "#2c7fb8"
76+ window .configure (background = bg_color )
77+
78+ # Configure styles
8579 style = _tkinter_ttk .Style ()
86- settings = {"TNotebook.Tab" : {"configure" : {"padding" : [5 , 1 ], "background" : "#fdd57e" }, "map" : {"background" : [("selected" , "#C70039" ), ("active" , "#fc9292" )], "foreground" : [("selected" , "#ffffff" ), ("active" , "#000000" )]}}}
87- style .theme_create ("custom" , parent = "alt" , settings = settings )
88- style .theme_use ("custom" )
80+
81+ # Try to use a more modern theme if available
82+ available_themes = style .theme_names ()
83+ if 'clam' in available_themes :
84+ style .theme_use ('clam' )
85+ elif 'alt' in available_themes :
86+ style .theme_use ('alt' )
87+
88+ # Configure notebook style
89+ style .configure ("TNotebook" , background = bg_color )
90+ style .configure ("TNotebook.Tab" ,
91+ padding = [10 , 4 ],
92+ background = "#e1e1e1" ,
93+ font = ('Helvetica' , 9 ))
94+ style .map ("TNotebook.Tab" ,
95+ background = [("selected" , accent_color ), ("active" , "#7fcdbb" )],
96+ foreground = [("selected" , "white" ), ("active" , "white" )])
97+
98+ # Configure button style
99+ style .configure ("TButton" ,
100+ padding = 4 ,
101+ relief = "flat" ,
102+ background = accent_color ,
103+ foreground = "white" ,
104+ font = ('Helvetica' , 9 ))
105+ style .map ("TButton" ,
106+ background = [('active' , '#41b6c4' )])
89107
90108 # Reference: https://stackoverflow.com/a/10018670
91109 def center (window ):
@@ -138,24 +156,26 @@ def run():
138156 config = {}
139157
140158 for key in window ._widgets :
141- dest , type = key
159+ dest , widget_type = key
142160 widget = window ._widgets [key ]
143161
144162 if hasattr (widget , "get" ) and not widget .get ():
145163 value = None
146- elif type == "string" :
164+ elif widget_type == "string" :
147165 value = widget .get ()
148- elif type == "float" :
166+ elif widget_type == "float" :
149167 value = float (widget .get ())
150- elif type == "int" :
168+ elif widget_type == "int" :
151169 value = int (widget .get ())
152170 else :
153171 value = bool (widget .var .get ())
154172
155173 config [dest ] = value
156174
157175 for option in parser .option_list :
158- config [option .dest ] = defaults .get (option .dest , None )
176+ # Only set default if not already set by the user
177+ if option .dest not in config or config [option .dest ] is None :
178+ config [option .dest ] = defaults .get (option .dest , None )
159179
160180 handle , configFile = tempfile .mkstemp (prefix = MKSTEMP_PREFIX .CONFIG , text = True )
161181 os .close (handle )
@@ -183,20 +203,27 @@ def enqueue(stream, queue):
183203
184204 top = _tkinter .Toplevel ()
185205 top .title ("Console" )
206+ top .configure (background = bg_color )
207+
208+ # Create a frame for the console
209+ console_frame = _tkinter .Frame (top , bg = bg_color )
210+ console_frame .pack (fill = _tkinter .BOTH , expand = True , padx = 10 , pady = 10 )
186211
187212 # Reference: https://stackoverflow.com/a/13833338
188- text = _tkinter_scrolledtext .ScrolledText (top , undo = True )
213+ text = _tkinter_scrolledtext .ScrolledText (console_frame , undo = True , wrap = _tkinter .WORD ,
214+ bg = "#2c3e50" , fg = "#ecf0f1" ,
215+ insertbackground = "white" ,
216+ font = ('Consolas' , 10 ))
189217 text .bind ("<Key>" , onKeyPress )
190218 text .bind ("<Return>" , onReturnPress )
191- text .pack ()
219+ text .pack (fill = _tkinter . BOTH , expand = True )
192220 text .focus ()
193221
194222 center (top )
195223
196224 while True :
197225 line = ""
198226 try :
199- # line = queue.get_nowait()
200227 line = queue .get (timeout = .1 )
201228 text .insert (_tkinter .END , line )
202229 except _queue .Empty :
@@ -206,9 +233,10 @@ def enqueue(stream, queue):
206233 if not alive :
207234 break
208235
209- menubar = _tkinter .Menu (window )
236+ # Create a menu bar
237+ menubar = _tkinter .Menu (window , bg = bg_color , fg = fg_color )
210238
211- filemenu = _tkinter .Menu (menubar , tearoff = 0 )
239+ filemenu = _tkinter .Menu (menubar , tearoff = 0 , bg = bg_color , fg = fg_color )
212240 filemenu .add_command (label = "Open" , state = _tkinter .DISABLED )
213241 filemenu .add_command (label = "Save" , state = _tkinter .DISABLED )
214242 filemenu .add_separator ()
@@ -217,7 +245,7 @@ def enqueue(stream, queue):
217245
218246 menubar .add_command (label = "Run" , command = run )
219247
220- helpmenu = _tkinter .Menu (menubar , tearoff = 0 )
248+ helpmenu = _tkinter .Menu (menubar , tearoff = 0 , bg = bg_color , fg = fg_color )
221249 helpmenu .add_command (label = "Official site" , command = lambda : webbrowser .open (SITE ))
222250 helpmenu .add_command (label = "Github pages" , command = lambda : webbrowser .open (GIT_PAGE ))
223251 helpmenu .add_command (label = "Wiki pages" , command = lambda : webbrowser .open (WIKI_PAGE ))
@@ -226,59 +254,173 @@ def enqueue(stream, queue):
226254 helpmenu .add_command (label = "About" , command = lambda : _tkinter_messagebox .showinfo ("About" , "Copyright (c) 2006-2025\n \n (%s)" % DEV_EMAIL_ADDRESS ))
227255 menubar .add_cascade (label = "Help" , menu = helpmenu )
228256
229- window .config (menu = menubar )
257+ window .config (menu = menubar , bg = bg_color )
230258 window ._widgets = {}
231259
232- notebook = AutoresizableNotebook (window )
260+ # Create header frame
261+ header_frame = _tkinter .Frame (window , bg = bg_color , height = 60 )
262+ header_frame .pack (fill = _tkinter .X , pady = (0 , 5 ))
263+ header_frame .pack_propagate (0 )
233264
234- first = None
235- frames = {}
265+ # Add header label
266+ title_label = _tkinter .Label (header_frame , text = "Configuration" ,
267+ font = ('Helvetica' , 14 ),
268+ fg = accent_color , bg = bg_color )
269+ title_label .pack (side = _tkinter .LEFT , padx = 15 )
236270
237- for group in parser .option_groups :
238- frame = frames [group .title ] = _tkinter .Frame (notebook , width = 200 , height = 200 )
239- notebook .add (frames [group .title ], text = group .title )
271+ # Add run button in header
272+ run_button = _tkinter_ttk .Button (header_frame , text = "Run" , command = run , width = 12 )
273+ run_button .pack (side = _tkinter .RIGHT , padx = 15 )
274+
275+ # Create notebook
276+ notebook = _tkinter_ttk .Notebook (window )
277+ notebook .pack (expand = 1 , fill = "both" , padx = 5 , pady = (0 , 5 ))
240278
241- _tkinter .Label (frame ).grid (column = 0 , row = 0 , sticky = _tkinter .W )
279+ # Store tab information for background loading
280+ tab_frames = {}
281+ tab_canvases = {}
282+ tab_scrollable_frames = {}
283+ tab_groups = {}
284+
285+ # Create empty tabs with scrollable areas first (fast)
286+ for group in parser .option_groups :
287+ # Create a frame with scrollbar for the tab
288+ tab_frame = _tkinter .Frame (notebook , bg = bg_color )
289+ tab_frames [group .title ] = tab_frame
290+
291+ # Create a canvas with scrollbar
292+ canvas = _tkinter .Canvas (tab_frame , bg = bg_color , highlightthickness = 0 )
293+ scrollbar = _tkinter_ttk .Scrollbar (tab_frame , orient = "vertical" , command = canvas .yview )
294+ scrollable_frame = _tkinter .Frame (canvas , bg = bg_color )
295+
296+ # Store references
297+ tab_canvases [group .title ] = canvas
298+ tab_scrollable_frames [group .title ] = scrollable_frame
299+ tab_groups [group .title ] = group
300+
301+ # Configure the canvas scrolling
302+ scrollable_frame .bind (
303+ "<Configure>" ,
304+ lambda e , canvas = canvas : canvas .configure (scrollregion = canvas .bbox ("all" ))
305+ )
306+
307+ canvas .create_window ((0 , 0 ), window = scrollable_frame , anchor = "nw" )
308+ canvas .configure (yscrollcommand = scrollbar .set )
309+
310+ # Pack the canvas and scrollbar
311+ canvas .pack (side = "left" , fill = "both" , expand = True )
312+ scrollbar .pack (side = "right" , fill = "y" )
313+
314+ # Add the tab to the notebook
315+ notebook .add (tab_frame , text = group .title )
316+
317+ # Add a loading indicator
318+ loading_label = _tkinter .Label (scrollable_frame , text = "Loading options..." ,
319+ font = ('Helvetica' , 12 ),
320+ fg = accent_color , bg = bg_color )
321+ loading_label .pack (expand = True )
322+
323+ # Function to populate a tab in the background
324+ def populate_tab (tab_name ):
325+ group = tab_groups [tab_name ]
326+ scrollable_frame = tab_scrollable_frames [tab_name ]
327+ canvas = tab_canvases [tab_name ]
328+
329+ # Remove loading indicator
330+ for child in scrollable_frame .winfo_children ():
331+ child .destroy ()
332+
333+ # Add content to the scrollable frame
334+ row = 0
242335
243- row = 1
244336 if group .get_description ():
245- _tkinter .Label (frame , text = "%s:" % group .get_description ()).grid (column = 0 , row = 1 , columnspan = 3 , sticky = _tkinter .W )
246- _tkinter .Label (frame ).grid (column = 0 , row = 2 , sticky = _tkinter .W )
247- row += 2
337+ desc_label = _tkinter .Label (scrollable_frame , text = group .get_description (),
338+ wraplength = 600 , justify = "left" ,
339+ font = ('Helvetica' , 9 ),
340+ fg = "#555555" , bg = bg_color )
341+ desc_label .grid (row = row , column = 0 , columnspan = 3 , sticky = "w" , padx = 10 , pady = (10 , 5 ))
342+ row += 1
248343
249344 for option in group .option_list :
250- _tkinter .Label (frame , text = "%s " % parser .formatter ._format_option_strings (option )).grid (column = 0 , row = row , sticky = _tkinter .W )
251-
345+ # Option label
346+ option_label = _tkinter .Label (scrollable_frame ,
347+ text = parser .formatter ._format_option_strings (option ) + ":" ,
348+ font = ('Helvetica' , 9 ),
349+ fg = fg_color , bg = bg_color ,
350+ anchor = "w" )
351+ option_label .grid (row = row , column = 0 , sticky = "w" , padx = 10 , pady = 2 )
352+
353+ # Input widget
252354 if option .type == "string" :
253- widget = _tkinter .Entry (frame )
355+ widget = _tkinter .Entry (scrollable_frame , font = ('Helvetica' , 9 ),
356+ relief = "sunken" , bd = 1 , width = 20 )
357+ widget .grid (row = row , column = 1 , sticky = "w" , padx = 5 , pady = 2 )
254358 elif option .type == "float" :
255- widget = ConstrainedEntry (frame , regex = r"\A\d*\.?\d*\Z" )
359+ widget = ConstrainedEntry (scrollable_frame , regex = r"\A\d*\.?\d*\Z" ,
360+ font = ('Helvetica' , 9 ),
361+ relief = "sunken" , bd = 1 , width = 10 )
362+ widget .grid (row = row , column = 1 , sticky = "w" , padx = 5 , pady = 2 )
256363 elif option .type == "int" :
257- widget = ConstrainedEntry (frame , regex = r"\A\d*\Z" )
364+ widget = ConstrainedEntry (scrollable_frame , regex = r"\A\d*\Z" ,
365+ font = ('Helvetica' , 9 ),
366+ relief = "sunken" , bd = 1 , width = 10 )
367+ widget .grid (row = row , column = 1 , sticky = "w" , padx = 5 , pady = 2 )
258368 else :
259369 var = _tkinter .IntVar ()
260- widget = _tkinter .Checkbutton (frame , variable = var )
370+ widget = _tkinter .Checkbutton (scrollable_frame , variable = var ,
371+ bg = bg_color , activebackground = bg_color )
261372 widget .var = var
373+ widget .grid (row = row , column = 1 , sticky = "w" , padx = 5 , pady = 2 )
374+
375+ # Help text (truncated to improve performance)
376+ help_text = option .help
377+ if len (help_text ) > 100 :
378+ help_text = help_text [:100 ] + "..."
262379
263- first = first or widget
264- widget .grid (column = 1 , row = row , sticky = _tkinter .W )
380+ help_label = _tkinter .Label (scrollable_frame , text = help_text ,
381+ font = ('Helvetica' , 8 ),
382+ fg = "#666666" , bg = bg_color ,
383+ wraplength = 400 , justify = "left" )
384+ help_label .grid (row = row , column = 2 , sticky = "w" , padx = 5 , pady = 2 )
265385
386+ # Store widget reference
266387 window ._widgets [(option .dest , option .type )] = widget
267388
389+ # Set default value
268390 default = defaults .get (option .dest )
269391 if default :
270392 if hasattr (widget , "insert" ):
271393 widget .insert (0 , default )
272-
273- _tkinter . Label ( frame , text = " %s" % option . help ). grid ( column = 2 , row = row , sticky = _tkinter . W )
394+ elif hasattr ( widget , "var" ):
395+ widget . var . set ( 1 if default else 0 )
274396
275397 row += 1
276398
277- _tkinter .Label (frame ).grid (column = 0 , row = row , sticky = _tkinter .W )
399+ # Add some padding at the bottom
400+ _tkinter .Label (scrollable_frame , bg = bg_color , height = 1 ).grid (row = row , column = 0 )
401+
402+ # Update the scroll region after adding all widgets
403+ canvas .update_idletasks ()
404+ canvas .configure (scrollregion = canvas .bbox ("all" ))
405+
406+ # Update the UI to show the tab is fully loaded
407+ window .update_idletasks ()
408+
409+ # Function to populate tabs in the background
410+ def populate_tabs_background ():
411+ for tab_name in tab_groups .keys ():
412+ # Schedule each tab to be populated with a small delay between them
413+ window .after (100 , lambda name = tab_name : populate_tab (name ))
414+
415+ # Start populating tabs in the background after a short delay
416+ window .after (500 , populate_tabs_background )
278417
279- notebook .pack (expand = 1 , fill = "both" )
280- notebook .enable_traversal ()
418+ # Set minimum window size
419+ window .update ()
420+ window .minsize (800 , 500 )
281421
282- first .focus ()
422+ # Center the window on screen
423+ center (window )
283424
425+ # Start the GUI
284426 window .mainloop ()
0 commit comments