xref: /optee_os/scripts/kconfig/kconfiglib/guiconfig.py (revision c689edbb2550c76ae81dcecab717d82a564b2d7b)
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