Skip to content

Commit b228109

Browse files
committed
Enhance guiconfig experience
This improves the user interface and user experience of guiconfig.py, bringing it closer to the professional standard of Linux kernel's qconf (make xconfig). - Menu bar: Add File and Options menus for standard application feel - Enhanced status bar: Split into status message (left) and real-time statistics display (right) showing symbol counts - Integrated menu path bar: Visual frame with back button in single-menu mode and path display with bold label - Semantic color tags: Red for invisible items, blue for NEW markers, dark green for menu/choice items - Zebra striping: Alternating row colors (white/light gray) in treeview for significantly better readability, implemented using tag-based approach following 2024 Tkinter best practices - Grouped button layout: Use LabelFrames to organize buttons into logical groups (File Operations, Navigation, View Options) with uniform button widths - Visual hierarchy: Better spacing, padding, and clear visual grouping throughout the interface
1 parent 7bbdb83 commit b228109

File tree

1 file changed

+164
-43
lines changed

1 file changed

+164
-43
lines changed

guiconfig.py

Lines changed: 164 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ def _main():
139139
# Label with status text shown at the bottom of the main window
140140
# ("Modified", "Saved to ...", etc.)
141141
#
142+
# _stats_label:
143+
# Label showing statistics (symbol count, changed count) in the status bar
144+
#
142145
# _id_to_node:
143146
# We can't use Node objects directly as Treeview item IDs, so we use their
144147
# id()s instead. This dictionary maps Node id()s back to Nodes. (The keys
@@ -182,10 +185,12 @@ def menuconfig(kconf):
182185
global _minconf_filename
183186
global _jump_to_tree
184187
global _cur_menu
188+
global _tree_row_index
185189

186190
_kconf = kconf
187191

188192
_jump_to_tree = None
193+
_tree_row_index = 0
189194

190195
_create_id_to_node()
191196

@@ -315,6 +320,7 @@ def _create_ui():
315320
_fix_treeview_issues()
316321

317322
_create_top_widgets()
323+
_create_menubar()
318324
# Create the pane with the Kconfig tree and description text
319325
panedwindow, _tree = _create_kconfig_tree_and_desc(_root)
320326
panedwindow.grid(column=0, row=1, sticky="nsew")
@@ -482,45 +488,109 @@ def _init_misc_ui():
482488
style.theme_use("clam")
483489

484490

491+
def _create_menubar():
492+
# Creates the menu bar at the top of the window
493+
# Note: This is called after _create_top_widgets() initializes the variables
494+
495+
menubar = Menu(_root)
496+
_root.config(menu=menubar)
497+
498+
# File menu
499+
file_menu = Menu(menubar, tearoff=0)
500+
menubar.add_cascade(label="File", menu=file_menu)
501+
file_menu.add_command(label="Save", command=_save)
502+
file_menu.add_command(label="Save As...", command=_save_as)
503+
file_menu.add_command(label="Save Minimal...", command=_save_minimal)
504+
file_menu.add_separator()
505+
file_menu.add_command(label="Open...", command=_open)
506+
file_menu.add_separator()
507+
file_menu.add_command(label="Quit", command=_on_quit)
508+
509+
# Options menu
510+
options_menu = Menu(menubar, tearoff=0)
511+
menubar.add_cascade(label="Options", menu=options_menu)
512+
options_menu.add_checkbutton(
513+
label="Show Name",
514+
variable=_show_name_var,
515+
command=_do_showname,
516+
)
517+
options_menu.add_checkbutton(
518+
label="Show All",
519+
variable=_show_all_var,
520+
command=_do_showall,
521+
)
522+
options_menu.add_checkbutton(
523+
label="Single-Menu Mode",
524+
variable=_single_menu_var,
525+
command=_do_tree_mode,
526+
)
527+
528+
485529
def _create_top_widgets():
486530
# Creates the controls above the Kconfig tree in the main window
531+
# Also initializes the option variables used by the menu bar
487532

