Source code for fsleyes_props.build

#!/usr/bin/env python
#
# build.py - Automatically build a wx GUI for a HasProperties object.

# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#

"""Automatically build a :mod:`wx` GUI for a :class:`.HasProperties` instance.

This module provides functionality to automatically build a :mod:`wx` GUI
containing widgets which allow the user to change the property values
(:class:`.PropertyBase` instances) of a specified :class:`.HasProperties`
instance.


This module has two main entry points:

 .. autosummary::
    :nosignatures:

    buildGUI
    buildDialog


A number of classes are defined in the separate :mod:`.build_parts` module.
The :class:`.ViewItem` class allows the layout of the generated interface to
be customised.  Property widgets may be grouped together by embedding them
within a :class:`.HGroup` or :class:`.VGroup` object; they will then
respectively be laid out horizontally or verticaly.  Groups may be embedded
within a :class:`.NotebookGroup` object, which will result in a notebook-like
interface containing a tab for each child :class:`.Group`.


The label for, and behaviour of, the widget for an individual property may be
customised with a :class:`.Widget` object. As an example::

    import wx
    import fsleyes_props as props

    class MyObj(props.HasProperties):
        myint  = props.Int()
        mybool = props.Boolean()

    # A reasonably complex view specification
    view = props.VGroup(
      label='MyObj properties',
      children=(
          props.Widget('mybool',
                       label='MyObj Boolean',
                       tooltip='If this is checked, you can '
                               'edit the MyObj Integer'),
          props.Widget('myint',
                       label='MyObj Integer',
                       enabledWhen=lambda mo: mo.mybool)))

    # A simpler view specification
    view = props.VGroup(('mybool', 'myint'))

    # The simplest view specification - a
    # default one will be generated
    view = None

    myobj = MyObj()

    app   = wx.App()
    frame = wx.Frame(None)

    myObjPanel = props.buildGUI(frame, myobj, view)


See the :mod:`.build_parts` module for details on the :class:`.Widget` (and
other :class:`.ViewItem`) definitions.


You may also pass in widget labels and tooltips to the :func:`buildGUI`
function::

    labels = {
        'myint':  'MyObj Integer value',
        'mybool': 'MyObj Boolean value'
    }

    tooltips = {
        'myint' : 'MyObj Integer tooltip'
    }

    props.buildGUI(frame, myobj, view=view, labels=labels, tooltips=tooltips)


As an alternative to passing in a view, labels, and tooltips to the
:func:`buildGUI` function, they may be specified as class attributes of the
``HasProperties`` instance or class, with respective names ``_view``,
``_labels``, and ``_tooltips``::

    class MyObj(props.HasProperties):
        myint  = props.Int()
        mybool = props.Boolean()

        _view = props.HGroup(('myint', 'mybool'))
        _labels = {
            'myint':  'MyObj integer',
            'mybool': 'MyObj boolean'
        }
        _tooltips = {
            'myint':  'MyObj integer tooltip',
            'mybool': 'MyObj boolean tooltip'
        }

    props.buildGUI(frame, myobj)

"""

import logging
import weakref
import copy
import sys

import wx

from . import                          widgets
from . import                          syncable
from . import build_parts           as parts
import fsleyes_widgets.notebook     as nb
import fsleyes_widgets.bitmaptoggle as bmptoggle


log = logging.getLogger(__name__)


