1*c689edbbSJens Wiklander#!/usr/bin/env python3 2*c689edbbSJens Wiklander 3*c689edbbSJens Wiklander# Copyright (c) 2019, Ulf Magnusson 4*c689edbbSJens Wiklander# SPDX-License-Identifier: ISC 5*c689edbbSJens Wiklander 6*c689edbbSJens Wiklander""" 7*c689edbbSJens WiklanderOverview 8*c689edbbSJens Wiklander======== 9*c689edbbSJens Wiklander 10*c689edbbSJens WiklanderA Tkinter-based menuconfig implementation, based around a treeview control and 11*c689edbbSJens Wiklandera help display. The interface should feel familiar to people used to qconf 12*c689edbbSJens Wiklander('make xconfig'). Compatible with both Python 2 and Python 3. 13*c689edbbSJens Wiklander 14*c689edbbSJens WiklanderThe display can be toggled between showing the full tree and showing just a 15*c689edbbSJens Wiklandersingle menu (like menuconfig.py). Only single-menu mode distinguishes between 16*c689edbbSJens Wiklandersymbols defined with 'config' and symbols defined with 'menuconfig'. 17*c689edbbSJens Wiklander 18*c689edbbSJens WiklanderA show-all mode is available that shows invisible items in red. 19*c689edbbSJens Wiklander 20*c689edbbSJens WiklanderSupports both mouse and keyboard controls. The following keyboard shortcuts are 21*c689edbbSJens Wiklanderavailable: 22*c689edbbSJens Wiklander 23*c689edbbSJens Wiklander Ctrl-S : Save configuration 24*c689edbbSJens Wiklander Ctrl-O : Open configuration 25*c689edbbSJens Wiklander Ctrl-A : Toggle show-all mode 26*c689edbbSJens Wiklander Ctrl-N : Toggle show-name mode 27*c689edbbSJens Wiklander Ctrl-M : Toggle single-menu mode 28*c689edbbSJens Wiklander Ctrl-F, /: Open jump-to dialog 29*c689edbbSJens Wiklander ESC : Close 30*c689edbbSJens Wiklander 31*c689edbbSJens WiklanderRunning 32*c689edbbSJens Wiklander======= 33*c689edbbSJens Wiklander 34*c689edbbSJens Wiklanderguiconfig.py can be run either as a standalone executable or by calling the 35*c689edbbSJens Wiklandermenuconfig() function with an existing Kconfig instance. The second option is a 36*c689edbbSJens Wiklanderbit inflexible in that it will still load and save .config, etc. 37*c689edbbSJens Wiklander 38*c689edbbSJens WiklanderWhen run in standalone mode, the top-level Kconfig file to load can be passed 39*c689edbbSJens Wiklanderas a command-line argument. With no argument, it defaults to "Kconfig". 40*c689edbbSJens Wiklander 41*c689edbbSJens WiklanderThe KCONFIG_CONFIG environment variable specifies the .config file to load (if 42*c689edbbSJens Wiklanderit exists) and save. If KCONFIG_CONFIG is unset, ".config" is used. 43*c689edbbSJens Wiklander 44*c689edbbSJens WiklanderWhen overwriting a configuration file, the old version is saved to 45*c689edbbSJens Wiklander<filename>.old (e.g. .config.old). 46*c689edbbSJens Wiklander 47*c689edbbSJens Wiklander$srctree is supported through Kconfiglib. 48*c689edbbSJens Wiklander""" 49*c689edbbSJens Wiklander 50*c689edbbSJens Wiklander# Note: There's some code duplication with menuconfig.py below, especially for 51*c689edbbSJens Wiklander# the help text. Maybe some of it could be moved into kconfiglib.py or a shared 52*c689edbbSJens Wiklander# helper script, but OTOH it's pretty nice to have things standalone and 53*c689edbbSJens Wiklander# customizable. 54*c689edbbSJens Wiklander 55*c689edbbSJens Wiklanderimport errno 56*c689edbbSJens Wiklanderimport os 57*c689edbbSJens Wiklanderimport sys 58*c689edbbSJens Wiklander 59*c689edbbSJens Wiklander_PY2 = sys.version_info[0] < 3 60*c689edbbSJens Wiklander 61*c689edbbSJens Wiklanderif _PY2: 62*c689edbbSJens Wiklander # Python 2 63*c689edbbSJens Wiklander from Tkinter import * 64*c689edbbSJens Wiklander import ttk 65*c689edbbSJens Wiklander import tkFont as font 66*c689edbbSJens Wiklander import tkFileDialog as filedialog 67*c689edbbSJens Wiklander import tkMessageBox as messagebox 68*c689edbbSJens Wiklanderelse: 69*c689edbbSJens Wiklander # Python 3 70*c689edbbSJens Wiklander from tkinter import * 71*c689edbbSJens Wiklander import tkinter.ttk as ttk 72*c689edbbSJens Wiklander import tkinter.font as font 73*c689edbbSJens Wiklander from tkinter import filedialog, messagebox 74*c689edbbSJens Wiklander 75*c689edbbSJens Wiklanderfrom kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \ 76*c689edbbSJens Wiklander BOOL, TRISTATE, STRING, INT, HEX, \ 77*c689edbbSJens Wiklander AND, OR, \ 78*c689edbbSJens Wiklander expr_str, expr_value, split_expr, \ 79*c689edbbSJens Wiklander standard_sc_expr_str, \ 80*c689edbbSJens Wiklander TRI_TO_STR, TYPE_TO_STR, \ 81*c689edbbSJens Wiklander standard_kconfig, standard_config_filename 82*c689edbbSJens Wiklander 83*c689edbbSJens Wiklander 84*c689edbbSJens Wiklander# If True, use GIF image data embedded in this file instead of separate GIF 85*c689edbbSJens Wiklander# files. See _load_images(). 86*c689edbbSJens Wiklander_USE_EMBEDDED_IMAGES = True 87*c689edbbSJens Wiklander 88*c689edbbSJens Wiklander 89*c689edbbSJens Wiklander# Help text for the jump-to dialog 90*c689edbbSJens Wiklander_JUMP_TO_HELP = """\ 91*c689edbbSJens WiklanderType one or more strings/regexes and press Enter to list items that match all 92*c689edbbSJens Wiklanderof them. Python's regex flavor is used (see the 're' module). Double-clicking 93*c689edbbSJens Wiklanderan item will jump to it. Item values can be toggled directly within the dialog.\ 94*c689edbbSJens Wiklander""" 95*c689edbbSJens Wiklander 96*c689edbbSJens Wiklander 97*c689edbbSJens Wiklanderdef _main(): 98*c689edbbSJens Wiklander menuconfig(standard_kconfig(__doc__)) 99*c689edbbSJens Wiklander 100*c689edbbSJens Wiklander 101*c689edbbSJens Wiklander# Global variables used below: 102*c689edbbSJens Wiklander# 103*c689edbbSJens Wiklander# _root: 104*c689edbbSJens Wiklander# The Toplevel instance for the main window 105*c689edbbSJens Wiklander# 106*c689edbbSJens Wiklander# _tree: 107*c689edbbSJens Wiklander# The Treeview in the main window 108*c689edbbSJens Wiklander# 109*c689edbbSJens Wiklander# _jump_to_tree: 110*c689edbbSJens Wiklander# The Treeview in the jump-to dialog. None if the jump-to dialog isn't 111*c689edbbSJens Wiklander# open. Doubles as a flag. 112*c689edbbSJens Wiklander# 113*c689edbbSJens Wiklander# _jump_to_matches: 114*c689edbbSJens Wiklander# List of Nodes shown in the jump-to dialog 115*c689edbbSJens Wiklander# 116*c689edbbSJens Wiklander# _menupath: 117*c689edbbSJens Wiklander# The Label that shows the menu path of the selected item 118*c689edbbSJens Wiklander# 119*c689edbbSJens Wiklander# _backbutton: 120*c689edbbSJens Wiklander# The button shown in single-menu mode for jumping to the parent menu 121*c689edbbSJens Wiklander# 122*c689edbbSJens Wiklander# _status_label: 123*c689edbbSJens Wiklander# Label with status text shown at the bottom of the main window 124*c689edbbSJens Wiklander# ("Modified", "Saved to ...", etc.) 125*c689edbbSJens Wiklander# 126*c689edbbSJens Wiklander# _id_to_node: 127*c689edbbSJens Wiklander# We can't use Node objects directly as Treeview item IDs, so we use their 128*c689edbbSJens Wiklander# id()s instead. This dictionary maps Node id()s back to Nodes. (The keys 129*c689edbbSJens Wiklander# are actually str(id(node)), just to simplify lookups.) 130*c689edbbSJens Wiklander# 131*c689edbbSJens Wiklander# _cur_menu: 132*c689edbbSJens Wiklander# The current menu. Ignored outside single-menu mode. 133*c689edbbSJens Wiklander# 134*c689edbbSJens Wiklander# _show_all_var/_show_name_var/_single_menu_var: 135*c689edbbSJens Wiklander# Tkinter Variable instances bound to the corresponding checkboxes 136*c689edbbSJens Wiklander# 137*c689edbbSJens Wiklander# _show_all/_single_menu: 138*c689edbbSJens Wiklander# Plain Python bools that track _show_all_var and _single_menu_var, to 139*c689edbbSJens Wiklander# speed up and simplify things a bit 140*c689edbbSJens Wiklander# 141*c689edbbSJens Wiklander# _conf_filename: 142*c689edbbSJens Wiklander# File to save the configuration to 143*c689edbbSJens Wiklander# 144*c689edbbSJens Wiklander# _minconf_filename: 145*c689edbbSJens Wiklander# File to save minimal configurations to 146*c689edbbSJens Wiklander# 147*c689edbbSJens Wiklander# _conf_changed: 148*c689edbbSJens Wiklander# True if the configuration has been changed. If False, we don't bother 149*c689edbbSJens Wiklander# showing the save-and-quit dialog. 150*c689edbbSJens Wiklander# 151*c689edbbSJens Wiklander# We reset this to False whenever the configuration is saved. 152*c689edbbSJens Wiklander# 153*c689edbbSJens Wiklander# _*_img: 154*c689edbbSJens Wiklander# PhotoImage instances for images 155*c689edbbSJens Wiklander 156*c689edbbSJens Wiklander 157*c689edbbSJens Wiklanderdef menuconfig(kconf): 158*c689edbbSJens Wiklander """ 159*c689edbbSJens Wiklander Launches the configuration interface, returning after the user exits. 160*c689edbbSJens Wiklander 161*c689edbbSJens Wiklander kconf: 162*c689edbbSJens Wiklander Kconfig instance to be configured 163*c689edbbSJens Wiklander """ 164*c689edbbSJens Wiklander global _kconf 165*c689edbbSJens Wiklander global _conf_filename 166*c689edbbSJens Wiklander global _minconf_filename 167*c689edbbSJens Wiklander global _jump_to_tree 168*c689edbbSJens Wiklander global _cur_menu 169*c689edbbSJens Wiklander 170*c689edbbSJens Wiklander _kconf = kconf 171*c689edbbSJens Wiklander 172*c689edbbSJens Wiklander _jump_to_tree = None 173*c689edbbSJens Wiklander 174*c689edbbSJens Wiklander _create_id_to_node() 175*c689edbbSJens Wiklander 176*c689edbbSJens Wiklander _create_ui() 177*c689edbbSJens Wiklander 178*c689edbbSJens Wiklander # Filename to save configuration to 179*c689edbbSJens Wiklander _conf_filename = standard_config_filename() 180*c689edbbSJens Wiklander 181*c689edbbSJens Wiklander # Load existing configuration and check if it's outdated 182*c689edbbSJens Wiklander _set_conf_changed(_load_config()) 183*c689edbbSJens Wiklander 184*c689edbbSJens Wiklander # Filename to save minimal configuration to 185*c689edbbSJens Wiklander _minconf_filename = "defconfig" 186*c689edbbSJens Wiklander 187*c689edbbSJens Wiklander # Current menu in single-menu mode 188*c689edbbSJens Wiklander _cur_menu = _kconf.top_node 189*c689edbbSJens Wiklander 190*c689edbbSJens Wiklander # Any visible items in the top menu? 191*c689edbbSJens Wiklander if not _shown_menu_nodes(kconf.top_node): 192*c689edbbSJens Wiklander # Nothing visible. Start in show-all mode and try again. 193*c689edbbSJens Wiklander _show_all_var.set(True) 194*c689edbbSJens Wiklander if not _shown_menu_nodes(kconf.top_node): 195*c689edbbSJens Wiklander # Give up and show an error. It's nice to be able to assume that 196*c689edbbSJens Wiklander # the tree is non-empty in the rest of the code. 197*c689edbbSJens Wiklander _root.wait_visibility() 198*c689edbbSJens Wiklander messagebox.showerror( 199*c689edbbSJens Wiklander "Error", 200*c689edbbSJens Wiklander "Empty configuration -- nothing to configure.\n\n" 201*c689edbbSJens Wiklander "Check that environment variables are set properly.") 202*c689edbbSJens Wiklander _root.destroy() 203*c689edbbSJens Wiklander return 204*c689edbbSJens Wiklander 205*c689edbbSJens Wiklander # Build the initial tree 206*c689edbbSJens Wiklander _update_tree() 207*c689edbbSJens Wiklander 208*c689edbbSJens Wiklander # Select the first item and focus the Treeview, so that keyboard controls 209*c689edbbSJens Wiklander # work immediately 210*c689edbbSJens Wiklander _select(_tree, _tree.get_children()[0]) 211*c689edbbSJens Wiklander _tree.focus_set() 212*c689edbbSJens Wiklander 213*c689edbbSJens Wiklander # Make geometry information available for centering the window. This 214*c689edbbSJens Wiklander # indirectly creates the window, so hide it so that it's never shown at the 215*c689edbbSJens Wiklander # old location. 216*c689edbbSJens Wiklander _root.withdraw() 217*c689edbbSJens Wiklander _root.update_idletasks() 218*c689edbbSJens Wiklander 219*c689edbbSJens Wiklander # Center the window 220*c689edbbSJens Wiklander _root.geometry("+{}+{}".format( 221*c689edbbSJens Wiklander (_root.winfo_screenwidth() - _root.winfo_reqwidth())//2, 222*c689edbbSJens Wiklander (_root.winfo_screenheight() - _root.winfo_reqheight())//2)) 223*c689edbbSJens Wiklander 224*c689edbbSJens Wiklander # Show it 225*c689edbbSJens Wiklander _root.deiconify() 226*c689edbbSJens Wiklander 227*c689edbbSJens Wiklander # Prevent the window from being automatically resized. Otherwise, it 228*c689edbbSJens Wiklander # changes size when scrollbars appear/disappear before the user has 229*c689edbbSJens Wiklander # manually resized it. 230*c689edbbSJens Wiklander _root.geometry(_root.geometry()) 231*c689edbbSJens Wiklander 232*c689edbbSJens Wiklander _root.mainloop() 233*c689edbbSJens Wiklander 234*c689edbbSJens Wiklander 235*c689edbbSJens Wiklanderdef _load_config(): 236*c689edbbSJens Wiklander # Loads any existing .config file. See the Kconfig.load_config() docstring. 237*c689edbbSJens Wiklander # 238*c689edbbSJens Wiklander # Returns True if .config is missing or outdated. We always prompt for 239*c689edbbSJens Wiklander # saving the configuration in that case. 240*c689edbbSJens Wiklander 241*c689edbbSJens Wiklander print(_kconf.load_config()) 242*c689edbbSJens Wiklander if not os.path.exists(_conf_filename): 243*c689edbbSJens Wiklander # No .config 244*c689edbbSJens Wiklander return True 245*c689edbbSJens Wiklander 246*c689edbbSJens Wiklander return _needs_save() 247*c689edbbSJens Wiklander 248*c689edbbSJens Wiklander 249*c689edbbSJens Wiklanderdef _needs_save(): 250*c689edbbSJens Wiklander # Returns True if a just-loaded .config file is outdated (would get 251*c689edbbSJens Wiklander # modified when saving) 252*c689edbbSJens Wiklander 253*c689edbbSJens Wiklander if _kconf.missing_syms: 254*c689edbbSJens Wiklander # Assignments to undefined symbols in the .config 255*c689edbbSJens Wiklander return True 256*c689edbbSJens Wiklander 257*c689edbbSJens Wiklander for sym in _kconf.unique_defined_syms: 258*c689edbbSJens Wiklander if sym.user_value is None: 259*c689edbbSJens Wiklander if sym.config_string: 260*c689edbbSJens Wiklander # Unwritten symbol 261*c689edbbSJens Wiklander return True 262*c689edbbSJens Wiklander elif sym.orig_type in (BOOL, TRISTATE): 263*c689edbbSJens Wiklander if sym.tri_value != sym.user_value: 264*c689edbbSJens Wiklander # Written bool/tristate symbol, new value 265*c689edbbSJens Wiklander return True 266*c689edbbSJens Wiklander elif sym.str_value != sym.user_value: 267*c689edbbSJens Wiklander # Written string/int/hex symbol, new value 268*c689edbbSJens Wiklander return True 269*c689edbbSJens Wiklander 270*c689edbbSJens Wiklander # No need to prompt for save 271*c689edbbSJens Wiklander return False 272*c689edbbSJens Wiklander 273*c689edbbSJens Wiklander 274*c689edbbSJens Wiklanderdef _create_id_to_node(): 275*c689edbbSJens Wiklander global _id_to_node 276*c689edbbSJens Wiklander 277*c689edbbSJens Wiklander _id_to_node = {str(id(node)): node for node in _kconf.node_iter()} 278*c689edbbSJens Wiklander 279*c689edbbSJens Wiklander 280*c689edbbSJens Wiklanderdef _create_ui(): 281*c689edbbSJens Wiklander # Creates the main window UI 282*c689edbbSJens Wiklander 283*c689edbbSJens Wiklander global _root 284*c689edbbSJens Wiklander global _tree 285*c689edbbSJens Wiklander 286*c689edbbSJens Wiklander # Create the root window. This initializes Tkinter and makes e.g. 287*c689edbbSJens Wiklander # PhotoImage available, so do it early. 288*c689edbbSJens Wiklander _root = Tk() 289*c689edbbSJens Wiklander 290*c689edbbSJens Wiklander _load_images() 291*c689edbbSJens Wiklander _init_misc_ui() 292*c689edbbSJens Wiklander _fix_treeview_issues() 293*c689edbbSJens Wiklander 294*c689edbbSJens Wiklander _create_top_widgets() 295*c689edbbSJens Wiklander # Create the pane with the Kconfig tree and description text 296*c689edbbSJens Wiklander panedwindow, _tree = _create_kconfig_tree_and_desc(_root) 297*c689edbbSJens Wiklander panedwindow.grid(column=0, row=1, sticky="nsew") 298*c689edbbSJens Wiklander _create_status_bar() 299*c689edbbSJens Wiklander 300*c689edbbSJens Wiklander _root.columnconfigure(0, weight=1) 301*c689edbbSJens Wiklander # Only the pane with the Kconfig tree and description grows vertically 302*c689edbbSJens Wiklander _root.rowconfigure(1, weight=1) 303*c689edbbSJens Wiklander 304*c689edbbSJens Wiklander # Start with show-name disabled 305*c689edbbSJens Wiklander _do_showname() 306*c689edbbSJens Wiklander 307*c689edbbSJens Wiklander _tree.bind("<Left>", _tree_left_key) 308*c689edbbSJens Wiklander _tree.bind("<Right>", _tree_right_key) 309*c689edbbSJens Wiklander # Note: Binding this for the jump-to tree as well would cause issues due to 310*c689edbbSJens Wiklander # the Tk bug mentioned in _tree_open() 311*c689edbbSJens Wiklander _tree.bind("<<TreeviewOpen>>", _tree_open) 312*c689edbbSJens Wiklander # add=True to avoid overriding the description text update 313*c689edbbSJens Wiklander _tree.bind("<<TreeviewSelect>>", _update_menu_path, add=True) 314*c689edbbSJens Wiklander 315*c689edbbSJens Wiklander _root.bind("<Control-s>", _save) 316*c689edbbSJens Wiklander _root.bind("<Control-o>", _open) 317*c689edbbSJens Wiklander _root.bind("<Control-a>", _toggle_showall) 318*c689edbbSJens Wiklander _root.bind("<Control-n>", _toggle_showname) 319*c689edbbSJens Wiklander _root.bind("<Control-m>", _toggle_tree_mode) 320*c689edbbSJens Wiklander _root.bind("<Control-f>", _jump_to_dialog) 321*c689edbbSJens Wiklander _root.bind("/", _jump_to_dialog) 322*c689edbbSJens Wiklander _root.bind("<Escape>", _on_quit) 323*c689edbbSJens Wiklander 324*c689edbbSJens Wiklander 325*c689edbbSJens Wiklanderdef _load_images(): 326*c689edbbSJens Wiklander # Loads GIF images, creating the global _*_img PhotoImage variables. 327*c689edbbSJens Wiklander # Base64-encoded images embedded in this script are used if 328*c689edbbSJens Wiklander # _USE_EMBEDDED_IMAGES is True, and separate image files in the same 329*c689edbbSJens Wiklander # directory as the script otherwise. 330*c689edbbSJens Wiklander # 331*c689edbbSJens Wiklander # Using a global variable indirectly prevents the image from being 332*c689edbbSJens Wiklander # garbage-collected. Passing an image to a Tkinter function isn't enough to 333*c689edbbSJens Wiklander # keep it alive. 334*c689edbbSJens Wiklander 335*c689edbbSJens Wiklander def load_image(name, data): 336*c689edbbSJens Wiklander var_name = "_{}_img".format(name) 337*c689edbbSJens Wiklander 338*c689edbbSJens Wiklander if _USE_EMBEDDED_IMAGES: 339*c689edbbSJens Wiklander globals()[var_name] = PhotoImage(data=data, format="gif") 340*c689edbbSJens Wiklander else: 341*c689edbbSJens Wiklander globals()[var_name] = PhotoImage( 342*c689edbbSJens Wiklander file=os.path.join(os.path.dirname(__file__), name + ".gif"), 343*c689edbbSJens Wiklander format="gif") 344*c689edbbSJens Wiklander 345*c689edbbSJens Wiklander # Note: Base64 data can be put on the clipboard with 346*c689edbbSJens Wiklander # $ base64 -w0 foo.gif | xclip 347*c689edbbSJens Wiklander 348*c689edbbSJens Wiklander load_image("icon", "R0lGODlhMAAwAPEDAAAAAADQAO7u7v///yH5BAUKAAMALAAAAAAwADAAAAL/nI+gy+2Pokyv2jazuZxryQjiSJZmyXxHeLbumH6sEATvW8OLNtf5bfLZRLFITzgEipDJ4mYxYv6A0ubuqYhWk66tVTE4enHer7jcKvt0LLUw6P45lvEprT6c0+v7OBuqhYdHohcoqIbSAHc4ljhDwrh1UlgSydRCWWlp5wiYZvmSuSh4IzrqV6p4cwhkCsmY+nhK6uJ6t1mrOhuJqfu6+WYiCiwl7HtLjNSZZZis/MeM7NY3TaRKS40ooDeoiVqIultsrav92bi9c3a5KkkOsOJZpSS99m4k/0zPng4Gks9JSbB+8DIcoQfnjwpZCHv5W+ip4aQrKrB0uOikYhiMCBw1/uPoQUMBADs=") 349*c689edbbSJens Wiklander load_image("n_bool", "R0lGODdhEAAQAPAAAAgICP///ywAAAAAEAAQAAACIISPacHtvp5kcb5qG85hZ2+BkyiRF8BBaEqtrKkqslEAADs=") 350*c689edbbSJens Wiklander load_image("y_bool", "R0lGODdhEAAQAPEAAAgICADQAP///wAAACwAAAAAEAAQAAACMoSPacLtvlh4YrIYsst2cV19AvaVF9CUXBNJJoum7ymrsKuCnhiupIWjSSjAFuWhSCIKADs=") 351*c689edbbSJens Wiklander load_image("n_tri", "R0lGODlhEAAQAPD/AAEBAf///yH5BAUKAAIALAAAAAAQABAAAAInlI+pBrAKQnCPSUlXvFhznlkfeGwjKZhnJ65h6nrfi6h0st2QXikFADs=") 352*c689edbbSJens Wiklander load_image("m_tri", "R0lGODlhEAAQAPEDAAEBAeQMuv///wAAACH5BAUKAAMALAAAAAAQABAAAAI5nI+pBrAWAhPCjYhiAJQCnWmdoElHGVBoiK5M21ofXFpXRIrgiecqxkuNciZIhNOZFRNI24PhfEoLADs=") 353*c689edbbSJens Wiklander load_image("y_tri", "R0lGODlhEAAQAPEDAAICAgDQAP///wAAACH5BAUKAAMALAAAAAAQABAAAAI0nI+pBrAYBhDCRRUypfmergmgZ4xjMpmaw2zmxk7cCB+pWiVqp4MzDwn9FhGZ5WFjIZeGAgA7") 354*c689edbbSJens Wiklander load_image("m_my", "R0lGODlhEAAQAPEDAAAAAOQMuv///wAAACH5BAUKAAMALAAAAAAQABAAAAI5nIGpxiAPI2ghxFinq/ZygQhc94zgZopmOLYf67anGr+oZdp02emfV5n9MEHN5QhqICETxkABbQ4KADs=") 355*c689edbbSJens Wiklander load_image("y_my", "R0lGODlhEAAQAPH/AAAAAADQAAPRA////yH5BAUKAAQALAAAAAAQABAAAAM+SArcrhCMSSuIM9Q8rxxBWIXawIBkmWonupLd565Um9G1PIs59fKmzw8WnAlusBYR2SEIN6DmAmqBLBxYSAIAOw==") 356*c689edbbSJens Wiklander load_image("n_locked", "R0lGODlhEAAQAPABAAAAAP///yH5BAUKAAEALAAAAAAQABAAAAIgjB8AyKwN04pu0vMutpqqz4Hih4ydlnUpyl2r23pxUAAAOw==") 357*c689edbbSJens Wiklander load_image("m_locked", "R0lGODlhEAAQAPD/AAAAAOQMuiH5BAUKAAIALAAAAAAQABAAAAIylC8AyKwN04ohnGcqqlZmfXDWI26iInZoyiore05walolV39ftxsYHgL9QBBMBGFEFAAAOw==") 358*c689edbbSJens Wiklander load_image("y_locked", "R0lGODlhEAAQAPD/AAAAAADQACH5BAUKAAIALAAAAAAQABAAAAIylC8AyKzNgnlCtoDTwvZwrHydIYpQmR3KWq4uK74IOnp0HQPmnD3cOVlUIAgKsShkFAAAOw==") 359*c689edbbSJens Wiklander load_image("not_selected", "R0lGODlhEAAQAPD/AAAAAP///yH5BAUKAAIALAAAAAAQABAAAAIrlA2px6IBw2IpWglOvTYhzmUbGD3kNZ5QqrKn2YrqigCxZoMelU6No9gdCgA7") 360*c689edbbSJens Wiklander load_image("selected", "R0lGODlhEAAQAPD/AAAAAP///yH5BAUKAAIALAAAAAAQABAAAAIzlA2px6IBw2IpWglOvTah/kTZhimASJomiqonlLov1qptHTsgKSEzh9H8QI0QzNPwmRoFADs=") 361*c689edbbSJens Wiklander load_image("edit", "R0lGODlhEAAQAPIFAAAAAKOLAMuuEPvXCvrxvgAAAAAAAAAAACH5BAUKAAUALAAAAAAQABAAAANCWLqw/gqMBp8cszJxcwVC2FEOEIAi5kVBi3IqWZhuCGMyfdpj2e4pnK+WAshmvxeAcETWlsxPkkBtsqBMa8TIBSQAADs=") 362*c689edbbSJens Wiklander 363*c689edbbSJens Wiklander 364*c689edbbSJens Wiklanderdef _fix_treeview_issues(): 365*c689edbbSJens Wiklander # Fixes some Treeview issues 366*c689edbbSJens Wiklander 367*c689edbbSJens Wiklander global _treeview_rowheight 368*c689edbbSJens Wiklander 369*c689edbbSJens Wiklander style = ttk.Style() 370*c689edbbSJens Wiklander 371*c689edbbSJens Wiklander # The treeview rowheight isn't adjusted automatically on high-DPI displays, 372*c689edbbSJens Wiklander # so do it ourselves. The font will probably always be TkDefaultFont, but 373*c689edbbSJens Wiklander # play it safe and look it up. 374*c689edbbSJens Wiklander 375*c689edbbSJens Wiklander _treeview_rowheight = font.Font(font=style.lookup("Treeview", "font")) \ 376*c689edbbSJens Wiklander .metrics("linespace") + 2 377*c689edbbSJens Wiklander 378*c689edbbSJens Wiklander style.configure("Treeview", rowheight=_treeview_rowheight) 379*c689edbbSJens Wiklander 380*c689edbbSJens Wiklander # Work around regression in https://core.tcl.tk/tk/tktview?name=509cafafae, 381*c689edbbSJens Wiklander # which breaks tag background colors 382*c689edbbSJens Wiklander 383*c689edbbSJens Wiklander for option in "foreground", "background": 384*c689edbbSJens Wiklander # Filter out any styles starting with ("!disabled", "!selected", ...). 385*c689edbbSJens Wiklander # style.map() returns an empty list for missing options, so this should 386*c689edbbSJens Wiklander # be future-safe. 387*c689edbbSJens Wiklander style.map( 388*c689edbbSJens Wiklander "Treeview", 389*c689edbbSJens Wiklander **{option: [elm for elm in style.map("Treeview", query_opt=option) 390*c689edbbSJens Wiklander if elm[:2] != ("!disabled", "!selected")]}) 391*c689edbbSJens Wiklander 392*c689edbbSJens Wiklander 393*c689edbbSJens Wiklanderdef _init_misc_ui(): 394*c689edbbSJens Wiklander # Does misc. UI initialization, like setting the title, icon, and theme 395*c689edbbSJens Wiklander 396*c689edbbSJens Wiklander _root.title(_kconf.mainmenu_text) 397*c689edbbSJens Wiklander # iconphoto() isn't available in Python 2's Tkinter 398*c689edbbSJens Wiklander _root.tk.call("wm", "iconphoto", _root._w, "-default", _icon_img) 399*c689edbbSJens Wiklander # Reducing the width of the window to 1 pixel makes it move around, at 400*c689edbbSJens Wiklander # least on GNOME. Prevent weird stuff like that. 401*c689edbbSJens Wiklander _root.minsize(128, 128) 402*c689edbbSJens Wiklander _root.protocol("WM_DELETE_WINDOW", _on_quit) 403*c689edbbSJens Wiklander 404*c689edbbSJens Wiklander # Use the 'clam' theme on *nix if it's available. It looks nicer than the 405*c689edbbSJens Wiklander # 'default' theme. 406*c689edbbSJens Wiklander if _root.tk.call("tk", "windowingsystem") == "x11": 407*c689edbbSJens Wiklander style = ttk.Style() 408*c689edbbSJens Wiklander if "clam" in style.theme_names(): 409*c689edbbSJens Wiklander style.theme_use("clam") 410*c689edbbSJens Wiklander 411*c689edbbSJens Wiklander 412*c689edbbSJens Wiklanderdef _create_top_widgets(): 413*c689edbbSJens Wiklander # Creates the controls above the Kconfig tree in the main window 414*c689edbbSJens Wiklander 415*c689edbbSJens Wiklander global _show_all_var 416*c689edbbSJens Wiklander global _show_name_var 417*c689edbbSJens Wiklander global _single_menu_var 418*c689edbbSJens Wiklander global _menupath 419*c689edbbSJens Wiklander global _backbutton 420*c689edbbSJens Wiklander 421*c689edbbSJens Wiklander topframe = ttk.Frame(_root) 422*c689edbbSJens Wiklander topframe.grid(column=0, row=0, sticky="ew") 423*c689edbbSJens Wiklander 424*c689edbbSJens Wiklander ttk.Button(topframe, text="Save", command=_save) \ 425*c689edbbSJens Wiklander .grid(column=0, row=0, sticky="ew", padx=".05c", pady=".05c") 426*c689edbbSJens Wiklander 427*c689edbbSJens Wiklander ttk.Button(topframe, text="Save as...", command=_save_as) \ 428*c689edbbSJens Wiklander .grid(column=1, row=0, sticky="ew") 429*c689edbbSJens Wiklander 430*c689edbbSJens Wiklander ttk.Button(topframe, text="Save minimal (advanced)...", 431*c689edbbSJens Wiklander command=_save_minimal) \ 432*c689edbbSJens Wiklander .grid(column=2, row=0, sticky="ew", padx=".05c") 433*c689edbbSJens Wiklander 434*c689edbbSJens Wiklander ttk.Button(topframe, text="Open...", command=_open) \ 435*c689edbbSJens Wiklander .grid(column=3, row=0) 436*c689edbbSJens Wiklander 437*c689edbbSJens Wiklander ttk.Button(topframe, text="Jump to...", command=_jump_to_dialog) \ 438*c689edbbSJens Wiklander .grid(column=4, row=0, padx=".05c") 439*c689edbbSJens Wiklander 440*c689edbbSJens Wiklander _show_name_var = BooleanVar() 441*c689edbbSJens Wiklander ttk.Checkbutton(topframe, text="Show name", command=_do_showname, 442*c689edbbSJens Wiklander variable=_show_name_var) \ 443*c689edbbSJens Wiklander .grid(column=0, row=1, sticky="nsew", padx=".05c", pady="0 .05c", 444*c689edbbSJens Wiklander ipady=".2c") 445*c689edbbSJens Wiklander 446*c689edbbSJens Wiklander _show_all_var = BooleanVar() 447*c689edbbSJens Wiklander ttk.Checkbutton(topframe, text="Show all", command=_do_showall, 448*c689edbbSJens Wiklander variable=_show_all_var) \ 449*c689edbbSJens Wiklander .grid(column=1, row=1, sticky="nsew", pady="0 .05c") 450*c689edbbSJens Wiklander 451*c689edbbSJens Wiklander # Allow the show-all and single-menu status to be queried via plain global 452*c689edbbSJens Wiklander # Python variables, which is faster and simpler 453*c689edbbSJens Wiklander 454*c689edbbSJens Wiklander def show_all_updated(*_): 455*c689edbbSJens Wiklander global _show_all 456*c689edbbSJens Wiklander _show_all = _show_all_var.get() 457*c689edbbSJens Wiklander 458*c689edbbSJens Wiklander _trace_write(_show_all_var, show_all_updated) 459*c689edbbSJens Wiklander _show_all_var.set(False) 460*c689edbbSJens Wiklander 461*c689edbbSJens Wiklander _single_menu_var = BooleanVar() 462*c689edbbSJens Wiklander ttk.Checkbutton(topframe, text="Single-menu mode", command=_do_tree_mode, 463*c689edbbSJens Wiklander variable=_single_menu_var) \ 464*c689edbbSJens Wiklander .grid(column=2, row=1, sticky="nsew", padx=".05c", pady="0 .05c") 465*c689edbbSJens Wiklander 466*c689edbbSJens Wiklander _backbutton = ttk.Button(topframe, text="<--", command=_leave_menu, 467*c689edbbSJens Wiklander state="disabled") 468*c689edbbSJens Wiklander _backbutton.grid(column=0, row=4, sticky="nsew", padx=".05c", pady="0 .05c") 469*c689edbbSJens Wiklander 470*c689edbbSJens Wiklander def tree_mode_updated(*_): 471*c689edbbSJens Wiklander global _single_menu 472*c689edbbSJens Wiklander _single_menu = _single_menu_var.get() 473*c689edbbSJens Wiklander 474*c689edbbSJens Wiklander if _single_menu: 475*c689edbbSJens Wiklander _backbutton.grid() 476*c689edbbSJens Wiklander else: 477*c689edbbSJens Wiklander _backbutton.grid_remove() 478*c689edbbSJens Wiklander 479*c689edbbSJens Wiklander _trace_write(_single_menu_var, tree_mode_updated) 480*c689edbbSJens Wiklander _single_menu_var.set(False) 481*c689edbbSJens Wiklander 482*c689edbbSJens Wiklander # Column to the right of the buttons that the menu path extends into, so 483*c689edbbSJens Wiklander # that it can grow wider than the buttons 484*c689edbbSJens Wiklander topframe.columnconfigure(5, weight=1) 485*c689edbbSJens Wiklander 486*c689edbbSJens Wiklander _menupath = ttk.Label(topframe) 487*c689edbbSJens Wiklander _menupath.grid(column=0, row=3, columnspan=6, sticky="w", padx="0.05c", 488*c689edbbSJens Wiklander pady="0 .05c") 489*c689edbbSJens Wiklander 490*c689edbbSJens Wiklander 491*c689edbbSJens Wiklanderdef _create_kconfig_tree_and_desc(parent): 492*c689edbbSJens Wiklander # Creates a Panedwindow with a Treeview that shows Kconfig nodes and a Text 493*c689edbbSJens Wiklander # that shows a description of the selected node. Returns a tuple with the 494*c689edbbSJens Wiklander # Panedwindow and the Treeview. This code is shared between the main window 495*c689edbbSJens Wiklander # and the jump-to dialog. 496*c689edbbSJens Wiklander 497*c689edbbSJens Wiklander panedwindow = ttk.Panedwindow(parent, orient=VERTICAL) 498*c689edbbSJens Wiklander 499*c689edbbSJens Wiklander tree_frame, tree = _create_kconfig_tree(panedwindow) 500*c689edbbSJens Wiklander desc_frame, desc = _create_kconfig_desc(panedwindow) 501*c689edbbSJens Wiklander 502*c689edbbSJens Wiklander panedwindow.add(tree_frame, weight=1) 503*c689edbbSJens Wiklander panedwindow.add(desc_frame) 504*c689edbbSJens Wiklander 505*c689edbbSJens Wiklander def tree_select(_): 506*c689edbbSJens Wiklander # The Text widget does not allow editing the text in its disabled 507*c689edbbSJens Wiklander # state. We need to temporarily enable it. 508*c689edbbSJens Wiklander desc["state"] = "normal" 509*c689edbbSJens Wiklander 510*c689edbbSJens Wiklander sel = tree.selection() 511*c689edbbSJens Wiklander if not sel: 512*c689edbbSJens Wiklander desc.delete("1.0", "end") 513*c689edbbSJens Wiklander desc["state"] = "disabled" 514*c689edbbSJens Wiklander return 515*c689edbbSJens Wiklander 516*c689edbbSJens Wiklander # Text.replace() is not available in Python 2's Tkinter 517*c689edbbSJens Wiklander desc.delete("1.0", "end") 518*c689edbbSJens Wiklander desc.insert("end", _info_str(_id_to_node[sel[0]])) 519*c689edbbSJens Wiklander 520*c689edbbSJens Wiklander desc["state"] = "disabled" 521*c689edbbSJens Wiklander 522*c689edbbSJens Wiklander tree.bind("<<TreeviewSelect>>", tree_select) 523*c689edbbSJens Wiklander tree.bind("<1>", _tree_click) 524*c689edbbSJens Wiklander tree.bind("<Double-1>", _tree_double_click) 525*c689edbbSJens Wiklander tree.bind("<Return>", _tree_enter) 526*c689edbbSJens Wiklander tree.bind("<KP_Enter>", _tree_enter) 527*c689edbbSJens Wiklander tree.bind("<space>", _tree_toggle) 528*c689edbbSJens Wiklander tree.bind("n", _tree_set_val(0)) 529*c689edbbSJens Wiklander tree.bind("m", _tree_set_val(1)) 530*c689edbbSJens Wiklander tree.bind("y", _tree_set_val(2)) 531*c689edbbSJens Wiklander 532*c689edbbSJens Wiklander return panedwindow, tree 533*c689edbbSJens Wiklander 534*c689edbbSJens Wiklander 535*c689edbbSJens Wiklanderdef _create_kconfig_tree(parent): 536*c689edbbSJens Wiklander # Creates a Treeview for showing Kconfig nodes 537*c689edbbSJens Wiklander 538*c689edbbSJens Wiklander frame = ttk.Frame(parent) 539*c689edbbSJens Wiklander 540*c689edbbSJens Wiklander tree = ttk.Treeview(frame, selectmode="browse", height=20, 541*c689edbbSJens Wiklander columns=("name",)) 542*c689edbbSJens Wiklander tree.heading("#0", text="Option", anchor="w") 543*c689edbbSJens Wiklander tree.heading("name", text="Name", anchor="w") 544*c689edbbSJens Wiklander 545*c689edbbSJens Wiklander tree.tag_configure("n-bool", image=_n_bool_img) 546*c689edbbSJens Wiklander tree.tag_configure("y-bool", image=_y_bool_img) 547*c689edbbSJens Wiklander tree.tag_configure("m-tri", image=_m_tri_img) 548*c689edbbSJens Wiklander tree.tag_configure("n-tri", image=_n_tri_img) 549*c689edbbSJens Wiklander tree.tag_configure("m-tri", image=_m_tri_img) 550*c689edbbSJens Wiklander tree.tag_configure("y-tri", image=_y_tri_img) 551*c689edbbSJens Wiklander tree.tag_configure("m-my", image=_m_my_img) 552*c689edbbSJens Wiklander tree.tag_configure("y-my", image=_y_my_img) 553*c689edbbSJens Wiklander tree.tag_configure("n-locked", image=_n_locked_img) 554*c689edbbSJens Wiklander tree.tag_configure("m-locked", image=_m_locked_img) 555*c689edbbSJens Wiklander tree.tag_configure("y-locked", image=_y_locked_img) 556*c689edbbSJens Wiklander tree.tag_configure("not-selected", image=_not_selected_img) 557*c689edbbSJens Wiklander tree.tag_configure("selected", image=_selected_img) 558*c689edbbSJens Wiklander tree.tag_configure("edit", image=_edit_img) 559*c689edbbSJens Wiklander tree.tag_configure("invisible", foreground="red") 560*c689edbbSJens Wiklander 561*c689edbbSJens Wiklander tree.grid(column=0, row=0, sticky="nsew") 562*c689edbbSJens Wiklander 563*c689edbbSJens Wiklander _add_vscrollbar(frame, tree) 564*c689edbbSJens Wiklander 565*c689edbbSJens Wiklander frame.columnconfigure(0, weight=1) 566*c689edbbSJens Wiklander frame.rowconfigure(0, weight=1) 567*c689edbbSJens Wiklander 568*c689edbbSJens Wiklander # Create items for all menu nodes. These can be detached/moved later. 569*c689edbbSJens Wiklander # Micro-optimize this a bit. 570*c689edbbSJens Wiklander insert = tree.insert 571*c689edbbSJens Wiklander id_ = id 572*c689edbbSJens Wiklander Symbol_ = Symbol 573*c689edbbSJens Wiklander for node in _kconf.node_iter(): 574*c689edbbSJens Wiklander item = node.item 575*c689edbbSJens Wiklander insert("", "end", iid=id_(node), 576*c689edbbSJens Wiklander values=item.name if item.__class__ is Symbol_ else "") 577*c689edbbSJens Wiklander 578*c689edbbSJens Wiklander return frame, tree 579*c689edbbSJens Wiklander 580*c689edbbSJens Wiklander 581*c689edbbSJens Wiklanderdef _create_kconfig_desc(parent): 582*c689edbbSJens Wiklander # Creates a Text for showing the description of the selected Kconfig node 583*c689edbbSJens Wiklander 584*c689edbbSJens Wiklander frame = ttk.Frame(parent) 585*c689edbbSJens Wiklander 586*c689edbbSJens Wiklander desc = Text(frame, height=12, wrap="none", borderwidth=0, 587*c689edbbSJens Wiklander state="disabled") 588*c689edbbSJens Wiklander desc.grid(column=0, row=0, sticky="nsew") 589*c689edbbSJens Wiklander 590*c689edbbSJens Wiklander # Work around not being to Ctrl-C/V text from a disabled Text widget, with a 591*c689edbbSJens Wiklander # tip found in https://stackoverflow.com/questions/3842155/is-there-a-way-to-make-the-tkinter-text-widget-read-only 592*c689edbbSJens Wiklander desc.bind("<1>", lambda _: desc.focus_set()) 593*c689edbbSJens Wiklander 594*c689edbbSJens Wiklander _add_vscrollbar(frame, desc) 595*c689edbbSJens Wiklander 596*c689edbbSJens Wiklander frame.columnconfigure(0, weight=1) 597*c689edbbSJens Wiklander frame.rowconfigure(0, weight=1) 598*c689edbbSJens Wiklander 599*c689edbbSJens Wiklander return frame, desc 600*c689edbbSJens Wiklander 601*c689edbbSJens Wiklander 602*c689edbbSJens Wiklanderdef _add_vscrollbar(parent, widget): 603*c689edbbSJens Wiklander # Adds a vertical scrollbar to 'widget' that's only shown as needed 604*c689edbbSJens Wiklander 605*c689edbbSJens Wiklander vscrollbar = ttk.Scrollbar(parent, orient="vertical", 606*c689edbbSJens Wiklander command=widget.yview) 607*c689edbbSJens Wiklander vscrollbar.grid(column=1, row=0, sticky="ns") 608*c689edbbSJens Wiklander 609*c689edbbSJens Wiklander def yscrollcommand(first, last): 610*c689edbbSJens Wiklander # Only show the scrollbar when needed. 'first' and 'last' are 611*c689edbbSJens Wiklander # strings. 612*c689edbbSJens Wiklander if float(first) <= 0.0 and float(last) >= 1.0: 613*c689edbbSJens Wiklander vscrollbar.grid_remove() 614*c689edbbSJens Wiklander else: 615*c689edbbSJens Wiklander vscrollbar.grid() 616*c689edbbSJens Wiklander 617*c689edbbSJens Wiklander vscrollbar.set(first, last) 618*c689edbbSJens Wiklander 619*c689edbbSJens Wiklander widget["yscrollcommand"] = yscrollcommand 620*c689edbbSJens Wiklander 621*c689edbbSJens Wiklander 622*c689edbbSJens Wiklanderdef _create_status_bar(): 623*c689edbbSJens Wiklander # Creates the status bar at the bottom of the main window 624*c689edbbSJens Wiklander 625*c689edbbSJens Wiklander global _status_label 626*c689edbbSJens Wiklander 627*c689edbbSJens Wiklander _status_label = ttk.Label(_root, anchor="e", padding="0 0 0.4c 0") 628*c689edbbSJens Wiklander _status_label.grid(column=0, row=3, sticky="ew") 629*c689edbbSJens Wiklander 630*c689edbbSJens Wiklander 631*c689edbbSJens Wiklanderdef _set_status(s): 632*c689edbbSJens Wiklander # Sets the text in the status bar to 's' 633*c689edbbSJens Wiklander 634*c689edbbSJens Wiklander _status_label["text"] = s 635*c689edbbSJens Wiklander 636*c689edbbSJens Wiklander 637*c689edbbSJens Wiklanderdef _set_conf_changed(changed): 638*c689edbbSJens Wiklander # Updates the status re. whether there are unsaved changes 639*c689edbbSJens Wiklander 640*c689edbbSJens Wiklander global _conf_changed 641*c689edbbSJens Wiklander 642*c689edbbSJens Wiklander _conf_changed = changed 643*c689edbbSJens Wiklander if changed: 644*c689edbbSJens Wiklander _set_status("Modified") 645*c689edbbSJens Wiklander 646*c689edbbSJens Wiklander 647*c689edbbSJens Wiklanderdef _update_tree(): 648*c689edbbSJens Wiklander # Updates the Kconfig tree in the main window by first detaching all nodes 649*c689edbbSJens Wiklander # and then updating and reattaching them. The tree structure might have 650*c689edbbSJens Wiklander # changed. 651*c689edbbSJens Wiklander 652*c689edbbSJens Wiklander # If a selected/focused item is detached and later reattached, it stays 653*c689edbbSJens Wiklander # selected/focused. That can give multiple selections even though 654*c689edbbSJens Wiklander # selectmode=browse. Save and later restore the selection and focus as a 655*c689edbbSJens Wiklander # workaround. 656*c689edbbSJens Wiklander old_selection = _tree.selection() 657*c689edbbSJens Wiklander old_focus = _tree.focus() 658*c689edbbSJens Wiklander 659*c689edbbSJens Wiklander # Detach all tree items before re-stringing them. This is relatively fast, 660*c689edbbSJens Wiklander # luckily. 661*c689edbbSJens Wiklander _tree.detach(*_id_to_node.keys()) 662*c689edbbSJens Wiklander 663*c689edbbSJens Wiklander if _single_menu: 664*c689edbbSJens Wiklander _build_menu_tree() 665*c689edbbSJens Wiklander else: 666*c689edbbSJens Wiklander _build_full_tree(_kconf.top_node) 667*c689edbbSJens Wiklander 668*c689edbbSJens Wiklander _tree.selection_set(old_selection) 669*c689edbbSJens Wiklander _tree.focus(old_focus) 670*c689edbbSJens Wiklander 671*c689edbbSJens Wiklander 672*c689edbbSJens Wiklanderdef _build_full_tree(menu): 673*c689edbbSJens Wiklander # Updates the tree starting from menu.list, in full-tree mode. To speed 674*c689edbbSJens Wiklander # things up, only open menus are updated. The menu-at-a-time logic here is 675*c689edbbSJens Wiklander # to deal with invisible items that can show up outside show-all mode (see 676*c689edbbSJens Wiklander # _shown_full_nodes()). 677*c689edbbSJens Wiklander 678*c689edbbSJens Wiklander for node in _shown_full_nodes(menu): 679*c689edbbSJens Wiklander _add_to_tree(node, _kconf.top_node) 680*c689edbbSJens Wiklander 681*c689edbbSJens Wiklander # _shown_full_nodes() includes nodes from menus rooted at symbols, so 682*c689edbbSJens Wiklander # we only need to check "real" menus/choices here 683*c689edbbSJens Wiklander if node.list and not isinstance(node.item, Symbol): 684*c689edbbSJens Wiklander if _tree.item(id(node), "open"): 685*c689edbbSJens Wiklander _build_full_tree(node) 686*c689edbbSJens Wiklander else: 687*c689edbbSJens Wiklander # We're just probing here, so _shown_menu_nodes() will work 688*c689edbbSJens Wiklander # fine, and might be a bit faster 689*c689edbbSJens Wiklander shown = _shown_menu_nodes(node) 690*c689edbbSJens Wiklander if shown: 691*c689edbbSJens Wiklander # Dummy element to make the open/closed toggle appear 692*c689edbbSJens Wiklander _tree.move(id(shown[0]), id(shown[0].parent), "end") 693*c689edbbSJens Wiklander 694*c689edbbSJens Wiklander 695*c689edbbSJens Wiklanderdef _shown_full_nodes(menu): 696*c689edbbSJens Wiklander # Returns the list of menu nodes shown in 'menu' (a menu node for a menu) 697*c689edbbSJens Wiklander # for full-tree mode. A tricky detail is that invisible items need to be 698*c689edbbSJens Wiklander # shown if they have visible children. 699*c689edbbSJens Wiklander 700*c689edbbSJens Wiklander def rec(node): 701*c689edbbSJens Wiklander res = [] 702*c689edbbSJens Wiklander 703*c689edbbSJens Wiklander while node: 704*c689edbbSJens Wiklander if _visible(node) or _show_all: 705*c689edbbSJens Wiklander res.append(node) 706*c689edbbSJens Wiklander if node.list and isinstance(node.item, Symbol): 707*c689edbbSJens Wiklander # Nodes from menu created from dependencies 708*c689edbbSJens Wiklander res += rec(node.list) 709*c689edbbSJens Wiklander 710*c689edbbSJens Wiklander elif node.list and isinstance(node.item, Symbol): 711*c689edbbSJens Wiklander # Show invisible symbols (defined with either 'config' and 712*c689edbbSJens Wiklander # 'menuconfig') if they have visible children. This can happen 713*c689edbbSJens Wiklander # for an m/y-valued symbol with an optional prompt 714*c689edbbSJens Wiklander # ('prompt "foo" is COND') that is currently disabled. 715*c689edbbSJens Wiklander shown_children = rec(node.list) 716*c689edbbSJens Wiklander if shown_children: 717*c689edbbSJens Wiklander res.append(node) 718*c689edbbSJens Wiklander res += shown_children 719*c689edbbSJens Wiklander 720*c689edbbSJens Wiklander node = node.next 721*c689edbbSJens Wiklander 722*c689edbbSJens Wiklander return res 723*c689edbbSJens Wiklander 724*c689edbbSJens Wiklander return rec(menu.list) 725*c689edbbSJens Wiklander 726*c689edbbSJens Wiklander 727*c689edbbSJens Wiklanderdef _build_menu_tree(): 728*c689edbbSJens Wiklander # Updates the tree in single-menu mode. See _build_full_tree() as well. 729*c689edbbSJens Wiklander 730*c689edbbSJens Wiklander for node in _shown_menu_nodes(_cur_menu): 731*c689edbbSJens Wiklander _add_to_tree(node, _cur_menu) 732*c689edbbSJens Wiklander 733*c689edbbSJens Wiklander 734*c689edbbSJens Wiklanderdef _shown_menu_nodes(menu): 735*c689edbbSJens Wiklander # Used for single-menu mode. Similar to _shown_full_nodes(), but doesn't 736*c689edbbSJens Wiklander # include children of symbols defined with 'menuconfig'. 737*c689edbbSJens Wiklander 738*c689edbbSJens Wiklander def rec(node): 739*c689edbbSJens Wiklander res = [] 740*c689edbbSJens Wiklander 741*c689edbbSJens Wiklander while node: 742*c689edbbSJens Wiklander if _visible(node) or _show_all: 743*c689edbbSJens Wiklander res.append(node) 744*c689edbbSJens Wiklander if node.list and not node.is_menuconfig: 745*c689edbbSJens Wiklander res += rec(node.list) 746*c689edbbSJens Wiklander 747*c689edbbSJens Wiklander elif node.list and isinstance(node.item, Symbol): 748*c689edbbSJens Wiklander shown_children = rec(node.list) 749*c689edbbSJens Wiklander if shown_children: 750*c689edbbSJens Wiklander # Invisible item with visible children 751*c689edbbSJens Wiklander res.append(node) 752*c689edbbSJens Wiklander if not node.is_menuconfig: 753*c689edbbSJens Wiklander res += shown_children 754*c689edbbSJens Wiklander 755*c689edbbSJens Wiklander node = node.next 756*c689edbbSJens Wiklander 757*c689edbbSJens Wiklander return res 758*c689edbbSJens Wiklander 759*c689edbbSJens Wiklander return rec(menu.list) 760*c689edbbSJens Wiklander 761*c689edbbSJens Wiklander 762*c689edbbSJens Wiklanderdef _visible(node): 763*c689edbbSJens Wiklander # Returns True if the node should appear in the menu (outside show-all 764*c689edbbSJens Wiklander # mode) 765*c689edbbSJens Wiklander 766*c689edbbSJens Wiklander return node.prompt and expr_value(node.prompt[1]) and not \ 767*c689edbbSJens Wiklander (node.item == MENU and not expr_value(node.visibility)) 768*c689edbbSJens Wiklander 769*c689edbbSJens Wiklander 770*c689edbbSJens Wiklanderdef _add_to_tree(node, top): 771*c689edbbSJens Wiklander # Adds 'node' to the tree, at the end of its menu. We rely on going through 772*c689edbbSJens Wiklander # the nodes linearly to get the correct order. 'top' holds the menu that 773*c689edbbSJens Wiklander # corresponds to the top-level menu, and can vary in single-menu mode. 774*c689edbbSJens Wiklander 775*c689edbbSJens Wiklander parent = node.parent 776*c689edbbSJens Wiklander _tree.move(id(node), "" if parent is top else id(parent), "end") 777*c689edbbSJens Wiklander _tree.item( 778*c689edbbSJens Wiklander id(node), 779*c689edbbSJens Wiklander text=_node_str(node), 780*c689edbbSJens Wiklander # The _show_all test avoids showing invisible items in red outside 781*c689edbbSJens Wiklander # show-all mode, which could look confusing/broken. Invisible symbols 782*c689edbbSJens Wiklander # are shown outside show-all mode if an invisible symbol has visible 783*c689edbbSJens Wiklander # children in an implicit menu. 784*c689edbbSJens Wiklander tags=_img_tag(node) if _visible(node) or not _show_all else 785*c689edbbSJens Wiklander _img_tag(node) + " invisible") 786*c689edbbSJens Wiklander 787*c689edbbSJens Wiklander 788*c689edbbSJens Wiklanderdef _node_str(node): 789*c689edbbSJens Wiklander # Returns the string shown to the right of the image (if any) for the node 790*c689edbbSJens Wiklander 791*c689edbbSJens Wiklander if node.prompt: 792*c689edbbSJens Wiklander if node.item == COMMENT: 793*c689edbbSJens Wiklander s = "*** {} ***".format(node.prompt[0]) 794*c689edbbSJens Wiklander else: 795*c689edbbSJens Wiklander s = node.prompt[0] 796*c689edbbSJens Wiklander 797*c689edbbSJens Wiklander if isinstance(node.item, Symbol): 798*c689edbbSJens Wiklander sym = node.item 799*c689edbbSJens Wiklander 800*c689edbbSJens Wiklander # Print "(NEW)" next to symbols without a user value (from e.g. a 801*c689edbbSJens Wiklander # .config), but skip it for choice symbols in choices in y mode, 802*c689edbbSJens Wiklander # and for symbols of UNKNOWN type (which generate a warning though) 803*c689edbbSJens Wiklander if sym.user_value is None and sym.type and not \ 804*c689edbbSJens Wiklander (sym.choice and sym.choice.tri_value == 2): 805*c689edbbSJens Wiklander 806*c689edbbSJens Wiklander s += " (NEW)" 807*c689edbbSJens Wiklander 808*c689edbbSJens Wiklander elif isinstance(node.item, Symbol): 809*c689edbbSJens Wiklander # Symbol without prompt (can show up in show-all) 810*c689edbbSJens Wiklander s = "<{}>".format(node.item.name) 811*c689edbbSJens Wiklander 812*c689edbbSJens Wiklander else: 813*c689edbbSJens Wiklander # Choice without prompt. Use standard_sc_expr_str() so that it shows up 814*c689edbbSJens Wiklander # as '<choice (name if any)>'. 815*c689edbbSJens Wiklander s = standard_sc_expr_str(node.item) 816*c689edbbSJens Wiklander 817*c689edbbSJens Wiklander 818*c689edbbSJens Wiklander if isinstance(node.item, Symbol): 819*c689edbbSJens Wiklander sym = node.item 820*c689edbbSJens Wiklander if sym.orig_type == STRING: 821*c689edbbSJens Wiklander s += ": " + sym.str_value 822*c689edbbSJens Wiklander elif sym.orig_type in (INT, HEX): 823*c689edbbSJens Wiklander s = "({}) {}".format(sym.str_value, s) 824*c689edbbSJens Wiklander 825*c689edbbSJens Wiklander elif isinstance(node.item, Choice) and node.item.tri_value == 2: 826*c689edbbSJens Wiklander # Print the prompt of the selected symbol after the choice for 827*c689edbbSJens Wiklander # choices in y mode 828*c689edbbSJens Wiklander sym = node.item.selection 829*c689edbbSJens Wiklander if sym: 830*c689edbbSJens Wiklander for sym_node in sym.nodes: 831*c689edbbSJens Wiklander # Use the prompt used at this choice location, in case the 832*c689edbbSJens Wiklander # choice symbol is defined in multiple locations 833*c689edbbSJens Wiklander if sym_node.parent is node and sym_node.prompt: 834*c689edbbSJens Wiklander s += " ({})".format(sym_node.prompt[0]) 835*c689edbbSJens Wiklander break 836*c689edbbSJens Wiklander else: 837*c689edbbSJens Wiklander # If the symbol isn't defined at this choice location, then 838*c689edbbSJens Wiklander # just use whatever prompt we can find for it 839*c689edbbSJens Wiklander for sym_node in sym.nodes: 840*c689edbbSJens Wiklander if sym_node.prompt: 841*c689edbbSJens Wiklander s += " ({})".format(sym_node.prompt[0]) 842*c689edbbSJens Wiklander break 843*c689edbbSJens Wiklander 844*c689edbbSJens Wiklander # In single-menu mode, print "--->" next to nodes that have menus that can 845*c689edbbSJens Wiklander # potentially be entered. Print "----" if the menu is empty. We don't allow 846*c689edbbSJens Wiklander # those to be entered. 847*c689edbbSJens Wiklander if _single_menu and node.is_menuconfig: 848*c689edbbSJens Wiklander s += " --->" if _shown_menu_nodes(node) else " ----" 849*c689edbbSJens Wiklander 850*c689edbbSJens Wiklander return s 851*c689edbbSJens Wiklander 852*c689edbbSJens Wiklander 853*c689edbbSJens Wiklanderdef _img_tag(node): 854*c689edbbSJens Wiklander # Returns the tag for the image that should be shown next to 'node', or the 855*c689edbbSJens Wiklander # empty string if it shouldn't have an image 856*c689edbbSJens Wiklander 857*c689edbbSJens Wiklander item = node.item 858*c689edbbSJens Wiklander 859*c689edbbSJens Wiklander if item in (MENU, COMMENT) or not item.orig_type: 860*c689edbbSJens Wiklander return "" 861*c689edbbSJens Wiklander 862*c689edbbSJens Wiklander if item.orig_type in (STRING, INT, HEX): 863*c689edbbSJens Wiklander return "edit" 864*c689edbbSJens Wiklander 865*c689edbbSJens Wiklander # BOOL or TRISTATE 866*c689edbbSJens Wiklander 867*c689edbbSJens Wiklander if _is_y_mode_choice_sym(item): 868*c689edbbSJens Wiklander # Choice symbol in y-mode choice 869*c689edbbSJens Wiklander return "selected" if item.choice.selection is item else "not-selected" 870*c689edbbSJens Wiklander 871*c689edbbSJens Wiklander if len(item.assignable) <= 1: 872*c689edbbSJens Wiklander # Pinned to a single value 873*c689edbbSJens Wiklander return "" if isinstance(item, Choice) else item.str_value + "-locked" 874*c689edbbSJens Wiklander 875*c689edbbSJens Wiklander if item.type == BOOL: 876*c689edbbSJens Wiklander return item.str_value + "-bool" 877*c689edbbSJens Wiklander 878*c689edbbSJens Wiklander # item.type == TRISTATE 879*c689edbbSJens Wiklander if item.assignable == (1, 2): 880*c689edbbSJens Wiklander return item.str_value + "-my" 881*c689edbbSJens Wiklander return item.str_value + "-tri" 882*c689edbbSJens Wiklander 883*c689edbbSJens Wiklander 884*c689edbbSJens Wiklanderdef _is_y_mode_choice_sym(item): 885*c689edbbSJens Wiklander # The choice mode is an upper bound on the visibility of choice symbols, so 886*c689edbbSJens Wiklander # we can check the choice symbols' own visibility to see if the choice is 887*c689edbbSJens Wiklander # in y mode 888*c689edbbSJens Wiklander return isinstance(item, Symbol) and item.choice and item.visibility == 2 889*c689edbbSJens Wiklander 890*c689edbbSJens Wiklander 891*c689edbbSJens Wiklanderdef _tree_click(event): 892*c689edbbSJens Wiklander # Click on the Kconfig Treeview 893*c689edbbSJens Wiklander 894*c689edbbSJens Wiklander tree = event.widget 895*c689edbbSJens Wiklander if tree.identify_element(event.x, event.y) == "image": 896*c689edbbSJens Wiklander item = tree.identify_row(event.y) 897*c689edbbSJens Wiklander # Select the item before possibly popping up a dialog for 898*c689edbbSJens Wiklander # string/int/hex items, so that its help is visible 899*c689edbbSJens Wiklander _select(tree, item) 900*c689edbbSJens Wiklander _change_node(_id_to_node[item], tree.winfo_toplevel()) 901*c689edbbSJens Wiklander return "break" 902*c689edbbSJens Wiklander 903*c689edbbSJens Wiklander 904*c689edbbSJens Wiklanderdef _tree_double_click(event): 905*c689edbbSJens Wiklander # Double-click on the Kconfig treeview 906*c689edbbSJens Wiklander 907*c689edbbSJens Wiklander # Do an extra check to avoid weirdness when double-clicking in the tree 908*c689edbbSJens Wiklander # heading area 909*c689edbbSJens Wiklander if not _in_heading(event): 910*c689edbbSJens Wiklander return _tree_enter(event) 911*c689edbbSJens Wiklander 912*c689edbbSJens Wiklander 913*c689edbbSJens Wiklanderdef _in_heading(event): 914*c689edbbSJens Wiklander # Returns True if 'event' took place in the tree heading 915*c689edbbSJens Wiklander 916*c689edbbSJens Wiklander tree = event.widget 917*c689edbbSJens Wiklander return hasattr(tree, "identify_region") and \ 918*c689edbbSJens Wiklander tree.identify_region(event.x, event.y) in ("heading", "separator") 919*c689edbbSJens Wiklander 920*c689edbbSJens Wiklander 921*c689edbbSJens Wiklanderdef _tree_enter(event): 922*c689edbbSJens Wiklander # Enter press or double-click within the Kconfig treeview. Prefer to 923*c689edbbSJens Wiklander # open/close/enter menus, but toggle the value if that's not possible. 924*c689edbbSJens Wiklander 925*c689edbbSJens Wiklander tree = event.widget 926*c689edbbSJens Wiklander sel = tree.focus() 927*c689edbbSJens Wiklander if sel: 928*c689edbbSJens Wiklander node = _id_to_node[sel] 929*c689edbbSJens Wiklander 930*c689edbbSJens Wiklander if tree.get_children(sel): 931*c689edbbSJens Wiklander _tree_toggle_open(sel) 932*c689edbbSJens Wiklander elif _single_menu_mode_menu(node, tree): 933*c689edbbSJens Wiklander _enter_menu_and_select_first(node) 934*c689edbbSJens Wiklander else: 935*c689edbbSJens Wiklander _change_node(node, tree.winfo_toplevel()) 936*c689edbbSJens Wiklander 937*c689edbbSJens Wiklander return "break" 938*c689edbbSJens Wiklander 939*c689edbbSJens Wiklander 940*c689edbbSJens Wiklanderdef _tree_toggle(event): 941*c689edbbSJens Wiklander # Space press within the Kconfig treeview. Prefer to toggle the value, but 942*c689edbbSJens Wiklander # open/close/enter the menu if that's not possible. 943*c689edbbSJens Wiklander 944*c689edbbSJens Wiklander tree = event.widget 945*c689edbbSJens Wiklander sel = tree.focus() 946*c689edbbSJens Wiklander if sel: 947*c689edbbSJens Wiklander node = _id_to_node[sel] 948*c689edbbSJens Wiklander 949*c689edbbSJens Wiklander if _changeable(node): 950*c689edbbSJens Wiklander _change_node(node, tree.winfo_toplevel()) 951*c689edbbSJens Wiklander elif _single_menu_mode_menu(node, tree): 952*c689edbbSJens Wiklander _enter_menu_and_select_first(node) 953*c689edbbSJens Wiklander elif tree.get_children(sel): 954*c689edbbSJens Wiklander _tree_toggle_open(sel) 955*c689edbbSJens Wiklander 956*c689edbbSJens Wiklander return "break" 957*c689edbbSJens Wiklander 958*c689edbbSJens Wiklander 959*c689edbbSJens Wiklanderdef _tree_left_key(_): 960*c689edbbSJens Wiklander # Left arrow key press within the Kconfig treeview 961*c689edbbSJens Wiklander 962*c689edbbSJens Wiklander if _single_menu: 963*c689edbbSJens Wiklander # Leave the current menu in single-menu mode 964*c689edbbSJens Wiklander _leave_menu() 965*c689edbbSJens Wiklander return "break" 966*c689edbbSJens Wiklander 967*c689edbbSJens Wiklander # Otherwise, default action 968*c689edbbSJens Wiklander 969*c689edbbSJens Wiklander 970*c689edbbSJens Wiklanderdef _tree_right_key(_): 971*c689edbbSJens Wiklander # Right arrow key press within the Kconfig treeview 972*c689edbbSJens Wiklander 973*c689edbbSJens Wiklander sel = _tree.focus() 974*c689edbbSJens Wiklander if sel: 975*c689edbbSJens Wiklander node = _id_to_node[sel] 976*c689edbbSJens Wiklander # If the node can be entered in single-menu mode, do it 977*c689edbbSJens Wiklander if _single_menu_mode_menu(node, _tree): 978*c689edbbSJens Wiklander _enter_menu_and_select_first(node) 979*c689edbbSJens Wiklander return "break" 980*c689edbbSJens Wiklander 981*c689edbbSJens Wiklander # Otherwise, default action 982*c689edbbSJens Wiklander 983*c689edbbSJens Wiklander 984*c689edbbSJens Wiklanderdef _single_menu_mode_menu(node, tree): 985*c689edbbSJens Wiklander # Returns True if single-menu mode is on and 'node' is an (interface) 986*c689edbbSJens Wiklander # menu that can be entered 987*c689edbbSJens Wiklander 988*c689edbbSJens Wiklander return _single_menu and tree is _tree and node.is_menuconfig and \ 989*c689edbbSJens Wiklander _shown_menu_nodes(node) 990*c689edbbSJens Wiklander 991*c689edbbSJens Wiklander 992*c689edbbSJens Wiklanderdef _changeable(node): 993*c689edbbSJens Wiklander # Returns True if 'node' is a Symbol/Choice whose value can be changed 994*c689edbbSJens Wiklander 995*c689edbbSJens Wiklander sc = node.item 996*c689edbbSJens Wiklander 997*c689edbbSJens Wiklander if not isinstance(sc, (Symbol, Choice)): 998*c689edbbSJens Wiklander return False 999*c689edbbSJens Wiklander 1000*c689edbbSJens Wiklander # This will hit for invisible symbols, which appear in show-all mode and 1001*c689edbbSJens Wiklander # when an invisible symbol has visible children (which can happen e.g. for 1002*c689edbbSJens Wiklander # symbols with optional prompts) 1003*c689edbbSJens Wiklander if not (node.prompt and expr_value(node.prompt[1])): 1004*c689edbbSJens Wiklander return False 1005*c689edbbSJens Wiklander 1006*c689edbbSJens Wiklander return sc.orig_type in (STRING, INT, HEX) or len(sc.assignable) > 1 \ 1007*c689edbbSJens Wiklander or _is_y_mode_choice_sym(sc) 1008*c689edbbSJens Wiklander 1009*c689edbbSJens Wiklander 1010*c689edbbSJens Wiklanderdef _tree_toggle_open(item): 1011*c689edbbSJens Wiklander # Opens/closes the Treeview item 'item' 1012*c689edbbSJens Wiklander 1013*c689edbbSJens Wiklander if _tree.item(item, "open"): 1014*c689edbbSJens Wiklander _tree.item(item, open=False) 1015*c689edbbSJens Wiklander else: 1016*c689edbbSJens Wiklander node = _id_to_node[item] 1017*c689edbbSJens Wiklander if not isinstance(node.item, Symbol): 1018*c689edbbSJens Wiklander # Can only get here in full-tree mode 1019*c689edbbSJens Wiklander _build_full_tree(node) 1020*c689edbbSJens Wiklander _tree.item(item, open=True) 1021*c689edbbSJens Wiklander 1022*c689edbbSJens Wiklander 1023*c689edbbSJens Wiklanderdef _tree_set_val(tri_val): 1024*c689edbbSJens Wiklander def tree_set_val(event): 1025*c689edbbSJens Wiklander # n/m/y press within the Kconfig treeview 1026*c689edbbSJens Wiklander 1027*c689edbbSJens Wiklander # Sets the value of the currently selected item to 'tri_val', if that 1028*c689edbbSJens Wiklander # value can be assigned 1029*c689edbbSJens Wiklander 1030*c689edbbSJens Wiklander sel = event.widget.focus() 1031*c689edbbSJens Wiklander if sel: 1032*c689edbbSJens Wiklander sc = _id_to_node[sel].item 1033*c689edbbSJens Wiklander if isinstance(sc, (Symbol, Choice)) and tri_val in sc.assignable: 1034*c689edbbSJens Wiklander _set_val(sc, tri_val) 1035*c689edbbSJens Wiklander 1036*c689edbbSJens Wiklander return tree_set_val 1037*c689edbbSJens Wiklander 1038*c689edbbSJens Wiklander 1039*c689edbbSJens Wiklanderdef _tree_open(_): 1040*c689edbbSJens Wiklander # Lazily populates the Kconfig tree when menus are opened in full-tree mode 1041*c689edbbSJens Wiklander 1042*c689edbbSJens Wiklander if _single_menu: 1043*c689edbbSJens Wiklander # Work around https://core.tcl.tk/tk/tktview?name=368fa4561e 1044*c689edbbSJens Wiklander # ("ttk::treeview open/closed indicators can be toggled while hidden"). 1045*c689edbbSJens Wiklander # Clicking on the hidden indicator will call _build_full_tree() in 1046*c689edbbSJens Wiklander # single-menu mode otherwise. 1047*c689edbbSJens Wiklander return 1048*c689edbbSJens Wiklander 1049*c689edbbSJens Wiklander node = _id_to_node[_tree.focus()] 1050*c689edbbSJens Wiklander # _shown_full_nodes() includes nodes from menus rooted at symbols, so we 1051*c689edbbSJens Wiklander # only need to check "real" menus and choices here 1052*c689edbbSJens Wiklander if not isinstance(node.item, Symbol): 1053*c689edbbSJens Wiklander _build_full_tree(node) 1054*c689edbbSJens Wiklander 1055*c689edbbSJens Wiklander 1056*c689edbbSJens Wiklanderdef _update_menu_path(_): 1057*c689edbbSJens Wiklander # Updates the displayed menu path when nodes are selected in the Kconfig 1058*c689edbbSJens Wiklander # treeview 1059*c689edbbSJens Wiklander 1060*c689edbbSJens Wiklander sel = _tree.selection() 1061*c689edbbSJens Wiklander _menupath["text"] = _menu_path_info(_id_to_node[sel[0]]) if sel else "" 1062*c689edbbSJens Wiklander 1063*c689edbbSJens Wiklander 1064*c689edbbSJens Wiklanderdef _item_row(item): 1065*c689edbbSJens Wiklander # Returns the row number 'item' appears on within the Kconfig treeview, 1066*c689edbbSJens Wiklander # starting from the top of the tree. Used to preserve scrolling. 1067*c689edbbSJens Wiklander # 1068*c689edbbSJens Wiklander # ttkTreeview.c in the Tk sources defines a RowNumber() function that does 1069*c689edbbSJens Wiklander # the same thing, but it's not exposed. 1070*c689edbbSJens Wiklander 1071*c689edbbSJens Wiklander row = 0 1072*c689edbbSJens Wiklander 1073*c689edbbSJens Wiklander while True: 1074*c689edbbSJens Wiklander prev = _tree.prev(item) 1075*c689edbbSJens Wiklander if prev: 1076*c689edbbSJens Wiklander item = prev 1077*c689edbbSJens Wiklander row += _n_rows(item) 1078*c689edbbSJens Wiklander else: 1079*c689edbbSJens Wiklander item = _tree.parent(item) 1080*c689edbbSJens Wiklander if not item: 1081*c689edbbSJens Wiklander return row 1082*c689edbbSJens Wiklander row += 1 1083*c689edbbSJens Wiklander 1084*c689edbbSJens Wiklander 1085*c689edbbSJens Wiklanderdef _n_rows(item): 1086*c689edbbSJens Wiklander # _item_row() helper. Returns the number of rows occupied by 'item' and # 1087*c689edbbSJens Wiklander # its children. 1088*c689edbbSJens Wiklander 1089*c689edbbSJens Wiklander rows = 1 1090*c689edbbSJens Wiklander 1091*c689edbbSJens Wiklander if _tree.item(item, "open"): 1092*c689edbbSJens Wiklander for child in _tree.get_children(item): 1093*c689edbbSJens Wiklander rows += _n_rows(child) 1094*c689edbbSJens Wiklander 1095*c689edbbSJens Wiklander return rows 1096*c689edbbSJens Wiklander 1097*c689edbbSJens Wiklander 1098*c689edbbSJens Wiklanderdef _attached(item): 1099*c689edbbSJens Wiklander # Heuristic for checking if a Treeview item is attached. Doesn't seem to be 1100*c689edbbSJens Wiklander # good APIs for this. Might fail for super-obscure cases with tiny trees, 1101*c689edbbSJens Wiklander # but you'd just get a small scroll mess-up. 1102*c689edbbSJens Wiklander 1103*c689edbbSJens Wiklander return bool(_tree.next(item) or _tree.prev(item) or _tree.parent(item)) 1104*c689edbbSJens Wiklander 1105*c689edbbSJens Wiklander 1106*c689edbbSJens Wiklanderdef _change_node(node, parent): 1107*c689edbbSJens Wiklander # Toggles/changes the value of 'node'. 'parent' is the parent window 1108*c689edbbSJens Wiklander # (either the main window or the jump-to dialog), in case we need to pop up 1109*c689edbbSJens Wiklander # a dialog. 1110*c689edbbSJens Wiklander 1111*c689edbbSJens Wiklander if not _changeable(node): 1112*c689edbbSJens Wiklander return 1113*c689edbbSJens Wiklander 1114*c689edbbSJens Wiklander # sc = symbol/choice 1115*c689edbbSJens Wiklander sc = node.item 1116*c689edbbSJens Wiklander 1117*c689edbbSJens Wiklander if sc.type in (INT, HEX, STRING): 1118*c689edbbSJens Wiklander s = _set_val_dialog(node, parent) 1119*c689edbbSJens Wiklander 1120*c689edbbSJens Wiklander # Tkinter can return 'unicode' strings on Python 2, which Kconfiglib 1121*c689edbbSJens Wiklander # can't deal with. UTF-8-encode the string to work around it. 1122*c689edbbSJens Wiklander if _PY2 and isinstance(s, unicode): 1123*c689edbbSJens Wiklander s = s.encode("utf-8", "ignore") 1124*c689edbbSJens Wiklander 1125*c689edbbSJens Wiklander if s is not None: 1126*c689edbbSJens Wiklander _set_val(sc, s) 1127*c689edbbSJens Wiklander 1128*c689edbbSJens Wiklander elif len(sc.assignable) == 1: 1129*c689edbbSJens Wiklander # Handles choice symbols for choices in y mode, which are a special 1130*c689edbbSJens Wiklander # case: .assignable can be (2,) while .tri_value is 0. 1131*c689edbbSJens Wiklander _set_val(sc, sc.assignable[0]) 1132*c689edbbSJens Wiklander 1133*c689edbbSJens Wiklander else: 1134*c689edbbSJens Wiklander # Set the symbol to the value after the current value in 1135*c689edbbSJens Wiklander # sc.assignable, with wrapping 1136*c689edbbSJens Wiklander val_index = sc.assignable.index(sc.tri_value) 1137*c689edbbSJens Wiklander _set_val(sc, sc.assignable[(val_index + 1) % len(sc.assignable)]) 1138*c689edbbSJens Wiklander 1139*c689edbbSJens Wiklander 1140*c689edbbSJens Wiklanderdef _set_val(sc, val): 1141*c689edbbSJens Wiklander # Wrapper around Symbol/Choice.set_value() for updating the menu state and 1142*c689edbbSJens Wiklander # _conf_changed 1143*c689edbbSJens Wiklander 1144*c689edbbSJens Wiklander # Use the string representation of tristate values. This makes the format 1145*c689edbbSJens Wiklander # consistent for all symbol types. 1146*c689edbbSJens Wiklander if val in TRI_TO_STR: 1147*c689edbbSJens Wiklander val = TRI_TO_STR[val] 1148*c689edbbSJens Wiklander 1149*c689edbbSJens Wiklander if val != sc.str_value: 1150*c689edbbSJens Wiklander sc.set_value(val) 1151*c689edbbSJens Wiklander _set_conf_changed(True) 1152*c689edbbSJens Wiklander 1153*c689edbbSJens Wiklander # Update the tree and try to preserve the scroll. Do a cheaper variant 1154*c689edbbSJens Wiklander # than in the show-all case, that might mess up the scroll slightly in 1155*c689edbbSJens Wiklander # rare cases, but is fast and flicker-free. 1156*c689edbbSJens Wiklander 1157*c689edbbSJens Wiklander stayput = _loc_ref_item() # Item to preserve scroll for 1158*c689edbbSJens Wiklander old_row = _item_row(stayput) 1159*c689edbbSJens Wiklander 1160*c689edbbSJens Wiklander _update_tree() 1161*c689edbbSJens Wiklander 1162*c689edbbSJens Wiklander # If the reference item disappeared (can happen if the change was done 1163*c689edbbSJens Wiklander # from the jump-to dialog), then avoid messing with the scroll and hope 1164*c689edbbSJens Wiklander # for the best 1165*c689edbbSJens Wiklander if _attached(stayput): 1166*c689edbbSJens Wiklander _tree.yview_scroll(_item_row(stayput) - old_row, "units") 1167*c689edbbSJens Wiklander 1168*c689edbbSJens Wiklander if _jump_to_tree: 1169*c689edbbSJens Wiklander _update_jump_to_display() 1170*c689edbbSJens Wiklander 1171*c689edbbSJens Wiklander 1172*c689edbbSJens Wiklanderdef _set_val_dialog(node, parent): 1173*c689edbbSJens Wiklander # Pops up a dialog for setting the value of the string/int/hex 1174*c689edbbSJens Wiklander # symbol at node 'node'. 'parent' is the parent window. 1175*c689edbbSJens Wiklander 1176*c689edbbSJens Wiklander def ok(_=None): 1177*c689edbbSJens Wiklander # No 'nonlocal' in Python 2 1178*c689edbbSJens Wiklander global _entry_res 1179*c689edbbSJens Wiklander 1180*c689edbbSJens Wiklander s = entry.get() 1181*c689edbbSJens Wiklander if sym.type == HEX and not s.startswith(("0x", "0X")): 1182*c689edbbSJens Wiklander s = "0x" + s 1183*c689edbbSJens Wiklander 1184*c689edbbSJens Wiklander if _check_valid(dialog, entry, sym, s): 1185*c689edbbSJens Wiklander _entry_res = s 1186*c689edbbSJens Wiklander dialog.destroy() 1187*c689edbbSJens Wiklander 1188*c689edbbSJens Wiklander def cancel(_=None): 1189*c689edbbSJens Wiklander global _entry_res 1190*c689edbbSJens Wiklander _entry_res = None 1191*c689edbbSJens Wiklander dialog.destroy() 1192*c689edbbSJens Wiklander 1193*c689edbbSJens Wiklander sym = node.item 1194*c689edbbSJens Wiklander 1195*c689edbbSJens Wiklander dialog = Toplevel(parent) 1196*c689edbbSJens Wiklander dialog.title("Enter {} value".format(TYPE_TO_STR[sym.type])) 1197*c689edbbSJens Wiklander dialog.resizable(False, False) 1198*c689edbbSJens Wiklander dialog.transient(parent) 1199*c689edbbSJens Wiklander dialog.protocol("WM_DELETE_WINDOW", cancel) 1200*c689edbbSJens Wiklander 1201*c689edbbSJens Wiklander ttk.Label(dialog, text=node.prompt[0] + ":") \ 1202*c689edbbSJens Wiklander .grid(column=0, row=0, columnspan=2, sticky="w", padx=".3c", 1203*c689edbbSJens Wiklander pady=".2c .05c") 1204*c689edbbSJens Wiklander 1205*c689edbbSJens Wiklander entry = ttk.Entry(dialog, width=30) 1206*c689edbbSJens Wiklander # Start with the previous value in the editbox, selected 1207*c689edbbSJens Wiklander entry.insert(0, sym.str_value) 1208*c689edbbSJens Wiklander entry.selection_range(0, "end") 1209*c689edbbSJens Wiklander entry.grid(column=0, row=1, columnspan=2, sticky="ew", padx=".3c") 1210*c689edbbSJens Wiklander entry.focus_set() 1211*c689edbbSJens Wiklander 1212*c689edbbSJens Wiklander range_info = _range_info(sym) 1213*c689edbbSJens Wiklander if range_info: 1214*c689edbbSJens Wiklander ttk.Label(dialog, text=range_info) \ 1215*c689edbbSJens Wiklander .grid(column=0, row=2, columnspan=2, sticky="w", padx=".3c", 1216*c689edbbSJens Wiklander pady=".2c 0") 1217*c689edbbSJens Wiklander 1218*c689edbbSJens Wiklander ttk.Button(dialog, text="OK", command=ok) \ 1219*c689edbbSJens Wiklander .grid(column=0, row=4 if range_info else 3, sticky="e", padx=".3c", 1220*c689edbbSJens Wiklander pady=".4c") 1221*c689edbbSJens Wiklander 1222*c689edbbSJens Wiklander ttk.Button(dialog, text="Cancel", command=cancel) \ 1223*c689edbbSJens Wiklander .grid(column=1, row=4 if range_info else 3, padx="0 .3c") 1224*c689edbbSJens Wiklander 1225*c689edbbSJens Wiklander # Give all horizontal space to the grid cell with the OK button, so that 1226*c689edbbSJens Wiklander # Cancel moves to the right 1227*c689edbbSJens Wiklander dialog.columnconfigure(0, weight=1) 1228*c689edbbSJens Wiklander 1229*c689edbbSJens Wiklander _center_on_root(dialog) 1230*c689edbbSJens Wiklander 1231*c689edbbSJens Wiklander # Hack to scroll the entry so that the end of the text is shown, from 1232*c689edbbSJens Wiklander # https://stackoverflow.com/questions/29334544/why-does-tkinters-entry-xview-moveto-fail. 1233*c689edbbSJens Wiklander # Related Tk ticket: https://core.tcl.tk/tk/info/2513186fff 1234*c689edbbSJens Wiklander def scroll_entry(_): 1235*c689edbbSJens Wiklander _root.update_idletasks() 1236*c689edbbSJens Wiklander entry.unbind("<Expose>") 1237*c689edbbSJens Wiklander entry.xview_moveto(1) 1238*c689edbbSJens Wiklander entry.bind("<Expose>", scroll_entry) 1239*c689edbbSJens Wiklander 1240*c689edbbSJens Wiklander # The dialog must be visible before we can grab the input 1241*c689edbbSJens Wiklander dialog.wait_visibility() 1242*c689edbbSJens Wiklander dialog.grab_set() 1243*c689edbbSJens Wiklander 1244*c689edbbSJens Wiklander dialog.bind("<Return>", ok) 1245*c689edbbSJens Wiklander dialog.bind("<KP_Enter>", ok) 1246*c689edbbSJens Wiklander dialog.bind("<Escape>", cancel) 1247*c689edbbSJens Wiklander 1248*c689edbbSJens Wiklander # Wait for the user to be done with the dialog 1249*c689edbbSJens Wiklander parent.wait_window(dialog) 1250*c689edbbSJens Wiklander 1251*c689edbbSJens Wiklander # Regrab the input in the parent 1252*c689edbbSJens Wiklander parent.grab_set() 1253*c689edbbSJens Wiklander 1254*c689edbbSJens Wiklander return _entry_res 1255*c689edbbSJens Wiklander 1256*c689edbbSJens Wiklander 1257*c689edbbSJens Wiklanderdef _center_on_root(dialog): 1258*c689edbbSJens Wiklander # Centers 'dialog' on the root window. It often ends up at some bad place 1259*c689edbbSJens Wiklander # like the top-left corner of the screen otherwise. See the menuconfig() 1260*c689edbbSJens Wiklander # function, which has similar logic. 1261*c689edbbSJens Wiklander 1262*c689edbbSJens Wiklander dialog.withdraw() 1263*c689edbbSJens Wiklander _root.update_idletasks() 1264*c689edbbSJens Wiklander 1265*c689edbbSJens Wiklander dialog_width = dialog.winfo_reqwidth() 1266*c689edbbSJens Wiklander dialog_height = dialog.winfo_reqheight() 1267*c689edbbSJens Wiklander 1268*c689edbbSJens Wiklander screen_width = _root.winfo_screenwidth() 1269*c689edbbSJens Wiklander screen_height = _root.winfo_screenheight() 1270*c689edbbSJens Wiklander 1271*c689edbbSJens Wiklander x = _root.winfo_rootx() + (_root.winfo_width() - dialog_width)//2 1272*c689edbbSJens Wiklander y = _root.winfo_rooty() + (_root.winfo_height() - dialog_height)//2 1273*c689edbbSJens Wiklander 1274*c689edbbSJens Wiklander # Clamp so that no part of the dialog is outside the screen 1275*c689edbbSJens Wiklander if x + dialog_width > screen_width: 1276*c689edbbSJens Wiklander x = screen_width - dialog_width 1277*c689edbbSJens Wiklander elif x < 0: 1278*c689edbbSJens Wiklander x = 0 1279*c689edbbSJens Wiklander if y + dialog_height > screen_height: 1280*c689edbbSJens Wiklander y = screen_height - dialog_height 1281*c689edbbSJens Wiklander elif y < 0: 1282*c689edbbSJens Wiklander y = 0 1283*c689edbbSJens Wiklander 1284*c689edbbSJens Wiklander dialog.geometry("+{}+{}".format(x, y)) 1285*c689edbbSJens Wiklander 1286*c689edbbSJens Wiklander dialog.deiconify() 1287*c689edbbSJens Wiklander 1288*c689edbbSJens Wiklander 1289*c689edbbSJens Wiklanderdef _check_valid(dialog, entry, sym, s): 1290*c689edbbSJens Wiklander # Returns True if the string 's' is a well-formed value for 'sym'. 1291*c689edbbSJens Wiklander # Otherwise, pops up an error and returns False. 1292*c689edbbSJens Wiklander 1293*c689edbbSJens Wiklander if sym.type not in (INT, HEX): 1294*c689edbbSJens Wiklander # Anything goes for non-int/hex symbols 1295*c689edbbSJens Wiklander return True 1296*c689edbbSJens Wiklander 1297*c689edbbSJens Wiklander base = 10 if sym.type == INT else 16 1298*c689edbbSJens Wiklander try: 1299*c689edbbSJens Wiklander int(s, base) 1300*c689edbbSJens Wiklander except ValueError: 1301*c689edbbSJens Wiklander messagebox.showerror( 1302*c689edbbSJens Wiklander "Bad value", 1303*c689edbbSJens Wiklander "'{}' is a malformed {} value".format( 1304*c689edbbSJens Wiklander s, TYPE_TO_STR[sym.type]), 1305*c689edbbSJens Wiklander parent=dialog) 1306*c689edbbSJens Wiklander entry.focus_set() 1307*c689edbbSJens Wiklander return False 1308*c689edbbSJens Wiklander 1309*c689edbbSJens Wiklander for low_sym, high_sym, cond in sym.ranges: 1310*c689edbbSJens Wiklander if expr_value(cond): 1311*c689edbbSJens Wiklander low_s = low_sym.str_value 1312*c689edbbSJens Wiklander high_s = high_sym.str_value 1313*c689edbbSJens Wiklander 1314*c689edbbSJens Wiklander if not int(low_s, base) <= int(s, base) <= int(high_s, base): 1315*c689edbbSJens Wiklander messagebox.showerror( 1316*c689edbbSJens Wiklander "Value out of range", 1317*c689edbbSJens Wiklander "{} is outside the range {}-{}".format(s, low_s, high_s), 1318*c689edbbSJens Wiklander parent=dialog) 1319*c689edbbSJens Wiklander entry.focus_set() 1320*c689edbbSJens Wiklander return False 1321*c689edbbSJens Wiklander 1322*c689edbbSJens Wiklander break 1323*c689edbbSJens Wiklander 1324*c689edbbSJens Wiklander return True 1325*c689edbbSJens Wiklander 1326*c689edbbSJens Wiklander 1327*c689edbbSJens Wiklanderdef _range_info(sym): 1328*c689edbbSJens Wiklander # Returns a string with information about the valid range for the symbol 1329*c689edbbSJens Wiklander # 'sym', or None if 'sym' doesn't have a range 1330*c689edbbSJens Wiklander 1331*c689edbbSJens Wiklander if sym.type in (INT, HEX): 1332*c689edbbSJens Wiklander for low, high, cond in sym.ranges: 1333*c689edbbSJens Wiklander if expr_value(cond): 1334*c689edbbSJens Wiklander return "Range: {}-{}".format(low.str_value, high.str_value) 1335*c689edbbSJens Wiklander 1336*c689edbbSJens Wiklander return None 1337*c689edbbSJens Wiklander 1338*c689edbbSJens Wiklander 1339*c689edbbSJens Wiklanderdef _save(_=None): 1340*c689edbbSJens Wiklander # Tries to save the configuration 1341*c689edbbSJens Wiklander 1342*c689edbbSJens Wiklander if _try_save(_kconf.write_config, _conf_filename, "configuration"): 1343*c689edbbSJens Wiklander _set_conf_changed(False) 1344*c689edbbSJens Wiklander 1345*c689edbbSJens Wiklander _tree.focus_set() 1346*c689edbbSJens Wiklander 1347*c689edbbSJens Wiklander 1348*c689edbbSJens Wiklanderdef _save_as(): 1349*c689edbbSJens Wiklander # Pops up a dialog for saving the configuration to a specific location 1350*c689edbbSJens Wiklander 1351*c689edbbSJens Wiklander global _conf_filename 1352*c689edbbSJens Wiklander 1353*c689edbbSJens Wiklander filename = _conf_filename 1354*c689edbbSJens Wiklander while True: 1355*c689edbbSJens Wiklander filename = filedialog.asksaveasfilename( 1356*c689edbbSJens Wiklander title="Save configuration as", 1357*c689edbbSJens Wiklander initialdir=os.path.dirname(filename), 1358*c689edbbSJens Wiklander initialfile=os.path.basename(filename), 1359*c689edbbSJens Wiklander parent=_root) 1360*c689edbbSJens Wiklander 1361*c689edbbSJens Wiklander if not filename: 1362*c689edbbSJens Wiklander break 1363*c689edbbSJens Wiklander 1364*c689edbbSJens Wiklander if _try_save(_kconf.write_config, filename, "configuration"): 1365*c689edbbSJens Wiklander _conf_filename = filename 1366*c689edbbSJens Wiklander break 1367*c689edbbSJens Wiklander 1368*c689edbbSJens Wiklander _tree.focus_set() 1369*c689edbbSJens Wiklander 1370*c689edbbSJens Wiklander 1371*c689edbbSJens Wiklanderdef _save_minimal(): 1372*c689edbbSJens Wiklander # Pops up a dialog for saving a minimal configuration (defconfig) to a 1373*c689edbbSJens Wiklander # specific location 1374*c689edbbSJens Wiklander 1375*c689edbbSJens Wiklander global _minconf_filename 1376*c689edbbSJens Wiklander 1377*c689edbbSJens Wiklander filename = _minconf_filename 1378*c689edbbSJens Wiklander while True: 1379*c689edbbSJens Wiklander filename = filedialog.asksaveasfilename( 1380*c689edbbSJens Wiklander title="Save minimal configuration as", 1381*c689edbbSJens Wiklander initialdir=os.path.dirname(filename), 1382*c689edbbSJens Wiklander initialfile=os.path.basename(filename), 1383*c689edbbSJens Wiklander parent=_root) 1384*c689edbbSJens Wiklander 1385*c689edbbSJens Wiklander if not filename: 1386*c689edbbSJens Wiklander break 1387*c689edbbSJens Wiklander 1388*c689edbbSJens Wiklander if _try_save(_kconf.write_min_config, filename, 1389*c689edbbSJens Wiklander "minimal configuration"): 1390*c689edbbSJens Wiklander 1391*c689edbbSJens Wiklander _minconf_filename = filename 1392*c689edbbSJens Wiklander break 1393*c689edbbSJens Wiklander 1394*c689edbbSJens Wiklander _tree.focus_set() 1395*c689edbbSJens Wiklander 1396*c689edbbSJens Wiklander 1397*c689edbbSJens Wiklanderdef _open(_=None): 1398*c689edbbSJens Wiklander # Pops up a dialog for loading a configuration 1399*c689edbbSJens Wiklander 1400*c689edbbSJens Wiklander global _conf_filename 1401*c689edbbSJens Wiklander 1402*c689edbbSJens Wiklander if _conf_changed and \ 1403*c689edbbSJens Wiklander not messagebox.askokcancel( 1404*c689edbbSJens Wiklander "Unsaved changes", 1405*c689edbbSJens Wiklander "You have unsaved changes. Load new configuration anyway?"): 1406*c689edbbSJens Wiklander 1407*c689edbbSJens Wiklander return 1408*c689edbbSJens Wiklander 1409*c689edbbSJens Wiklander filename = _conf_filename 1410*c689edbbSJens Wiklander while True: 1411*c689edbbSJens Wiklander filename = filedialog.askopenfilename( 1412*c689edbbSJens Wiklander title="Open configuration", 1413*c689edbbSJens Wiklander initialdir=os.path.dirname(filename), 1414*c689edbbSJens Wiklander initialfile=os.path.basename(filename), 1415*c689edbbSJens Wiklander parent=_root) 1416*c689edbbSJens Wiklander 1417*c689edbbSJens Wiklander if not filename: 1418*c689edbbSJens Wiklander break 1419*c689edbbSJens Wiklander 1420*c689edbbSJens Wiklander if _try_load(filename): 1421*c689edbbSJens Wiklander # Maybe something fancier could be done here later to try to 1422*c689edbbSJens Wiklander # preserve the scroll 1423*c689edbbSJens Wiklander 1424*c689edbbSJens Wiklander _conf_filename = filename 1425*c689edbbSJens Wiklander _set_conf_changed(_needs_save()) 1426*c689edbbSJens Wiklander 1427*c689edbbSJens Wiklander if _single_menu and not _shown_menu_nodes(_cur_menu): 1428*c689edbbSJens Wiklander # Turn on show-all if we're in single-menu mode and would end 1429*c689edbbSJens Wiklander # up with an empty menu 1430*c689edbbSJens Wiklander _show_all_var.set(True) 1431*c689edbbSJens Wiklander 1432*c689edbbSJens Wiklander _update_tree() 1433*c689edbbSJens Wiklander 1434*c689edbbSJens Wiklander break 1435*c689edbbSJens Wiklander 1436*c689edbbSJens Wiklander _tree.focus_set() 1437*c689edbbSJens Wiklander 1438*c689edbbSJens Wiklander 1439*c689edbbSJens Wiklanderdef _toggle_showname(_): 1440*c689edbbSJens Wiklander # Toggles show-name mode on/off 1441*c689edbbSJens Wiklander 1442*c689edbbSJens Wiklander _show_name_var.set(not _show_name_var.get()) 1443*c689edbbSJens Wiklander _do_showname() 1444*c689edbbSJens Wiklander 1445*c689edbbSJens Wiklander 1446*c689edbbSJens Wiklanderdef _do_showname(): 1447*c689edbbSJens Wiklander # Updates the UI for the current show-name setting 1448*c689edbbSJens Wiklander 1449*c689edbbSJens Wiklander # Columns do not automatically shrink/expand, so we have to update 1450*c689edbbSJens Wiklander # column widths ourselves 1451*c689edbbSJens Wiklander 1452*c689edbbSJens Wiklander tree_width = _tree.winfo_width() 1453*c689edbbSJens Wiklander 1454*c689edbbSJens Wiklander if _show_name_var.get(): 1455*c689edbbSJens Wiklander _tree["displaycolumns"] = ("name",) 1456*c689edbbSJens Wiklander _tree["show"] = "tree headings" 1457*c689edbbSJens Wiklander name_width = tree_width//3 1458*c689edbbSJens Wiklander _tree.column("#0", width=max(tree_width - name_width, 1)) 1459*c689edbbSJens Wiklander _tree.column("name", width=name_width) 1460*c689edbbSJens Wiklander else: 1461*c689edbbSJens Wiklander _tree["displaycolumns"] = () 1462*c689edbbSJens Wiklander _tree["show"] = "tree" 1463*c689edbbSJens Wiklander _tree.column("#0", width=tree_width) 1464*c689edbbSJens Wiklander 1465*c689edbbSJens Wiklander _tree.focus_set() 1466*c689edbbSJens Wiklander 1467*c689edbbSJens Wiklander 1468*c689edbbSJens Wiklanderdef _toggle_showall(_): 1469*c689edbbSJens Wiklander # Toggles show-all mode on/off 1470*c689edbbSJens Wiklander 1471*c689edbbSJens Wiklander _show_all_var.set(not _show_all) 1472*c689edbbSJens Wiklander _do_showall() 1473*c689edbbSJens Wiklander 1474*c689edbbSJens Wiklander 1475*c689edbbSJens Wiklanderdef _do_showall(): 1476*c689edbbSJens Wiklander # Updates the UI for the current show-all setting 1477*c689edbbSJens Wiklander 1478*c689edbbSJens Wiklander # Don't allow turning off show-all if we'd end up with no visible nodes 1479*c689edbbSJens Wiklander if _nothing_shown(): 1480*c689edbbSJens Wiklander _show_all_var.set(True) 1481*c689edbbSJens Wiklander return 1482*c689edbbSJens Wiklander 1483*c689edbbSJens Wiklander # Save scroll information. old_scroll can end up negative here, if the 1484*c689edbbSJens Wiklander # reference item isn't shown (only invisible items on the screen, and 1485*c689edbbSJens Wiklander # show-all being turned off). 1486*c689edbbSJens Wiklander 1487*c689edbbSJens Wiklander stayput = _vis_loc_ref_item() 1488*c689edbbSJens Wiklander # Probe the middle of the first row, to play it safe. identify_row(0) seems 1489*c689edbbSJens Wiklander # to return the row before the top row. 1490*c689edbbSJens Wiklander old_scroll = _item_row(stayput) - \ 1491*c689edbbSJens Wiklander _item_row(_tree.identify_row(_treeview_rowheight//2)) 1492*c689edbbSJens Wiklander 1493*c689edbbSJens Wiklander _update_tree() 1494*c689edbbSJens Wiklander 1495*c689edbbSJens Wiklander if _show_all: 1496*c689edbbSJens Wiklander # Deep magic: Unless we call update_idletasks(), the scroll adjustment 1497*c689edbbSJens Wiklander # below is restricted to the height of the old tree, instead of the 1498*c689edbbSJens Wiklander # height of the new tree. Since the tree with show-all on is guaranteed 1499*c689edbbSJens Wiklander # to be taller, and we want the maximum range, we only call it when 1500*c689edbbSJens Wiklander # turning show-all on. 1501*c689edbbSJens Wiklander # 1502*c689edbbSJens Wiklander # Strictly speaking, something similar ought to be done when changing 1503*c689edbbSJens Wiklander # symbol values, but it causes annoying flicker, and in 99% of cases 1504*c689edbbSJens Wiklander # things work anyway there (with usually minor scroll mess-ups in the 1505*c689edbbSJens Wiklander # 1% case). 1506*c689edbbSJens Wiklander _root.update_idletasks() 1507*c689edbbSJens Wiklander 1508*c689edbbSJens Wiklander # Restore scroll 1509*c689edbbSJens Wiklander _tree.yview(_item_row(stayput) - old_scroll) 1510*c689edbbSJens Wiklander 1511*c689edbbSJens Wiklander _tree.focus_set() 1512*c689edbbSJens Wiklander 1513*c689edbbSJens Wiklander 1514*c689edbbSJens Wiklanderdef _nothing_shown(): 1515*c689edbbSJens Wiklander # _do_showall() helper. Returns True if no nodes would get 1516*c689edbbSJens Wiklander # shown with the current show-all setting. Also handles the 1517*c689edbbSJens Wiklander # (obscure) case when there are no visible nodes in the entire 1518*c689edbbSJens Wiklander # tree, meaning guiconfig was automatically started in 1519*c689edbbSJens Wiklander # show-all mode, which mustn't be turned off. 1520*c689edbbSJens Wiklander 1521*c689edbbSJens Wiklander return not _shown_menu_nodes( 1522*c689edbbSJens Wiklander _cur_menu if _single_menu else _kconf.top_node) 1523*c689edbbSJens Wiklander 1524*c689edbbSJens Wiklander 1525*c689edbbSJens Wiklanderdef _toggle_tree_mode(_): 1526*c689edbbSJens Wiklander # Toggles single-menu mode on/off 1527*c689edbbSJens Wiklander 1528*c689edbbSJens Wiklander _single_menu_var.set(not _single_menu) 1529*c689edbbSJens Wiklander _do_tree_mode() 1530*c689edbbSJens Wiklander 1531*c689edbbSJens Wiklander 1532*c689edbbSJens Wiklanderdef _do_tree_mode(): 1533*c689edbbSJens Wiklander # Updates the UI for the current tree mode (full-tree or single-menu) 1534*c689edbbSJens Wiklander 1535*c689edbbSJens Wiklander loc_ref_node = _id_to_node[_loc_ref_item()] 1536*c689edbbSJens Wiklander 1537*c689edbbSJens Wiklander if not _single_menu: 1538*c689edbbSJens Wiklander # _jump_to() -> _enter_menu() already updates the tree, but 1539*c689edbbSJens Wiklander # _jump_to() -> load_parents() doesn't, because it isn't always needed. 1540*c689edbbSJens Wiklander # We always need to update the tree here, e.g. to add/remove "--->". 1541*c689edbbSJens Wiklander _update_tree() 1542*c689edbbSJens Wiklander 1543*c689edbbSJens Wiklander _jump_to(loc_ref_node) 1544*c689edbbSJens Wiklander _tree.focus_set() 1545*c689edbbSJens Wiklander 1546*c689edbbSJens Wiklander 1547*c689edbbSJens Wiklanderdef _enter_menu_and_select_first(menu): 1548*c689edbbSJens Wiklander # Enters the menu 'menu' and selects the first item. Used in single-menu 1549*c689edbbSJens Wiklander # mode. 1550*c689edbbSJens Wiklander 1551*c689edbbSJens Wiklander _enter_menu(menu) 1552*c689edbbSJens Wiklander _select(_tree, _tree.get_children()[0]) 1553*c689edbbSJens Wiklander 1554*c689edbbSJens Wiklander 1555*c689edbbSJens Wiklanderdef _enter_menu(menu): 1556*c689edbbSJens Wiklander # Enters the menu 'menu'. Used in single-menu mode. 1557*c689edbbSJens Wiklander 1558*c689edbbSJens Wiklander global _cur_menu 1559*c689edbbSJens Wiklander 1560*c689edbbSJens Wiklander _cur_menu = menu 1561*c689edbbSJens Wiklander _update_tree() 1562*c689edbbSJens Wiklander 1563*c689edbbSJens Wiklander _backbutton["state"] = "disabled" if menu is _kconf.top_node else "normal" 1564*c689edbbSJens Wiklander 1565*c689edbbSJens Wiklander 1566*c689edbbSJens Wiklanderdef _leave_menu(): 1567*c689edbbSJens Wiklander # Leaves the current menu. Used in single-menu mode. 1568*c689edbbSJens Wiklander 1569*c689edbbSJens Wiklander global _cur_menu 1570*c689edbbSJens Wiklander 1571*c689edbbSJens Wiklander if _cur_menu is not _kconf.top_node: 1572*c689edbbSJens Wiklander old_menu = _cur_menu 1573*c689edbbSJens Wiklander 1574*c689edbbSJens Wiklander _cur_menu = _parent_menu(_cur_menu) 1575*c689edbbSJens Wiklander _update_tree() 1576*c689edbbSJens Wiklander 1577*c689edbbSJens Wiklander _select(_tree, id(old_menu)) 1578*c689edbbSJens Wiklander 1579*c689edbbSJens Wiklander if _cur_menu is _kconf.top_node: 1580*c689edbbSJens Wiklander _backbutton["state"] = "disabled" 1581*c689edbbSJens Wiklander 1582*c689edbbSJens Wiklander _tree.focus_set() 1583*c689edbbSJens Wiklander 1584*c689edbbSJens Wiklander 1585*c689edbbSJens Wiklanderdef _select(tree, item): 1586*c689edbbSJens Wiklander # Selects, focuses, and see()s 'item' in 'tree' 1587*c689edbbSJens Wiklander 1588*c689edbbSJens Wiklander tree.selection_set(item) 1589*c689edbbSJens Wiklander tree.focus(item) 1590*c689edbbSJens Wiklander tree.see(item) 1591*c689edbbSJens Wiklander 1592*c689edbbSJens Wiklander 1593*c689edbbSJens Wiklanderdef _loc_ref_item(): 1594*c689edbbSJens Wiklander # Returns a Treeview item that can serve as a reference for the current 1595*c689edbbSJens Wiklander # scroll location. We try to make this item stay on the same row on the 1596*c689edbbSJens Wiklander # screen when updating the tree. 1597*c689edbbSJens Wiklander 1598*c689edbbSJens Wiklander # If the selected item is visible, use that 1599*c689edbbSJens Wiklander sel = _tree.selection() 1600*c689edbbSJens Wiklander if sel and _tree.bbox(sel[0]): 1601*c689edbbSJens Wiklander return sel[0] 1602*c689edbbSJens Wiklander 1603*c689edbbSJens Wiklander # Otherwise, use the middle item on the screen. If it doesn't exist, the 1604*c689edbbSJens Wiklander # tree is probably really small, so use the first item in the entire tree. 1605*c689edbbSJens Wiklander return _tree.identify_row(_tree.winfo_height()//2) or \ 1606*c689edbbSJens Wiklander _tree.get_children()[0] 1607*c689edbbSJens Wiklander 1608*c689edbbSJens Wiklander 1609*c689edbbSJens Wiklanderdef _vis_loc_ref_item(): 1610*c689edbbSJens Wiklander # Like _loc_ref_item(), but finds a visible item around the reference item. 1611*c689edbbSJens Wiklander # Used when changing show-all mode, where non-visible (red) items will 1612*c689edbbSJens Wiklander # disappear. 1613*c689edbbSJens Wiklander 1614*c689edbbSJens Wiklander item = _loc_ref_item() 1615*c689edbbSJens Wiklander 1616*c689edbbSJens Wiklander vis_before = _vis_before(item) 1617*c689edbbSJens Wiklander if vis_before and _tree.bbox(vis_before): 1618*c689edbbSJens Wiklander return vis_before 1619*c689edbbSJens Wiklander 1620*c689edbbSJens Wiklander vis_after = _vis_after(item) 1621*c689edbbSJens Wiklander if vis_after and _tree.bbox(vis_after): 1622*c689edbbSJens Wiklander return vis_after 1623*c689edbbSJens Wiklander 1624*c689edbbSJens Wiklander return vis_before or vis_after 1625*c689edbbSJens Wiklander 1626*c689edbbSJens Wiklander 1627*c689edbbSJens Wiklanderdef _vis_before(item): 1628*c689edbbSJens Wiklander # _vis_loc_ref_item() helper. Returns the first visible (not red) item, 1629*c689edbbSJens Wiklander # searching backwards from 'item'. 1630*c689edbbSJens Wiklander 1631*c689edbbSJens Wiklander while item: 1632*c689edbbSJens Wiklander if not _tree.tag_has("invisible", item): 1633*c689edbbSJens Wiklander return item 1634*c689edbbSJens Wiklander 1635*c689edbbSJens Wiklander prev = _tree.prev(item) 1636*c689edbbSJens Wiklander item = prev if prev else _tree.parent(item) 1637*c689edbbSJens Wiklander 1638*c689edbbSJens Wiklander return None 1639*c689edbbSJens Wiklander 1640*c689edbbSJens Wiklander 1641*c689edbbSJens Wiklanderdef _vis_after(item): 1642*c689edbbSJens Wiklander # _vis_loc_ref_item() helper. Returns the first visible (not red) item, 1643*c689edbbSJens Wiklander # searching forwards from 'item'. 1644*c689edbbSJens Wiklander 1645*c689edbbSJens Wiklander while item: 1646*c689edbbSJens Wiklander if not _tree.tag_has("invisible", item): 1647*c689edbbSJens Wiklander return item 1648*c689edbbSJens Wiklander 1649*c689edbbSJens Wiklander next = _tree.next(item) 1650*c689edbbSJens Wiklander if next: 1651*c689edbbSJens Wiklander item = next 1652*c689edbbSJens Wiklander else: 1653*c689edbbSJens Wiklander item = _tree.parent(item) 1654*c689edbbSJens Wiklander if not item: 1655*c689edbbSJens Wiklander break 1656*c689edbbSJens Wiklander item = _tree.next(item) 1657*c689edbbSJens Wiklander 1658*c689edbbSJens Wiklander return None 1659*c689edbbSJens Wiklander 1660*c689edbbSJens Wiklander 1661*c689edbbSJens Wiklanderdef _on_quit(_=None): 1662*c689edbbSJens Wiklander # Called when the user wants to exit 1663*c689edbbSJens Wiklander 1664*c689edbbSJens Wiklander if not _conf_changed: 1665*c689edbbSJens Wiklander _quit("No changes to save (for '{}')".format(_conf_filename)) 1666*c689edbbSJens Wiklander return 1667*c689edbbSJens Wiklander 1668*c689edbbSJens Wiklander while True: 1669*c689edbbSJens Wiklander ync = messagebox.askyesnocancel("Quit", "Save changes?") 1670*c689edbbSJens Wiklander if ync is None: 1671*c689edbbSJens Wiklander return 1672*c689edbbSJens Wiklander 1673*c689edbbSJens Wiklander if not ync: 1674*c689edbbSJens Wiklander _quit("Configuration ({}) was not saved".format(_conf_filename)) 1675*c689edbbSJens Wiklander return 1676*c689edbbSJens Wiklander 1677*c689edbbSJens Wiklander if _try_save(_kconf.write_config, _conf_filename, "configuration"): 1678*c689edbbSJens Wiklander # _try_save() already prints the "Configuration saved to ..." 1679*c689edbbSJens Wiklander # message 1680*c689edbbSJens Wiklander _quit() 1681*c689edbbSJens Wiklander return 1682*c689edbbSJens Wiklander 1683*c689edbbSJens Wiklander 1684*c689edbbSJens Wiklanderdef _quit(msg=None): 1685*c689edbbSJens Wiklander # Quits the application 1686*c689edbbSJens Wiklander 1687*c689edbbSJens Wiklander # Do not call sys.exit() here, in case we're being run from a script 1688*c689edbbSJens Wiklander _root.destroy() 1689*c689edbbSJens Wiklander if msg: 1690*c689edbbSJens Wiklander print(msg) 1691*c689edbbSJens Wiklander 1692*c689edbbSJens Wiklander 1693*c689edbbSJens Wiklanderdef _try_save(save_fn, filename, description): 1694*c689edbbSJens Wiklander # Tries to save a configuration file. Pops up an error and returns False on 1695*c689edbbSJens Wiklander # failure. 1696*c689edbbSJens Wiklander # 1697*c689edbbSJens Wiklander # save_fn: 1698*c689edbbSJens Wiklander # Function to call with 'filename' to save the file 1699*c689edbbSJens Wiklander # 1700*c689edbbSJens Wiklander # description: 1701*c689edbbSJens Wiklander # String describing the thing being saved 1702*c689edbbSJens Wiklander 1703*c689edbbSJens Wiklander try: 1704*c689edbbSJens Wiklander # save_fn() returns a message to print 1705*c689edbbSJens Wiklander msg = save_fn(filename) 1706*c689edbbSJens Wiklander _set_status(msg) 1707*c689edbbSJens Wiklander print(msg) 1708*c689edbbSJens Wiklander return True 1709*c689edbbSJens Wiklander except EnvironmentError as e: 1710*c689edbbSJens Wiklander messagebox.showerror( 1711*c689edbbSJens Wiklander "Error saving " + description, 1712*c689edbbSJens Wiklander "Error saving {} to '{}': {} (errno: {})" 1713*c689edbbSJens Wiklander .format(description, e.filename, e.strerror, 1714*c689edbbSJens Wiklander errno.errorcode[e.errno])) 1715*c689edbbSJens Wiklander return False 1716*c689edbbSJens Wiklander 1717*c689edbbSJens Wiklander 1718*c689edbbSJens Wiklanderdef _try_load(filename): 1719*c689edbbSJens Wiklander # Tries to load a configuration file. Pops up an error and returns False on 1720*c689edbbSJens Wiklander # failure. 1721*c689edbbSJens Wiklander # 1722*c689edbbSJens Wiklander # filename: 1723*c689edbbSJens Wiklander # Configuration file to load 1724*c689edbbSJens Wiklander 1725*c689edbbSJens Wiklander try: 1726*c689edbbSJens Wiklander msg = _kconf.load_config(filename) 1727*c689edbbSJens Wiklander _set_status(msg) 1728*c689edbbSJens Wiklander print(msg) 1729*c689edbbSJens Wiklander return True 1730*c689edbbSJens Wiklander except EnvironmentError as e: 1731*c689edbbSJens Wiklander messagebox.showerror( 1732*c689edbbSJens Wiklander "Error loading configuration", 1733*c689edbbSJens Wiklander "Error loading '{}': {} (errno: {})" 1734*c689edbbSJens Wiklander .format(filename, e.strerror, errno.errorcode[e.errno])) 1735*c689edbbSJens Wiklander return False 1736*c689edbbSJens Wiklander 1737*c689edbbSJens Wiklander 1738*c689edbbSJens Wiklanderdef _jump_to_dialog(_=None): 1739*c689edbbSJens Wiklander # Pops up a dialog for jumping directly to a particular node. Symbol values 1740*c689edbbSJens Wiklander # can also be changed within the dialog. 1741*c689edbbSJens Wiklander # 1742*c689edbbSJens Wiklander # Note: There's nothing preventing this from doing an incremental search 1743*c689edbbSJens Wiklander # like menuconfig.py does, but currently it's a bit jerky for large Kconfig 1744*c689edbbSJens Wiklander # trees, at least when inputting the beginning of the search string. We'd 1745*c689edbbSJens Wiklander # need to somehow only update the tree items that are shown in the Treeview 1746*c689edbbSJens Wiklander # to fix it. 1747*c689edbbSJens Wiklander 1748*c689edbbSJens Wiklander global _jump_to_tree 1749*c689edbbSJens Wiklander 1750*c689edbbSJens Wiklander def search(_=None): 1751*c689edbbSJens Wiklander _update_jump_to_matches(msglabel, entry.get()) 1752*c689edbbSJens Wiklander 1753*c689edbbSJens Wiklander def jump_to_selected(event=None): 1754*c689edbbSJens Wiklander # Jumps to the selected node and closes the dialog 1755*c689edbbSJens Wiklander 1756*c689edbbSJens Wiklander # Ignore double clicks on the image and in the heading area 1757*c689edbbSJens Wiklander if event and (tree.identify_element(event.x, event.y) == "image" or 1758*c689edbbSJens Wiklander _in_heading(event)): 1759*c689edbbSJens Wiklander return 1760*c689edbbSJens Wiklander 1761*c689edbbSJens Wiklander sel = tree.selection() 1762*c689edbbSJens Wiklander if not sel: 1763*c689edbbSJens Wiklander return 1764*c689edbbSJens Wiklander 1765*c689edbbSJens Wiklander node = _id_to_node[sel[0]] 1766*c689edbbSJens Wiklander 1767*c689edbbSJens Wiklander if node not in _shown_menu_nodes(_parent_menu(node)): 1768*c689edbbSJens Wiklander _show_all_var.set(True) 1769*c689edbbSJens Wiklander if not _single_menu: 1770*c689edbbSJens Wiklander # See comment in _do_tree_mode() 1771*c689edbbSJens Wiklander _update_tree() 1772*c689edbbSJens Wiklander 1773*c689edbbSJens Wiklander _jump_to(node) 1774*c689edbbSJens Wiklander 1775*c689edbbSJens Wiklander dialog.destroy() 1776*c689edbbSJens Wiklander 1777*c689edbbSJens Wiklander def tree_select(_): 1778*c689edbbSJens Wiklander jumpto_button["state"] = "normal" if tree.selection() else "disabled" 1779*c689edbbSJens Wiklander 1780*c689edbbSJens Wiklander 1781*c689edbbSJens Wiklander dialog = Toplevel(_root) 1782*c689edbbSJens Wiklander dialog.geometry("+{}+{}".format( 1783*c689edbbSJens Wiklander _root.winfo_rootx() + 50, _root.winfo_rooty() + 50)) 1784*c689edbbSJens Wiklander dialog.title("Jump to symbol/choice/menu/comment") 1785*c689edbbSJens Wiklander dialog.minsize(128, 128) # See _create_ui() 1786*c689edbbSJens Wiklander dialog.transient(_root) 1787*c689edbbSJens Wiklander 1788*c689edbbSJens Wiklander ttk.Label(dialog, text=_JUMP_TO_HELP) \ 1789*c689edbbSJens Wiklander .grid(column=0, row=0, columnspan=2, sticky="w", padx=".1c", 1790*c689edbbSJens Wiklander pady=".1c") 1791*c689edbbSJens Wiklander 1792*c689edbbSJens Wiklander entry = ttk.Entry(dialog) 1793*c689edbbSJens Wiklander entry.grid(column=0, row=1, sticky="ew", padx=".1c", pady=".1c") 1794*c689edbbSJens Wiklander entry.focus_set() 1795*c689edbbSJens Wiklander 1796*c689edbbSJens Wiklander entry.bind("<Return>", search) 1797*c689edbbSJens Wiklander entry.bind("<KP_Enter>", search) 1798*c689edbbSJens Wiklander 1799*c689edbbSJens Wiklander ttk.Button(dialog, text="Search", command=search) \ 1800*c689edbbSJens Wiklander .grid(column=1, row=1, padx="0 .1c", pady="0 .1c") 1801*c689edbbSJens Wiklander 1802*c689edbbSJens Wiklander msglabel = ttk.Label(dialog) 1803*c689edbbSJens Wiklander msglabel.grid(column=0, row=2, sticky="w", pady="0 .1c") 1804*c689edbbSJens Wiklander 1805*c689edbbSJens Wiklander panedwindow, tree = _create_kconfig_tree_and_desc(dialog) 1806*c689edbbSJens Wiklander panedwindow.grid(column=0, row=3, columnspan=2, sticky="nsew") 1807*c689edbbSJens Wiklander 1808*c689edbbSJens Wiklander # Clear tree 1809*c689edbbSJens Wiklander tree.set_children("") 1810*c689edbbSJens Wiklander 1811*c689edbbSJens Wiklander _jump_to_tree = tree 1812*c689edbbSJens Wiklander 1813*c689edbbSJens Wiklander jumpto_button = ttk.Button(dialog, text="Jump to selected item", 1814*c689edbbSJens Wiklander state="disabled", command=jump_to_selected) 1815*c689edbbSJens Wiklander jumpto_button.grid(column=0, row=4, columnspan=2, sticky="ns", pady=".1c") 1816*c689edbbSJens Wiklander 1817*c689edbbSJens Wiklander dialog.columnconfigure(0, weight=1) 1818*c689edbbSJens Wiklander # Only the pane with the Kconfig tree and description grows vertically 1819*c689edbbSJens Wiklander dialog.rowconfigure(3, weight=1) 1820*c689edbbSJens Wiklander 1821*c689edbbSJens Wiklander # See the menuconfig() function 1822*c689edbbSJens Wiklander _root.update_idletasks() 1823*c689edbbSJens Wiklander dialog.geometry(dialog.geometry()) 1824*c689edbbSJens Wiklander 1825*c689edbbSJens Wiklander # The dialog must be visible before we can grab the input 1826*c689edbbSJens Wiklander dialog.wait_visibility() 1827*c689edbbSJens Wiklander dialog.grab_set() 1828*c689edbbSJens Wiklander 1829*c689edbbSJens Wiklander tree.bind("<Double-1>", jump_to_selected) 1830*c689edbbSJens Wiklander tree.bind("<Return>", jump_to_selected) 1831*c689edbbSJens Wiklander tree.bind("<KP_Enter>", jump_to_selected) 1832*c689edbbSJens Wiklander # add=True to avoid overriding the description text update 1833*c689edbbSJens Wiklander tree.bind("<<TreeviewSelect>>", tree_select, add=True) 1834*c689edbbSJens Wiklander 1835*c689edbbSJens Wiklander dialog.bind("<Escape>", lambda _: dialog.destroy()) 1836*c689edbbSJens Wiklander 1837*c689edbbSJens Wiklander # Wait for the user to be done with the dialog 1838*c689edbbSJens Wiklander _root.wait_window(dialog) 1839*c689edbbSJens Wiklander 1840*c689edbbSJens Wiklander _jump_to_tree = None 1841*c689edbbSJens Wiklander 1842*c689edbbSJens Wiklander _tree.focus_set() 1843*c689edbbSJens Wiklander 1844*c689edbbSJens Wiklander 1845*c689edbbSJens Wiklanderdef _update_jump_to_matches(msglabel, search_string): 1846*c689edbbSJens Wiklander # Searches for nodes matching the search string and updates 1847*c689edbbSJens Wiklander # _jump_to_matches. Puts a message in 'msglabel' if there are no matches, 1848*c689edbbSJens Wiklander # or regex errors. 1849*c689edbbSJens Wiklander 1850*c689edbbSJens Wiklander global _jump_to_matches 1851*c689edbbSJens Wiklander 1852*c689edbbSJens Wiklander _jump_to_tree.selection_set(()) 1853*c689edbbSJens Wiklander 1854*c689edbbSJens Wiklander try: 1855*c689edbbSJens Wiklander # We could use re.IGNORECASE here instead of lower(), but this is 1856*c689edbbSJens Wiklander # faster for regexes like '.*debug$' (though the '.*' is redundant 1857*c689edbbSJens Wiklander # there). Those probably have bad interactions with re.search(), which 1858*c689edbbSJens Wiklander # matches anywhere in the string. 1859*c689edbbSJens Wiklander regex_searches = [re.compile(regex).search 1860*c689edbbSJens Wiklander for regex in search_string.lower().split()] 1861*c689edbbSJens Wiklander except re.error as e: 1862*c689edbbSJens Wiklander msg = "Bad regular expression" 1863*c689edbbSJens Wiklander # re.error.msg was added in Python 3.5 1864*c689edbbSJens Wiklander if hasattr(e, "msg"): 1865*c689edbbSJens Wiklander msg += ": " + e.msg 1866*c689edbbSJens Wiklander msglabel["text"] = msg 1867*c689edbbSJens Wiklander # Clear tree 1868*c689edbbSJens Wiklander _jump_to_tree.set_children("") 1869*c689edbbSJens Wiklander return 1870*c689edbbSJens Wiklander 1871*c689edbbSJens Wiklander _jump_to_matches = [] 1872*c689edbbSJens Wiklander add_match = _jump_to_matches.append 1873*c689edbbSJens Wiklander 1874*c689edbbSJens Wiklander for node in _sorted_sc_nodes(): 1875*c689edbbSJens Wiklander # Symbol/choice 1876*c689edbbSJens Wiklander sc = node.item 1877*c689edbbSJens Wiklander 1878*c689edbbSJens Wiklander for search in regex_searches: 1879*c689edbbSJens Wiklander # Both the name and the prompt might be missing, since 1880*c689edbbSJens Wiklander # we're searching both symbols and choices 1881*c689edbbSJens Wiklander 1882*c689edbbSJens Wiklander # Does the regex match either the symbol name or the 1883*c689edbbSJens Wiklander # prompt (if any)? 1884*c689edbbSJens Wiklander if not (sc.name and search(sc.name.lower()) or 1885*c689edbbSJens Wiklander node.prompt and search(node.prompt[0].lower())): 1886*c689edbbSJens Wiklander 1887*c689edbbSJens Wiklander # Give up on the first regex that doesn't match, to 1888*c689edbbSJens Wiklander # speed things up a bit when multiple regexes are 1889*c689edbbSJens Wiklander # entered 1890*c689edbbSJens Wiklander break 1891*c689edbbSJens Wiklander 1892*c689edbbSJens Wiklander else: 1893*c689edbbSJens Wiklander add_match(node) 1894*c689edbbSJens Wiklander 1895*c689edbbSJens Wiklander # Search menus and comments 1896*c689edbbSJens Wiklander 1897*c689edbbSJens Wiklander for node in _sorted_menu_comment_nodes(): 1898*c689edbbSJens Wiklander for search in regex_searches: 1899*c689edbbSJens Wiklander if not search(node.prompt[0].lower()): 1900*c689edbbSJens Wiklander break 1901*c689edbbSJens Wiklander else: 1902*c689edbbSJens Wiklander add_match(node) 1903*c689edbbSJens Wiklander 1904*c689edbbSJens Wiklander msglabel["text"] = "" if _jump_to_matches else "No matches" 1905*c689edbbSJens Wiklander 1906*c689edbbSJens Wiklander _update_jump_to_display() 1907*c689edbbSJens Wiklander 1908*c689edbbSJens Wiklander if _jump_to_matches: 1909*c689edbbSJens Wiklander item = id(_jump_to_matches[0]) 1910*c689edbbSJens Wiklander _jump_to_tree.selection_set(item) 1911*c689edbbSJens Wiklander _jump_to_tree.focus(item) 1912*c689edbbSJens Wiklander 1913*c689edbbSJens Wiklander 1914*c689edbbSJens Wiklanderdef _update_jump_to_display(): 1915*c689edbbSJens Wiklander # Updates the images and text for the items in _jump_to_matches, and sets 1916*c689edbbSJens Wiklander # them as the items of _jump_to_tree 1917*c689edbbSJens Wiklander 1918*c689edbbSJens Wiklander # Micro-optimize a bit 1919*c689edbbSJens Wiklander item = _jump_to_tree.item 1920*c689edbbSJens Wiklander id_ = id 1921*c689edbbSJens Wiklander node_str = _node_str 1922*c689edbbSJens Wiklander img_tag = _img_tag 1923*c689edbbSJens Wiklander visible = _visible 1924*c689edbbSJens Wiklander for node in _jump_to_matches: 1925*c689edbbSJens Wiklander item(id_(node), 1926*c689edbbSJens Wiklander text=node_str(node), 1927*c689edbbSJens Wiklander tags=img_tag(node) if visible(node) else 1928*c689edbbSJens Wiklander img_tag(node) + " invisible") 1929*c689edbbSJens Wiklander 1930*c689edbbSJens Wiklander _jump_to_tree.set_children("", *map(id, _jump_to_matches)) 1931*c689edbbSJens Wiklander 1932*c689edbbSJens Wiklander 1933*c689edbbSJens Wiklanderdef _jump_to(node): 1934*c689edbbSJens Wiklander # Jumps directly to 'node' and selects it 1935*c689edbbSJens Wiklander 1936*c689edbbSJens Wiklander if _single_menu: 1937*c689edbbSJens Wiklander _enter_menu(_parent_menu(node)) 1938*c689edbbSJens Wiklander else: 1939*c689edbbSJens Wiklander _load_parents(node) 1940*c689edbbSJens Wiklander 1941*c689edbbSJens Wiklander _select(_tree, id(node)) 1942*c689edbbSJens Wiklander 1943*c689edbbSJens Wiklander 1944*c689edbbSJens Wiklander# Obscure Python: We never pass a value for cached_nodes, and it keeps pointing 1945*c689edbbSJens Wiklander# to the same list. This avoids a global. 1946*c689edbbSJens Wiklanderdef _sorted_sc_nodes(cached_nodes=[]): 1947*c689edbbSJens Wiklander # Returns a sorted list of symbol and choice nodes to search. The symbol 1948*c689edbbSJens Wiklander # nodes appear first, sorted by name, and then the choice nodes, sorted by 1949*c689edbbSJens Wiklander # prompt and (secondarily) name. 1950*c689edbbSJens Wiklander 1951*c689edbbSJens Wiklander if not cached_nodes: 1952*c689edbbSJens Wiklander # Add symbol nodes 1953*c689edbbSJens Wiklander for sym in sorted(_kconf.unique_defined_syms, 1954*c689edbbSJens Wiklander key=lambda sym: sym.name): 1955*c689edbbSJens Wiklander # += is in-place for lists 1956*c689edbbSJens Wiklander cached_nodes += sym.nodes 1957*c689edbbSJens Wiklander 1958*c689edbbSJens Wiklander # Add choice nodes 1959*c689edbbSJens Wiklander 1960*c689edbbSJens Wiklander choices = sorted(_kconf.unique_choices, 1961*c689edbbSJens Wiklander key=lambda choice: choice.name or "") 1962*c689edbbSJens Wiklander 1963*c689edbbSJens Wiklander cached_nodes += sorted( 1964*c689edbbSJens Wiklander [node for choice in choices for node in choice.nodes], 1965*c689edbbSJens Wiklander key=lambda node: node.prompt[0] if node.prompt else "") 1966*c689edbbSJens Wiklander 1967*c689edbbSJens Wiklander return cached_nodes 1968*c689edbbSJens Wiklander 1969*c689edbbSJens Wiklander 1970*c689edbbSJens Wiklanderdef _sorted_menu_comment_nodes(cached_nodes=[]): 1971*c689edbbSJens Wiklander # Returns a list of menu and comment nodes to search, sorted by prompt, 1972*c689edbbSJens Wiklander # with the menus first 1973*c689edbbSJens Wiklander 1974*c689edbbSJens Wiklander if not cached_nodes: 1975*c689edbbSJens Wiklander def prompt_text(mc): 1976*c689edbbSJens Wiklander return mc.prompt[0] 1977*c689edbbSJens Wiklander 1978*c689edbbSJens Wiklander cached_nodes += sorted(_kconf.menus, key=prompt_text) 1979*c689edbbSJens Wiklander cached_nodes += sorted(_kconf.comments, key=prompt_text) 1980*c689edbbSJens Wiklander 1981*c689edbbSJens Wiklander return cached_nodes 1982*c689edbbSJens Wiklander 1983*c689edbbSJens Wiklander 1984*c689edbbSJens Wiklanderdef _load_parents(node): 1985*c689edbbSJens Wiklander # Menus are lazily populated as they're opened in full-tree mode, but 1986*c689edbbSJens Wiklander # jumping to an item needs its parent menus to be populated. This function 1987*c689edbbSJens Wiklander # populates 'node's parents. 1988*c689edbbSJens Wiklander 1989*c689edbbSJens Wiklander # Get all parents leading up to 'node', sorted with the root first 1990*c689edbbSJens Wiklander parents = [] 1991*c689edbbSJens Wiklander cur = node.parent 1992*c689edbbSJens Wiklander while cur is not _kconf.top_node: 1993*c689edbbSJens Wiklander parents.append(cur) 1994*c689edbbSJens Wiklander cur = cur.parent 1995*c689edbbSJens Wiklander parents.reverse() 1996*c689edbbSJens Wiklander 1997*c689edbbSJens Wiklander for i, parent in enumerate(parents): 1998*c689edbbSJens Wiklander if not _tree.item(id(parent), "open"): 1999*c689edbbSJens Wiklander # Found a closed menu. Populate it and all the remaining menus 2000*c689edbbSJens Wiklander # leading up to 'node'. 2001*c689edbbSJens Wiklander for parent in parents[i:]: 2002*c689edbbSJens Wiklander # We only need to populate "real" menus/choices. Implicit menus 2003*c689edbbSJens Wiklander # are populated when their parents menus are entered. 2004*c689edbbSJens Wiklander if not isinstance(parent.item, Symbol): 2005*c689edbbSJens Wiklander _build_full_tree(parent) 2006*c689edbbSJens Wiklander return 2007*c689edbbSJens Wiklander 2008*c689edbbSJens Wiklander 2009*c689edbbSJens Wiklanderdef _parent_menu(node): 2010*c689edbbSJens Wiklander # Returns the menu node of the menu that contains 'node'. In addition to 2011*c689edbbSJens Wiklander # proper 'menu's, this might also be a 'menuconfig' symbol or a 'choice'. 2012*c689edbbSJens Wiklander # "Menu" here means a menu in the interface. 2013*c689edbbSJens Wiklander 2014*c689edbbSJens Wiklander menu = node.parent 2015*c689edbbSJens Wiklander while not menu.is_menuconfig: 2016*c689edbbSJens Wiklander menu = menu.parent 2017*c689edbbSJens Wiklander return menu 2018*c689edbbSJens Wiklander 2019*c689edbbSJens Wiklander 2020*c689edbbSJens Wiklanderdef _trace_write(var, fn): 2021*c689edbbSJens Wiklander # Makes fn() be called whenever the Tkinter Variable 'var' changes value 2022*c689edbbSJens Wiklander 2023*c689edbbSJens Wiklander # trace_variable() is deprecated according to the docstring, 2024*c689edbbSJens Wiklander # which recommends trace_add() 2025*c689edbbSJens Wiklander if hasattr(var, "trace_add"): 2026*c689edbbSJens Wiklander var.trace_add("write", fn) 2027*c689edbbSJens Wiklander else: 2028*c689edbbSJens Wiklander var.trace_variable("w", fn) 2029*c689edbbSJens Wiklander 2030*c689edbbSJens Wiklander 2031*c689edbbSJens Wiklanderdef _info_str(node): 2032*c689edbbSJens Wiklander # Returns information about the menu node 'node' as a string. 2033*c689edbbSJens Wiklander # 2034*c689edbbSJens Wiklander # The helper functions are responsible for adding newlines. This allows 2035*c689edbbSJens Wiklander # them to return "" if they don't want to add any output. 2036*c689edbbSJens Wiklander 2037*c689edbbSJens Wiklander if isinstance(node.item, Symbol): 2038*c689edbbSJens Wiklander sym = node.item 2039*c689edbbSJens Wiklander 2040*c689edbbSJens Wiklander return ( 2041*c689edbbSJens Wiklander _name_info(sym) + 2042*c689edbbSJens Wiklander _help_info(sym) + 2043*c689edbbSJens Wiklander _direct_dep_info(sym) + 2044*c689edbbSJens Wiklander _defaults_info(sym) + 2045*c689edbbSJens Wiklander _select_imply_info(sym) + 2046*c689edbbSJens Wiklander _kconfig_def_info(sym) 2047*c689edbbSJens Wiklander ) 2048*c689edbbSJens Wiklander 2049*c689edbbSJens Wiklander if isinstance(node.item, Choice): 2050*c689edbbSJens Wiklander choice = node.item 2051*c689edbbSJens Wiklander 2052*c689edbbSJens Wiklander return ( 2053*c689edbbSJens Wiklander _name_info(choice) + 2054*c689edbbSJens Wiklander _help_info(choice) + 2055*c689edbbSJens Wiklander 'Mode: {}\n\n'.format(choice.str_value) + 2056*c689edbbSJens Wiklander _choice_syms_info(choice) + 2057*c689edbbSJens Wiklander _direct_dep_info(choice) + 2058*c689edbbSJens Wiklander _defaults_info(choice) + 2059*c689edbbSJens Wiklander _kconfig_def_info(choice) 2060*c689edbbSJens Wiklander ) 2061*c689edbbSJens Wiklander 2062*c689edbbSJens Wiklander # node.item in (MENU, COMMENT) 2063*c689edbbSJens Wiklander return _kconfig_def_info(node) 2064*c689edbbSJens Wiklander 2065*c689edbbSJens Wiklander 2066*c689edbbSJens Wiklanderdef _name_info(sc): 2067*c689edbbSJens Wiklander # Returns a string with the name of the symbol/choice. Choices are shown as 2068*c689edbbSJens Wiklander # <choice (name if any)>. 2069*c689edbbSJens Wiklander 2070*c689edbbSJens Wiklander return (sc.name if sc.name else standard_sc_expr_str(sc)) + "\n\n" 2071*c689edbbSJens Wiklander 2072*c689edbbSJens Wiklander 2073*c689edbbSJens Wiklanderdef _value_info(sym): 2074*c689edbbSJens Wiklander # Returns a string showing 'sym's value 2075*c689edbbSJens Wiklander 2076*c689edbbSJens Wiklander # Only put quotes around the value for string symbols 2077*c689edbbSJens Wiklander return "Value: {}\n".format( 2078*c689edbbSJens Wiklander '"{}"'.format(sym.str_value) 2079*c689edbbSJens Wiklander if sym.orig_type == STRING 2080*c689edbbSJens Wiklander else sym.str_value) 2081*c689edbbSJens Wiklander 2082*c689edbbSJens Wiklander 2083*c689edbbSJens Wiklanderdef _choice_syms_info(choice): 2084*c689edbbSJens Wiklander # Returns a string listing the choice symbols in 'choice'. Adds 2085*c689edbbSJens Wiklander # "(selected)" next to the selected one. 2086*c689edbbSJens Wiklander 2087*c689edbbSJens Wiklander s = "Choice symbols:\n" 2088*c689edbbSJens Wiklander 2089*c689edbbSJens Wiklander for sym in choice.syms: 2090*c689edbbSJens Wiklander s += " - " + sym.name 2091*c689edbbSJens Wiklander if sym is choice.selection: 2092*c689edbbSJens Wiklander s += " (selected)" 2093*c689edbbSJens Wiklander s += "\n" 2094*c689edbbSJens Wiklander 2095*c689edbbSJens Wiklander return s + "\n" 2096*c689edbbSJens Wiklander 2097*c689edbbSJens Wiklander 2098*c689edbbSJens Wiklanderdef _help_info(sc): 2099*c689edbbSJens Wiklander # Returns a string with the help text(s) of 'sc' (Symbol or Choice). 2100*c689edbbSJens Wiklander # Symbols and choices defined in multiple locations can have multiple help 2101*c689edbbSJens Wiklander # texts. 2102*c689edbbSJens Wiklander 2103*c689edbbSJens Wiklander s = "" 2104*c689edbbSJens Wiklander 2105*c689edbbSJens Wiklander for node in sc.nodes: 2106*c689edbbSJens Wiklander if node.help is not None: 2107*c689edbbSJens Wiklander s += node.help + "\n\n" 2108*c689edbbSJens Wiklander 2109*c689edbbSJens Wiklander return s 2110*c689edbbSJens Wiklander 2111*c689edbbSJens Wiklander 2112*c689edbbSJens Wiklanderdef _direct_dep_info(sc): 2113*c689edbbSJens Wiklander # Returns a string describing the direct dependencies of 'sc' (Symbol or 2114*c689edbbSJens Wiklander # Choice). The direct dependencies are the OR of the dependencies from each 2115*c689edbbSJens Wiklander # definition location. The dependencies at each definition location come 2116*c689edbbSJens Wiklander # from 'depends on' and dependencies inherited from parent items. 2117*c689edbbSJens Wiklander 2118*c689edbbSJens Wiklander return "" if sc.direct_dep is _kconf.y else \ 2119*c689edbbSJens Wiklander 'Direct dependencies (={}):\n{}\n' \ 2120*c689edbbSJens Wiklander .format(TRI_TO_STR[expr_value(sc.direct_dep)], 2121*c689edbbSJens Wiklander _split_expr_info(sc.direct_dep, 2)) 2122*c689edbbSJens Wiklander 2123*c689edbbSJens Wiklander 2124*c689edbbSJens Wiklanderdef _defaults_info(sc): 2125*c689edbbSJens Wiklander # Returns a string describing the defaults of 'sc' (Symbol or Choice) 2126*c689edbbSJens Wiklander 2127*c689edbbSJens Wiklander if not sc.defaults: 2128*c689edbbSJens Wiklander return "" 2129*c689edbbSJens Wiklander 2130*c689edbbSJens Wiklander s = "Default" 2131*c689edbbSJens Wiklander if len(sc.defaults) > 1: 2132*c689edbbSJens Wiklander s += "s" 2133*c689edbbSJens Wiklander s += ":\n" 2134*c689edbbSJens Wiklander 2135*c689edbbSJens Wiklander for val, cond in sc.orig_defaults: 2136*c689edbbSJens Wiklander s += " - " 2137*c689edbbSJens Wiklander if isinstance(sc, Symbol): 2138*c689edbbSJens Wiklander s += _expr_str(val) 2139*c689edbbSJens Wiklander 2140*c689edbbSJens Wiklander # Skip the tristate value hint if the expression is just a single 2141*c689edbbSJens Wiklander # symbol. _expr_str() already shows its value as a string. 2142*c689edbbSJens Wiklander # 2143*c689edbbSJens Wiklander # This also avoids showing the tristate value for string/int/hex 2144*c689edbbSJens Wiklander # defaults, which wouldn't make any sense. 2145*c689edbbSJens Wiklander if isinstance(val, tuple): 2146*c689edbbSJens Wiklander s += ' (={})'.format(TRI_TO_STR[expr_value(val)]) 2147*c689edbbSJens Wiklander else: 2148*c689edbbSJens Wiklander # Don't print the value next to the symbol name for choice 2149*c689edbbSJens Wiklander # defaults, as it looks a bit confusing 2150*c689edbbSJens Wiklander s += val.name 2151*c689edbbSJens Wiklander s += "\n" 2152*c689edbbSJens Wiklander 2153*c689edbbSJens Wiklander if cond is not _kconf.y: 2154*c689edbbSJens Wiklander s += " Condition (={}):\n{}" \ 2155*c689edbbSJens Wiklander .format(TRI_TO_STR[expr_value(cond)], 2156*c689edbbSJens Wiklander _split_expr_info(cond, 4)) 2157*c689edbbSJens Wiklander 2158*c689edbbSJens Wiklander return s + "\n" 2159*c689edbbSJens Wiklander 2160*c689edbbSJens Wiklander 2161*c689edbbSJens Wiklanderdef _split_expr_info(expr, indent): 2162*c689edbbSJens Wiklander # Returns a string with 'expr' split into its top-level && or || operands, 2163*c689edbbSJens Wiklander # with one operand per line, together with the operand's value. This is 2164*c689edbbSJens Wiklander # usually enough to get something readable for long expressions. A fancier 2165*c689edbbSJens Wiklander # recursive thingy would be possible too. 2166*c689edbbSJens Wiklander # 2167*c689edbbSJens Wiklander # indent: 2168*c689edbbSJens Wiklander # Number of leading spaces to add before the split expression. 2169*c689edbbSJens Wiklander 2170*c689edbbSJens Wiklander if len(split_expr(expr, AND)) > 1: 2171*c689edbbSJens Wiklander split_op = AND 2172*c689edbbSJens Wiklander op_str = "&&" 2173*c689edbbSJens Wiklander else: 2174*c689edbbSJens Wiklander split_op = OR 2175*c689edbbSJens Wiklander op_str = "||" 2176*c689edbbSJens Wiklander 2177*c689edbbSJens Wiklander s = "" 2178*c689edbbSJens Wiklander for i, term in enumerate(split_expr(expr, split_op)): 2179*c689edbbSJens Wiklander s += "{}{} {}".format(indent*" ", 2180*c689edbbSJens Wiklander " " if i == 0 else op_str, 2181*c689edbbSJens Wiklander _expr_str(term)) 2182*c689edbbSJens Wiklander 2183*c689edbbSJens Wiklander # Don't bother showing the value hint if the expression is just a 2184*c689edbbSJens Wiklander # single symbol. _expr_str() already shows its value. 2185*c689edbbSJens Wiklander if isinstance(term, tuple): 2186*c689edbbSJens Wiklander s += " (={})".format(TRI_TO_STR[expr_value(term)]) 2187*c689edbbSJens Wiklander 2188*c689edbbSJens Wiklander s += "\n" 2189*c689edbbSJens Wiklander 2190*c689edbbSJens Wiklander return s 2191*c689edbbSJens Wiklander 2192*c689edbbSJens Wiklander 2193*c689edbbSJens Wiklanderdef _select_imply_info(sym): 2194*c689edbbSJens Wiklander # Returns a string with information about which symbols 'select' or 'imply' 2195*c689edbbSJens Wiklander # 'sym'. The selecting/implying symbols are grouped according to which 2196*c689edbbSJens Wiklander # value they select/imply 'sym' to (n/m/y). 2197*c689edbbSJens Wiklander 2198*c689edbbSJens Wiklander def sis(expr, val, title): 2199*c689edbbSJens Wiklander # sis = selects/implies 2200*c689edbbSJens Wiklander sis = [si for si in split_expr(expr, OR) if expr_value(si) == val] 2201*c689edbbSJens Wiklander if not sis: 2202*c689edbbSJens Wiklander return "" 2203*c689edbbSJens Wiklander 2204*c689edbbSJens Wiklander res = title 2205*c689edbbSJens Wiklander for si in sis: 2206*c689edbbSJens Wiklander res += " - {}\n".format(split_expr(si, AND)[0].name) 2207*c689edbbSJens Wiklander return res + "\n" 2208*c689edbbSJens Wiklander 2209*c689edbbSJens Wiklander s = "" 2210*c689edbbSJens Wiklander 2211*c689edbbSJens Wiklander if sym.rev_dep is not _kconf.n: 2212*c689edbbSJens Wiklander s += sis(sym.rev_dep, 2, 2213*c689edbbSJens Wiklander "Symbols currently y-selecting this symbol:\n") 2214*c689edbbSJens Wiklander s += sis(sym.rev_dep, 1, 2215*c689edbbSJens Wiklander "Symbols currently m-selecting this symbol:\n") 2216*c689edbbSJens Wiklander s += sis(sym.rev_dep, 0, 2217*c689edbbSJens Wiklander "Symbols currently n-selecting this symbol (no effect):\n") 2218*c689edbbSJens Wiklander 2219*c689edbbSJens Wiklander if sym.weak_rev_dep is not _kconf.n: 2220*c689edbbSJens Wiklander s += sis(sym.weak_rev_dep, 2, 2221*c689edbbSJens Wiklander "Symbols currently y-implying this symbol:\n") 2222*c689edbbSJens Wiklander s += sis(sym.weak_rev_dep, 1, 2223*c689edbbSJens Wiklander "Symbols currently m-implying this symbol:\n") 2224*c689edbbSJens Wiklander s += sis(sym.weak_rev_dep, 0, 2225*c689edbbSJens Wiklander "Symbols currently n-implying this symbol (no effect):\n") 2226*c689edbbSJens Wiklander 2227*c689edbbSJens Wiklander return s 2228*c689edbbSJens Wiklander 2229*c689edbbSJens Wiklander 2230*c689edbbSJens Wiklanderdef _kconfig_def_info(item): 2231*c689edbbSJens Wiklander # Returns a string with the definition of 'item' in Kconfig syntax, 2232*c689edbbSJens Wiklander # together with the definition location(s) and their include and menu paths 2233*c689edbbSJens Wiklander 2234*c689edbbSJens Wiklander nodes = [item] if isinstance(item, MenuNode) else item.nodes 2235*c689edbbSJens Wiklander 2236*c689edbbSJens Wiklander s = "Kconfig definition{}, with parent deps. propagated to 'depends on'\n" \ 2237*c689edbbSJens Wiklander .format("s" if len(nodes) > 1 else "") 2238*c689edbbSJens Wiklander s += (len(s) - 1)*"=" 2239*c689edbbSJens Wiklander 2240*c689edbbSJens Wiklander for node in nodes: 2241*c689edbbSJens Wiklander s += "\n\n" \ 2242*c689edbbSJens Wiklander "At {}:{}\n" \ 2243*c689edbbSJens Wiklander "{}" \ 2244*c689edbbSJens Wiklander "Menu path: {}\n\n" \ 2245*c689edbbSJens Wiklander "{}" \ 2246*c689edbbSJens Wiklander .format(node.filename, node.linenr, 2247*c689edbbSJens Wiklander _include_path_info(node), 2248*c689edbbSJens Wiklander _menu_path_info(node), 2249*c689edbbSJens Wiklander node.custom_str(_name_and_val_str)) 2250*c689edbbSJens Wiklander 2251*c689edbbSJens Wiklander return s 2252*c689edbbSJens Wiklander 2253*c689edbbSJens Wiklander 2254*c689edbbSJens Wiklanderdef _include_path_info(node): 2255*c689edbbSJens Wiklander if not node.include_path: 2256*c689edbbSJens Wiklander # In the top-level Kconfig file 2257*c689edbbSJens Wiklander return "" 2258*c689edbbSJens Wiklander 2259*c689edbbSJens Wiklander return "Included via {}\n".format( 2260*c689edbbSJens Wiklander " -> ".join("{}:{}".format(filename, linenr) 2261*c689edbbSJens Wiklander for filename, linenr in node.include_path)) 2262*c689edbbSJens Wiklander 2263*c689edbbSJens Wiklander 2264*c689edbbSJens Wiklanderdef _menu_path_info(node): 2265*c689edbbSJens Wiklander # Returns a string describing the menu path leading up to 'node' 2266*c689edbbSJens Wiklander 2267*c689edbbSJens Wiklander path = "" 2268*c689edbbSJens Wiklander 2269*c689edbbSJens Wiklander while node.parent is not _kconf.top_node: 2270*c689edbbSJens Wiklander node = node.parent 2271*c689edbbSJens Wiklander 2272*c689edbbSJens Wiklander # Promptless choices might appear among the parents. Use 2273*c689edbbSJens Wiklander # standard_sc_expr_str() for them, so that they show up as 2274*c689edbbSJens Wiklander # '<choice (name if any)>'. 2275*c689edbbSJens Wiklander path = " -> " + (node.prompt[0] if node.prompt else 2276*c689edbbSJens Wiklander standard_sc_expr_str(node.item)) + path 2277*c689edbbSJens Wiklander 2278*c689edbbSJens Wiklander return "(Top)" + path 2279*c689edbbSJens Wiklander 2280*c689edbbSJens Wiklander 2281*c689edbbSJens Wiklanderdef _name_and_val_str(sc): 2282*c689edbbSJens Wiklander # Custom symbol/choice printer that shows symbol values after symbols 2283*c689edbbSJens Wiklander 2284*c689edbbSJens Wiklander # Show the values of non-constant (non-quoted) symbols that don't look like 2285*c689edbbSJens Wiklander # numbers. Things like 123 are actually symbol references, and only work as 2286*c689edbbSJens Wiklander # expected due to undefined symbols getting their name as their value. 2287*c689edbbSJens Wiklander # Showing the symbol value for those isn't helpful though. 2288*c689edbbSJens Wiklander if isinstance(sc, Symbol) and not sc.is_constant and not _is_num(sc.name): 2289*c689edbbSJens Wiklander if not sc.nodes: 2290*c689edbbSJens Wiklander # Undefined symbol reference 2291*c689edbbSJens Wiklander return "{}(undefined/n)".format(sc.name) 2292*c689edbbSJens Wiklander 2293*c689edbbSJens Wiklander return '{}(={})'.format(sc.name, sc.str_value) 2294*c689edbbSJens Wiklander 2295*c689edbbSJens Wiklander # For other items, use the standard format 2296*c689edbbSJens Wiklander return standard_sc_expr_str(sc) 2297*c689edbbSJens Wiklander 2298*c689edbbSJens Wiklander 2299*c689edbbSJens Wiklanderdef _expr_str(expr): 2300*c689edbbSJens Wiklander # Custom expression printer that shows symbol values 2301*c689edbbSJens Wiklander return expr_str(expr, _name_and_val_str) 2302*c689edbbSJens Wiklander 2303*c689edbbSJens Wiklander 2304*c689edbbSJens Wiklanderdef _is_num(name): 2305*c689edbbSJens Wiklander # Heuristic to see if a symbol name looks like a number, for nicer output 2306*c689edbbSJens Wiklander # when printing expressions. Things like 16 are actually symbol names, only 2307*c689edbbSJens Wiklander # they get their name as their value when the symbol is undefined. 2308*c689edbbSJens Wiklander 2309*c689edbbSJens Wiklander try: 2310*c689edbbSJens Wiklander int(name) 2311*c689edbbSJens Wiklander except ValueError: 2312*c689edbbSJens Wiklander if not name.startswith(("0x", "0X")): 2313*c689edbbSJens Wiklander return False 2314*c689edbbSJens Wiklander 2315*c689edbbSJens Wiklander try: 2316*c689edbbSJens Wiklander int(name, 16) 2317*c689edbbSJens Wiklander except ValueError: 2318*c689edbbSJens Wiklander return False 2319*c689edbbSJens Wiklander 2320*c689edbbSJens Wiklander return True 2321*c689edbbSJens Wiklander 2322*c689edbbSJens Wiklander 2323*c689edbbSJens Wiklanderif __name__ == "__main__": 2324*c689edbbSJens Wiklander _main() 2325