util.attributeEditor module

A Tk dialog for viewing and editing the attributes of an AttributedObject (see util.attribute): one row per attribute – its value, type and inheritance restrictions – with the values it inherits shown above it. editAttrsBox() returns the edits; applyEditAttrsBox() applies them.

Inheritance diagram of AttrEditor, ChangeDescr, ColorEditor, HierarchicalAttrEditor

Created on Mar 17, 2025

Code author: Rob Kremer <robkremer@me.com>

util.attributeEditor.GROUP_SEPARATOR = '/'

Attribute names are grouped, like folders, by the parts of their names between these (eg: “gui/label”).

util.attributeEditor.GROUP_BACKGROUND = 'grey85'

The background of a group’s header line (see AttrEditor.makeGroupHeader()).

util.attributeEditor.TREE_INDENT = 18

How far (in pixels) each level of the grouped attribute names is indented.

util.attributeEditor.isHiddenKey(key: str) → bool
Parameters:

key (str) – An attribute’s name.

Returns:

Whether the editor hides it: if any part of its name (see GROUP_SEPARATOR) starts with “.” – eg: “.openWindows” (hidden itself), “gui/.cache” (likewise, in the gui group), or “.internal/x” (in a hidden group). Such attributes are for the program, not the user, to edit.

Return type:

bool

util.attributeEditor.treeOrder(keys: list[str]) → list[tuple[str, str, int]]

The order to show attributes in, grouped by the parts of their names (see GROUP_SEPARATOR), like a tree of folders: at each level, the names with no more separators first, then each group – its header, and then its own contents, likewise – all sorted (ignoring case). Eg: “a”, “b”, “a/a”, “a/b”, “a/a/a”, “a/a/b”, “b/a” are shown as:

a
b
a/      (a group)
  a/a
  a/b
  a/a/    (a group)
    a/a/a
    a/a/b
b/      (a group)
  b/a
Parameters:

keys (list[str]) – The attribute names.

Returns:

(kind, name, depth) for each line: kind is “key” (name is an attribute’s name) or “group” (name is the group’s path, eg: “a/a”).

Return type:

list[tuple[str, str, int]]

util.attributeEditor.editAttrsBox(parent: Misc, attrs: AttributedObject, title='Attributes Editor', editKey: str | None = None, disabled: bool = False, displayValues: dict[str, Any] | None = None, hideColumns: Iterable[str] = (), listItemMakers: dict[str, Callable[[Misc], str | None] | dict[str, Callable[[Misc], str | None]]] | None = None) → dict[str, Attribute] | None
Parameters:
  • parent – The parent widget

  • attrs – The AttributedObject object to be updated.

  • title – The title of the dialog window.

  • displayValues – [Optional] Values to show (and edit) for some keys in place of their stored values – eg: a fuller form of the value, which the caller interprets. A key counts as changed only if its value differs from what was shown.

Returns:

An dict str->Attribute with any updates

displays an AttrsEditor or a HierarchicalAttrsEditor (depanding on whether attrs is an AttributedObject or a AttributedObject ) and updates the result to attrs .

Parameters:
  • editKey (Optional[str])

  • disabled (bool)

  • hideColumns (Iterable[str]) – The columns not to show (see AttrEditor.HIDEABLE_COLUMNS).

  • listItemMakers (Optional[dict[str, ItemMaker]]) – For “list[str]” attributes: what their “Add…” does, by key (see ListEditor).

util.attributeEditor.applyEditAttrsBox(parent: Misc, attrs: AttributedObject, title='Attributes Editor', editKey: str | None = None, hideColumns: Iterable[str] = (), listItemMakers: dict[str, Callable[[Misc], str | None] | dict[str, Callable[[Misc], str | None]]] | None = None) → None

Same as editAttrsBox, but applies the changes.

Parameters:
  • parent (tk.Misc)

  • attrs (AttributedObject)

  • title (str)

  • editKey (Optional[str])

  • hideColumns (Iterable[str]) – The columns not to show (see AttrEditor.HIDEABLE_COLUMNS).

  • listItemMakers (Optional[dict[str, ItemMaker]]) – As for editAttrsBox().

