summaryrefslogtreecommitdiff
path: root/silx/gui/plot3d/SFViewParamTree.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot3d/SFViewParamTree.py')
-rw-r--r--silx/gui/plot3d/SFViewParamTree.py1817
1 files changed, 0 insertions, 1817 deletions
diff --git a/silx/gui/plot3d/SFViewParamTree.py b/silx/gui/plot3d/SFViewParamTree.py
deleted file mode 100644
index 4e179fc..0000000
--- a/silx/gui/plot3d/SFViewParamTree.py
+++ /dev/null
@@ -1,1817 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2015-2020 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""
-This module provides a tree widget to set/view parameters of a ScalarFieldView.
-"""
-
-from __future__ import absolute_import
-
-__authors__ = ["D. N."]
-__license__ = "MIT"
-__date__ = "24/04/2018"
-
-import logging
-import sys
-import weakref
-
-import numpy
-
-from silx.gui import qt
-from silx.gui.icons import getQIcon
-from silx.gui.colors import Colormap
-from silx.gui.widgets.FloatEdit import FloatEdit
-
-from .ScalarFieldView import Isosurface
-
-
-_logger = logging.getLogger(__name__)
-
-
-class ModelColumns(object):
- NameColumn, ValueColumn, ColumnMax = range(3)
- ColumnNames = ['Name', 'Value']
-
-
-class SubjectItem(qt.QStandardItem):
- """
- Base class for observers items.
-
- Subclassing:
- ------------
- The following method can/should be reimplemented:
- - _init
- - _pullData
- - _pushData
- - _setModelData
- - _subjectChanged
- - getEditor
- - getSignals
- - leftClicked
- - queryRemove
- - setEditorData
-
- Also the following attributes are available:
- - editable
- - persistent
-
- :param subject: object that this item will be observing.
- """
-
- editable = False
- """ boolean: set to True to make the item editable. """
-
- persistent = False
- """
- boolean: set to True to make the editor persistent.
- See : Qt.QAbstractItemView.openPersistentEditor
- """
-
- def __init__(self, subject, *args):
-
- super(SubjectItem, self).__init__(*args)
-
- self.setEditable(self.editable)
-
- self.__subject = None
- self.subject = subject
-
- def setData(self, value, role=qt.Qt.UserRole, pushData=True):
- """
- Overloaded method from QStandardItem. The pushData keyword tells
- the item to push data to the subject if the role is equal to EditRole.
- This is useful to let this method know if the setData method was called
- internally or from the view.
-
- :param value: the value ti set to data
- :param role: role in the item
- :param pushData: if True push value in the existing data.
- """
- if role == qt.Qt.EditRole and pushData:
- setValue = self._pushData(value, role)
- if setValue != value:
- value = setValue
- super(SubjectItem, self).setData(value, role)
-
- @property
- def subject(self):
- """The subject this item is observing"""
- return None if self.__subject is None else self.__subject()
-
- @subject.setter
- def subject(self, subject):
- if self.__subject is not None:
- raise ValueError('Subject already set '
- ' (subject change not supported).')
- if subject is None:
- self.__subject = None
- else:
- self.__subject = weakref.ref(subject)
- if subject is not None:
- self._init()
- self._connectSignals()
-
- def _connectSignals(self):
- """
- Connects the signals. Called when the subject is set.
- """
-
- def gen_slot(_sigIdx):
- def slotfn(*args, **kwargs):
- self._subjectChanged(signalIdx=_sigIdx,
- args=args,
- kwargs=kwargs)
- return slotfn
-
- if self.__subject is not None:
- self.__slots = slots = []
-
- signals = self.getSignals()
-
- if signals:
- if not isinstance(signals, (list, tuple)):
- signals = [signals]
- for sigIdx, signal in enumerate(signals):
- slot = gen_slot(sigIdx)
- signal.connect(slot)
- slots.append((signal, slot))
-
- def _disconnectSignals(self):
- """
- Disconnects all subject's signal
- """
- if self.__slots:
- for signal, slot in self.__slots:
- try:
- signal.disconnect(slot)
- except TypeError:
- pass
-
- def _enableRow(self, enable):
- """
- Set the enabled state for this cell, or for the whole row
- if this item has a parent.
-
- :param bool enable: True if we wan't to enable the cell
- """
- parent = self.parent()
- model = self.model()
- if model is None or parent is None:
- # no parent -> no siblings
- self.setEnabled(enable)
- return
-
- for col in range(model.columnCount()):
- sibling = parent.child(self.row(), col)
- sibling.setEnabled(enable)
-
- #################################################################
- # Overloadable methods
- #################################################################
-
- def getSignals(self):
- """
- Returns the list of this items subject's signals that
- this item will be listening to.
-
- :return: list.
- """
- return None
-
- def _subjectChanged(self, signalIdx=None, args=None, kwargs=None):
- """
- Called when one of the signals is triggered. Default implementation
- just calls _pullData, compares the result to the current value stored
- as Qt.EditRole, and stores the new value if it is different. It also
- stores its str representation as Qt.DisplayRole
-
- :param signalIdx: index of the triggered signal. The value passed
- is the same as the signal position in the list returned by
- SubjectItem.getSignals.
- :param args: arguments received from the signal
- :param kwargs: keyword arguments received from the signal
- """
- data = self._pullData()
- if data == self.data(qt.Qt.EditRole):
- return
- self.setData(data, role=qt.Qt.DisplayRole, pushData=False)
- self.setData(data, role=qt.Qt.EditRole, pushData=False)
-
- def _pullData(self):
- """
- Pulls data from the subject.
-
- :return: subject data
- """
- return None
-
- def _pushData(self, value, role=qt.Qt.UserRole):
- """
- Pushes data to the subject and returns the actual value that was stored
-
- :return: the value that was stored
- """
- return value
-
- def _init(self):
- """
- Called when the subject is set.
- :return:
- """
- self._subjectChanged()
-
- def getEditor(self, parent, option, index):
- """
- Returns the editor widget used to edit this item's data. The arguments
- are the one passed to the QStyledItemDelegate.createEditor method.
-
- :param parent: the Qt parent of the editor
- :param option:
- :param index:
- :return:
- """
- return None
-
- def setEditorData(self, editor):
- """
- This is called by the View's delegate just before the editor is shown,
- its purpose it to setup the editors contents. Return False to use
- the delegate's default behaviour.
-
- :param editor:
- :return:
- """
- return True
-
- def _setModelData(self, editor):
- """
- This is called by the View's delegate just before the editor is closed,
- its allows this item to update itself with data from the editor.
-
- :param editor:
- :return:
- """
- return False
-
- def queryRemove(self, view=None):
- """
- This is called by the view to ask this items if it (the view) can
- remove it. Return True to let the view know that the item can be
- removed.
-
- :param view:
- :return:
- """
- return False
-
- def leftClicked(self):
- """
- This method is called by the view when the item's cell if left clicked.
-
- :return:
- """
- pass
-
-
-# View settings ###############################################################
-
-class ColorItem(SubjectItem):
- """color item."""
- editable = True
- persistent = True
-
- def getEditor(self, parent, option, index):
- editor = QColorEditor(parent)
- editor.color = self.getColor()
-
- # Wrapping call in lambda is a workaround for PySide with Python 3
- editor.sigColorChanged.connect(
- lambda color: self._editorSlot(color))
- return editor
-
- def _editorSlot(self, color):
- self.setData(color, qt.Qt.EditRole)
-
- def _pushData(self, value, role=qt.Qt.UserRole):
- self.setColor(value)
- return self.getColor()
-
- def _pullData(self):
- self.getColor()
-
- def setColor(self, color):
- """Override to implement actual color setter"""
- pass
-
-
-class BackgroundColorItem(ColorItem):
- itemName = 'Background'
-
- def setColor(self, color):
- self.subject.setBackgroundColor(color)
-
- def getColor(self):
- return self.subject.getBackgroundColor()
-
-
-class ForegroundColorItem(ColorItem):
- itemName = 'Foreground'
-
- def setColor(self, color):
- self.subject.setForegroundColor(color)
-
- def getColor(self):
- return self.subject.getForegroundColor()
-
-
-class HighlightColorItem(ColorItem):
- itemName = 'Highlight'
-
- def setColor(self, color):
- self.subject.setHighlightColor(color)
-
- def getColor(self):
- return self.subject.getHighlightColor()
-
-
-class _LightDirectionAngleBaseItem(SubjectItem):
- """Base class for directional light angle item."""
- editable = True
- persistent = True
-
- def _init(self):
- pass
-
- def getSignals(self):
- """Override to provide signals to listen"""
- raise NotImplementedError("MUST be implemented in subclass")
-
- def _pullData(self):
- """Override in subclass to get current angle"""
- raise NotImplementedError("MUST be implemented in subclass")
-
- def _pushData(self, value, role=qt.Qt.UserRole):
- """Override in subclass to set the angle"""
- raise NotImplementedError("MUST be implemented in subclass")
-
- def getEditor(self, parent, option, index):
- editor = qt.QSlider(parent)
- editor.setOrientation(qt.Qt.Horizontal)
- editor.setMinimum(-90)
- editor.setMaximum(90)
- editor.setValue(int(self._pullData()))
-
- # Wrapping call in lambda is a workaround for PySide with Python 3
- editor.valueChanged.connect(
- lambda value: self._pushData(value))
-
- return editor
-
- def setEditorData(self, editor):
- editor.setValue(int(self._pullData()))
- return True
-
- def _setModelData(self, editor):
- value = editor.value()
- self._pushData(value)
- return True
-
-
-class LightAzimuthAngleItem(_LightDirectionAngleBaseItem):
- """Light direction azimuth angle item."""
-
- def getSignals(self):
- return self.subject.sigAzimuthAngleChanged
-
- def _pullData(self):
- return self.subject.getAzimuthAngle()
-
- def _pushData(self, value, role=qt.Qt.UserRole):
- self.subject.setAzimuthAngle(value)
-
-
-class LightAltitudeAngleItem(_LightDirectionAngleBaseItem):
- """Light direction altitude angle item."""
-
- def getSignals(self):
- return self.subject.sigAltitudeAngleChanged
-
- def _pullData(self):
- return self.subject.getAltitudeAngle()
-
- def _pushData(self, value, role=qt.Qt.UserRole):
- self.subject.setAltitudeAngle(value)
-
-
-class _DirectionalLightProxy(qt.QObject):
- """Proxy to handle directional light with angles rather than vector.
- """
-
- sigAzimuthAngleChanged = qt.Signal()
- """Signal sent when the azimuth angle has changed."""
-
- sigAltitudeAngleChanged = qt.Signal()
- """Signal sent when altitude angle has changed."""
-
- def __init__(self, light):
- super(_DirectionalLightProxy, self).__init__()
- self._light = light
- light.addListener(self._directionUpdated)
- self._azimuth = 0.
- self._altitude = 0.
-
- def getAzimuthAngle(self):
- """Returns the signed angle in the horizontal plane.
-
- Unit: degrees.
- The 0 angle corresponds to the axis perpendicular to the screen.
-
- :rtype: float
- """
- return self._azimuth
-
- def getAltitudeAngle(self):
- """Returns the signed vertical angle from the horizontal plane.
-
- Unit: degrees.
- Range: [-90, +90]
-
- :rtype: float
- """
- return self._altitude
-
- def setAzimuthAngle(self, angle):
- """Set the horizontal angle.
-
- :param float angle: Angle from -z axis in zx plane in degrees.
- """
- if angle != self._azimuth:
- self._azimuth = angle
- self._updateLight()
- self.sigAzimuthAngleChanged.emit()
-
- def setAltitudeAngle(self, angle):
- """Set the horizontal angle.
-
- :param float angle: Angle from -z axis in zy plane in degrees.
- """
- if angle != self._altitude:
- self._altitude = angle
- self._updateLight()
- self.sigAltitudeAngleChanged.emit()
-
- def _directionUpdated(self, *args, **kwargs):
- """Handle light direction update in the scene"""
- # Invert direction to manipulate the 'source' pointing to
- # the center of the viewport
- x, y, z = - self._light.direction
-
- # Horizontal plane is plane xz
- azimuth = numpy.degrees(numpy.arctan2(x, z))
- altitude = numpy.degrees(numpy.pi/2. - numpy.arccos(y))
-
- if (abs(azimuth - self.getAzimuthAngle()) > 0.01 and
- abs(abs(altitude) - 90.) >= 0.001): # Do not update when at zenith
- self.setAzimuthAngle(azimuth)
-
- if abs(altitude - self.getAltitudeAngle()) > 0.01:
- self.setAltitudeAngle(altitude)
-
- def _updateLight(self):
- """Update light direction in the scene"""
- azimuth = numpy.radians(self._azimuth)
- delta = numpy.pi/2. - numpy.radians(self._altitude)
- z = - numpy.sin(delta) * numpy.cos(azimuth)
- x = - numpy.sin(delta) * numpy.sin(azimuth)
- y = - numpy.cos(delta)
- self._light.direction = x, y, z
-
-
-class DirectionalLightGroup(SubjectItem):
- """
- Root Item for the directional light
- """
-
- def __init__(self,subject, *args):
- self._light = _DirectionalLightProxy(
- subject.getPlot3DWidget().viewport.light)
-
- super(DirectionalLightGroup, self).__init__(subject, *args)
-
- def _init(self):
-
- nameItem = qt.QStandardItem('Azimuth')
- nameItem.setEditable(False)
- valueItem = LightAzimuthAngleItem(self._light)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Altitude')
- nameItem.setEditable(False)
- valueItem = LightAltitudeAngleItem(self._light)
- self.appendRow([nameItem, valueItem])
-
-
-class BoundingBoxItem(SubjectItem):
- """Bounding box, axes labels and grid visibility item.
-
- Item is checkable.
- """
- itemName = 'Bounding Box'
-
- def _init(self):
- visible = self.subject.isBoundingBoxVisible()
- self.setCheckable(True)
- self.setCheckState(qt.Qt.Checked if visible else qt.Qt.Unchecked)
-
- def leftClicked(self):
- checked = (self.checkState() == qt.Qt.Checked)
- if checked != self.subject.isBoundingBoxVisible():
- self.subject.setBoundingBoxVisible(checked)
-
-
-class OrientationIndicatorItem(SubjectItem):
- """Orientation indicator visibility item.
-
- Item is checkable.
- """
- itemName = 'Axes indicator'
-
- def _init(self):
- plot3d = self.subject.getPlot3DWidget()
- visible = plot3d.isOrientationIndicatorVisible()
- self.setCheckable(True)
- self.setCheckState(qt.Qt.Checked if visible else qt.Qt.Unchecked)
-
- def leftClicked(self):
- plot3d = self.subject.getPlot3DWidget()
- checked = (self.checkState() == qt.Qt.Checked)
- if checked != plot3d.isOrientationIndicatorVisible():
- plot3d.setOrientationIndicatorVisible(checked)
-
-
-class ViewSettingsItem(qt.QStandardItem):
- """Viewport settings"""
-
- def __init__(self, subject, *args):
-
- super(ViewSettingsItem, self).__init__(*args)
-
- self.setEditable(False)
-
- classes = (BackgroundColorItem,
- ForegroundColorItem,
- HighlightColorItem,
- BoundingBoxItem,
- OrientationIndicatorItem)
- for cls in classes:
- titleItem = qt.QStandardItem(cls.itemName)
- titleItem.setEditable(False)
- self.appendRow([titleItem, cls(subject)])
-
- nameItem = DirectionalLightGroup(subject, 'Light Direction')
- valueItem = qt.QStandardItem()
- self.appendRow([nameItem, valueItem])
-
-
-# Data information ############################################################
-
-class DataChangedItem(SubjectItem):
- """
- Base class for items listening to ScalarFieldView.sigDataChanged
- """
-
- def getSignals(self):
- subject = self.subject
- if subject:
- return subject.sigDataChanged, subject.sigTransformChanged
- return None
-
- def _init(self):
- self._subjectChanged()
-
-
-class DataTypeItem(DataChangedItem):
- itemName = 'dtype'
-
- def _pullData(self):
- data = self.subject.getData(copy=False)
- return ((data is not None) and str(data.dtype)) or 'N/A'
-
-
-class DataShapeItem(DataChangedItem):
- itemName = 'size'
-
- def _pullData(self):
- data = self.subject.getData(copy=False)
- if data is None:
- return 'N/A'
- else:
- return str(list(reversed(data.shape)))
-
-
-class OffsetItem(DataChangedItem):
- itemName = 'offset'
-
- def _pullData(self):
- offset = self.subject.getTranslation()
- return ((offset is not None) and str(offset)) or 'N/A'
-
-
-class ScaleItem(DataChangedItem):
- itemName = 'scale'
-
- def _pullData(self):
- scale = self.subject.getScale()
- return ((scale is not None) and str(scale)) or 'N/A'
-
-
-class MatrixItem(DataChangedItem):
-
- def __init__(self, subject, row, *args):
- self.__row = row
- super(MatrixItem, self).__init__(subject, *args)
-
- def _pullData(self):
- matrix = self.subject.getTransformMatrix()
- return str(matrix[self.__row])
-
-
-class DataSetItem(qt.QStandardItem):
-
- def __init__(self, subject, *args):
-
- super(DataSetItem, self).__init__(*args)
-
- self.setEditable(False)
-
- klasses = [DataTypeItem, DataShapeItem, OffsetItem]
- for klass in klasses:
- titleItem = qt.QStandardItem(klass.itemName)
- titleItem.setEditable(False)
- self.appendRow([titleItem, klass(subject)])
-
- matrixItem = qt.QStandardItem('matrix')
- matrixItem.setEditable(False)
- valueItem = qt.QStandardItem()
- self.appendRow([matrixItem, valueItem])
-
- for row in range(3):
- titleItem = qt.QStandardItem()
- titleItem.setEditable(False)
- valueItem = MatrixItem(subject, row)
- matrixItem.appendRow([titleItem, valueItem])
-
- titleItem = qt.QStandardItem(ScaleItem.itemName)
- titleItem.setEditable(False)
- self.appendRow([titleItem, ScaleItem(subject)])
-
-
-# Isosurface ##################################################################
-
-class IsoSurfaceRootItem(SubjectItem):
- """
- Root (i.e : column index 0) Isosurface item.
- """
-
- def __init__(self, subject, normalization, *args):
- self._isoLevelSliderNormalization = normalization
- super(IsoSurfaceRootItem, self).__init__(subject, *args)
-
- def getSignals(self):
- subject = self.subject
- return [subject.sigColorChanged,
- subject.sigVisibilityChanged]
-
- def _subjectChanged(self, signalIdx=None, args=None, kwargs=None):
- if signalIdx == 0:
- color = self.subject.getColor()
- self.setData(color, qt.Qt.DecorationRole)
- elif signalIdx == 1:
- visible = args[0]
- self.setCheckState((visible and qt.Qt.Checked) or qt.Qt.Unchecked)
-
- def _init(self):
- self.setCheckable(True)
-
- isosurface = self.subject
- color = isosurface.getColor()
- visible = isosurface.isVisible()
- self.setData(color, qt.Qt.DecorationRole)
- self.setCheckState((visible and qt.Qt.Checked) or qt.Qt.Unchecked)
-
- nameItem = qt.QStandardItem('Level')
- sliderItem = IsoSurfaceLevelSlider(self.subject,
- self._isoLevelSliderNormalization)
- self.appendRow([nameItem, sliderItem])
-
- nameItem = qt.QStandardItem('Color')
- nameItem.setEditable(False)
- valueItem = IsoSurfaceColorItem(self.subject)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Opacity')
- nameItem.setTextAlignment(qt.Qt.AlignLeft | qt.Qt.AlignTop)
- nameItem.setEditable(False)
- valueItem = IsoSurfaceAlphaItem(self.subject)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem()
- nameItem.setEditable(False)
- valueItem = IsoSurfaceAlphaLegendItem(self.subject)
- valueItem.setEditable(False)
- self.appendRow([nameItem, valueItem])
-
- def queryRemove(self, view=None):
- buttons = qt.QMessageBox.Ok | qt.QMessageBox.Cancel
- ans = qt.QMessageBox.question(view,
- 'Remove isosurface',
- 'Remove the selected iso-surface?',
- buttons=buttons)
- if ans == qt.QMessageBox.Ok:
- sfview = self.subject.parent()
- if sfview:
- sfview.removeIsosurface(self.subject)
- return False
- return False
-
- def leftClicked(self):
- checked = (self.checkState() == qt.Qt.Checked)
- visible = self.subject.isVisible()
- if checked != visible:
- self.subject.setVisible(checked)
-
-
-class IsoSurfaceLevelItem(SubjectItem):
- """
- Base class for the isosurface level items.
- """
- editable = True
-
- def getSignals(self):
- subject = self.subject
- return [subject.sigLevelChanged,
- subject.sigVisibilityChanged]
-
- def getEditor(self, parent, option, index):
- return FloatEdit(parent)
-
- def setEditorData(self, editor):
- editor.setValue(self._pullData())
- return False
-
- def _setModelData(self, editor):
- self._pushData(editor.value())
- return True
-
- def _pullData(self):
- return self.subject.getLevel()
-
- def _pushData(self, value, role=qt.Qt.UserRole):
- self.subject.setLevel(value)
- return self.subject.getLevel()
-
-
-class _IsoLevelSlider(qt.QSlider):
- """QSlider used for iso-surface level with linear scale"""
-
- def __init__(self, parent, subject, normalization):
- super(_IsoLevelSlider, self).__init__(parent=parent)
- self.subject = subject
-
- if normalization == 'arcsinh':
- self.__norm = numpy.arcsinh
- self.__invNorm = numpy.sinh
- elif normalization == 'linear':
- self.__norm = lambda x: x
- self.__invNorm = lambda x: x
- else:
- raise ValueError(
- "Unsupported normalization %s", normalization)
-
- self.sliderReleased.connect(self.__sliderReleased)
-
- self.subject.sigLevelChanged.connect(self.setLevel)
- self.subject.parent().sigDataChanged.connect(self.__dataChanged)
-
- def setLevel(self, level):
- """Set slider from iso-surface level"""
- dataRange = self.subject.parent().getDataRange()
-
- if dataRange is not None:
- min_ = self.__norm(dataRange[0])
- max_ = self.__norm(dataRange[-1])
-
- width = max_ - min_
- if width > 0:
- sliderWidth = self.maximum() - self.minimum()
- sliderPosition = sliderWidth * (self.__norm(level) - min_) / width
- self.setValue(int(sliderPosition))
-
- def __dataChanged(self):
- """Handles data update to refresh slider range if needed"""
- self.setLevel(self.subject.getLevel())
-
- def __sliderReleased(self):
- value = self.value()
- dataRange = self.subject.parent().getDataRange()
- if dataRange is not None:
- min_ = self.__norm(dataRange[0])
- max_ = self.__norm(dataRange[-1])
- width = max_ - min_
- sliderWidth = self.maximum() - self.minimum()
- level = min_ + width * value / sliderWidth
- self.subject.setLevel(self.__invNorm(level))
-
-
-class IsoSurfaceLevelSlider(IsoSurfaceLevelItem):
- """
- Isosurface level item with a slider editor.
- """
- nTicks = 1000
- persistent = True
-
- def __init__(self, subject, normalization):
- self.normalization = normalization
- super(IsoSurfaceLevelSlider, self).__init__(subject)
-
- def getEditor(self, parent, option, index):
- editor = _IsoLevelSlider(parent, self.subject, self.normalization)
- editor.setOrientation(qt.Qt.Horizontal)
- editor.setMinimum(0)
- editor.setMaximum(self.nTicks)
-
- editor.setSingleStep(1)
-
- editor.setLevel(self.subject.getLevel())
- return editor
-
- def setEditorData(self, editor):
- return True
-
- def _setModelData(self, editor):
- return True
-
-
-class IsoSurfaceColorItem(SubjectItem):
- """
- Isosurface color item.
- """
- editable = True
- persistent = True
-
- def getSignals(self):
- return self.subject.sigColorChanged
-
- def getEditor(self, parent, option, index):
- editor = QColorEditor(parent)
- color = self.subject.getColor()
- color.setAlpha(255)
- editor.color = color
- # Wrapping call in lambda is a workaround for PySide with Python 3
- editor.sigColorChanged.connect(
- lambda color: self.__editorChanged(color))
- return editor
-
- def __editorChanged(self, color):
- color.setAlpha(self.subject.getColor().alpha())
- self.subject.setColor(color)
-
- def _pushData(self, value, role=qt.Qt.UserRole):
- self.subject.setColor(value)
- return self.subject.getColor()
-
-
-class QColorEditor(qt.QWidget):
- """
- QColor editor.
- """
- sigColorChanged = qt.Signal(object)
-
- color = property(lambda self: qt.QColor(self.__color))
-
- @color.setter
- def color(self, color):
- self._setColor(color)
- self.__previousColor = color
-
- def __init__(self, *args, **kwargs):
- super(QColorEditor, self).__init__(*args, **kwargs)
- layout = qt.QHBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- button = qt.QToolButton()
- icon = qt.QIcon(qt.QPixmap(32, 32))
- button.setIcon(icon)
- layout.addWidget(button)
- button.clicked.connect(self.__showColorDialog)
- layout.addStretch(1)
-
- self.__color = None
- self.__previousColor = None
-
- def sizeHint(self):
- return qt.QSize(0, 0)
-
- def _setColor(self, qColor):
- button = self.findChild(qt.QToolButton)
- pixmap = qt.QPixmap(32, 32)
- pixmap.fill(qColor)
- button.setIcon(qt.QIcon(pixmap))
- self.__color = qColor
-
- def __showColorDialog(self):
- dialog = qt.QColorDialog(parent=self)
- if sys.platform == 'darwin':
- # Use of native color dialog on macos might cause problems
- dialog.setOption(qt.QColorDialog.DontUseNativeDialog, True)
-
- self.__previousColor = self.__color
- dialog.setAttribute(qt.Qt.WA_DeleteOnClose)
- dialog.setModal(True)
- dialog.currentColorChanged.connect(self.__colorChanged)
- dialog.finished.connect(self.__dialogClosed)
- dialog.show()
-
- def __colorChanged(self, color):
- self.__color = color
- self._setColor(color)
- self.sigColorChanged.emit(color)
-
- def __dialogClosed(self, result):
- if result == qt.QDialog.Rejected:
- self.__colorChanged(self.__previousColor)
- self.__previousColor = None
-
-
-class IsoSurfaceAlphaItem(SubjectItem):
- """
- Isosurface alpha item.
- """
- editable = True
- persistent = True
-
- def _init(self):
- pass
-
- def getSignals(self):
- return self.subject.sigColorChanged
-
- def getEditor(self, parent, option, index):
- editor = qt.QSlider(parent)
- editor.setOrientation(qt.Qt.Horizontal)
- editor.setMinimum(0)
- editor.setMaximum(255)
-
- color = self.subject.getColor()
- editor.setValue(color.alpha())
-
- # Wrapping call in lambda is a workaround for PySide with Python 3
- editor.valueChanged.connect(
- lambda value: self.__editorChanged(value))
-
- return editor
-
- def __editorChanged(self, value):
- color = self.subject.getColor()
- color.setAlpha(value)
- self.subject.setColor(color)
-
- def setEditorData(self, editor):
- return True
-
- def _setModelData(self, editor):
- return True
-
-
-class IsoSurfaceAlphaLegendItem(SubjectItem):
- """Legend to place under opacity slider"""
-
- editable = False
- persistent = True
-
- def getEditor(self, parent, option, index):
- layout = qt.QHBoxLayout()
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
- layout.addWidget(qt.QLabel('0'))
- layout.addStretch(1)
- layout.addWidget(qt.QLabel('1'))
-
- editor = qt.QWidget(parent)
- editor.setLayout(layout)
- return editor
-
-
-class IsoSurfaceCount(SubjectItem):
- """
- Item displaying the number of isosurfaces.
- """
-
- def getSignals(self):
- subject = self.subject
- return [subject.sigIsosurfaceAdded, subject.sigIsosurfaceRemoved]
-
- def _pullData(self):
- return len(self.subject.getIsosurfaces())
-
-
-class IsoSurfaceAddRemoveWidget(qt.QWidget):
-
- sigViewTask = qt.Signal(str)
- """Signal for the tree view to perform some task"""
-
- def __init__(self, parent, item):
- super(IsoSurfaceAddRemoveWidget, self).__init__(parent)
- self._item = item
- layout = qt.QHBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
-
- addBtn = qt.QToolButton(self)
- addBtn.setText('+')
- addBtn.setToolButtonStyle(qt.Qt.ToolButtonTextOnly)
- layout.addWidget(addBtn)
- addBtn.clicked.connect(self.__addClicked)
-
- removeBtn = qt.QToolButton(self)
- removeBtn.setText('-')
- removeBtn.setToolButtonStyle(qt.Qt.ToolButtonTextOnly)
- layout.addWidget(removeBtn)
- removeBtn.clicked.connect(self.__removeClicked)
-
- layout.addStretch(1)
-
- def __addClicked(self):
- sfview = self._item.subject
- if not sfview:
- return
- dataRange = sfview.getDataRange()
- if dataRange is None:
- dataRange = [0, 1]
-
- sfview.addIsosurface(
- numpy.mean((dataRange[0], dataRange[-1])), '#0000FF')
-
- def __removeClicked(self):
- self.sigViewTask.emit('remove_iso')
-
-
-class IsoSurfaceAddRemoveItem(SubjectItem):
- """
- Item displaying a simple QToolButton allowing to add an isosurface.
- """
- persistent = True
-
- def getEditor(self, parent, option, index):
- return IsoSurfaceAddRemoveWidget(parent, self)
-
-
-class IsoSurfaceGroup(SubjectItem):
- """
- Root item for the list of isosurface items.
- """
-
- def __init__(self, subject, normalization, *args):
- self._isoLevelSliderNormalization = normalization
- super(IsoSurfaceGroup, self).__init__(subject, *args)
-
- def getSignals(self):
- subject = self.subject
- return [subject.sigIsosurfaceAdded, subject.sigIsosurfaceRemoved]
-
- def _subjectChanged(self, signalIdx=None, args=None, kwargs=None):
- if signalIdx == 0:
- if len(args) >= 1:
- isosurface = args[0]
- if not isinstance(isosurface, Isosurface):
- raise ValueError('Expected an isosurface instance.')
- self.__addIsosurface(isosurface)
- else:
- raise ValueError('Expected an isosurface instance.')
- elif signalIdx == 1:
- if len(args) >= 1:
- isosurface = args[0]
- if not isinstance(isosurface, Isosurface):
- raise ValueError('Expected an isosurface instance.')
- self.__removeIsosurface(isosurface)
- else:
- raise ValueError('Expected an isosurface instance.')
-
- def __addIsosurface(self, isosurface):
- valueItem = IsoSurfaceRootItem(
- subject=isosurface,
- normalization=self._isoLevelSliderNormalization)
- nameItem = IsoSurfaceLevelItem(subject=isosurface)
- self.insertRow(max(0, self.rowCount() - 1), [valueItem, nameItem])
-
- def __removeIsosurface(self, isosurface):
- for row in range(self.rowCount()):
- child = self.child(row)
- subject = getattr(child, 'subject', None)
- if subject == isosurface:
- self.takeRow(row)
- break
-
- def _init(self):
- nameItem = IsoSurfaceAddRemoveItem(self.subject)
- valueItem = qt.QStandardItem()
- valueItem.setEditable(False)
- self.appendRow([nameItem, valueItem])
-
- subject = self.subject
- isosurfaces = subject.getIsosurfaces()
- for isosurface in isosurfaces:
- self.__addIsosurface(isosurface)
-
-
-# Cutting Plane ###############################################################
-
-class ColormapBase(SubjectItem):
- """
- Mixin class for colormap items.
- """
-
- def getSignals(self):
- return [self.subject.getCutPlanes()[0].sigColormapChanged]
-
-
-class PlaneMinRangeItem(ColormapBase):
- """
- colormap minVal item.
- Editor is a QLineEdit with a QDoubleValidator
- """
- editable = True
-
- def _pullData(self):
- colormap = self.subject.getCutPlanes()[0].getColormap()
- auto = colormap.isAutoscale()
- if auto == self.isEnabled():
- self._enableRow(not auto)
- return colormap.getVMin()
-
- def _pushData(self, value, role=qt.Qt.UserRole):
- self._setVMin(value)
-
- def _setVMin(self, value):
- colormap = self.subject.getCutPlanes()[0].getColormap()
- vMin = value
- vMax = colormap.getVMax()
-
- if vMax is not None and value > vMax:
- vMin = vMax
- vMax = value
- colormap.setVRange(vMin, vMax)
-
- def getEditor(self, parent, option, index):
- return FloatEdit(parent)
-
- def setEditorData(self, editor):
- editor.setValue(self._pullData())
- return True
-
- def _setModelData(self, editor):
- value = editor.value()
- self._setVMin(value)
- return True
-
-
-class PlaneMaxRangeItem(ColormapBase):
- """
- colormap maxVal item.
- Editor is a QLineEdit with a QDoubleValidator
- """
- editable = True
-
- def _pullData(self):
- colormap = self.subject.getCutPlanes()[0].getColormap()
- auto = colormap.isAutoscale()
- if auto == self.isEnabled():
- self._enableRow(not auto)
- return self.subject.getCutPlanes()[0].getColormap().getVMax()
-
- def _setVMax(self, value):
- colormap = self.subject.getCutPlanes()[0].getColormap()
- vMin = colormap.getVMin()
- vMax = value
- if vMin is not None and value < vMin:
- vMax = vMin
- vMin = value
- colormap.setVRange(vMin, vMax)
-
- def getEditor(self, parent, option, index):
- return FloatEdit(parent)
-
- def setEditorData(self, editor):
- editor.setText(str(self._pullData()))
- return True
-
- def _setModelData(self, editor):
- value = editor.value()
- self._setVMax(value)
- return True
-
-
-class PlaneOrientationItem(SubjectItem):
- """
- Plane orientation item.
- Editor is a QComboBox.
- """
- editable = True
-
- _PLANE_ACTIONS = (
- ('3d-plane-normal-x', 'Plane 0',
- 'Set plane perpendicular to red axis', (1., 0., 0.)),
- ('3d-plane-normal-y', 'Plane 1',
- 'Set plane perpendicular to green axis', (0., 1., 0.)),
- ('3d-plane-normal-z', 'Plane 2',
- 'Set plane perpendicular to blue axis', (0., 0., 1.)),
- )
-
- def getSignals(self):
- return [self.subject.getCutPlanes()[0].sigPlaneChanged]
-
- def _pullData(self):
- currentNormal = self.subject.getCutPlanes()[0].getNormal(
- coordinates='scene')
- for _, text, _, normal in self._PLANE_ACTIONS:
- if numpy.allclose(normal, currentNormal):
- return text
- return ''
-
- def getEditor(self, parent, option, index):
- editor = qt.QComboBox(parent)
- for iconName, text, tooltip, normal in self._PLANE_ACTIONS:
- editor.addItem(getQIcon(iconName), text)
-
- # Wrapping call in lambda is a workaround for PySide with Python 3
- editor.currentIndexChanged[int].connect(
- lambda index: self.__editorChanged(index))
- return editor
-
- def __editorChanged(self, index):
- normal = self._PLANE_ACTIONS[index][3]
- plane = self.subject.getCutPlanes()[0]
- plane.setNormal(normal, coordinates='scene')
- plane.moveToCenter()
-
- def setEditorData(self, editor):
- currentText = self._pullData()
- index = 0
- for normIdx, (_, text, _, _) in enumerate(self._PLANE_ACTIONS):
- if text == currentText:
- index = normIdx
- break
- editor.setCurrentIndex(index)
- return True
-
- def _setModelData(self, editor):
- return True
-
-
-class PlaneInterpolationItem(SubjectItem):
- """Toggle cut plane interpolation method: nearest or linear.
-
- Item is checkable
- """
-
- def _init(self):
- interpolation = self.subject.getCutPlanes()[0].getInterpolation()
- self.setCheckable(True)
- self.setCheckState(
- qt.Qt.Checked if interpolation == 'linear' else qt.Qt.Unchecked)
- self.setData(self._pullData(), role=qt.Qt.DisplayRole, pushData=False)
-
- def getSignals(self):
- return [self.subject.getCutPlanes()[0].sigInterpolationChanged]
-
- def leftClicked(self):
- checked = self.checkState() == qt.Qt.Checked
- self._setInterpolation('linear' if checked else 'nearest')
-
- def _pullData(self):
- interpolation = self.subject.getCutPlanes()[0].getInterpolation()
- self._setInterpolation(interpolation)
- return interpolation[0].upper() + interpolation[1:]
-
- def _setInterpolation(self, interpolation):
- self.subject.getCutPlanes()[0].setInterpolation(interpolation)
-
-
-class PlaneDisplayBelowMinItem(SubjectItem):
- """Toggle whether to display or not values <= colormap min of the cut plane
-
- Item is checkable
- """
-
- def _init(self):
- display = self.subject.getCutPlanes()[0].getDisplayValuesBelowMin()
- self.setCheckable(True)
- self.setCheckState(
- qt.Qt.Checked if display else qt.Qt.Unchecked)
- self.setData(self._pullData(), role=qt.Qt.DisplayRole, pushData=False)
-
- def getSignals(self):
- return [self.subject.getCutPlanes()[0].sigTransparencyChanged]
-
- def leftClicked(self):
- checked = self.checkState() == qt.Qt.Checked
- self._setDisplayValuesBelowMin(checked)
-
- def _pullData(self):
- display = self.subject.getCutPlanes()[0].getDisplayValuesBelowMin()
- self._setDisplayValuesBelowMin(display)
- return "Displayed" if display else "Hidden"
-
- def _setDisplayValuesBelowMin(self, display):
- self.subject.getCutPlanes()[0].setDisplayValuesBelowMin(display)
-
-
-class PlaneColormapItem(ColormapBase):
- """
- colormap name item.
- Editor is a QComboBox
- """
- editable = True
-
- listValues = ['gray', 'reversed gray',
- 'temperature', 'red',
- 'green', 'blue',
- 'viridis', 'magma', 'inferno', 'plasma']
-
- def getEditor(self, parent, option, index):
- editor = qt.QComboBox(parent)
- editor.addItems(self.listValues)
-
- # Wrapping call in lambda is a workaround for PySide with Python 3
- editor.currentIndexChanged[int].connect(
- lambda index: self.__editorChanged(index))
-
- return editor
-
- def __editorChanged(self, index):
- colormapName = self.listValues[index]
- colormap = self.subject.getCutPlanes()[0].getColormap()
- colormap.setName(colormapName)
-
- def setEditorData(self, editor):
- colormapName = self.subject.getCutPlanes()[0].getColormap().getName()
- try:
- index = self.listValues.index(colormapName)
- except ValueError:
- _logger.error('Unsupported colormap: %s', colormapName)
- else:
- editor.setCurrentIndex(index)
- return True
-
- def _setModelData(self, editor):
- self.__editorChanged(editor.currentIndex())
- return True
-
- def _pullData(self):
- return self.subject.getCutPlanes()[0].getColormap().getName()
-
-
-class PlaneAutoScaleItem(ColormapBase):
- """
- colormap autoscale item.
- Item is checkable.
- """
-
- def _init(self):
- colorMap = self.subject.getCutPlanes()[0].getColormap()
- self.setCheckable(True)
- self.setCheckState((colorMap.isAutoscale() and qt.Qt.Checked)
- or qt.Qt.Unchecked)
- self.setData(self._pullData(), role=qt.Qt.DisplayRole, pushData=False)
-
- def leftClicked(self):
- checked = (self.checkState() == qt.Qt.Checked)
- self._setAutoScale(checked)
-
- def _setAutoScale(self, auto):
- view3d = self.subject
- colormap = view3d.getCutPlanes()[0].getColormap()
-
- if auto != colormap.isAutoscale():
- if auto:
- vMin = vMax = None
- else:
- dataRange = view3d.getDataRange()
- if dataRange is None:
- vMin = vMax = None
- else:
- vMin, vMax = dataRange[0], dataRange[-1]
- colormap.setVRange(vMin, vMax)
-
- def _pullData(self):
- auto = self.subject.getCutPlanes()[0].getColormap().isAutoscale()
- self._setAutoScale(auto)
- if auto:
- data = 'Auto'
- else:
- data = 'User'
- return data
-
-
-class NormalizationNode(ColormapBase):
- """
- colormap normalization item.
- Item is a QComboBox.
- """
- editable = True
- listValues = list(Colormap.NORMALIZATIONS)
-
- def getEditor(self, parent, option, index):
- editor = qt.QComboBox(parent)
- editor.addItems(self.listValues)
-
- # Wrapping call in lambda is a workaround for PySide with Python 3
- editor.currentIndexChanged[int].connect(
- lambda index: self.__editorChanged(index))
-
- return editor
-
- def __editorChanged(self, index):
- colorMap = self.subject.getCutPlanes()[0].getColormap()
- normalization = self.listValues[index]
- self.subject.getCutPlanes()[0].setColormap(name=colorMap.getName(),
- norm=normalization,
- vmin=colorMap.getVMin(),
- vmax=colorMap.getVMax())
-
- def setEditorData(self, editor):
- normalization = self.subject.getCutPlanes()[0].getColormap().getNormalization()
- index = self.listValues.index(normalization)
- editor.setCurrentIndex(index)
- return True
-
- def _setModelData(self, editor):
- self.__editorChanged(editor.currentIndex())
- return True
-
- def _pullData(self):
- return self.subject.getCutPlanes()[0].getColormap().getNormalization()
-
-
-class PlaneGroup(SubjectItem):
- """
- Root Item for the plane items.
- """
- def _init(self):
- valueItem = qt.QStandardItem()
- valueItem.setEditable(False)
- nameItem = PlaneVisibleItem(self.subject, 'Visible')
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Colormap')
- nameItem.setEditable(False)
- valueItem = PlaneColormapItem(self.subject)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Normalization')
- nameItem.setEditable(False)
- valueItem = NormalizationNode(self.subject)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Orientation')
- nameItem.setEditable(False)
- valueItem = PlaneOrientationItem(self.subject)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Interpolation')
- nameItem.setEditable(False)
- valueItem = PlaneInterpolationItem(self.subject)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Autoscale')
- nameItem.setEditable(False)
- valueItem = PlaneAutoScaleItem(self.subject)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Min')
- nameItem.setEditable(False)
- valueItem = PlaneMinRangeItem(self.subject)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Max')
- nameItem.setEditable(False)
- valueItem = PlaneMaxRangeItem(self.subject)
- self.appendRow([nameItem, valueItem])
-
- nameItem = qt.QStandardItem('Values<=Min')
- nameItem.setEditable(False)
- valueItem = PlaneDisplayBelowMinItem(self.subject)
- self.appendRow([nameItem, valueItem])
-
-
-class PlaneVisibleItem(SubjectItem):
- """
- Plane visibility item.
- Item is checkable.
- """
- def _init(self):
- plane = self.subject.getCutPlanes()[0]
- self.setCheckable(True)
- self.setCheckState((plane.isVisible() and qt.Qt.Checked)
- or qt.Qt.Unchecked)
-
- def leftClicked(self):
- plane = self.subject.getCutPlanes()[0]
- checked = (self.checkState() == qt.Qt.Checked)
- if checked != plane.isVisible():
- plane.setVisible(checked)
- if plane.isVisible():
- plane.moveToCenter()
-
-
-# Tree ########################################################################
-
-class ItemDelegate(qt.QStyledItemDelegate):
- """
- Delegate for the QTreeView filled with SubjectItems.
- """
-
- sigDelegateEvent = qt.Signal(str)
-
- def __init__(self, parent=None):
- super(ItemDelegate, self).__init__(parent)
-
- def createEditor(self, parent, option, index):
- item = index.model().itemFromIndex(index)
- if item:
- if isinstance(item, SubjectItem):
- editor = item.getEditor(parent, option, index)
- if editor:
- editor.setAutoFillBackground(True)
- if hasattr(editor, 'sigViewTask'):
- editor.sigViewTask.connect(self.__viewTask)
- return editor
-
- editor = super(ItemDelegate, self).createEditor(parent,
- option,
- index)
- return editor
-
- def updateEditorGeometry(self, editor, option, index):
- editor.setGeometry(option.rect)
-
- def setEditorData(self, editor, index):
- item = index.model().itemFromIndex(index)
- if item:
- if isinstance(item, SubjectItem) and item.setEditorData(editor):
- return
- super(ItemDelegate, self).setEditorData(editor, index)
-
- def setModelData(self, editor, model, index):
- item = index.model().itemFromIndex(index)
- if isinstance(item, SubjectItem) and item._setModelData(editor):
- return
- super(ItemDelegate, self).setModelData(editor, model, index)
-
- def __viewTask(self, task):
- self.sigDelegateEvent.emit(task)
-
-
-class TreeView(qt.QTreeView):
- """
- TreeView displaying the SubjectItems for the ScalarFieldView.
- """
-
- def __init__(self, parent=None):
- super(TreeView, self).__init__(parent)
- self.__openedIndex = None
- self._isoLevelSliderNormalization = 'linear'
-
- self.setIconSize(qt.QSize(16, 16))
-
- header = self.header()
- if hasattr(header, 'setSectionResizeMode'): # Qt5
- header.setSectionResizeMode(qt.QHeaderView.ResizeToContents)
- else: # Qt4
- header.setResizeMode(qt.QHeaderView.ResizeToContents)
-
- delegate = ItemDelegate()
- self.setItemDelegate(delegate)
- delegate.sigDelegateEvent.connect(self.__delegateEvent)
- self.setSelectionBehavior(qt.QAbstractItemView.SelectRows)
- self.setSelectionMode(qt.QAbstractItemView.SingleSelection)
-
- self.clicked.connect(self.__clicked)
-
- def setSfView(self, sfView):
- """
- Sets the ScalarFieldView this view is controlling.
-
- :param sfView: A `ScalarFieldView`
- """
- model = qt.QStandardItemModel()
- model.setColumnCount(ModelColumns.ColumnMax)
- model.setHorizontalHeaderLabels(['Name', 'Value'])
-
- item = qt.QStandardItem()
- item.setEditable(False)
- model.appendRow([ViewSettingsItem(sfView, 'Style'), item])
-
- item = qt.QStandardItem()
- item.setEditable(False)
- model.appendRow([DataSetItem(sfView, 'Data'), item])
-
- item = IsoSurfaceCount(sfView)
- item.setEditable(False)
- model.appendRow([IsoSurfaceGroup(sfView,
- self._isoLevelSliderNormalization,
- 'Isosurfaces'),
- item])
-
- item = qt.QStandardItem()
- item.setEditable(False)
- model.appendRow([PlaneGroup(sfView, 'Cutting Plane'), item])
-
- self.setModel(model)
-
- def setModel(self, model):
- """
- Reimplementation of the QTreeView.setModel method. It connects the
- rowsRemoved signal and opens the persistent editors.
-
- :param qt.QStandardItemModel model: the model
- """
-
- prevModel = self.model()
- if prevModel:
- self.__openPersistentEditors(qt.QModelIndex(), False)
- try:
- prevModel.rowsRemoved.disconnect(self.rowsRemoved)
- except TypeError:
- pass
-
- super(TreeView, self).setModel(model)
- model.rowsRemoved.connect(self.rowsRemoved)
- self.__openPersistentEditors(qt.QModelIndex())
-
- def __openPersistentEditors(self, parent=None, openEditor=True):
- """
- Opens or closes the items persistent editors.
-
- :param qt.QModelIndex parent: starting index, or None if the whole tree
- is to be considered.
- :param bool openEditor: True to open the editors, False to close them.
- """
- model = self.model()
-
- if not model:
- return
-
- if not parent or not parent.isValid():
- parent = self.model().invisibleRootItem().index()
-
- if openEditor:
- meth = self.openPersistentEditor
- else:
- meth = self.closePersistentEditor
-
- curParent = parent
- children = [model.index(row, 0, curParent)
- for row in range(model.rowCount(curParent))]
-
- columnCount = model.columnCount()
-
- while len(children) > 0:
- curParent = children.pop(-1)
-
- children.extend([model.index(row, 0, curParent)
- for row in range(model.rowCount(curParent))])
-
- for colIdx in range(columnCount):
- sibling = model.sibling(curParent.row(),
- colIdx,
- curParent)
- item = model.itemFromIndex(sibling)
- if isinstance(item, SubjectItem) and item.persistent:
- meth(sibling)
-
- def rowsAboutToBeRemoved(self, parent, start, end):
- """
- Reimplementation of the QTreeView.rowsAboutToBeRemoved. Closes all
- persistent editors under parent.
-
- :param qt.QModelIndex parent: Parent index
- :param int start: Start index from parent index (inclusive)
- :param int end: End index from parent index (inclusive)
- """
- self.__openPersistentEditors(parent, False)
- super(TreeView, self).rowsAboutToBeRemoved(parent, start, end)
-
- def rowsRemoved(self, parent, start, end):
- """
- Called when QTreeView.rowsRemoved is emitted. Opens all persistent
- editors under parent.
-
- :param qt.QModelIndex parent: Parent index
- :param int start: Start index from parent index (inclusive)
- :param int end: End index from parent index (inclusive)
- """
- super(TreeView, self).rowsRemoved(parent, start, end)
- self.__openPersistentEditors(parent, True)
-
- def rowsInserted(self, parent, start, end):
- """
- Reimplementation of the QTreeView.rowsInserted. Opens all persistent
- editors under parent.
-
- :param qt.QModelIndex parent: Parent index
- :param int start: Start index from parent index
- :param int end: End index from parent index
- """
- self.__openPersistentEditors(parent, False)
- super(TreeView, self).rowsInserted(parent, start, end)
- self.__openPersistentEditors(parent)
-
- def keyReleaseEvent(self, event):
- """
- Reimplementation of the QTreeView.keyReleaseEvent.
- At the moment only Key_Delete is handled. It calls the selected item's
- queryRemove method, and deleted the item if needed.
-
- :param qt.QKeyEvent event: A key event
- """
-
- # TODO : better filtering
- key = event.key()
- modifiers = event.modifiers()
-
- if key == qt.Qt.Key_Delete and modifiers == qt.Qt.NoModifier:
- self.__removeIsosurfaces()
-
- super(TreeView, self).keyReleaseEvent(event)
-
- def __removeIsosurfaces(self):
- model = self.model()
- selected = self.selectedIndexes()
- items = []
- # WARNING : the selection mode is set to single, so we re not
- # supposed to have more than one item here.
- # Multiple selection deletion has not been tested.
- # Watch out for index invalidation
- for index in selected:
- leftIndex = model.sibling(index.row(), 0, index)
- leftItem = model.itemFromIndex(leftIndex)
- if isinstance(leftItem, SubjectItem) and leftItem not in items:
- items.append(leftItem)
-
- isos = [item for item in items if isinstance(item, IsoSurfaceRootItem)]
- if isos:
- for iso in isos:
- if iso.queryRemove(self):
- parentItem = iso.parent()
- parentItem.removeRow(iso.row())
- else:
- qt.QMessageBox.information(
- self,
- 'Remove isosurface',
- 'Select an iso-surface to remove it')
-
- def __clicked(self, index):
- """
- Called when the QTreeView.clicked signal is emitted. Calls the item's
- leftClick method.
-
- :param qt.QIndex index: An index
- """
- item = self.model().itemFromIndex(index)
- if isinstance(item, SubjectItem):
- item.leftClicked()
-
- def __delegateEvent(self, task):
- if task == 'remove_iso':
- self.__removeIsosurfaces()
-
- def setIsoLevelSliderNormalization(self, normalization):
- """Set the normalization for iso level slider
-
- This MUST be called *before* :meth:`setSfView` to have an effect.
-
- :param str normalization: Either 'linear' or 'arcsinh'
- """
- assert normalization in ('linear', 'arcsinh')
- self._isoLevelSliderNormalization = normalization