Source code for fsleyes.controls.overlaydisplaypanel

#
# overlaydisplaypanel.py - The OverlayDisplayPanel.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>

"""This module provides the :class:`OverlayDisplayPanel` class, a *FSLeyes
control* panel which allows the user to change overlay display settings.
"""


import logging
import functools
import collections
import collections.abc as abc

import fsleyes_props                  as props

import fsleyes.controls.controlpanel  as ctrlpanel
import fsleyes.strings                as strings
import fsleyes.tooltips               as fsltooltips

from . import overlaydisplaywidgets   as odwidgets


log = logging.getLogger(__name__)


[docs]class OverlayDisplayPanel(ctrlpanel.SettingsPanel): """The ``OverlayDisplayPanel`` is a :class:`.SettingsPanel` which allows the user to change the display settings of the currently selected overlay (which is defined by the :attr:`.DisplayContext.selectedOverlay` property). The display settings for an overlay are contained in the :class:`.Display` and :class:`.DisplayOpts` instances associated with that overlay. An ``OverlayDisplayPanel`` looks something like the following: .. image:: images/overlaydisplaypanel.png :scale: 50% :align: center An ``OverlayDisplayPanel`` uses a :class:`.WidgetList` to organise the settings into two main sections: - Settings which are common across all overlays - these are defined in the :class:`.Display` class. - Settings which are specific to the current :attr:`.Display.overlayType` - these are defined in the :class:`.DisplayOpts` sub-classes. The settings that are displayed on an ``OverlayDisplayPanel`` are defined in the :attr:`_DISPLAY_PROPS` and :attr:`_DISPLAY_WIDGETS` dictionaries. """
[docs] def __init__(self, parent, overlayList, displayCtx, frame): """Create an ``OverlayDisplayPanel``. :arg parent: The :mod:`wx` parent object. :arg overlayList: The :class:`.OverlayList` instance. :arg displayCtx: The :class:`.DisplayContext` instance. :arg frame: The :class:`.FSLeyesFrame` instance. """ from fsleyes.views.scene3dpanel import Scene3DPanel ctrlpanel.SettingsPanel.__init__(self, parent, overlayList, displayCtx, frame, kbFocus=True) displayCtx .addListener('selectedOverlay', self.name, self.__selectedOverlayChanged) overlayList.addListener('overlays', self.name, self.__selectedOverlayChanged) self.__threedee = isinstance(parent, Scene3DPanel) self.__viewPanel = parent self.__widgets = None self.__currentOverlay = None self.__selectedOverlayChanged()
[docs] def destroy(self): """Must be called when this ``OverlayDisplayPanel`` is no longer needed. Removes property listeners, and calls the :meth:`.SettingsPanel.destroy` method. """ self.displayCtx .removeListener('selectedOverlay', self.name) self.overlayList.removeListener('overlays', self.name) if self.__currentOverlay is not None and \ self.__currentOverlay in self.overlayList: display = self.displayCtx.getDisplay(self.__currentOverlay) display.removeListener('overlayType', self.name) self.__viewPanel = None self.__widgets = None self.__currentOverlay = None ctrlpanel.SettingsPanel.destroy(self)
[docs] @staticmethod def supportedViews(): """Overrides :meth:`.ControlMixin.supportedViews`. The ``OverlayDisplayPanel`` is only intended to be added to :class:`.OrthoPanel`, :class:`.LightBoxPanel`, or :class:`.Scene3DPanel` views. """ from fsleyes.views.orthopanel import OrthoPanel from fsleyes.views.lightboxpanel import LightBoxPanel from fsleyes.views.scene3dpanel import Scene3DPanel return [OrthoPanel, LightBoxPanel, Scene3DPanel]
def __selectedOverlayChanged(self, *a): """Called when the :class:`.OverlayList` or :attr:`.DisplayContext.selectedOverlay` changes. Refreshes this ``OverlayDisplayPanel`` so that the display settings for the newly selected overlay are shown. """ overlay = self.displayCtx.getSelectedOverlay() lastOverlay = self.__currentOverlay widgetList = self.getWidgetList() if overlay is None: self.__currentOverlay = None self.__widgets = None widgetList.Clear() self.Layout() return if overlay is lastOverlay: return self.__currentOverlay = overlay self.__widgets = collections.OrderedDict() display = self.displayCtx.getDisplay(overlay) opts = display.opts if self.__threedee: groups = ['display', 'opts', '3d'] targets = [ display, opts, opts] labels = [strings.labels[self, display], strings.labels[self, opts], strings.labels[self, '3d']] else: groups = ['display', 'opts'] targets = [ display, opts] labels = [strings.labels[self, display], strings.labels[self, opts]] keepExpanded = {g : True for g in groups} if lastOverlay is not None and lastOverlay in self.overlayList: lastDisplay = self.displayCtx.getDisplay(lastOverlay) lastDisplay.removeListener('overlayType', self.name) if lastOverlay is not None: for g in groups: keepExpanded[g] = widgetList.IsExpanded(g) display.addListener('overlayType', self.name, self.__ovlTypeChanged) widgetList.Clear() for g, l, t in zip(groups, labels, targets): widgetList.AddGroup(g, l) self.__widgets[g] = self.__updateWidgets(t, g) widgetList.Expand(g, keepExpanded[g]) self.setNavOrder() self.Layout()
[docs] def setNavOrder(self): allWidgets = self.__widgets.items() allWidgets = functools.reduce(lambda a, b: a + b, allWidgets) ctrlpanel.SettingsPanel.setNavOrder(self, allWidgets)
def __ovlTypeChanged(self, *a): """Called when the :attr:`.Display.overlayType` of the current overlay changes. Refreshes the :class:`.DisplayOpts` settings which are shown, as a new :class:`.DisplayOpts` instance will have been created for the overlay. """ opts = self.displayCtx.getOpts(self.__currentOverlay) widgetList = self.getWidgetList() self.__widgets[opts] = self.__updateWidgets(opts, 'opts') widgetList.RenameGroup('opts', strings.labels[self, opts]) if '3d' in self.__widgets: self.__widgets['3d'] = self.__updateWidgets(opts, '3d') self.setNavOrder() self.Layout()
[docs] def updateWidgets(self, target, groupName): """Re-generates the widgets for the given target/group. """ self.__widgets[target] = self.__updateWidgets(target, groupName) self.setNavOrder() self.Layout()
def __updateWidgets(self, target, groupName): """Called by the :meth:`__selectedOverlayChanged` and :meth:`__ovlTypeChanged` methods. Re-creates the controls on this ``OverlayDisplayPanel`` for the specified group. :arg target: A :class:`.Display` or :class:`.DisplayOpts` instance, which contains the properties that controls are to be created for. :arg groupName: Either ``'display'`` or ``'opts'``/``'3d'``, corresponding to :class:`.Display` or :class:`.DisplayOpts` properties. :returns: A list containing all of the new widgets that were created. """ widgetList = self.getWidgetList() widgetList.ClearGroup(groupName) if groupName == '3d': dispProps = odwidgets.get3DPropertyList(target) dispSpecs = odwidgets.get3DWidgetSpecs( target) else: dispProps = odwidgets.getPropertyList(target, self.__threedee) dispSpecs = odwidgets.getWidgetSpecs( target, self.__threedee) allLabels = [] allTooltips = [] allWidgets = [] allContainers = [] for p in dispProps: spec = dispSpecs[p] specs = [spec] labels = [strings .properties.get((target, p), None)] tooltips = [fsltooltips.properties.get((target, p), None)] if callable(spec): # Will either return a contsiner # widget/sizer and a list of widgets # for setting the navigation order, # or will return a list of specs # (with an irrelevant second parameter) container, widgets = spec( target, widgetList, self, self.overlayList, self.displayCtx, self.__threedee) if isinstance(container, abc.Sequence): specs = container keys = [s.key for s in specs] labels = [strings.properties.get((target, k), None) for k in keys] tooltips = [fsltooltips.properties.get((target, k), None) for k in keys] else: allContainers.append(container) allWidgets .extend(widgets) specs = [] for s in specs: widget = props.buildGUI(widgetList, target, s) allWidgets .append(widget) allContainers.append(widget) allLabels .extend(labels) allTooltips.extend(tooltips) for widget, label, tooltip in zip(allContainers, allLabels, allTooltips): if label is None: label = '' widgetList.AddWidget( widget, label, tooltip=tooltip, groupName=groupName) return allWidgets