@@ -61,18 +61,6 @@ def check(self, *args):
61
61
else :
62
62
self .set (self .old_value )
63
63
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
-
76
64
try :
77
65
window = _tkinter .Tk ()
78
66
except Exception as ex :
@@ -81,11 +69,41 @@ def _on_tab_changed(self, event):
81
69
82
70
window .title (VERSION_STRING )
83
71
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
85
79
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' )])
89
107
90
108
# Reference: https://stackoverflow.com/a/10018670
91
109
def center (window ):
@@ -138,24 +156,26 @@ def run():
138
156
config = {}
139
157
140
158
for key in window ._widgets :
141
- dest , type = key
159
+ dest , widget_type = key
142
160
widget = window ._widgets [key ]
143
161
144
162
if hasattr (widget , "get" ) and not widget .get ():
145
163
value = None
146
- elif type == "string" :
164
+ elif widget_type == "string" :
147
165
value = widget .get ()
148
- elif type == "float" :
166
+ elif widget_type == "float" :
149
167
value = float (widget .get ())
150
- elif type == "int" :
168
+ elif widget_type == "int" :
151
169
value = int (widget .get ())
152
170
else :
153
171
value = bool (widget .var .get ())
154
172
155
173
config [dest ] = value
156
174
157
175
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 )
159
179
160
180
handle , configFile = tempfile .mkstemp (prefix = MKSTEMP_PREFIX .CONFIG , text = True )
161
181
os .close (handle )
@@ -183,20 +203,27 @@ def enqueue(stream, queue):
183
203
184
204
top = _tkinter .Toplevel ()
185
205
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 )
186
211
187
212
# 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 ))
189
217
text .bind ("<Key>" , onKeyPress )
190
218
text .bind ("<Return>" , onReturnPress )
191
- text .pack ()
219
+ text .pack (fill = _tkinter . BOTH , expand = True )
192
220
text .focus ()
193
221
194
222
center (top )
195
223
196
224
while True :
197
225
line = ""
198
226
try :
199
- # line = queue.get_nowait()
200
227
line = queue .get (timeout = .1 )
201
228
text .insert (_tkinter .END , line )
202
229
except _queue .Empty :
@@ -206,9 +233,10 @@ def enqueue(stream, queue):
206
233
if not alive :
207
234
break
208
235
209
- menubar = _tkinter .Menu (window )
236
+ # Create a menu bar
237
+ menubar = _tkinter .Menu (window , bg = bg_color , fg = fg_color )
210
238
211
- filemenu = _tkinter .Menu (menubar , tearoff = 0 )
239
+ filemenu = _tkinter .Menu (menubar , tearoff = 0 , bg = bg_color , fg = fg_color )
212
240
filemenu .add_command (label = "Open" , state = _tkinter .DISABLED )
213
241
filemenu .add_command (label = "Save" , state = _tkinter .DISABLED )
214
242
filemenu .add_separator ()
@@ -217,7 +245,7 @@ def enqueue(stream, queue):
217
245
218
246
menubar .add_command (label = "Run" , command = run )
219
247
220
- helpmenu = _tkinter .Menu (menubar , tearoff = 0 )
248
+ helpmenu = _tkinter .Menu (menubar , tearoff = 0 , bg = bg_color , fg = fg_color )
221
249
helpmenu .add_command (label = "Official site" , command = lambda : webbrowser .open (SITE ))
222
250
helpmenu .add_command (label = "Github pages" , command = lambda : webbrowser .open (GIT_PAGE ))
223
251
helpmenu .add_command (label = "Wiki pages" , command = lambda : webbrowser .open (WIKI_PAGE ))
@@ -226,59 +254,173 @@ def enqueue(stream, queue):
226
254
helpmenu .add_command (label = "About" , command = lambda : _tkinter_messagebox .showinfo ("About" , "Copyright (c) 2006-2025\n \n (%s)" % DEV_EMAIL_ADDRESS ))
227
255
menubar .add_cascade (label = "Help" , menu = helpmenu )
228
256
229
- window .config (menu = menubar )
257
+ window .config (menu = menubar , bg = bg_color )
230
258
window ._widgets = {}
231
259
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 )
233
264
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 )
236
270
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 ))
240
278
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
242
335
243
- row = 1
244
336
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
248
343
249
344
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
252
354
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 )
254
358
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 )
256
363
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 )
258
368
else :
259
369
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 )
261
372
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 ] + "..."
262
379
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 )
265
385
386
+ # Store widget reference
266
387
window ._widgets [(option .dest , option .type )] = widget
267
388
389
+ # Set default value
268
390
default = defaults .get (option .dest )
269
391
if default :
270
392
if hasattr (widget , "insert" ):
271
393
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 )
274
396
275
397
row += 1
276
398
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 )
278
417
279
- notebook .pack (expand = 1 , fill = "both" )
280
- notebook .enable_traversal ()
418
+ # Set minimum window size
419
+ window .update ()
420
+ window .minsize (800 , 500 )
281
421
282
- first .focus ()
422
+ # Center the window on screen
423
+ center (window )
283
424
425
+ # Start the GUI
284
426
window .mainloop ()
0 commit comments