488533
global _show_all_var
489534
global _show_name_var
490535
global _single_menu_var
491536
global _menupath
492537
global _backbutton
493538

539+
# Initialize option variables first (needed by menubar)
540+
_show_name_var = BooleanVar()
541+
_show_all_var = BooleanVar()
542+
_single_menu_var = BooleanVar()
543+
494544
topframe = ttk.Frame(_root)
495-
topframe.grid(column=0, row=0, sticky="ew")
545+
topframe.grid(column=0, row=0, sticky="ew", padx=".1c", pady=".1c")
546+
547+
# Create button groups with separators
548+
# File operations group
549+
file_group = ttk.LabelFrame(topframe, text="File Operations", padding="5")
550+
file_group.grid(column=0, row=0, sticky="ew", padx="0 .1c")
496551

497-
ttk.Button(topframe, text="Save", command=_save).grid(
498-
column=0, row=0, sticky="ew", padx=".05c", pady=".05c"
552+
ttk.Button(file_group, text="Save", command=_save, width=12).grid(
553+
column=0, row=0, sticky="ew", padx="2", pady="2"
499554
)
500555

501-
ttk.Button(topframe, text="Save as...", command=_save_as).grid(
502-
column=1, row=0, sticky="ew"
556+
ttk.Button(file_group, text="Save as...", command=_save_as, width=12).grid(
557+
column=1, row=0, sticky="ew", padx="2", pady="2"
503558
)
504559

505-
ttk.Button(topframe, text="Save minimal (advanced)...", command=_save_minimal).grid(
506-
column=2, row=0, sticky="ew", padx=".05c"
560+
ttk.Button(
561+
file_group, text="Save minimal...", command=_save_minimal, width=12
562+
).grid(column=2, row=0, sticky="ew", padx="2", pady="2")
563+
564+
ttk.Button(file_group, text="Open...", command=_open, width=12).grid(
565+
column=3, row=0, sticky="ew", padx="2", pady="2"
507566
)
508567

509-
ttk.Button(topframe, text="Open...", command=_open).grid(column=3, row=0)
568+
# Navigation group
569+
nav_group = ttk.LabelFrame(topframe, text="Navigation", padding="5")
570+
nav_group.grid(column=1, row=0, sticky="ew")
510571

511-
ttk.Button(topframe, text="Jump to...", command=_jump_to_dialog).grid(
512-
column=4, row=0, padx=".05c"
572+
ttk.Button(nav_group, text="Jump to...", command=_jump_to_dialog, width=12).grid(
573+
column=0, row=0, sticky="ew", padx="2", pady="2"
513574
)
514575

515-
_show_name_var = BooleanVar()
576+
# View options group
577+
options_group = ttk.LabelFrame(topframe, text="View Options", padding="5")
578+
options_group.grid(column=0, row=1, columnspan=2, sticky="ew", pady=".1c 0")
579+
516580
ttk.Checkbutton(
517-
topframe, text="Show name", command=_do_showname, variable=_show_name_var
518-
).grid(column=0, row=1, sticky="nsew", padx=".05c", pady="0 .05c", ipady=".2c")
581+
options_group, text="Show name", command=_do_showname, variable=_show_name_var
582+
).grid(column=0, row=0, sticky="w", padx="5", pady="2")
583+
584+
ttk.Checkbutton(
585+
options_group, text="Show all", command=_do_showall, variable=_show_all_var
586+
).grid(column=1, row=0, sticky="w", padx="5", pady="2")
519587

520-
_show_all_var = BooleanVar()
521588
ttk.Checkbutton(
522-
topframe, text="Show all", command=_do_showall, variable=_show_all_var
523-
).grid(column=1, row=1, sticky="nsew", pady="0 .05c")
589+
options_group,
590+
text="Single-menu mode",
591+
command=_do_tree_mode,
592+
variable=_single_menu_var,
593+
).grid(column=2, row=0, sticky="w", padx="5", pady="2")
524594

525595
# Allow the show-all and single-menu status to be queried via plain global
526596
# Python variables, which is faster and simpler
@@ -532,40 +602,37 @@ def show_all_updated(*_):
532602
_trace_write(_show_all_var, show_all_updated)
533603
_show_all_var.set(False)
534604

535-
_single_menu_var = BooleanVar()
536-
ttk.Checkbutton(
537-
topframe,
538-
text="Single-menu mode",
539-
command=_do_tree_mode,
540-
variable=_single_menu_var,
541-
).grid(column=2, row=1, sticky="nsew", padx=".05c", pady="0 .05c")
605+
# Create integrated menu path bar with back button
606+
path_frame = ttk.Frame(topframe, relief="groove", borderwidth=1)
607+
path_frame.grid(column=0, row=2, columnspan=2, sticky="ew", pady=".1c 0")
542608

543609
_backbutton = ttk.Button(
544-
topframe, text="<--", command=_leave_menu, state="disabled"
610+
path_frame, text="\u25c0 Back", command=_leave_menu, state="disabled", width=8
545611
)
546-
_backbutton.grid(column=0, row=4, sticky="nsew", padx=".05c", pady="0 .05c")
612+
_backbutton.pack(side="left", padx=2, pady=2)
613+
_backbutton.pack_forget() # Initially hidden
547614

548615
def tree_mode_updated(*_):
549616
global _single_menu
550617
_single_menu = _single_menu_var.get()
551618

552619
if _single_menu:
553-
_backbutton.grid()
620+
_backbutton.pack(
621+
side="left", padx=2, pady=2, before=path_frame.winfo_children()[1]
622+
)
554623
else:
555-
_backbutton.grid_remove()
624+
_backbutton.pack_forget()
556625

557626
_trace_write(_single_menu_var, tree_mode_updated)
558627
_single_menu_var.set(False)
559628

560-
# Column to the right of the buttons that the menu path extends into, so
561-
# that it can grow wider than the buttons
562-
topframe.columnconfigure(5, weight=1)
563-
564-
_menupath = ttk.Label(topframe)
565-
_menupath.grid(
566-
column=0, row=3, columnspan=6, sticky="w", padx="0.05c", pady="0 .05c"
629+
ttk.Label(path_frame, text="Path:", font=("TkDefaultFont", 9, "bold")).pack(
630+
side="left", padx=(10, 2)
567631
)
568632

633+
_menupath = ttk.Label(path_frame, anchor="w", relief="flat", padding="2 4")
634+
_menupath.pack(side="left", fill="x", expand=True, padx=2, pady=2)
635+
569636

570637
def _create_kconfig_tree_and_desc(parent):
571638
# Creates a Panedwindow with a Treeview that shows Kconfig nodes and a Text
@@ -617,6 +684,11 @@ def _create_kconfig_tree(parent):
617684
frame = ttk.Frame(parent)
618685

619686
tree = ttk.Treeview(frame, selectmode="browse", height=20, columns=("name",))
687+
688+
# Configure column widths and headers
689+
tree.column("#0", width=400, minwidth=200, stretch=True)
690+
tree.column("name", width=200, minwidth=100, stretch=True)
691+
620692
tree.heading("#0", text="Option", anchor="w")
621693
tree.heading("name", text="Name", anchor="w")
622694

@@ -634,7 +706,17 @@ def _create_kconfig_tree(parent):
634706
tree.tag_configure("not-selected", image=_not_selected_img)
635707
tree.tag_configure("selected", image=_selected_img)
636708
tree.tag_configure("edit", image=_edit_img)
637-
tree.tag_configure("invisible", foreground="red")
709+
710+
# Enhanced semantic color tags
711+
tree.tag_configure("invisible", foreground="#cc0000") # Red for invisible items
712+
tree.tag_configure("new-item", foreground="#0066cc") # Blue for NEW items
713+
tree.tag_configure(
714+
"menu-item", foreground="#006600"
715+
) # Dark green for menu/choice items
716+
717+
# Alternating row colors (zebra striping) for better readability
718+
tree.tag_configure("oddrow", background="#f0f0f0")
719+
tree.tag_configure("evenrow", background="#ffffff")
638720

639721
tree.grid(column=0, row=0, sticky="nsew")
640722

@@ -700,12 +782,23 @@ def yscrollcommand(first, last):
700782

701783

702784
def _create_status_bar():
703-
# Creates the status bar at the bottom of the main window
785+
# Creates an enhanced status bar at the bottom of the main window
704786

705787
global _status_label
788+
global _stats_label
789+
790+
status_frame = ttk.Frame(_root, relief="sunken", borderwidth=1)
791+
status_frame.grid(column=0, row=3, sticky="ew")
706792

707-
_status_label = ttk.Label(_root, anchor="e", padding="0 0 0.4c 0")
708-
_status_label.grid(column=0, row=3, sticky="ew")
793+
# Left side: Status message
794+
_status_label = ttk.Label(status_frame, anchor="w", padding="2 2")
795+
_status_label.pack(side="left", fill="x", expand=True)
796+
797+
# Right side: Statistics
798+
_stats_label = ttk.Label(status_frame, anchor="e", padding="2 2")
799+
_stats_label.pack(side="right")
800+
801+
_update_stats()
709802

710803

711804
def _set_status(s):
@@ -714,6 +807,19 @@ def _set_status(s):
714807
_status_label["text"] = s
715808

716809

810+
def _update_stats():
811+
# Updates the statistics label in the status bar
812+
813+
if not _kconf:
814+
return
815+
816+
total_syms = len(_kconf.unique_defined_syms)
817+
changed_syms = sum(
818+
1 for sym in _kconf.unique_defined_syms if sym.user_value is not None
819+
)
820+
_stats_label["text"] = "Symbols: {} | Changed: {}".format(total_syms, changed_syms)
821+
822+
717823
def _set_conf_changed(changed):
718824
# Updates the status re. whether there are unsaved changes
719825

@@ -722,6 +828,7 @@ def _set_conf_changed(changed):
722828
_conf_changed = changed
723829
if changed:
724830
_set_status("Modified")
831+
_update_stats()
725832

726833

727834
def _update_tree():
@@ -740,6 +847,10 @@ def _update_tree():
740847
# luckily.
741848
_tree.detach(*_id_to_node.keys())
742849

850+
# Reset row counter for alternating colors
851+
global _tree_row_index
852+
_tree_row_index = 0
853+
743854
if _single_menu:
744855
_build_menu_tree()
745856
else:
@@ -855,22 +966,32 @@ def _add_to_tree(node, top):
855966
# the nodes linearly to get the correct order. 'top' holds the menu that
856967
# corresponds to the top-level menu, and can vary in single-menu mode.
857968

969+
global _tree_row_index
970+
858971
parent = node.parent
859972
_tree.move(id(node), "" if parent is top else id(parent), "end")
973+
974+
# Build tags: base tags + row color + optional invisible
975+
base_tags = _img_tag(node)
976+
row_tag = "oddrow" if _tree_row_index % 2 else "evenrow"
977+
978+
if _visible(node) or not _show_all:
979+
tags = base_tags + " " + row_tag
980+
else:
981+
tags = base_tags + " invisible " + row_tag
982+
860983
_tree.item(
861984
id(node),
862985
text=_node_str(node),
863986
# The _show_all test avoids showing invisible items in red outside
864987
# show-all mode, which could look confusing/broken. Invisible symbols
865988
# are shown outside show-all mode if an invisible symbol has visible
866989
# children in an implicit menu.
867-
tags=(
868-
_img_tag(node)
869-
if _visible(node) or not _show_all
870-
else _img_tag(node) + " invisible"
871-
),
990+
tags=tags,
872991
)
873992

993+
_tree_row_index += 1
994+
874995

875996
def _get_force_info(sym):
876997
# Returns a string indicating what's forcing a symbol's value, or None

0 commit comments

Comments
 (0)