123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313 |
- #!/usr/bin/env python
- # Copyright (c) 2019, Ulf Magnusson
- # SPDX-License-Identifier: ISC
- """
- Overview
- ========
- A Tkinter-based menuconfig implementation, based around a treeview control and
- a help display. The interface should feel familiar to people used to qconf
- ('make xconfig'). Compatible with both Python 2 and Python 3.
- The display can be toggled between showing the full tree and showing just a
- single menu (like menuconfig.py). Only single-menu mode distinguishes between
- symbols defined with 'config' and symbols defined with 'menuconfig'.
- A show-all mode is available that shows invisible items in red.
- Supports both mouse and keyboard controls. The following keyboard shortcuts are
- available:
- Ctrl-S : Save configuration
- Ctrl-O : Open configuration
- Ctrl-A : Toggle show-all mode
- Ctrl-N : Toggle show-name mode
- Ctrl-M : Toggle single-menu mode
- Ctrl-F, /: Open jump-to dialog
- ESC : Close
- Running
- =======
- guiconfig.py can be run either as a standalone executable or by calling the
- menuconfig() function with an existing Kconfig instance. The second option is a
- bit inflexible in that it will still load and save .config, etc.
- When run in standalone mode, the top-level Kconfig file to load can be passed
- as a command-line argument. With no argument, it defaults to "Kconfig".
- The KCONFIG_CONFIG environment variable specifies the .config file to load (if
- it exists) and save. If KCONFIG_CONFIG is unset, ".config" is used.
- When overwriting a configuration file, the old version is saved to
- <filename>.old (e.g. .config.old).
- $srctree is supported through Kconfiglib.
- """
- # Note: There's some code duplication with menuconfig.py below, especially for
- # the help text. Maybe some of it could be moved into kconfiglib.py or a shared
- # helper script, but OTOH it's pretty nice to have things standalone and
- # customizable.
- import errno
- import os
- import sys
- _PY2 = sys.version_info[0] < 3
- if _PY2:
- # Python 2
- from Tkinter import *
- import ttk
- import tkFont as font
- import tkFileDialog as filedialog
- import tkMessageBox as messagebox
- else:
- # Python 3
- from tkinter import *
- import tkinter.ttk as ttk
- import tkinter.font as font
- from tkinter import filedialog, messagebox
- from kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \
- BOOL, TRISTATE, STRING, INT, HEX, \
- AND, OR, \
- expr_str, expr_value, split_expr, \
- standard_sc_expr_str, \
- TRI_TO_STR, TYPE_TO_STR, \
- standard_kconfig, standard_config_filename
- # If True, use GIF image data embedded in this file instead of separate GIF
- # files. See _load_images().
- _USE_EMBEDDED_IMAGES = True
- # Help text for the jump-to dialog
- _JUMP_TO_HELP = """\
- Type one or more strings/regexes and press Enter to list items that match all
- of them. Python's regex flavor is used (see the 're' module). Double-clicking
- an item will jump to it. Item values can be toggled directly within the dialog.\
- """
- def _main():
- menuconfig(standard_kconfig())
- # Global variables used below:
- #
- # _root:
- # The Toplevel instance for the main window
- #
- # _tree:
- # The Treeview in the main window
- #
- # _jump_to_tree:
- # The Treeview in the jump-to dialog. None if the jump-to dialog isn't
- # open. Doubles as a flag.
- #
- # _jump_to_matches:
- # List of Nodes shown in the jump-to dialog
- #
- # _menupath:
- # The Label that shows the menu path of the selected item
- #
- # _backbutton:
- # The button shown in single-menu mode for jumping to the parent menu
- #
- # _status_label:
- # Label with status text shown at the bottom of the main window
- # ("Modified", "Saved to ...", etc.)
- #
- # _id_to_node:
- # We can't use Node objects directly as Treeview item IDs, so we use their
- # id()s instead. This dictionary maps Node id()s back to Nodes. (The keys
- # are actually str(id(node)), just to simplify lookups.)
- #
- # _cur_menu:
- # The current menu. Ignored outside single-menu mode.
- #
- # _show_all_var/_show_name_var/_single_menu_var:
- # Tkinter Variable instances bound to the corresponding checkboxes
- #
- # _show_all/_single_menu:
- # Plain Python bools that track _show_all_var and _single_menu_var, to
- # speed up and simplify things a bit
- #
- # _conf_filename:
- # File to save the configuration to
- #
- # _minconf_filename:
- # File to save minimal configurations to
- #
- # _conf_changed:
- # True if the configuration has been changed. If False, we don't bother
- # showing the save-and-quit dialog.
- #
- # We reset this to False whenever the configuration is saved.
- #
- # _*_img:
- # PhotoImage instances for images
- def menuconfig(kconf):
- """
- Launches the configuration interface, returning after the user exits.
- kconf:
- Kconfig instance to be configured
- """
- global _kconf
- global _conf_filename
- global _minconf_filename
- global _jump_to_tree
- global _cur_menu
- _kconf = kconf
- _jump_to_tree = None
- _create_id_to_node()
- _create_ui()
- # Load existing configuration and check if it's outdated
- _set_conf_changed(_load_config())
- # Filename to save configuration to
- _conf_filename = standard_config_filename()
- # Filename to save minimal configuration to
- _minconf_filename = "defconfig"
- # Current menu in single-menu mode
- _cur_menu = _kconf.top_node
- # Any visible items in the top menu?
- if not _shown_menu_nodes(kconf.top_node):
- # Nothing visible. Start in show-all mode and try again.
- _show_all_var.set(True)
- if not _shown_menu_nodes(kconf.top_node):
- # Give up and show an error. It's nice to be able to assume that
- # the tree is non-empty in the rest of the code.
- _root.wait_visibility()
- messagebox.showerror(
- "Error",
- "Empty configuration -- nothing to configure.\n\n"
- "Check that environment variables are set properly.")
- _root.destroy()
- return
- # Build the initial tree
- _update_tree()
- # Select the first item and focus the Treeview, so that keyboard controls
- # work immediately
- _select(_tree, _tree.get_children()[0])
- _tree.focus_set()
- # Make geometry information available for centering the window. This
- # indirectly creates the window, so hide it so that it's never shown at the
- # old location.
- _root.withdraw()
- _root.update_idletasks()
- # Center the window
- _root.geometry("+{}+{}".format(
- (_root.winfo_screenwidth() - _root.winfo_reqwidth())//2,
- (_root.winfo_screenheight() - _root.winfo_reqheight())//2))
- # Show it
- _root.deiconify()
- # Prevent the window from being automatically resized. Otherwise, it
- # changes size when scrollbars appear/disappear before the user has
- # manually resized it.
- _root.geometry(_root.geometry())
- _root.mainloop()
- def _load_config():
- # Loads any existing .config file. See the Kconfig.load_config() docstring.
- #
- # Returns True if .config is missing or outdated. We always prompt for
- # saving the configuration in that case.
- if not _kconf.load_config():
- # No .config
- return True
- return _needs_save()
- def _needs_save():
- # Returns True if a just-loaded .config file is outdated (would get
- # modified when saving)
- if _kconf.missing_syms:
- # Assignments to undefined symbols in the .config
- return True
- for sym in _kconf.unique_defined_syms:
- if sym.user_value is None:
- if sym.config_string:
- # Unwritten symbol
- return True
- elif sym.orig_type in (BOOL, TRISTATE):
- if sym.tri_value != sym.user_value:
- # Written bool/tristate symbol, new value
- return True
- elif sym.str_value != sym.user_value:
- # Written string/int/hex symbol, new value
- return True
- # No need to prompt for save
- return False
- def _create_id_to_node():
- global _id_to_node
- _id_to_node = {str(id(node)): node for node in _kconf.node_iter()}
- def _create_ui():
- # Creates the main window UI
- global _root
- global _tree
- # Create the root window. This initializes Tkinter and makes e.g.
- # PhotoImage available, so do it early.
- _root = Tk()
- _load_images()
- _init_misc_ui()
- _fix_treeview_issues()
- _create_top_widgets()
- # Create the pane with the Kconfig tree and description text
- panedwindow, _tree = _create_kconfig_tree_and_desc(_root)
- panedwindow.grid(column=0, row=1, sticky="nsew")
- _create_status_bar()
- _root.columnconfigure(0, weight=1)
- # Only the pane with the Kconfig tree and description grows vertically
- _root.rowconfigure(1, weight=1)
- # Start with show-name disabled
- _do_showname()
- _tree.bind("<Left>", _tree_left_key)
- _tree.bind("<Right>", _tree_right_key)
- # Note: Binding this for the jump-to tree as well would cause issues due to
- # the Tk bug mentioned in _tree_open()
- _tree.bind("<<TreeviewOpen>>", _tree_open)
- # add=True to avoid overriding the description text update
- _tree.bind("<<TreeviewSelect>>", _update_menu_path, add=True)
- _root.bind("<Control-s>", _save)
- _root.bind("<Control-o>", _open)
- _root.bind("<Control-a>", _toggle_showall)
- _root.bind("<Control-n>", _toggle_showname)
- _root.bind("<Control-m>", _toggle_tree_mode)
- _root.bind("<Control-f>", _jump_to_dialog)
- _root.bind("/", _jump_to_dialog)
- _root.bind("<Escape>", _on_quit)
- def _load_images():
- # Loads GIF images, creating the global _*_img PhotoImage variables.
- # Base64-encoded images embedded in this script are used if
- # _USE_EMBEDDED_IMAGES is True, and separate image files in the same
- # directory as the script otherwise.
- #
- # Using a global variable indirectly prevents the image from being
- # garbage-collected. Passing an image to a Tkinter function isn't enough to
- # keep it alive.
- def load_image(name, data):
- var_name = "_{}_img".format(name)
- if _USE_EMBEDDED_IMAGES:
- globals()[var_name] = PhotoImage(data=data, format="gif")
- else:
- globals()[var_name] = PhotoImage(
- file=os.path.join(os.path.dirname(__file__), name + ".gif"),
- format="gif")
- # Note: Base64 data can be put on the clipboard with
- # $ base64 -w0 foo.gif | xclip
- load_image("icon", "R0lGODlhMAAwAPEDAAAAAADQAO7u7v///yH5BAUKAAMALAAAAAAwADAAAAL/nI+gy+2Pokyv2jazuZxryQjiSJZmyXxHeLbumH6sEATvW8OLNtf5bfLZRLFITzgEipDJ4mYxYv6A0ubuqYhWk66tVTE4enHer7jcKvt0LLUw6P45lvEprT6c0+v7OBuqhYdHohcoqIbSAHc4ljhDwrh1UlgSydRCWWlp5wiYZvmSuSh4IzrqV6p4cwhkCsmY+nhK6uJ6t1mrOhuJqfu6+WYiCiwl7HtLjNSZZZis/MeM7NY3TaRKS40ooDeoiVqIultsrav92bi9c3a5KkkOsOJZpSS99m4k/0zPng4Gks9JSbB+8DIcoQfnjwpZCHv5W+ip4aQrKrB0uOikYhiMCBw1/uPoQUMBADs=")
- load_image("n_bool", "R0lGODdhEAAQAPAAAAgICP///ywAAAAAEAAQAAACIISPacHtvp5kcb5qG85hZ2+BkyiRF8BBaEqtrKkqslEAADs=")
- load_image("y_bool", "R0lGODdhEAAQAPEAAAgICADQAP///wAAACwAAAAAEAAQAAACMoSPacLtvlh4YrIYsst2cV19AvaVF9CUXBNJJoum7ymrsKuCnhiupIWjSSjAFuWhSCIKADs=")
- load_image("n_tri", "R0lGODlhEAAQAPD/AAEBAf///yH5BAUKAAIALAAAAAAQABAAAAInlI+pBrAKQnCPSUlXvFhznlkfeGwjKZhnJ65h6nrfi6h0st2QXikFADs=")
- load_image("m_tri", "R0lGODlhEAAQAPEDAAEBAeQMuv///wAAACH5BAUKAAMALAAAAAAQABAAAAI5nI+pBrAWAhPCjYhiAJQCnWmdoElHGVBoiK5M21ofXFpXRIrgiecqxkuNciZIhNOZFRNI24PhfEoLADs=")
- load_image("y_tri", "R0lGODlhEAAQAPEDAAICAgDQAP///wAAACH5BAUKAAMALAAAAAAQABAAAAI0nI+pBrAYBhDCRRUypfmergmgZ4xjMpmaw2zmxk7cCB+pWiVqp4MzDwn9FhGZ5WFjIZeGAgA7")
- load_image("m_my", "R0lGODlhEAAQAPEDAAAAAOQMuv///wAAACH5BAUKAAMALAAAAAAQABAAAAI5nIGpxiAPI2ghxFinq/ZygQhc94zgZopmOLYf67anGr+oZdp02emfV5n9MEHN5QhqICETxkABbQ4KADs=")
- load_image("y_my", "R0lGODlhEAAQAPH/AAAAAADQAAPRA////yH5BAUKAAQALAAAAAAQABAAAAM+SArcrhCMSSuIM9Q8rxxBWIXawIBkmWonupLd565Um9G1PIs59fKmzw8WnAlusBYR2SEIN6DmAmqBLBxYSAIAOw==")
- load_image("n_locked", "R0lGODlhEAAQAPABAAAAAP///yH5BAUKAAEALAAAAAAQABAAAAIgjB8AyKwN04pu0vMutpqqz4Hih4ydlnUpyl2r23pxUAAAOw==")
- load_image("m_locked", "R0lGODlhEAAQAPD/AAAAAOQMuiH5BAUKAAIALAAAAAAQABAAAAIylC8AyKwN04ohnGcqqlZmfXDWI26iInZoyiore05walolV39ftxsYHgL9QBBMBGFEFAAAOw==")
- load_image("y_locked", "R0lGODlhEAAQAPD/AAAAAADQACH5BAUKAAIALAAAAAAQABAAAAIylC8AyKzNgnlCtoDTwvZwrHydIYpQmR3KWq4uK74IOnp0HQPmnD3cOVlUIAgKsShkFAAAOw==")
- load_image("not_selected", "R0lGODlhEAAQAPD/AAAAAP///yH5BAUKAAIALAAAAAAQABAAAAIrlA2px6IBw2IpWglOvTYhzmUbGD3kNZ5QqrKn2YrqigCxZoMelU6No9gdCgA7")
- load_image("selected", "R0lGODlhEAAQAPD/AAAAAP///yH5BAUKAAIALAAAAAAQABAAAAIzlA2px6IBw2IpWglOvTah/kTZhimASJomiqonlLov1qptHTsgKSEzh9H8QI0QzNPwmRoFADs=")
- load_image("edit", "R0lGODlhEAAQAPIFAAAAAKOLAMuuEPvXCvrxvgAAAAAAAAAAACH5BAUKAAUALAAAAAAQABAAAANCWLqw/gqMBp8cszJxcwVC2FEOEIAi5kVBi3IqWZhuCGMyfdpj2e4pnK+WAshmvxeAcETWlsxPkkBtsqBMa8TIBSQAADs=")
- def _fix_treeview_issues():
- # Fixes some Treeview issues
- global _treeview_rowheight
- style = ttk.Style()
- # The treeview rowheight isn't adjusted automatically on high-DPI displays,
- # so do it ourselves. The font will probably always be TkDefaultFont, but
- # play it safe and look it up.
- _treeview_rowheight = font.Font(font=style.lookup("Treeview", "font")) \
- .metrics("linespace") + 2
- style.configure("Treeview", rowheight=_treeview_rowheight)
- # Work around regression in https://core.tcl.tk/tk/tktview?name=509cafafae,
- # which breaks tag background colors
- for option in "foreground", "background":
- # Filter out any styles starting with ("!disabled", "!selected", ...).
- # style.map() returns an empty list for missing options, so this should
- # be future-safe.
- style.map(
- "Treeview",
- **{option: [elm for elm in style.map("Treeview", query_opt=option)
- if elm[:2] != ("!disabled", "!selected")]})
- def _init_misc_ui():
- # Does misc. UI initialization, like setting the title, icon, and theme
- _root.title(_kconf.mainmenu_text)
- # iconphoto() isn't available in Python 2's Tkinter
- _root.tk.call("wm", "iconphoto", _root._w, "-default", _icon_img)
- # Reducing the width of the window to 1 pixel makes it move around, at
- # least on GNOME. Prevent weird stuff like that.
- _root.minsize(128, 128)
- _root.protocol("WM_DELETE_WINDOW", _on_quit)
- # Use the 'clam' theme on *nix if it's available. It looks nicer than the
- # 'default' theme.
- if _root.tk.call("tk", "windowingsystem") == "x11":
- style = ttk.Style()
- if "clam" in style.theme_names():
- style.theme_use("clam")
- def _create_top_widgets():
- # Creates the controls above the Kconfig tree in the main window
- global _show_all_var
- global _show_name_var
- global _single_menu_var
- global _menupath
- global _backbutton
- topframe = ttk.Frame(_root)
- topframe.grid(column=0, row=0, sticky="ew")
- ttk.Button(topframe, text="Save", command=_save) \
- .grid(column=0, row=0, sticky="ew", padx=".05c", pady=".05c")
- ttk.Button(topframe, text="Save as...", command=_save_as) \
- .grid(column=1, row=0, sticky="ew")
- ttk.Button(topframe, text="Save minimal (advanced)...",
- command=_save_minimal) \
- .grid(column=2, row=0, sticky="ew", padx=".05c")
- ttk.Button(topframe, text="Open...", command=_open) \
- .grid(column=3, row=0)
- ttk.Button(topframe, text="Jump to...", command=_jump_to_dialog) \
- .grid(column=4, row=0, padx=".05c")
- _show_name_var = BooleanVar()
- ttk.Checkbutton(topframe, text="Show name", command=_do_showname,
- variable=_show_name_var) \
- .grid(column=0, row=1, sticky="nsew", padx=".05c", pady="0 .05c",
- ipady=".2c")
- _show_all_var = BooleanVar()
- ttk.Checkbutton(topframe, text="Show all", command=_do_showall,
- variable=_show_all_var) \
- .grid(column=1, row=1, sticky="nsew", pady="0 .05c")
- # Allow the show-all and single-menu status to be queried via plain global
- # Python variables, which is faster and simpler
- def show_all_updated(*_):
- global _show_all
- _show_all = _show_all_var.get()
- _trace_write(_show_all_var, show_all_updated)
- _show_all_var.set(False)
- _single_menu_var = BooleanVar()
- ttk.Checkbutton(topframe, text="Single-menu mode", command=_do_tree_mode,
- variable=_single_menu_var) \
- .grid(column=2, row=1, sticky="nsew", padx=".05c", pady="0 .05c")
- _backbutton = ttk.Button(topframe, text="<--", command=_leave_menu,
- state="disabled")
- _backbutton.grid(column=0, row=4, sticky="nsew", padx=".05c", pady="0 .05c")
- def tree_mode_updated(*_):
- global _single_menu
- _single_menu = _single_menu_var.get()
- if _single_menu:
- _backbutton.grid()
- else:
- _backbutton.grid_remove()
- _trace_write(_single_menu_var, tree_mode_updated)
- _single_menu_var.set(False)
- # Column to the right of the buttons that the menu path extends into, so
- # that it can grow wider than the buttons
- topframe.columnconfigure(5, weight=1)
- _menupath = ttk.Label(topframe)
- _menupath.grid(column=0, row=3, columnspan=6, sticky="w", padx="0.05c",
- pady="0 .05c")
- def _create_kconfig_tree_and_desc(parent):
- # Creates a Panedwindow with a Treeview that shows Kconfig nodes and a Text
- # that shows a description of the selected node. Returns a tuple with the
- # Panedwindow and the Treeview. This code is shared between the main window
- # and the jump-to dialog.
- panedwindow = ttk.Panedwindow(parent, orient=VERTICAL)
- tree_frame, tree = _create_kconfig_tree(panedwindow)
- desc_frame, desc = _create_kconfig_desc(panedwindow)
- panedwindow.add(tree_frame, weight=1)
- panedwindow.add(desc_frame)
- def tree_select(_):
- # The Text widget does not allow editing the text in its disabled
- # state. We need to temporarily enable it.
- desc["state"] = "normal"
- sel = tree.selection()
- if not sel:
- desc.delete("1.0", "end")
- desc["state"] = "disabled"
- return
- # Text.replace() is not available in Python 2's Tkinter
- desc.delete("1.0", "end")
- desc.insert("end", _info_str(_id_to_node[sel[0]]))
- desc["state"] = "disabled"
- tree.bind("<<TreeviewSelect>>", tree_select)
- tree.bind("<1>", _tree_click)
- tree.bind("<Double-1>", _tree_double_click)
- tree.bind("<Return>", _tree_enter)
- tree.bind("<KP_Enter>", _tree_enter)
- tree.bind("<space>", _tree_toggle)
- tree.bind("n", _tree_set_val(0))
- tree.bind("m", _tree_set_val(1))
- tree.bind("y", _tree_set_val(2))
- return panedwindow, tree
- def _create_kconfig_tree(parent):
- # Creates a Treeview for showing Kconfig nodes
- frame = ttk.Frame(parent)
- tree = ttk.Treeview(frame, selectmode="browse", height=20,
- columns=("name",))
- tree.heading("#0", text="Option", anchor="w")
- tree.heading("name", text="Name", anchor="w")
- tree.tag_configure("n-bool", image=_n_bool_img)
- tree.tag_configure("y-bool", image=_y_bool_img)
- tree.tag_configure("m-tri", image=_m_tri_img)
- tree.tag_configure("n-tri", image=_n_tri_img)
- tree.tag_configure("m-tri", image=_m_tri_img)
- tree.tag_configure("y-tri", image=_y_tri_img)
- tree.tag_configure("m-my", image=_m_my_img)
- tree.tag_configure("y-my", image=_y_my_img)
- tree.tag_configure("n-locked", image=_n_locked_img)
- tree.tag_configure("m-locked", image=_m_locked_img)
- tree.tag_configure("y-locked", image=_y_locked_img)
- tree.tag_configure("not-selected", image=_not_selected_img)
- tree.tag_configure("selected", image=_selected_img)
- tree.tag_configure("edit", image=_edit_img)
- tree.tag_configure("invisible", foreground="red")
- tree.grid(column=0, row=0, sticky="nsew")
- _add_vscrollbar(frame, tree)
- frame.columnconfigure(0, weight=1)
- frame.rowconfigure(0, weight=1)
- # Create items for all menu nodes. These can be detached/moved later.
- # Micro-optimize this a bit.
- insert = tree.insert
- id_ = id
- Symbol_ = Symbol
- for node in _kconf.node_iter():
- item = node.item
- insert("", "end", iid=id_(node),
- values=item.name if item.__class__ is Symbol_ else "")
- return frame, tree
- def _create_kconfig_desc(parent):
- # Creates a Text for showing the description of the selected Kconfig node
- frame = ttk.Frame(parent)
- desc = Text(frame, height=12, wrap="none", borderwidth=0,
- state="disabled")
- desc.grid(column=0, row=0, sticky="nsew")
- # Work around not being to Ctrl-C/V text from a disabled Text widget, with a
- # tip found in https://stackoverflow.com/questions/3842155/is-there-a-way-to-make-the-tkinter-text-widget-read-only
- desc.bind("<1>", lambda _: desc.focus_set())
- _add_vscrollbar(frame, desc)
- frame.columnconfigure(0, weight=1)
- frame.rowconfigure(0, weight=1)
- return frame, desc
- def _add_vscrollbar(parent, widget):
- # Adds a vertical scrollbar to 'widget' that's only shown as needed
- vscrollbar = ttk.Scrollbar(parent, orient="vertical",
- command=widget.yview)
- vscrollbar.grid(column=1, row=0, sticky="ns")
- def yscrollcommand(first, last):
- # Only show the scrollbar when needed. 'first' and 'last' are
- # strings.
- if float(first) <= 0.0 and float(last) >= 1.0:
- vscrollbar.grid_remove()
- else:
- vscrollbar.grid()
- vscrollbar.set(first, last)
- widget["yscrollcommand"] = yscrollcommand
- def _create_status_bar():
- # Creates the status bar at the bottom of the main window
- global _status_label
- _status_label = ttk.Label(_root, anchor="e", padding="0 0 0.4c 0")
- _status_label.grid(column=0, row=3, sticky="ew")
- def _set_status(s):
- # Sets the text in the status bar to 's'
- _status_label["text"] = s
- def _set_conf_changed(changed):
- # Updates the status re. whether there are unsaved changes
- global _conf_changed
- _conf_changed = changed
- _set_status("Modified" if changed else "")
- def _update_tree():
- # Updates the Kconfig tree in the main window by first detaching all nodes
- # and then updating and reattaching them. The tree structure might have
- # changed.
- # If a selected/focused item is detached and later reattached, it stays
- # selected/focused. That can give multiple selections even though
- # selectmode=browse. Save and later restore the selection and focus as a
- # workaround.
- old_selection = _tree.selection()
- old_focus = _tree.focus()
- # Detach all tree items before re-stringing them. This is relatively fast,
- # luckily.
- _tree.detach(*_id_to_node.keys())
- if _single_menu:
- _build_menu_tree()
- else:
- _build_full_tree(_kconf.top_node)
- _tree.selection_set(old_selection)
- _tree.focus(old_focus)
- def _build_full_tree(menu):
- # Updates the tree starting from menu.list, in full-tree mode. To speed
- # things up, only open menus are updated. The menu-at-a-time logic here is
- # to deal with invisible items that can show up outside show-all mode (see
- # _shown_full_nodes()).
- for node in _shown_full_nodes(menu):
- _add_to_tree(node, _kconf.top_node)
- # _shown_full_nodes() includes nodes from menus rooted at symbols, so
- # we only need to check "real" menus/choices here
- if node.list and not isinstance(node.item, Symbol):
- if _tree.item(id(node), "open"):
- _build_full_tree(node)
- else:
- # We're just probing here, so _shown_menu_nodes() will work
- # fine, and might be a bit faster
- shown = _shown_menu_nodes(node)
- if shown:
- # Dummy element to make the open/closed toggle appear
- _tree.move(id(shown[0]), id(shown[0].parent), "end")
- def _shown_full_nodes(menu):
- # Returns the list of menu nodes shown in 'menu' (a menu node for a menu)
- # for full-tree mode. A tricky detail is that invisible items need to be
- # shown if they have visible children.
- def rec(node):
- res = []
- while node:
- if _visible(node) or _show_all:
- res.append(node)
- if node.list and isinstance(node.item, Symbol):
- # Nodes from menu created from dependencies
- res += rec(node.list)
- elif node.list and isinstance(node.item, Symbol):
- # Show invisible symbols (defined with either 'config' and
- # 'menuconfig') if they have visible children. This can happen
- # for an m/y-valued symbol with an optional prompt
- # ('prompt "foo" is COND') that is currently disabled.
- shown_children = rec(node.list)
- if shown_children:
- res.append(node)
- res += shown_children
- node = node.next
- return res
- return rec(menu.list)
- def _build_menu_tree():
- # Updates the tree in single-menu mode. See _build_full_tree() as well.
- for node in _shown_menu_nodes(_cur_menu):
- _add_to_tree(node, _cur_menu)
- def _shown_menu_nodes(menu):
- # Used for single-menu mode. Similar to _shown_full_nodes(), but doesn't
- # include children of symbols defined with 'menuconfig'.
- def rec(node):
- res = []
- while node:
- if _visible(node) or _show_all:
- res.append(node)
- if node.list and not node.is_menuconfig:
- res += rec(node.list)
- elif node.list and isinstance(node.item, Symbol):
- shown_children = rec(node.list)
- if shown_children:
- # Invisible item with visible children
- res.append(node)
- if not node.is_menuconfig:
- res += shown_children
- node = node.next
- return res
- return rec(menu.list)
- def _visible(node):
- # Returns True if the node should appear in the menu (outside show-all
- # mode)
- return node.prompt and expr_value(node.prompt[1]) and not \
- (node.item == MENU and not expr_value(node.visibility))
- def _add_to_tree(node, top):
- # Adds 'node' to the tree, at the end of its menu. We rely on going through
- # the nodes linearly to get the correct order. 'top' holds the menu that
- # corresponds to the top-level menu, and can vary in single-menu mode.
- parent = node.parent
- _tree.move(id(node), "" if parent is top else id(parent), "end")
- _tree.item(
- id(node),
- text=_node_str(node),
- # The _show_all test avoids showing invisible items in red outside
- # show-all mode, which could look confusing/broken. Invisible symbols
- # are shown outside show-all mode if an invisible symbol has visible
- # children in an implicit menu.
- tags=_img_tag(node) if _visible(node) or not _show_all else
- _img_tag(node) + " invisible")
- def _node_str(node):
- # Returns the string shown to the right of the image (if any) for the node
- if node.prompt:
- if node.item == COMMENT:
- s = "*** {} ***".format(node.prompt[0])
- else:
- s = node.prompt[0]
- if isinstance(node.item, Symbol):
- sym = node.item
- # Print "(NEW)" next to symbols without a user value (from e.g. a
- # .config), but skip it for choice symbols in choices in y mode,
- # and for symbols of UNKNOWN type (which generate a warning though)
- if sym.user_value is None and sym.type and not \
- (sym.choice and sym.choice.tri_value == 2):
- s += " (NEW)"
- elif isinstance(node.item, Symbol):
- # Symbol without prompt (can show up in show-all)
- s = "<{}>".format(node.item.name)
- else:
- # Choice without prompt. Use standard_sc_expr_str() so that it shows up
- # as '<choice (name if any)>'.
- s = standard_sc_expr_str(node.item)
- if isinstance(node.item, Symbol):
- sym = node.item
- if sym.orig_type == STRING:
- s += ": " + sym.str_value
- elif sym.orig_type in (INT, HEX):
- s = "({}) {}".format(sym.str_value, s)
- elif isinstance(node.item, Choice) and node.item.tri_value == 2:
- # Print the prompt of the selected symbol after the choice for
- # choices in y mode
- sym = node.item.selection
- if sym:
- for sym_node in sym.nodes:
- # Use the prompt used at this choice location, in case the
- # choice symbol is defined in multiple locations
- if sym_node.parent is node and sym_node.prompt:
- s += " ({})".format(sym_node.prompt[0])
- break
- else:
- # If the symbol isn't defined at this choice location, then
- # just use whatever prompt we can find for it
- for sym_node in sym.nodes:
- if sym_node.prompt:
- s += " ({})".format(sym_node.prompt[0])
- break
- # In single-menu mode, print "--->" next to nodes that have menus that can
- # potentially be entered. Print "----" if the menu is empty. We don't allow
- # those to be entered.
- if _single_menu and node.is_menuconfig:
- s += " --->" if _shown_menu_nodes(node) else " ----"
- return s
- def _img_tag(node):
- # Returns the tag for the image that should be shown next to 'node', or the
- # empty string if it shouldn't have an image
- item = node.item
- if item in (MENU, COMMENT) or not item.orig_type:
- return ""
- if item.orig_type in (STRING, INT, HEX):
- return "edit"
- # BOOL or TRISTATE
- if _is_y_mode_choice_sym(item):
- # Choice symbol in y-mode choice
- return "selected" if item.choice.selection is item else "not-selected"
- if len(item.assignable) <= 1:
- # Pinned to a single value
- return "" if isinstance(item, Choice) else item.str_value + "-locked"
- if item.type == BOOL:
- return item.str_value + "-bool"
- # item.type == TRISTATE
- if item.assignable == (1, 2):
- return item.str_value + "-my"
- return item.str_value + "-tri"
- def _is_y_mode_choice_sym(item):
- # The choice mode is an upper bound on the visibility of choice symbols, so
- # we can check the choice symbols' own visibility to see if the choice is
- # in y mode
- return isinstance(item, Symbol) and item.choice and item.visibility == 2
- def _tree_click(event):
- # Click on the Kconfig Treeview
- tree = event.widget
- if tree.identify_element(event.x, event.y) == "image":
- item = tree.identify_row(event.y)
- # Select the item before possibly popping up a dialog for
- # string/int/hex items, so that its help is visible
- _select(tree, item)
- _change_node(_id_to_node[item], tree.winfo_toplevel())
- return "break"
- def _tree_double_click(event):
- # Double-click on the Kconfig treeview
- # Do an extra check to avoid weirdness when double-clicking in the tree
- # heading area
- if not _in_heading(event):
- return _tree_enter(event)
- def _in_heading(event):
- # Returns True if 'event' took place in the tree heading
- tree = event.widget
- return hasattr(tree, "identify_region") and \
- tree.identify_region(event.x, event.y) in ("heading", "separator")
- def _tree_enter(event):
- # Enter press or double-click within the Kconfig treeview. Prefer to
- # open/close/enter menus, but toggle the value if that's not possible.
- tree = event.widget
- sel = tree.focus()
- if sel:
- node = _id_to_node[sel]
- if tree.get_children(sel):
- _tree_toggle_open(sel)
- elif _single_menu_mode_menu(node, tree):
- _enter_menu_and_select_first(node)
- else:
- _change_node(node, tree.winfo_toplevel())
- return "break"
- def _tree_toggle(event):
- # Space press within the Kconfig treeview. Prefer to toggle the value, but
- # open/close/enter the menu if that's not possible.
- tree = event.widget
- sel = tree.focus()
- if sel:
- node = _id_to_node[sel]
- if _changeable(node):
- _change_node(node, tree.winfo_toplevel())
- elif _single_menu_mode_menu(node, tree):
- _enter_menu_and_select_first(node)
- elif tree.get_children(sel):
- _tree_toggle_open(sel)
- return "break"
- def _tree_left_key(_):
- # Left arrow key press within the Kconfig treeview
- if _single_menu:
- # Leave the current menu in single-menu mode
- _leave_menu()
- return "break"
- # Otherwise, default action
- def _tree_right_key(_):
- # Right arrow key press within the Kconfig treeview
- sel = _tree.focus()
- if sel:
- node = _id_to_node[sel]
- # If the node can be entered in single-menu mode, do it
- if _single_menu_mode_menu(node, _tree):
- _enter_menu_and_select_first(node)
- return "break"
- # Otherwise, default action
- def _single_menu_mode_menu(node, tree):
- # Returns True if single-menu mode is on and 'node' is an (interface)
- # menu that can be entered
- return _single_menu and tree is _tree and node.is_menuconfig and \
- _shown_menu_nodes(node)
- def _changeable(node):
- # Returns True if 'node' is a Symbol/Choice whose value can be changed
- sc = node.item
- if not isinstance(sc, (Symbol, Choice)):
- return False
- # This will hit for invisible symbols, which appear in show-all mode and
- # when an invisible symbol has visible children (which can happen e.g. for
- # symbols with optional prompts)
- if not (node.prompt and expr_value(node.prompt[1])):
- return False
- return sc.orig_type in (STRING, INT, HEX) or len(sc.assignable) > 1 \
- or _is_y_mode_choice_sym(sc)
- def _tree_toggle_open(item):
- # Opens/closes the Treeview item 'item'
- if _tree.item(item, "open"):
- _tree.item(item, open=False)
- else:
- node = _id_to_node[item]
- if not isinstance(node.item, Symbol):
- # Can only get here in full-tree mode
- _build_full_tree(node)
- _tree.item(item, open=True)
- def _tree_set_val(tri_val):
- def tree_set_val(event):
- # n/m/y press within the Kconfig treeview
- # Sets the value of the currently selected item to 'tri_val', if that
- # value can be assigned
- sel = event.widget.focus()
- if sel:
- sc = _id_to_node[sel].item
- if isinstance(sc, (Symbol, Choice)) and tri_val in sc.assignable:
- _set_val(sc, tri_val)
- return tree_set_val
- def _tree_open(_):
- # Lazily populates the Kconfig tree when menus are opened in full-tree mode
- if _single_menu:
- # Work around https://core.tcl.tk/tk/tktview?name=368fa4561e
- # ("ttk::treeview open/closed indicators can be toggled while hidden").
- # Clicking on the hidden indicator will call _build_full_tree() in
- # single-menu mode otherwise.
- return
- node = _id_to_node[_tree.focus()]
- # _shown_full_nodes() includes nodes from menus rooted at symbols, so we
- # only need to check "real" menus and choices here
- if not isinstance(node.item, Symbol):
- _build_full_tree(node)
- def _update_menu_path(_):
- # Updates the displayed menu path when nodes are selected in the Kconfig
- # treeview
- sel = _tree.selection()
- _menupath["text"] = _menu_path_info(_id_to_node[sel[0]]) if sel else ""
- def _item_row(item):
- # Returns the row number 'item' appears on within the Kconfig treeview,
- # starting from the top of the tree. Used to preserve scrolling.
- #
- # ttkTreeview.c in the Tk sources defines a RowNumber() function that does
- # the same thing, but it's not exposed.
- row = 0
- while True:
- prev = _tree.prev(item)
- if prev:
- item = prev
- row += _n_rows(item)
- else:
- item = _tree.parent(item)
- if not item:
- return row
- row += 1
- def _n_rows(item):
- # _item_row() helper. Returns the number of rows occupied by 'item' and #
- # its children.
- rows = 1
- if _tree.item(item, "open"):
- for child in _tree.get_children(item):
- rows += _n_rows(child)
- return rows
- def _attached(item):
- # Heuristic for checking if a Treeview item is attached. Doesn't seem to be
- # good APIs for this. Might fail for super-obscure cases with tiny trees,
- # but you'd just get a small scroll mess-up.
- return bool(_tree.next(item) or _tree.prev(item) or _tree.parent(item))
- def _change_node(node, parent):
- # Toggles/changes the value of 'node'. 'parent' is the parent window
- # (either the main window or the jump-to dialog), in case we need to pop up
- # a dialog.
- if not _changeable(node):
- return
- # sc = symbol/choice
- sc = node.item
- if sc.type in (INT, HEX, STRING):
- s = _set_val_dialog(node, parent)
- # Tkinter can return 'unicode' strings on Python 2, which Kconfiglib
- # can't deal with. UTF-8-encode the string to work around it.
- if _PY2 and isinstance(s, unicode):
- s = s.encode("utf-8", "ignore")
- if s is not None:
- _set_val(sc, s)
- elif len(sc.assignable) == 1:
- # Handles choice symbols for choices in y mode, which are a special
- # case: .assignable can be (2,) while .tri_value is 0.
- _set_val(sc, sc.assignable[0])
- else:
- # Set the symbol to the value after the current value in
- # sc.assignable, with wrapping
- val_index = sc.assignable.index(sc.tri_value)
- _set_val(sc, sc.assignable[(val_index + 1) % len(sc.assignable)])
- def _set_val(sc, val):
- # Wrapper around Symbol/Choice.set_value() for updating the menu state and
- # _conf_changed
- # Use the string representation of tristate values. This makes the format
- # consistent for all symbol types.
- if val in TRI_TO_STR:
- val = TRI_TO_STR[val]
- if val != sc.str_value:
- sc.set_value(val)
- _set_conf_changed(True)
- # Update the tree and try to preserve the scroll. Do a cheaper variant
- # than in the show-all case, that might mess up the scroll slightly in
- # rare cases, but is fast and flicker-free.
- stayput = _loc_ref_item() # Item to preserve scroll for
- old_row = _item_row(stayput)
- _update_tree()
- # If the reference item disappeared (can happen if the change was done
- # from the jump-to dialog), then avoid messing with the scroll and hope
- # for the best
- if _attached(stayput):
- _tree.yview_scroll(_item_row(stayput) - old_row, "units")
- if _jump_to_tree:
- _update_jump_to_display()
- def _set_val_dialog(node, parent):
- # Pops up a dialog for setting the value of the string/int/hex
- # symbol at node 'node'. 'parent' is the parent window.
- def ok(_=None):
- # No 'nonlocal' in Python 2
- global _entry_res
- s = entry.get()
- if sym.type == HEX and not s.startswith(("0x", "0X")):
- s = "0x" + s
- if _check_valid(dialog, entry, sym, s):
- _entry_res = s
- dialog.destroy()
- def cancel(_=None):
- global _entry_res
- _entry_res = None
- dialog.destroy()
- sym = node.item
- dialog = Toplevel(parent)
- dialog.title("Enter {} value".format(TYPE_TO_STR[sym.type]))
- dialog.resizable(False, False)
- dialog.transient(parent)
- dialog.protocol("WM_DELETE_WINDOW", cancel)
- ttk.Label(dialog, text=node.prompt[0] + ":") \
- .grid(column=0, row=0, columnspan=2, sticky="w", padx=".3c",
- pady=".2c .05c")
- entry = ttk.Entry(dialog, width=30)
- # Start with the previous value in the editbox, selected
- entry.insert(0, sym.str_value)
- entry.selection_range(0, "end")
- entry.grid(column=0, row=1, columnspan=2, sticky="ew", padx=".3c")
- entry.focus_set()
- range_info = _range_info(sym)
- if range_info:
- ttk.Label(dialog, text=range_info) \
- .grid(column=0, row=2, columnspan=2, sticky="w", padx=".3c",
- pady=".2c 0")
- ttk.Button(dialog, text="OK", command=ok) \
- .grid(column=0, row=4 if range_info else 3, sticky="e", padx=".3c",
- pady=".4c")
- ttk.Button(dialog, text="Cancel", command=cancel) \
- .grid(column=1, row=4 if range_info else 3, padx="0 .3c")
- # Give all horizontal space to the grid cell with the OK button, so that
- # Cancel moves to the right
- dialog.columnconfigure(0, weight=1)
- _center_on_root(dialog)
- # Hack to scroll the entry so that the end of the text is shown, from
- # https://stackoverflow.com/questions/29334544/why-does-tkinters-entry-xview-moveto-fail.
- # Related Tk ticket: https://core.tcl.tk/tk/info/2513186fff
- def scroll_entry(_):
- _root.update_idletasks()
- entry.unbind("<Expose>")
- entry.xview_moveto(1)
- entry.bind("<Expose>", scroll_entry)
- # The dialog must be visible before we can grab the input
- dialog.wait_visibility()
- dialog.grab_set()
- dialog.bind("<Return>", ok)
- dialog.bind("<KP_Enter>", ok)
- dialog.bind("<Escape>", cancel)
- # Wait for the user to be done with the dialog
- parent.wait_window(dialog)
- # Regrab the input in the parent
- parent.grab_set()
- return _entry_res
- def _center_on_root(dialog):
- # Centers 'dialog' on the root window. It often ends up at some bad place
- # like the top-left corner of the screen otherwise. See the menuconfig()
- # function, which has similar logic.
- dialog.withdraw()
- _root.update_idletasks()
- dialog_width = dialog.winfo_reqwidth()
- dialog_height = dialog.winfo_reqheight()
- screen_width = _root.winfo_screenwidth()
- screen_height = _root.winfo_screenheight()
- x = _root.winfo_rootx() + (_root.winfo_width() - dialog_width)//2
- y = _root.winfo_rooty() + (_root.winfo_height() - dialog_height)//2
- # Clamp so that no part of the dialog is outside the screen
- if x + dialog_width > screen_width:
- x = screen_width - dialog_width
- elif x < 0:
- x = 0
- if y + dialog_height > screen_height:
- y = screen_height - dialog_height
- elif y < 0:
- y = 0
- dialog.geometry("+{}+{}".format(x, y))
- dialog.deiconify()
- def _check_valid(dialog, entry, sym, s):
- # Returns True if the string 's' is a well-formed value for 'sym'.
- # Otherwise, pops up an error and returns False.
- if sym.type not in (INT, HEX):
- # Anything goes for non-int/hex symbols
- return True
- base = 10 if sym.type == INT else 16
- try:
- int(s, base)
- except ValueError:
- messagebox.showerror(
- "Bad value",
- "'{}' is a malformed {} value".format(
- s, TYPE_TO_STR[sym.type]),
- parent=dialog)
- entry.focus_set()
- return False
- for low_sym, high_sym, cond in sym.ranges:
- if expr_value(cond):
- low = int(low_sym.str_value, base)
- val = int(s, base)
- high = int(high_sym.str_value, base)
- if not low <= val <= high:
- messagebox.showerror(
- "Value out of range",
- "{} is outside the range {}-{}".format(
- s, low_sym.str_value, high_sym.str_value),
- parent=dialog)
- entry.focus_set()
- return False
- break
- return True
- def _range_info(sym):
- # Returns a string with information about the valid range for the symbol
- # 'sym', or None if 'sym' doesn't have a range
- if sym.type in (INT, HEX):
- for low, high, cond in sym.ranges:
- if expr_value(cond):
- return "Range: {}-{}".format(low.str_value, high.str_value)
- return None
- def _save(_=None):
- # Tries to save the configuration
- if _try_save(_kconf.write_config, _conf_filename, "configuration"):
- _set_conf_changed(False)
- _set_status("Configuration saved to " + _conf_filename)
- _tree.focus_set()
- def _save_as():
- # Pops up a dialog for saving the configuration to a specific location
- global _conf_filename
- filename = _conf_filename
- while True:
- filename = filedialog.asksaveasfilename(
- title="Save configuration as",
- initialdir=os.path.dirname(filename),
- initialfile=os.path.basename(filename),
- parent=_root)
- if not filename:
- break
- if _try_save(_kconf.write_config, filename, "configuration"):
- _set_status("Configuration saved to " + filename)
- _conf_filename = filename
- break
- _tree.focus_set()
- def _save_minimal():
- # Pops up a dialog for saving a minimal configuration (defconfig) to a
- # specific location
- global _minconf_filename
- filename = _minconf_filename
- while True:
- filename = filedialog.asksaveasfilename(
- title="Save minimal configuration as",
- initialdir=os.path.dirname(filename),
- initialfile=os.path.basename(filename),
- parent=_root)
- if not filename:
- break
- if _try_save(_kconf.write_min_config, filename,
- "minimal configuration"):
- _minconf_filename = filename
- break
- _tree.focus_set()
- def _open(_=None):
- # Pops up a dialog for loading a configuration
- global _conf_filename
- if _conf_changed and \
- not messagebox.askokcancel(
- "Unsaved changes",
- "You have unsaved changes. Load new configuration anyway?"):
- return
- filename = _conf_filename
- while True:
- filename = filedialog.askopenfilename(
- title="Open configuration",
- initialdir=os.path.dirname(filename),
- initialfile=os.path.basename(filename),
- parent=_root)
- if not filename:
- break
- if _try_load(filename):
- # Maybe something fancier could be done here later to try to
- # preserve the scroll
- _conf_filename = filename
- _set_conf_changed(_needs_save())
- if _single_menu and not _shown_menu_nodes(_cur_menu):
- # Turn on show-all if we're in single-menu mode and would end
- # up with an empty menu
- _show_all_var.set(True)
- _update_tree()
- _set_status("Configuration loaded from " + filename)
- break
- _tree.focus_set()
- def _toggle_showname(_):
- # Toggles show-name mode on/off
- _show_name_var.set(not _show_name_var.get())
- _do_showname()
- def _do_showname():
- # Updates the UI for the current show-name setting
- # Columns do not automatically shrink/expand, so we have to update
- # column widths ourselves
- tree_width = _tree.winfo_width()
- if _show_name_var.get():
- _tree["displaycolumns"] = ("name",)
- _tree["show"] = "tree headings"
- name_width = tree_width//3
- _tree.column("#0", width=max(tree_width - name_width, 1))
- _tree.column("name", width=name_width)
- else:
- _tree["displaycolumns"] = ()
- _tree["show"] = "tree"
- _tree.column("#0", width=tree_width)
- _tree.focus_set()
- def _toggle_showall(_):
- # Toggles show-all mode on/off
- _show_all_var.set(not _show_all)
- _do_showall()
- def _do_showall():
- # Updates the UI for the current show-all setting
- # Don't allow turning off show-all if we're in single-menu mode and the
- # current menu would become empty
- if _single_menu and not _shown_menu_nodes(_cur_menu):
- _show_all_var.set(True)
- return
- # Save scroll information. old_scroll can end up negative here, if the
- # reference item isn't shown (only invisible items on the screen, and
- # show-all being turned off).
- stayput = _vis_loc_ref_item()
- # Probe the middle of the first row, to play it safe. identify_row(0) seems
- # to return the row before the top row.
- old_scroll = _item_row(stayput) - \
- _item_row(_tree.identify_row(_treeview_rowheight//2))
- _update_tree()
- if _show_all:
- # Deep magic: Unless we call update_idletasks(), the scroll adjustment
- # below is restricted to the height of the old tree, instead of the
- # height of the new tree. Since the tree with show-all on is guaranteed
- # to be taller, and we want the maximum range, we only call it when
- # turning show-all on.
- #
- # Strictly speaking, something similar ought to be done when changing
- # symbol values, but it causes annoying flicker, and in 99% of cases
- # things work anyway there (with usually minor scroll mess-ups in the
- # 1% case).
- _root.update_idletasks()
- # Restore scroll
- _tree.yview(_item_row(stayput) - old_scroll)
- _tree.focus_set()
- def _toggle_tree_mode(_):
- # Toggles single-menu mode on/off
- _single_menu_var.set(not _single_menu)
- _do_tree_mode()
- def _do_tree_mode():
- # Updates the UI for the current tree mode (full-tree or single-menu)
- loc_ref_node = _id_to_node[_loc_ref_item()]
- if not _single_menu:
- # _jump_to() -> _enter_menu() already updates the tree, but
- # _jump_to() -> load_parents() doesn't, because it isn't always needed.
- # We always need to update the tree here, e.g. to add/remove "--->".
- _update_tree()
- _jump_to(loc_ref_node)
- _tree.focus_set()
- def _enter_menu_and_select_first(menu):
- # Enters the menu 'menu' and selects the first item. Used in single-menu
- # mode.
- _enter_menu(menu)
- _select(_tree, _tree.get_children()[0])
- def _enter_menu(menu):
- # Enters the menu 'menu'. Used in single-menu mode.
- global _cur_menu
- _cur_menu = menu
- _update_tree()
- _backbutton["state"] = "disabled" if menu is _kconf.top_node else "normal"
- def _leave_menu():
- # Leaves the current menu. Used in single-menu mode.
- global _cur_menu
- if _cur_menu is not _kconf.top_node:
- old_menu = _cur_menu
- _cur_menu = _parent_menu(_cur_menu)
- _update_tree()
- _select(_tree, id(old_menu))
- if _cur_menu is _kconf.top_node:
- _backbutton["state"] = "disabled"
- _tree.focus_set()
- def _select(tree, item):
- # Selects, focuses, and see()s 'item' in 'tree'
- tree.selection_set(item)
- tree.focus(item)
- tree.see(item)
- def _loc_ref_item():
- # Returns a Treeview item that can serve as a reference for the current
- # scroll location. We try to make this item stay on the same row on the
- # screen when updating the tree.
- # If the selected item is visible, use that
- sel = _tree.selection()
- if sel and _tree.bbox(sel[0]):
- return sel[0]
- # Otherwise, use the middle item on the screen. If it doesn't exist, the
- # tree is probably really small, so use the first item in the entire tree.
- return _tree.identify_row(_tree.winfo_height()//2) or \
- _tree.get_children()[0]
- def _vis_loc_ref_item():
- # Like _loc_ref_item(), but finds a visible item around the reference item.
- # Used when changing show-all mode, where non-visible (red) items will
- # disappear.
- item = _loc_ref_item()
- vis_before = _vis_before(item)
- if vis_before and _tree.bbox(vis_before):
- return vis_before
- vis_after = _vis_after(item)
- if vis_after and _tree.bbox(vis_after):
- return vis_after
- return vis_before or vis_after
- def _vis_before(item):
- # _vis_loc_ref_item() helper. Returns the first visible (not red) item,
- # searching backwards from 'item'.
- while item:
- if not _tree.tag_has("invisible", item):
- return item
- prev = _tree.prev(item)
- item = prev if prev else _tree.parent(item)
- return None
- def _vis_after(item):
- # _vis_loc_ref_item() helper. Returns the first visible (not red) item,
- # searching forwards from 'item'.
- while item:
- if not _tree.tag_has("invisible", item):
- return item
- next = _tree.next(item)
- if next:
- item = next
- else:
- item = _tree.parent(item)
- if not item:
- break
- item = _tree.next(item)
- return None
- def _on_quit(_=None):
- # Called when the user wants to exit
- if not _conf_changed:
- _quit("No changes to save (for '{}')".format(_conf_filename))
- return
- while True:
- ync = messagebox.askyesnocancel("Quit", "Save changes?")
- if ync is None:
- return
- if not ync:
- _quit("Configuration ({}) was not saved".format(_conf_filename))
- return
- if _try_save(_kconf.write_config, _conf_filename, "configuration"):
- # _try_save() already prints the "Configuration saved to ..."
- # message
- _quit()
- return
- def _quit(msg=None):
- # Quits the application
- # Do not call sys.exit() here, in case we're being run from a script
- _root.destroy()
- if msg:
- print(msg)
- def _try_save(save_fn, filename, description):
- # Tries to save a configuration file. Pops up an error and returns False on
- # failure.
- #
- # save_fn:
- # Function to call with 'filename' to save the file
- #
- # description:
- # String describing the thing being saved
- try:
- save_fn(filename)
- print("{} saved to '{}'".format(description, filename))
- return True
- except (OSError, IOError) as e:
- messagebox.showerror(
- "Error saving " + description,
- "Error saving {} to '{}': {} (errno: {})"
- .format(description, e.filename, e.strerror,
- errno.errorcode[e.errno]))
- return False
- def _try_load(filename):
- # Tries to load a configuration file. Pops up an error and returns False on
- # failure.
- #
- # filename:
- # Configuration file to load
- try:
- _kconf.load_config(filename)
- print("configuration loaded from " + filename)
- return True
- except (OSError, IOError) as e:
- messagebox.showerror(
- "Error loading configuration",
- "Error loading '{}': {} (errno: {})"
- .format(filename, e.strerror, errno.errorcode[e.errno]))
- return False
- def _jump_to_dialog(_=None):
- # Pops up a dialog for jumping directly to a particular node. Symbol values
- # can also be changed within the dialog.
- #
- # Note: There's nothing preventing this from doing an incremental search
- # like menuconfig.py does, but currently it's a bit jerky for large Kconfig
- # trees, at least when inputting the beginning of the search string. We'd
- # need to somehow only update the tree items that are shown in the Treeview
- # to fix it.
- global _jump_to_tree
- def search(_=None):
- _update_jump_to_matches(msglabel, entry.get())
- def jump_to_selected(event=None):
- # Jumps to the selected node and closes the dialog
- # Ignore double clicks on the image and in the heading area
- if event and (tree.identify_element(event.x, event.y) == "image" or
- _in_heading(event)):
- return
- sel = tree.selection()
- if not sel:
- return
- node = _id_to_node[sel[0]]
- if node not in _shown_menu_nodes(_parent_menu(node)):
- _show_all_var.set(True)
- if not _single_menu:
- # See comment in _do_tree_mode()
- _update_tree()
- _jump_to(node)
- dialog.destroy()
- def tree_select(_):
- jumpto_button["state"] = "normal" if tree.selection() else "disabled"
- dialog = Toplevel(_root)
- dialog.geometry("+{}+{}".format(
- _root.winfo_rootx() + 50, _root.winfo_rooty() + 50))
- dialog.title("Jump to symbol/choice/menu/comment")
- dialog.minsize(128, 128) # See _create_ui()
- dialog.transient(_root)
- ttk.Label(dialog, text=_JUMP_TO_HELP) \
- .grid(column=0, row=0, columnspan=2, sticky="w", padx=".1c",
- pady=".1c")
- entry = ttk.Entry(dialog)
- entry.grid(column=0, row=1, sticky="ew", padx=".1c", pady=".1c")
- entry.focus_set()
- entry.bind("<Return>", search)
- entry.bind("<KP_Enter>", search)
- ttk.Button(dialog, text="Search", command=search) \
- .grid(column=1, row=1, padx="0 .1c", pady="0 .1c")
- msglabel = ttk.Label(dialog)
- msglabel.grid(column=0, row=2, sticky="w", pady="0 .1c")
- panedwindow, tree = _create_kconfig_tree_and_desc(dialog)
- panedwindow.grid(column=0, row=3, columnspan=2, sticky="nsew")
- # Clear tree
- tree.set_children("")
- _jump_to_tree = tree
- jumpto_button = ttk.Button(dialog, text="Jump to selected item",
- state="disabled", command=jump_to_selected)
- jumpto_button.grid(column=0, row=4, columnspan=2, sticky="ns", pady=".1c")
- dialog.columnconfigure(0, weight=1)
- # Only the pane with the Kconfig tree and description grows vertically
- dialog.rowconfigure(3, weight=1)
- # See the menuconfig() function
- _root.update_idletasks()
- dialog.geometry(dialog.geometry())
- # The dialog must be visible before we can grab the input
- dialog.wait_visibility()
- dialog.grab_set()
- tree.bind("<Double-1>", jump_to_selected)
- tree.bind("<Return>", jump_to_selected)
- tree.bind("<KP_Enter>", jump_to_selected)
- # add=True to avoid overriding the description text update
- tree.bind("<<TreeviewSelect>>", tree_select, add=True)
- dialog.bind("<Escape>", lambda _: dialog.destroy())
- # Wait for the user to be done with the dialog
- _root.wait_window(dialog)
- _jump_to_tree = None
- _tree.focus_set()
- def _update_jump_to_matches(msglabel, search_string):
- # Searches for nodes matching the search string and updates
- # _jump_to_matches. Puts a message in 'msglabel' if there are no matches,
- # or regex errors.
- global _jump_to_matches
- _jump_to_tree.selection_set(())
- try:
- # We could use re.IGNORECASE here instead of lower(), but this is
- # faster for regexes like '.*debug$' (though the '.*' is redundant
- # there). Those probably have bad interactions with re.search(), which
- # matches anywhere in the string.
- regex_searches = [re.compile(regex).search
- for regex in search_string.lower().split()]
- except re.error as e:
- msg = "Bad regular expression"
- # re.error.msg was added in Python 3.5
- if hasattr(e, "msg"):
- msg += ": " + e.msg
- msglabel["text"] = msg
- # Clear tree
- _jump_to_tree.set_children("")
- return
- _jump_to_matches = []
- add_match = _jump_to_matches.append
- for node in _sorted_sc_nodes():
- # Symbol/choice
- sc = node.item
- for search in regex_searches:
- # Both the name and the prompt might be missing, since
- # we're searching both symbols and choices
- # Does the regex match either the symbol name or the
- # prompt (if any)?
- if not (sc.name and search(sc.name.lower()) or
- node.prompt and search(node.prompt[0].lower())):
- # Give up on the first regex that doesn't match, to
- # speed things up a bit when multiple regexes are
- # entered
- break
- else:
- add_match(node)
- # Search menus and comments
- for node in _sorted_menu_comment_nodes():
- for search in regex_searches:
- if not search(node.prompt[0].lower()):
- break
- else:
- add_match(node)
- msglabel["text"] = "" if _jump_to_matches else "No matches"
- _update_jump_to_display()
- if _jump_to_matches:
- item = id(_jump_to_matches[0])
- _jump_to_tree.selection_set(item)
- _jump_to_tree.focus(item)
- def _update_jump_to_display():
- # Updates the images and text for the items in _jump_to_matches, and sets
- # them as the items of _jump_to_tree
- # Micro-optimize a bit
- item = _jump_to_tree.item
- id_ = id
- node_str = _node_str
- img_tag = _img_tag
- visible = _visible
- for node in _jump_to_matches:
- item(id_(node),
- text=node_str(node),
- tags=img_tag(node) if visible(node) else
- img_tag(node) + " invisible")
- _jump_to_tree.set_children("", *map(id, _jump_to_matches))
- def _jump_to(node):
- # Jumps directly to 'node' and selects it
- if _single_menu:
- _enter_menu(_parent_menu(node))
- else:
- _load_parents(node)
- _select(_tree, id(node))
- # Obscure Python: We never pass a value for cached_nodes, and it keeps pointing
- # to the same list. This avoids a global.
- def _sorted_sc_nodes(cached_nodes=[]):
- # Returns a sorted list of symbol and choice nodes to search. The symbol
- # nodes appear first, sorted by name, and then the choice nodes, sorted by
- # prompt and (secondarily) name.
- if not cached_nodes:
- # Add symbol nodes
- for sym in sorted(_kconf.unique_defined_syms,
- key=lambda sym: sym.name):
- # += is in-place for lists
- cached_nodes += sym.nodes
- # Add choice nodes
- choices = sorted(_kconf.unique_choices,
- key=lambda choice: choice.name or "")
- cached_nodes += sorted(
- [node
- for choice in choices
- for node in choice.nodes],
- key=lambda node: node.prompt[0] if node.prompt else "")
- return cached_nodes
- def _sorted_menu_comment_nodes(cached_nodes=[]):
- # Returns a list of menu and comment nodes to search, sorted by prompt,
- # with the menus first
- if not cached_nodes:
- def prompt_text(mc):
- return mc.prompt[0]
- cached_nodes += sorted(_kconf.menus, key=prompt_text)
- cached_nodes += sorted(_kconf.comments, key=prompt_text)
- return cached_nodes
- def _load_parents(node):
- # Menus are lazily populated as they're opened in full-tree mode, but
- # jumping to an item needs its parent menus to be populated. This function
- # populates 'node's parents.
- # Get all parents leading up to 'node', sorted with the root first
- parents = []
- cur = node.parent
- while cur is not _kconf.top_node:
- parents.append(cur)
- cur = cur.parent
- parents.reverse()
- for i, parent in enumerate(parents):
- if not _tree.item(id(parent), "open"):
- # Found a closed menu. Populate it and all the remaining menus
- # leading up to 'node'.
- for parent in parents[i:]:
- # We only need to populate "real" menus/choices. Implicit menus
- # are populated when their parents menus are entered.
- if not isinstance(parent.item, Symbol):
- _build_full_tree(parent)
- return
- def _parent_menu(node):
- # Returns the menu node of the menu that contains 'node'. In addition to
- # proper 'menu's, this might also be a 'menuconfig' symbol or a 'choice'.
- # "Menu" here means a menu in the interface.
- menu = node.parent
- while not menu.is_menuconfig:
- menu = menu.parent
- return menu
- def _trace_write(var, fn):
- # Makes fn() be called whenever the Tkinter Variable 'var' changes value
- # trace_variable() is deprecated according to the docstring,
- # which recommends trace_add()
- if hasattr(var, "trace_add"):
- var.trace_add("write", fn)
- else:
- var.trace_variable("w", fn)
- def _info_str(node):
- # Returns information about the menu node 'node' as a string.
- #
- # The helper functions are responsible for adding newlines. This allows
- # them to return "" if they don't want to add any output.
- if isinstance(node.item, Symbol):
- sym = node.item
- return (
- _name_info(sym) +
- _help_info(sym) +
- _direct_dep_info(sym) +
- _defaults_info(sym) +
- _select_imply_info(sym) +
- _kconfig_def_info(sym)
- )
- if isinstance(node.item, Choice):
- choice = node.item
- return (
- _name_info(choice) +
- _help_info(choice) +
- 'Mode: {}\n\n'.format(choice.str_value) +
- _choice_syms_info(choice) +
- _direct_dep_info(choice) +
- _defaults_info(choice) +
- _kconfig_def_info(choice)
- )
- # node.item in (MENU, COMMENT)
- return _kconfig_def_info(node)
- def _name_info(sc):
- # Returns a string with the name of the symbol/choice. Choices are shown as
- # <choice (name if any)>.
- return (sc.name if sc.name else standard_sc_expr_str(sc)) + "\n\n"
- def _value_info(sym):
- # Returns a string showing 'sym's value
- # Only put quotes around the value for string symbols
- return "Value: {}\n".format(
- '"{}"'.format(sym.str_value)
- if sym.orig_type == STRING
- else sym.str_value)
- def _choice_syms_info(choice):
- # Returns a string listing the choice symbols in 'choice'. Adds
- # "(selected)" next to the selected one.
- s = "Choice symbols:\n"
- for sym in choice.syms:
- s += " - " + sym.name
- if sym is choice.selection:
- s += " (selected)"
- s += "\n"
- return s + "\n"
- def _help_info(sc):
- # Returns a string with the help text(s) of 'sc' (Symbol or Choice).
- # Symbols and choices defined in multiple locations can have multiple help
- # texts.
- s = ""
- for node in sc.nodes:
- if node.help is not None:
- s += node.help + "\n\n"
- return s
- def _direct_dep_info(sc):
- # Returns a string describing the direct dependencies of 'sc' (Symbol or
- # Choice). The direct dependencies are the OR of the dependencies from each
- # definition location. The dependencies at each definition location come
- # from 'depends on' and dependencies inherited from parent items.
- return "" if sc.direct_dep is _kconf.y else \
- 'Direct dependencies (={}):\n{}\n' \
- .format(TRI_TO_STR[expr_value(sc.direct_dep)],
- _split_expr_info(sc.direct_dep, 2))
- def _defaults_info(sc):
- # Returns a string describing the defaults of 'sc' (Symbol or Choice)
- if not sc.defaults:
- return ""
- s = "Defaults:\n"
- for val, cond in sc.defaults:
- s += " - "
- if isinstance(sc, Symbol):
- s += _expr_str(val)
- # Skip the tristate value hint if the expression is just a single
- # symbol. _expr_str() already shows its value as a string.
- #
- # This also avoids showing the tristate value for string/int/hex
- # defaults, which wouldn't make any sense.
- if isinstance(val, tuple):
- s += ' (={})'.format(TRI_TO_STR[expr_value(val)])
- else:
- # Don't print the value next to the symbol name for choice
- # defaults, as it looks a bit confusing
- s += val.name
- s += "\n"
- if cond is not _kconf.y:
- s += " Condition (={}):\n{}" \
- .format(TRI_TO_STR[expr_value(cond)],
- _split_expr_info(cond, 4))
- return s + "\n"
- def _split_expr_info(expr, indent):
- # Returns a string with 'expr' split into its top-level && or || operands,
- # with one operand per line, together with the operand's value. This is
- # usually enough to get something readable for long expressions. A fancier
- # recursive thingy would be possible too.
- #
- # indent:
- # Number of leading spaces to add before the split expression.
- if len(split_expr(expr, AND)) > 1:
- split_op = AND
- op_str = "&&"
- else:
- split_op = OR
- op_str = "||"
- s = ""
- for i, term in enumerate(split_expr(expr, split_op)):
- s += "{}{} {}".format(indent*" ",
- " " if i == 0 else op_str,
- _expr_str(term))
- # Don't bother showing the value hint if the expression is just a
- # single symbol. _expr_str() already shows its value.
- if isinstance(term, tuple):
- s += " (={})".format(TRI_TO_STR[expr_value(term)])
- s += "\n"
- return s
- def _select_imply_info(sym):
- # Returns a string with information about which symbols 'select' or 'imply'
- # 'sym'. The selecting/implying symbols are grouped according to which
- # value they select/imply 'sym' to (n/m/y).
- def sis(expr, val, title):
- # sis = selects/implies
- sis = [si for si in split_expr(expr, OR) if expr_value(si) == val]
- if not sis:
- return ""
- res = title
- for si in sis:
- res += " - {}\n".format(split_expr(si, AND)[0].name)
- return res + "\n"
- s = ""
- if sym.rev_dep is not _kconf.n:
- s += sis(sym.rev_dep, 2,
- "Symbols currently y-selecting this symbol:\n")
- s += sis(sym.rev_dep, 1,
- "Symbols currently m-selecting this symbol:\n")
- s += sis(sym.rev_dep, 0,
- "Symbols currently n-selecting this symbol (no effect):\n")
- if sym.weak_rev_dep is not _kconf.n:
- s += sis(sym.weak_rev_dep, 2,
- "Symbols currently y-implying this symbol:\n")
- s += sis(sym.weak_rev_dep, 1,
- "Symbols currently m-implying this symbol:\n")
- s += sis(sym.weak_rev_dep, 0,
- "Symbols currently n-implying this symbol (no effect):\n")
- return s
- def _kconfig_def_info(item):
- # Returns a string with the definition of 'item' in Kconfig syntax,
- # together with the definition location(s) and their include and menu paths
- nodes = [item] if isinstance(item, MenuNode) else item.nodes
- s = "Kconfig definition{}, with propagated dependencies\n" \
- .format("s" if len(nodes) > 1 else "")
- s += (len(s) - 1)*"="
- for node in nodes:
- s += "\n\n" \
- "At {}:{}\n" \
- "{}" \
- "Menu path: {}\n\n" \
- "{}" \
- .format(node.filename, node.linenr,
- _include_path_info(node),
- _menu_path_info(node),
- node.custom_str(_name_and_val_str))
- return s
- def _include_path_info(node):
- if not node.include_path:
- # In the top-level Kconfig file
- return ""
- return "Included via {}\n".format(
- " -> ".join("{}:{}".format(filename, linenr)
- for filename, linenr in node.include_path))
- def _menu_path_info(node):
- # Returns a string describing the menu path leading up to 'node'
- path = ""
- while node.parent is not _kconf.top_node:
- node = node.parent
- # Promptless choices might appear among the parents. Use
- # standard_sc_expr_str() for them, so that they show up as
- # '<choice (name if any)>'.
- path = " -> " + (node.prompt[0] if node.prompt else
- standard_sc_expr_str(node.item)) + path
- return "(Top)" + path
- def _name_and_val_str(sc):
- # Custom symbol/choice printer that shows symbol values after symbols
- # Show the values of non-constant (non-quoted) symbols that don't look like
- # numbers. Things like 123 are actually symbol references, and only work as
- # expected due to undefined symbols getting their name as their value.
- # Showing the symbol value for those isn't helpful though.
- if isinstance(sc, Symbol) and not sc.is_constant and not _is_num(sc.name):
- if not sc.nodes:
- # Undefined symbol reference
- return "{}(undefined/n)".format(sc.name)
- return '{}(={})'.format(sc.name, sc.str_value)
- # For other items, use the standard format
- return standard_sc_expr_str(sc)
- def _expr_str(expr):
- # Custom expression printer that shows symbol values
- return expr_str(expr, _name_and_val_str)
- def _is_num(name):
- # Heuristic to see if a symbol name looks like a number, for nicer output
- # when printing expressions. Things like 16 are actually symbol names, only
- # they get their name as their value when the symbol is undefined.
- try:
- int(name)
- except ValueError:
- if not name.startswith(("0x", "0X")):
- return False
- try:
- int(name, 16)
- except ValueError:
- return False
- return True
- if __name__ == "__main__":
- _main()
|