util.attributeEditor._updateWith(attrs: AttributedObject, attrChangeList)
Parameters:
util.attributeEditor._keyedMessage(key: str, ex: Exception) → str

An error message that names key exactly once.

Parameters:
  • key (str)

  • ex (Exception)

Returns:

Return type:

str

util.attributeEditor.getEditedAttrsCommandObj(parent: Widget, attrs: AttributedObject, title='Attributes Editor', editKey: str | None = None, disabled: bool = False)
Returns:

An UpdateAttrsCommand object that displays an AttrsEditor or a HierarchicalAttrsEditor (depending on whether attrs is an AttributedObject or a HierarchicalAttributedObject ) with the appropriate do/undo properties.

Parameters:
  • parent (tk.Widget)

  • attrs (AttributedObject)

  • title (str)

  • editKey (Optional[str])

  • disabled (bool)

util.attributeEditor.clearGrid(widget: Widget, row=None, column=None)
Parameters:
  • widget (tk.Widget)

  • row

  • column

class util.attributeEditor.ChangeDescr(tkVar: Variable | None, editor: Widget | None, delete: bool, saveMethod: Callable, qual: Restriction | None = None)

Bases: object

The editor’s record of one attribute’s row: its Tk variable and widget, whether it’s to be deleted, and how to save it.

tkVar: Variable | None
editor: Widget | None
delete: bool
saveMethod: Callable
typeLabel = None
qual: Restriction | None = None
getValue() → Any
Returns:

Return type:

Any

setValue(val: Any)
Parameters:

val (Any)

Returns:

Return type:

__init__(tkVar: Variable | None, editor: Widget | None, delete: bool, saveMethod: Callable, qual: Restriction | None = None) → None
util.attributeEditor.SYS_ATTRIBUTES = ['name']

Used to highlight the ‘system’ labels on the grid

util.attributeEditor.ItemMaker = typing.Callable[[tkinter.Misc], str | None] | dict[str, typing.Callable[[tkinter.Misc], str | None]]

What a list editor’s “Add…” does (see ListEditor): given the editor, the item to add (or None); or several such, by their buttons’ labels (eg: “Add file…”, “Add URL…”).

class util.attributeEditor.ListEditor(master, items: list[str], newItem: Callable[[Misc], str | None] | dict[str, Callable[[Misc], str | None]] | None = None)

Bases: Frame

An editor for a list of strings (an attribute of type “list[str]”): the items in order, in a list box, with buttons to add one, remove the selected one, and move it up or down (the order may matter: eg: super-models are searched in order). Double-click an item to edit it.

MAX_HEIGHT = 6

The most items shown at once (the rest scroll).

__init__(master, items: list[str], newItem: Callable[[Misc], str | None] | dict[str, Callable[[Misc], str | None]] | None = None)
Parameters:
  • master

  • items (list[str]) – The list (not changed: see getList()).

  • newItem (Optional[ItemMaker]) – What “Add…” does: given this editor (as a parent for any dialog), returns the item to add, or None to add nothing. By default, it asks for a string. Or several ways to add one, by their buttons’ labels (eg: {“Add file…”: …, “Add URL…”: …}): a button each.

state: str
buttons: dict[str, Button]
_height(n: int) → int
getList() → list[str]
Returns:

The items, in order.

Return type:

list[str]

setList(items: list[str]) → None
Parameters:

items (list[str]) – The items to show, in order.

_selected() → int | None
_select(i: int) → None
_updateButtons() → None
add(maker: Callable[[Misc], str | None] | None = None) → None

“Add…”: add an item (see newItem) after the selected one, or at the end.

Parameters:

maker (Optional[Callable[[tk.Misc], Optional[str]]]) – What makes it (one of newItem’s, if it has several); by default, the first.

remove() → None

“Remove”: remove the selected item.

move(step: int) → None

Move the selected item up (step -1) or down (1).