[docs] class PropGUI(object): """An internal container class used for convenience. Stores references to all :mod:`wx` objects that are created, and to all conditional callbacks (which control visibility/state). """ def __init__(self): # onChangeCallbacks is a list of # tuples, each of which contains: # # ([weakref(HasProperties instances)], # [propertyNames], # listenerName, # callback) # # If a property name is None, that # means that it is a global listener # on the HasProps instance self.onChangeCallbacks = [] # A dictionary of {ViewItem.key : wx.Window} mappings self.guiObjects = {} # The top level GUI container (typically a wx.Panel) self.topLevel = None
def _configureEnabledWhen(viewItem, guiObj, hasProps, propGui): """Configures a callback function for this view item, if its ``enabledWhen`` attribute was set. :param viewItem: The :class:`.ViewItem` object :param guiObj: The GUI object created from the :class:`.ViewItem` :param hasProps: The :class:`.HasProperties` instance :param propGui: The :class:`PropGui` instance, in which references to all event callbacks are stored """ if viewItem.enabledWhen is None: return None # Recursively toggle the enabled/disabled state # of the given object and all its children. def toggleAll(obj, state): obj.Enable(state) for child in obj.GetChildren(): toggleAll(child, state) def _toggleEnabled(*args): """Calls the viewItem.enabledWhen function and enables/disables the GUI object, depending upon the result. """ parent = guiObj.GetParent() isNotebookPage = isinstance(parent, nb.Notebook) state = viewItem.enabledWhen(*args) if isNotebookPage: if state: parent.EnablePage( parent.FindPage(guiObj)) else: parent.DisablePage(parent.FindPage(guiObj)) elif guiObj.IsEnabled() != state: toggleAll(guiObj, state) guiObj.Refresh() guiObj.Update() _configureEventCallback( viewItem, _toggleEnabled, 'enable', hasProps, guiObj, propGui) def _configureVisibleWhen(viewItem, guiObj, hasProps, propGui): """Configures a callback function for this view item, if its visibleWhen attribute was set. See :func:`_configureEnabledWhen`. :param viewItem: The :class:`.ViewItem` object :param guiObj: The GUI object created from the :class:`.ViewItem` :param hasProps: The :class:`.HasProperties` instance :param propGui: The :class:`PropGui` instance, in which references to all event callbacks are stored """ if viewItem.visibleWhen is None: return None def _toggleVis(*args): parent = guiObj.GetParent() isNotebookPage = isinstance(parent, nb.Notebook) visible = viewItem.visibleWhen(*args) if isNotebookPage: if visible: parent.ShowPage(parent.FindPage(guiObj)) else: parent.HidePage(parent.FindPage(guiObj)) elif visible != guiObj.IsShown(): parent.GetSizer().Show(guiObj, visible) parent.GetSizer().Layout() _configureEventCallback( viewItem, _toggleVis, 'visible', hasProps, guiObj, propGui) def _configureEventCallback( viewItem, callback, evType, hasProps, guiObj, propGui): """Called by both the :func:`_configureVisibleWhen` and :func:`_configureEnabledWhen` functions. Wraps the given ``callback`` function (which is essentially a ``ViewItem.visibleWhen`` or ``ViewItem.enabledWhen`` function) inside another function, which handles the marshalling of arguments to be passed to the ``callback``. The arguments that are passed to the function depend on the value of the ``ViewItem.dependencies`` attribute - see the :mod:`build_parts` module for an explanation. The resulting callback function is added to the ``PropGui.onChangeCallbacks`` list. :arg viewItem: The :class:`.ViewItem` instance :arg callback: The callback function to be encapsulated. :arg evType: Purely for logging. Either 'visible' or 'enable'. :arg hasProps: The :class:`.HasProperties` instance. :arg guiObj: The :mod:`wx` GUI object which has been created from the ``viewItem`` specification. :arg propGui: The :class:`PropGui` instance which stores references to all event callbacks. """ lName = 'build_py_{}_{}_{}_{}_{}'.format( evType, type(hasProps).__name__, viewItem.key, id(guiObj), id(propGui.topLevel)) log.debug('Configuring event callback for ' '({}.{}) ({} dependencies)'.format( type(hasProps).__name__, viewItem.key, len(viewItem.dependencies) if viewItem.dependencies is not None else '0')) # If this viewitem has no dependencies, # we just add a global listener to the # hasProps instance, and call the callback # whenever any properties change if viewItem.dependencies is None: def onEvent(*a): callback(hasProps) propGui.topLevel.Layout() propGui.topLevel.Refresh() propGui.topLevel.Update() hasProps.addGlobalListener(lName, onEvent, weak=False) propGui.onChangeCallbacks.append( ([weakref.ref(hasProps)], [None], lName, onEvent)) return # Otherwise, we have a list # of dependencies to process targets = [] propNames = [] def onEvent(*a): # targets are all weakrefs, hence the # 't()' - should I check for None here? args = [hasProps] args += [getattr(t(), pn) for t, pn in zip(targets, propNames)] callback(*args) propGui.topLevel.Layout() propGui.topLevel.Refresh() propGui.topLevel.Update() for dep in viewItem.dependencies: target = None propName = None # Each dependency is either the name of # a property on the hasProps instance.. if isinstance(dep, str): target = hasProps propName = dep # Or a tuple which specifies a different # instance, and the property name on that # instance else: instance, propName = dep # the first tuple element could # be an instance, or a function # which returns an instance if hasattr(instance, '__call__'): instance = instance(hasProps) target = instance propName = propName targets .append(weakref.ref(target)) propNames.append(propName) target.addListener(propName, lName, onEvent, weak=False) propGui.onChangeCallbacks.append( (targets, propNames, lName, onEvent)) def _createLinkBox(parent, viewItem, hasProps, propGui): """Creates a checkbox which can be used to link/unlink a property from its parent property. """ propName = viewItem.propKey linkBox = widgets.makeSyncWidget(parent, hasProps, propName) if (hasProps.getParent() is None) or \ (not hasProps.canBeSyncedToParent( propName)) or \ (not hasProps.canBeUnsyncedFromParent(propName)): viewItem.enabledWhen = None return linkBox def _createLabel(parent, viewItem, hasProps, propGui): """Creates a :class:`wx.StaticText` object containing a label for the given :class:`.ViewItem`. """ label = wx.StaticText(parent, label=viewItem.label) return label def _createButton(parent, viewItem, hasProps, propGui): """Creates a :class:`wx.Button` object for the given :class:`.Button` object. """ btnText = None if viewItem.text is not None: btnText = viewItem.text elif viewItem.label is not None: btnText = viewItem.label elif viewItem.key is not None: btnText = viewItem.key if viewItem.icon is not None: bmp = wx.Bitmap(viewItem.icon, wx.BITMAP_TYPE_PNG) style = wx.BU_EXACTFIT | wx.ALIGN_CENTRE | wx.BU_NOTEXT button = wx.Button(parent, style=style) button.SetBitmapLabel(bitmap=bmp) else: button = wx.Button(parent, label=btnText, style=wx.BU_EXACTFIT) button.Bind(wx.EVT_BUTTON, lambda e: viewItem.callback(hasProps, button)) return button def _createToggle(parent, viewItem, hasProps, propGui): """Creates a widget for the given :class:`.Toggle` object. If no icons have been set, a ``wx.CheckBox`` is used. Otherwise a :class:`.BitmapToggleButton` is used. """ widget = None icon = viewItem.icon # If no icons are set, use a CheckBox if icon is None: widget = wx.CheckBox(parent) event = wx.EVT_CHECKBOX # Otherwise, use a BitmapToggleButton else: if isinstance(icon, str): icon = [icon] for i in range(len(icon)): icon[i] = wx.Bitmap(icon[i], wx.BITMAP_TYPE_PNG) if len(icon) == 1: icon = icon + [None] style = wx.BU_EXACTFIT | wx.ALIGN_CENTRE | wx.BU_NOTEXT widget = bmptoggle.BitmapToggleButton(parent, trueBmp=icon[0], falseBmp=icon[1], style=style) event = bmptoggle.EVT_BITMAP_TOGGLE widget.Bind(event, lambda e: viewItem.callback(hasProps, widget)) return widget def _createWidget(parent, viewItem, hasProps, propGui): """Creates a widget for the given :class:`.Widget` object, using the :func:`.makeWidget` function (see the :mod:`props.widgets` module for more details). """ if viewItem.index is not None: widget = widgets.makeListWidget(parent, hasProps, viewItem.key, viewItem.index, **viewItem.kwargs) else: widget = widgets.makeWidget( parent, hasProps, viewItem.key, **viewItem.kwargs) return widget def _makeGroupBorder(parent, group, ctr, *args, **kwargs): """Makes a border for a :class:`.Group`. If a the ``border`` attribute of a :class:`.Group` object has been set to ``True``, this function is called. It creates a parent :class:`wx.Panel` with a border and title, then creates and embeds the GUI object representing the group (via the `ctr` argument). Returns the parent border panel, and the group GUI object. Parameters: :param parent: Parent GUI object :param group: :class:`.VGroup`, :class:`.HGroup` or :class:`.NotebookGroup` :param ctr: Constructor for a :class:`wx.Window` object. :param args: Passed to `ctr`. You don't need to pass in the parent. :param kwargs: Passed to `ctr`. """ borderPanel = wx.Panel(parent, style=wx.SUNKEN_BORDER) borderSizer = wx.BoxSizer(wx.VERTICAL) groupObject = ctr(borderPanel, *args, **kwargs) if group.label is not None: label = wx.StaticText(borderPanel, label=group.label) line = wx.StaticLine(borderPanel, style=wx.LI_HORIZONTAL) font = label.GetFont() font.SetPointSize(font.GetPointSize() - 2) font.SetWeight(wx.FONTWEIGHT_LIGHT) label.SetFont(font) borderSizer.Add(label, border=5, flag=wx.ALL) borderSizer.Add(line, border=5, flag=wx.EXPAND | wx.ALL) borderSizer.Add( groupObject, border=5, flag=wx.EXPAND | wx.ALL, proportion=1) borderPanel.SetSizer(borderSizer) borderSizer.Layout() borderSizer.Fit(borderPanel) return borderPanel, groupObject def _createNotebookGroup(parent, group, hasProps, propGui): """Creates a :class:`fsleyes_widgets.notebook.Notebook` object from the given :class:`.NotebookGroup` object. The children of the group object are also created via recursive calls to the :func:`_create` function. """ if group.border: borderPanel, notebook = _makeGroupBorder( parent, group, nb.Notebook) else: notebook = nb.Notebook(parent, style=wx.TOP | wx.HORIZONTAL) for i, child in enumerate(group.children): if child.label is None: pageLabel = '{}'.format(i) else: pageLabel = child.label if isinstance(child, parts.Group): child.border = False page = _create(notebook, child, hasProps, propGui) notebook.InsertPage(i, page, pageLabel) page._notebookIdx = i notebook.SetSelection(0) notebook.Layout() notebook.Fit() if group.border: return borderPanel else: return notebook def _layoutHGroup(group, parent, children, labels): """Lays out the children (and labels, if not ``None``) of the given :class:`.HGroup` object. Parameters: :param group: :class:`.HGroup` object :param parent: GUI object which represents the group :param children: List of GUI objects, the children of the group. :param labels: ``None`` if no labels, otherwise a list of GUI Label objects, one for each child. """ if group.wrap: sizer = wx.WrapSizer(wx.HORIZONTAL) else: sizer = wx.BoxSizer(wx.HORIZONTAL) for cidx in range(len(children)): vItem = group.children[cidx] if isinstance(vItem, parts.LinkBox): sizer.Add(children[cidx], flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL) else: if labels is not None and labels[cidx] is not None: if group.vertLabels: panel = wx.Panel(parent, style=wx.SUNKEN_BORDER) pSizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(pSizer) labels[ cidx].Reparent(panel) children[cidx].Reparent(panel) pSizer.Add(labels[ cidx], flag=wx.EXPAND) pSizer.Add(children[cidx], flag=wx.EXPAND) sizer .Add(panel, flag=wx.EXPAND) else: sizer.Add(labels[ cidx], flag=wx.EXPAND) sizer.Add(children[cidx], flag=wx.EXPAND, proportion=1) else: sizer.Add(children[cidx], flag=wx.EXPAND, proportion=1) # TODO I have not added support # for child groups with borders parent.SetSizer(sizer) def _layoutVGroup(group, parent, children, labels): """Lays out the children (and labels, if not ``None``) of the given :class:`.VGroup` object. Parameters the same as :func:`_layoutHGroup`. """ sizer = wx.GridBagSizer(1, 1) sizer.SetEmptyCellSize((0, 0)) growableRows = [] for cidx, child in enumerate(children): vItem = group.children[cidx] label = labels[cidx] childParams = {} if isinstance(vItem, parts.Group) and vItem.grow: growableRows.append(cidx) # Groups within VGroups, which don't have a border, are # laid out the same as any other widget, which probably # looks a bit ugly. If they do have a border, however, # they are laid out so as to span the entire width of # the parent VGroup. Instead of having a separate label # widget, the label is embedded in the border. The # _createGroup function takes care of creating the # border/label for the child GUI object. if (isinstance(vItem, parts.Group) and vItem.border): label = None childParams['pos'] = (cidx, 0) childParams['span'] = (1, 2) childParams['border'] = 20 childParams['flag'] = wx.EXPAND | wx.ALL # No labels are being drawn for any child, so all # children should span both columns. In this case # we could just use a vertical BoxSizer instead of # a GridBagSizer, but I'm going to leave that for # the time being. elif not group.showLabels: childParams['pos'] = (cidx, 0) childParams['span'] = (1, 2) childParams['border'] = 2 childParams['flag'] = wx.EXPAND | wx.BOTTOM # Otherwise the child is drawn in the standard way - # label on the left column, child on the right. else: childParams['pos'] = (cidx, 1) childParams['border'] = 2 childParams['flag'] = wx.EXPAND | wx.BOTTOM if label is not None: sizer.Add(labels[cidx], pos=(cidx, 0), flag=wx.ALIGN_CENTER_VERTICAL) sizer.Add(child, **childParams) sizer.AddGrowableCol(1) for row in growableRows: sizer.AddGrowableRow(row) parent.SetSizer(sizer) def _createGroup(parent, group, hasProps, propGui): """Creates a GUI panel object for the given :class:`.HGroup` or :class:`.VGroup`. Children of the group are recursively created via calls to :func:`_create`, and laid out via the :class:`_layoutHGroup` or :class:`_layoutVGroup` functions. """ if group.border: borderPanel, panel = _makeGroupBorder(parent, group, wx.Panel) else: panel = wx.Panel(parent) childObjs = [] labelObjs = [] for i, child in enumerate(group.children): childObj = _create(panel, child, hasProps, propGui) # Create a label for the child if necessary if group.showLabels and group.childLabels[i] is not None: labelObj = _create(panel, group.childLabels[i], hasProps, propGui) else: labelObj = None labelObjs.append(labelObj) childObjs.append(childObj) if isinstance(group, parts.HGroup): _layoutHGroup(group, panel, childObjs, labelObjs) elif isinstance(group, parts.VGroup): _layoutVGroup(group, panel, childObjs, labelObjs) panel.Layout() panel.Fit() if group.border: borderPanel.Layout() borderPanel.Fit() return borderPanel else: return panel # These aliases are defined so we can introspectively look # up the appropriate _create* function based upon the class # name of the ViewItem being created, in the _create # function below. _createHGroup = _createGroup """Alias for the :func:`_createGroup` function.""" _createVGroup = _createGroup """Alias for the :func:`_createGroup` function.""" def _getCreateFunction(viewItemClass): """Searches within this module for a function which can parse instances of the specified :class:`.ViewItem` class. A match will be found if the given class is one of those defined in the :mod:`.build_parts` module, or has one of those classes in its base class hierarchy. In other words, application-defined subclasses of any of the :mod:`.build_parts` classes will still be built. """ cls = viewItemClass createFunc = getattr( sys.modules[__name__], '_create{}'.format(cls.__name__), None) if createFunc is not None: return createFunc bases = cls .__bases__ for baseCls in bases: createFunc = _getCreateFunction(baseCls) if createFunc is not None: return createFunc return None def _create(parent, viewItem, hasProps, propGui): """Creates a GUI object for the given :class:`.ViewItem` object and, if it is a group, all of its children. """ createFunc = _getCreateFunction(viewItem.__class__) if createFunc is None: raise ValueError('Unrecognised ViewItem: {}'.format( viewItem.__class__.__name__)) guiObject = createFunc(parent, viewItem, hasProps, propGui) _configureVisibleWhen(viewItem, guiObject, hasProps, propGui) _configureEnabledWhen(viewItem, guiObject, hasProps, propGui) if viewItem.tooltip is not None: # Add the tooltip to the GUI object, and # also do so recursively to any children def setToolTip(obj): obj.SetToolTip(wx.ToolTip(viewItem.tooltip)) children = obj.GetChildren() for c in children: setToolTip(c) setToolTip(guiObject) if viewItem.setup is not None: viewItem.setup(hasProps, parent, guiObject) propGui.guiObjects[viewItem.key] = guiObject return guiObject def _defaultView(hasProps): """Creates a default view specification for the given :class:`.HasProperties` object, with all properties laid out vertically. This function is only called if a view specification was not provided in the call to the :func:`buildGUI` function """ propNames, propObjs = hasProps.getAllProperties() widgets = [parts.Widget(name, label=name) for name in propNames] return parts.VGroup(label=hasProps.__class__.__name__, children=widgets) def _prepareView(hasProps, viewItem, labels, tooltips, showUnlink): """Recursively steps through the given ``viewItem`` and its children (if any). If the ``viewItem`` is a string, it is assumed to be a property name, and it is turned into a :class:`.Widget` object. If the ``viewItem`` does not have a label/tooltip, and there is a label/tooltip for it in the given labels/tooltips dict, then its label/tooltip is set. Returns a reference to the updated/newly created :class:`ViewItem`. """ if isinstance(viewItem, str): viewItem = parts.Widget(viewItem) if not isinstance(viewItem, parts.ViewItem): raise ValueError('Not a ViewItem') if viewItem.label is None: viewItem.label = labels .get(viewItem.key, viewItem.key) if viewItem.tooltip is None: viewItem.tooltip = tooltips.get(viewItem.key, None) if isinstance(viewItem, parts.Group): # children may have been specified as a tuple, # so we cast it to a list, making it mutable viewItem.children = list(viewItem.children) viewItem.childLabels = [] for i, child in enumerate(viewItem.children): viewItem.children[i] = _prepareView(hasProps, child, labels, tooltips, showUnlink) # Create a Label object for each # child of this group if necessary for child in viewItem.children: # unless no labels are to be shown # for the items in this group mkLabel = viewItem.showLabels # or there is no label specified for this child mkLabel = mkLabel and (child.label is not None) # or this child is a group with a border mkLabel = mkLabel and \ not (isinstance(child, parts.Group) and child.border) # unless there is no label specified if mkLabel: viewItem.childLabels.append(parts.Label(child)) else: viewItem.childLabels.append(None) # Add link/unlink checkboxes if necessary elif (showUnlink and isinstance(viewItem, parts.Widget) and isinstance(hasProps, syncable.SyncableHasProperties) and hasProps.getParent() is not None): linkBox = parts.LinkBox(viewItem) viewItem = parts.HGroup((linkBox, viewItem), showLabels=False, label=viewItem.label) return viewItem def _finaliseCallbacks(hasProps, propGui): """Calls all defined :class:`.ViewItem` ``visibleWhen`` and ``enabledWhen`` callback functions, in order to set the initial GUI state. Also registers a listener on the top level GUI panel to remove all property listeners from the ``hasProps`` instance when the panel is destroyed. """ if len(propGui.onChangeCallbacks) == 0: return # A first call to all of the # visibleWhen/enabledWhen # functions, so the initial # GUI state is valid def onShow(ev=None): # If the top level GUI panel is not yet # visible, reschedule this function to be # called later # onShow directly. Otherwise, schedule it # to be called on the first paint. if not propGui.topLevel.IsShownOnScreen(): wx.CallLater(250, onShow) return # We only want this function # to be called once, so on the # first call, deregister the # wx event listener if ev is not None: ev.Skip() propGui.topLevel.Unbind(wx.EVT_PAINT) for _, n, _, callback in propGui.onChangeCallbacks: callback() # De-register all property # listeners when the # top level panel/frame # is destroyed def onDestroy(ev): ev.Skip() for targets, propNames, lName, callback in propGui.onChangeCallbacks: for target, propName in zip(targets, propNames): # target is a weakref - it may # have been GC'd, in which case # we don't need to remove property # listeners from it target = target() if target is None: continue log.debug('Removing listener from {}.{}'.format( type(target).__name__, propName)) # If propName is none, it's a # global property listener if propName is None: target.removeGlobalListener(lName) else: target.removeListener(propName, lName) propGui.onChangeCallbacks = None propGui.topLevel = None propGui.guiObjects = None propGui.topLevel.Bind(wx.EVT_WINDOW_DESTROY, onDestroy) onShow()
[docs] def buildGUI(parent, hasProps, view=None, labels=None, tooltips=None, showUnlink=False): """Builds a GUI interface which allows the properties of the given :class:`.HasProperties` object to be edited. Returns a reference to the top level GUI object (typically a :class:`wx.Frame`, :class:`wx.Panel` or :class:`~fsleyes_widgets.notebook.Notebook`). Parameters: :param parent: The parent GUI object. If ``None``, the interface is embedded within a :class:`wx.Frame`. :param hasProps: The :class:`.HasProperties` instance. :param view: A :class:`.ViewItem` object, specifying the interface layout. :param labels: A dictionary specifying labels. :param tooltips: A dictionary specifying tooltips. :param showUnlink: If the given ``hasProps`` instance is a :class:`.SyncableHasProperties` instance, and it has a parent, a 'link/unlink' checkbox will be shown next to any properties that can be bound/unbound from the parent object. """ if view is None: if hasattr(hasProps, '_view'): view = hasProps._view else: view = _defaultView(hasProps) if labels is None: if hasattr(hasProps, '_labels'): labels = hasProps._labels else: labels = {} if tooltips is None: if hasattr(hasProps, '_tooltips'): tooltips = hasProps._tooltips else: tooltips = {} if parent is None: parentObj = wx.Frame(None) else: parentObj = parent view = copy.deepcopy(view) propGui = PropGUI() view = _prepareView(hasProps, view, labels, tooltips, showUnlink) log.debug('Creating GUI for {} from view: \n{}'.format( type(hasProps).__name__, view)) mainPanel = _create(parentObj, view, hasProps, propGui) propGui.topLevel = mainPanel _finaliseCallbacks(hasProps, propGui) # TODO return the propGui object, so the caller # has access to all of the GUI objects that were # created, via the propGui.guiObjects dict. ?? if parent is None: parentObj.Layout() parentObj.Fit() return parentObj else: return mainPanel
[docs] def buildDialog(parent, hasProps, view=None, labels=None, tooltips=None, showUnlink=False, dlgButtons=True): """Convenience method which embeds the result of a call to :func:`buildGUI` in a :class:`wx.Dialog`. See the :func:`buildGUI` documentation for details on the paramters. :arg dlgButtons: If ``True``, the dialog will have 'Ok' and 'Cancel' buttons. """ dialog = wx.Dialog(parent, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) panel = buildGUI(dialog, hasProps, view, labels, tooltips, showUnlink) sizer = wx.BoxSizer(wx.VERTICAL) dialog.SetSizer(sizer) sizer.Add(panel, flag=wx.EXPAND, proportion=1) if dlgButtons: ok = wx.Button(dialog, wx.ID_OK, label='Ok') cancel = wx.Button(dialog, wx.ID_CANCEL, label='Cancel') btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer.Add((10, 1), flag=wx.EXPAND, proportion=1) btnSizer.Add(ok, flag=wx.EXPAND) btnSizer.Add((10, 1), flag=wx.EXPAND) btnSizer.Add(cancel, flag=wx.EXPAND) btnSizer.Add((10, 1), flag=wx.EXPAND, proportion=1) sizer.Add((1, 10), flag=wx.EXPAND) sizer.Add(btnSizer, flag=wx.EXPAND) sizer.Add((1, 10), flag=wx.EXPAND) dialog.Layout() dialog.Fit() return dialog