Parameters:

step (int) – -1 or 1.

editSelected() → None

Edit the selected item’s text (on a double-click).

config(**conf)

“state” (“normal” or “disabled”) applies to the whole editor; colours to the list box; anything else to the frame.

configure(**conf)

“state” (“normal” or “disabled”) applies to the whole editor; colours to the list box; anything else to the frame.

class util.attributeEditor.ColorEditor(master, var: StringVar, color: str)

Bases: Frame

An editor for a colour value: a swatch that opens the system colour chooser, and a button to make it transparent.

__init__(master, var: StringVar, color: str)
Parameters:
  • master

  • var (tk.StringVar)

  • color (str)

setTransparent()
colorChooser()
config(**conf)
Parameters:

conf

class util.attributeEditor.AttrEditor(parent, attrs: AttributedObject, title='Attribute Editor', disabled=False, displayValues: dict[str, Any] | None = None, hideColumns: Iterable[str] = (), listItemMakers: dict[str, Callable[[Misc], str | None] | dict[str, Callable[[Misc], str | None]]] | None = None)

Bases: Toplevel

Implements a graphical editor for an AttributedObject of the form:

<editor lines> …

[<Message line>]

[<Answer line>]

<Add New Attribute button>

<Cancel button> <Save button>

where <editor lines> look like this:

<stat>

<label>

<value editor>

<type>

<note>

<button> <button>…

<stat>

<label>

<value editor>

<type>

<note>

<button> <button>…

If any attribute’s name has a “/” in it, the lines are grouped by the parts of the names, like folders (see treeOrder()): each group has a header line with a triangle that collapses or expands it, and each attribute is labelled with just the last part of its name, indented under its group.

The <Message line> and <Answer iine> are only visiable when the setMessage() or ask() methods are called.

collapsedGroups: set[str] = {}

The groups collapsed when an editor was last closed: the next one starts with them collapsed too.

HIDEABLE_COLUMNS: dict[str, str] = {'actions': 'cDel', 'note': 'cNote', 'qualifiers': 'cQuals', 'type': 'cKind'}

The columns that can be hidden (see hideColumns), by name: name -> the attribute holding the column’s number. (“qualifiers” – the inheritance restrictions – is only in a HierarchicalAttrEditor.)

__init__(parent, attrs: AttributedObject, title='Attribute Editor', disabled=False, displayValues: dict[str, Any] | None = None, hideColumns: Iterable[str] = (), listItemMakers: dict[str, Callable[[Misc], str | None] | dict[str, Callable[[Misc], str | None]]] | None = None)
Parameters:
  • parent

  • attrs (AttributedObject)

  • title (str)

  • disabled (bool)

  • displayValues (Optional[dict[str, Any]])

  • hideColumns (Iterable[str]) – The columns not to show, by name (see HIDEABLE_COLUMNS): eg: (“type”, “qualifiers”, “note”) for settings, whose notes are their documentation – still shown as their names’ tooltips.

  • listItemMakers (Optional[dict[str, ItemMaker]]) – For “list[str]” attributes: what their “Add…” does, by key – eg: a file chooser (see ListEditor). By default, it asks for a string.

listItemMakers: dict[str, Callable[[Misc], str | None] | dict[str, Callable[[Misc], str | None]]]
hideColumns: set[str]

The columns not shown (see _applyHiddenColumns()).

displayValues: dict[str, Any]

Values shown for some keys in place of their stored ones (see editAttrsBox()).

attrs: AttributedObject
vars: dict[str, ChangeDescr]
disabled: bool
rows: int
editKey: str | None
editWidget: Widget | None
noteEdits: dict[str, str]

Attributes’ notes as edited (see editNote()), by key: only those that have been.

treeMode: bool

Whether the attributes are shown grouped by the parts of their names (see treeOrder()).

_labels: dict[str, tuple[str, int]]

Each attribute’s label, and its depth in the tree (see makeNewEntry()).

_groupSpans: dict[str, tuple[int, int]]

Each group’s rows in the grid – the first and the one after the last – below its header.

_groupHeaders: dict[str, Label]
_hiddenWidgets: dict[str, list[Widget]]

The widgets each collapsed group hid (see toggleGroup()), to show again when it’s expanded.

rEditors: int
rMessage: int
rEnter: int
rButtons1: int
rButtons2: int
rowsPerEntry: int
EntryOffset: int
cStatus: int
cLabel: int
cEdit: int
cKind: int
cNote: int
cDel: int
cspan: int
outerSpan: int
show(editKey: str | None = None)
Parameters:

editKey – [Optional str] If present, set the edit focus to the editKey creating a local attribute if necessary.

_validateUpdates() → list[str]

Dry-run the validation setAttr() will apply to each changed attribute.

Returns:

A message for each change that would be rejected.

_onReturn(event: Event) → str | None

<Return>: the default button – “Save Changes” (or “Dismiss”, if nothing can be edited) – except in a multi-line text (where it’s a new line), or while answering a question (see ask(): the answer’s own <Return>).

Parameters:

event (tk.Event)

Returns:

“break” if it was dealt with.

Return type:

str | None

_placeOverParent() → None

Put this window over the middle of its parent’s window, so it’s seen where it was asked for – not at the window system’s default place, which may be behind the parent, or on another screen or desktop.

dismiss()

Call this method to destroy the window. It will also release the modal window.

addNewAttribute()
_addNewAttribute2(event, name: str)
Parameters:
  • event

  • name (str)

_showEntry(key: str) → None

Scroll the list of attributes to show key’s row, and put the focus in its value: eg: a new attribute, to type its value.

Parameters:

key (str)

deleteAttr(key, item, editor, row, button)
Parameters:
  • key

  • item

  • editor

  • row

  • button

typeInconsistent(event, ans, key, row, newType)
Parameters:
  • event

  • ans

  • key

  • row

  • newType

resetKind(kind, key, row, value: Any = NOVALUE)

Change key’s type to kind, with value (default: its current one) – which must suit kind (see _convertValue()).

Parameters:
  • kind

  • key

  • row

  • value (Any)

editTypeAnswer(button, event, ans, key, row)
Parameters:
  • button

  • event

  • ans

  • key

  • row

editType(b: Button, parent, key, typ, column, row)
Parameters:
  • b (tk.Button)

  • parent

  • key

  • typ

  • column

  • row

hasChanged(k: str) → bool
Parameters:

k (str)

Returns:

Return type:

bool

_saveKey(k: str, origAttr: Attribute)
Parameters:
save()

Stores a key/Attribute dictionary of all attributes that have been modified in the self.updatedKeys dictionary. In the case of deleted Attributes, the key maps to None .

getEditorForType(parent: Any, key: str, typ: type | str | None, value: Any, keyExt, inhType: str | type | None = None) → Any
Parameters:
  • parent (Any)

  • key (str)

  • typ (Union[type, str, NoneType])

  • value (Any)

  • keyExt

  • inhType (str | type | None)

Returns:

Return type:

Any

makeNewEntry(k, v, row, disabled, keyExt, inhType: type | str | None = None)
Parameters:
  • k

  • v

  • row

  • disabled

  • keyExt

  • inhType (type | str | None)

makeGroupHeader(path: str, depth: int, row: int) → Label

The header line for a group of attributes (see treeOrder()): a triangle and the group’s name, indented by its depth. Clicking it collapses or expands the group (see toggleGroup()).

Parameters:
  • path (str) – The group’s path, eg: “gui/colors”.

  • depth (int) – How deep the group is in the tree (0 at the top).

  • row (int) – Its row in the grid.

Returns:

The header’s text (the triangle and name).

Return type:

tk.Label

toggleGroup(path: str, remember: bool = True) → None

Collapse the group path – hide all the rows below its header, up to the end of the group – or, if it’s collapsed, expand it: show again what it hid. (A collapsed subgroup’s rows were hidden by the subgroup, so they stay hidden until it’s expanded too.)

Parameters:
  • path (str) – The group’s path.

  • remember (bool) – Whether to remember the change for the next editor shown (see collapsedGroups).

_noteFor(key: str, attr: Attribute, keyExt: str = '') → str
Parameters:
  • key (str) – The attribute’s name.

  • attr (Attribute) – The attribute shown on the line (its own, or the inherited one).

  • keyExt (str) – “” for the attribute’s own line; INHERIT_KEY_EXT for its inherited one.

Returns:

The note to show for the line: as edited, if it has been; else the attribute’s own note, or failing that the one it inherits.

Return type:

str

makeNoteButton(key: str, attr: Attribute, row: int, readonly: bool, keyExt: str = '') → Button

The button in the NOTE column, which shows the note in a pop-up (see editNote()): “note” if there is one, “+” if not. Its tooltip shows the note.

Parameters:
  • key (str) – The attribute’s name.

  • attr (Attribute) – The attribute shown on the line.

  • row (int) – The line’s row in the grid.

  • readonly (bool) – Whether the note can only be read (eg: an inherited one).

  • keyExt (str) – “” for the attribute’s own line; INHERIT_KEY_EXT for its inherited one.

Returns:

The button.

Return type:

ttk.Button

editNote(key: str, attr: Attribute, button: Button, readonly: bool, keyExt: str = '') → None

Show the attribute’s note in a small pop-up text editor – or, if readonly, viewer. OK keeps the edited note (it’s saved with the other changes: see save()).

Parameters:
  • key (str) – The attribute’s name.

  • attr (Attribute) – The attribute shown on the line.

  • button (ttk.Button) – The line’s note button (its text is updated).

  • readonly (bool) – Whether the note can only be read.

  • keyExt (str) – “” for the attribute’s own line; INHERIT_KEY_EXT for its inherited one.

makeTitle(col: int, text: str, helpText: str, row: int, **kwargs)
Parameters:
  • col (int)

  • text (str)

  • helpText (str)

  • row (int)

  • kwargs

addTooltip(widget: Widget, helpText: str)
Parameters:
  • widget (Widget)

  • helpText (str)

_applyHiddenColumns() → None

Take the widgets in the hidden columns (see hideColumns) off the grid – those made since last time too (eg: a new attribute’s row). Only widgets in just that column: not ones spanning it, like the separators and the groups’ header bands.

makeHeader(row, **kwargs)
Parameters:
  • row

  • kwargs

makeEditor(parent, key, item, column, row, disabled, keyExt, inhType: str | type | None = None)
Parameters:
  • parent

  • key

  • item

  • column

  • row

  • disabled

  • keyExt

  • inhType (str | type | None)

Returns:

Return type:

makeButton(parent, key, item, text, command=None, **kwargs)

Unlike the other “make*() methods, this one takes a parent that is a frame that will be used as a pack() frame as opposed to the others that take a parent that’s a grid() frame.

Parameters:
  • parent

  • key

  • item

  • text

  • command

  • kwargs

Returns:

Return type:

makeLabel(parent, key, text, column, row, justify='e', padx: int | tuple[int, int] = 0, **kwargs)
Parameters:
  • parent

  • key

  • text

  • column

  • row

  • justify

  • padx (int | tuple[int, int]) – Space to the left and right of it (eg: an indent).

  • kwargs

Returns:

Return type:

makeKind(parent, key, typ: type | str, column, row, command, disabled, keyExt, inhType: str | type | None = None, **kwargs)
Parameters:
  • parent

  • key

  • typ (Union[type, str])

  • column

  • row

  • command

  • disabled

  • keyExt

  • inhType (str | type | None)

  • kwargs

Returns:

Return type:

setStatus(parent, key, text, column, row, command=None, button=None, **kwargs)
Parameters:
  • parent

  • key

  • text

  • column

  • row

  • command

  • button

  • kwargs

setMessage(text, hideButton=True, **kwargs)
Parameters:
  • text

  • hideButton (bool)

  • kwargs

clearMessage(*args)
Parameters:

args

ask(question: str, handler: Callable[[Event, str], Any], default=None, **kwargs)

Asks a question and waits for an answer input within the window. The user is expected to fill in the answer and hit <Return> (or <Tab>) to answer. If the focus is shifted to another widget, the question is removed (as if a “cancel” button was pressed.

If the user hits return, self.answer will be called, and the user’s input can be obtained using self.var.get()

Parameters:
  • question – The text of the question.

  • handler – A Callable taking an event argument and a string argument containing the answer

  • kwargs – A dictionary of legal arguments to ttk.Text() which controls the attributes of the question Text widget.

  • default

checkInt(newval)
Parameters:

newval

Returns:

Return type:

checkFloat(newval)
Parameters:

newval

Returns:

Return type:

conf(tkObj, **kwargs)

Configure the tk widget tkObj with each of the key/value pairs in kwargs one at a time catching printing any exceptions but otherwise ignoring failures.

Parameters:
  • tkObj

  • kwargs

class util.attributeEditor.HierarchicalAttrEditor(parent, attrs: AttributedObject, title='Attribute Editor', disabled=False, displayValues: dict[str, Any] | None = None, hideColumns: Iterable[str] = (), listItemMakers: dict[str, Callable[[Misc], str | None] | dict[str, Callable[[Misc], str | None]]] | None = None)

Bases: AttrEditor

An AttrEditor for an AttributedObject with supertypes: it shows each attribute’s inherited values (greyed, above its own) and its inheritance restrictions, which can be edited too.

__init__(parent, attrs: AttributedObject, title='Attribute Editor', disabled=False, displayValues: dict[str, Any] | None = None, hideColumns: Iterable[str] = (), listItemMakers: dict[str, Callable[[Misc], str | None] | dict[str, Callable[[Misc], str | None]]] | None = None)
Parameters:
  • parent

  • attrs (AttributedObject)

  • title (str)

  • disabled (bool)

  • displayValues (Optional[dict[str, Any]])

  • hideColumns (Iterable[str]) – As for AttrEditor.

  • listItemMakers (Optional[dict[str, ItemMaker]]) – As for AttrEditor.

assertedKeys: set[str]

The keys of the inferred attributes whose “assert” button was pressed (see assertAttr()).

makeNewEntry(k, v, row, disabled, keyExt, inhType: str | type | None = None)
Parameters:
  • k

  • v

  • row

  • disabled

  • keyExt

  • inhType (str | type | None)

assertAttr(key: str, button: Button) → None

The “assert” button of an inferred attribute (see Attribute.inferred) was pressed: saving makes it an ordinary (given) attribute, with the same value (see save()).

Parameters:
  • key (str) – The attribute’s key.

  • button (ttk.Button) – The button pressed.

makeHeader(row, **kwargs)
Parameters:
  • row

  • kwargs

makeQuals(parent, key, quals: Restriction, column, row, disabled, keyExt, **kwargs)
Parameters:
  • parent

  • key

  • quals (Restriction)

  • column

  • row

  • disabled

  • keyExt

  • kwargs

hasChanged(k: str) → bool
Parameters:

k (str)

Returns:

Return type:

bool

_effectiveRestriction(k: str, origAttr: Attribute | None) → Restriction | None
Returns:

The restriction the qualifier menu started with: the local one, or failing that the inherited one (see makeQuals()).

Parameters:
_restrictionChanged(k: str, origAttr: Attribute | None) → bool
Parameters:
Returns:

Return type:

bool

_saveKey(k: str, origAttr: Attribute)
Parameters:
util.attributeEditor._convertValue(typ: str, text: str) → Any
Returns:

text (an editor’s value) as a value of the type named typ, or NOVALUE if it can’t be one. Text types take any text; “” isn’t a number or boolean.

Parameters:
  • typ (str)

  • text (str)

util.attributeEditor._defaultValue(typ: str) → Any

A reasonable starting value for the type named typ.

Parameters:

typ (str)

Returns:

Return type:

Any