diff options
Diffstat (limited to 'src/silx/gui/plot/tools/profile/editors.py')
-rw-r--r-- | src/silx/gui/plot/tools/profile/editors.py | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/src/silx/gui/plot/tools/profile/editors.py b/src/silx/gui/plot/tools/profile/editors.py new file mode 100644 index 0000000..d53f775 --- /dev/null +++ b/src/silx/gui/plot/tools/profile/editors.py @@ -0,0 +1,309 @@ +# /*########################################################################## +# +# Copyright (c) 2018-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 editors which are used to custom profile ROI properties. +""" + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "28/06/2018" + +import logging + +from silx.gui import qt + +from silx.gui.utils import blockSignals +from silx.gui.plot.PlotToolButtons import ProfileOptionToolButton +from silx.gui.plot.PlotToolButtons import ProfileToolButton +from . import rois +from . import core + + +_logger = logging.getLogger(__name__) + + +class _NoProfileRoiEditor(qt.QWidget): + sigDataCommited = qt.Signal() + + def setEditorData(self, roi): + pass + + def setRoiData(self, roi): + pass + + +class _DefaultImageProfileRoiEditor(qt.QWidget): + sigDataCommited = qt.Signal() + + def __init__(self, parent=None): + qt.QWidget.__init__(self, parent=parent) + layout = qt.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + self._initLayout(layout) + + def _initLayout(self, layout): + self._lineWidth = qt.QSpinBox(self) + self._lineWidth.setRange(1, 1000) + self._lineWidth.setValue(1) + self._lineWidth.valueChanged[int].connect(self._widgetChanged) + + self._methodsButton = ProfileOptionToolButton(parent=self, plot=None) + self._methodsButton.sigMethodChanged.connect(self._widgetChanged) + + label = qt.QLabel("W:") + label.setToolTip("Line width in pixels") + layout.addWidget(label) + layout.addWidget(self._lineWidth) + layout.addWidget(self._methodsButton) + + def _widgetChanged(self, value=None): + self.commitData() + + def commitData(self): + self.sigDataCommited.emit() + + def setEditorData(self, roi): + with blockSignals(self._lineWidth): + self._lineWidth.setValue(roi.getProfileLineWidth()) + with blockSignals(self._methodsButton): + method = roi.getProfileMethod() + self._methodsButton.setMethod(method) + + def setRoiData(self, roi): + lineWidth = self._lineWidth.value() + roi.setProfileLineWidth(lineWidth) + method = self._methodsButton.getMethod() + roi.setProfileMethod(method) + + +class _DefaultImageStackProfileRoiEditor(_DefaultImageProfileRoiEditor): + def _initLayout(self, layout): + super(_DefaultImageStackProfileRoiEditor, self)._initLayout(layout) + self._profileDim = ProfileToolButton(parent=self, plot=None) + self._profileDim.sigDimensionChanged.connect(self._widgetChanged) + layout.addWidget(self._profileDim) + + def setEditorData(self, roi): + super(_DefaultImageStackProfileRoiEditor, self).setEditorData(roi) + with blockSignals(self._profileDim): + kind = roi.getProfileType() + dim = {"1D": 1, "2D": 2}[kind] + self._profileDim.setDimension(dim) + + def setRoiData(self, roi): + super(_DefaultImageStackProfileRoiEditor, self).setRoiData(roi) + dim = self._profileDim.getDimension() + kind = {1: "1D", 2: "2D"}[dim] + roi.setProfileType(kind) + + +class _DefaultScatterProfileRoiEditor(qt.QWidget): + sigDataCommited = qt.Signal() + + def __init__(self, parent=None): + qt.QWidget.__init__(self, parent=parent) + + self._nPoints = qt.QSpinBox(self) + self._nPoints.setRange(1, 9999) + self._nPoints.setValue(1024) + self._nPoints.valueChanged[int].connect(self.__widgetChanged) + + layout = qt.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + label = qt.QLabel("Samples:") + label.setToolTip("Number of sample points of the profile") + layout.addWidget(label) + layout.addWidget(self._nPoints) + + def __widgetChanged(self, value=None): + self.commitData() + + def commitData(self): + self.sigDataCommited.emit() + + def setEditorData(self, roi): + with blockSignals(self._nPoints): + self._nPoints.setValue(roi.getNPoints()) + + def setRoiData(self, roi): + nPoints = self._nPoints.value() + roi.setNPoints(nPoints) + + +class ProfileRoiEditorAction(qt.QWidgetAction): + """ + Action displaying GUI to edit the selected ROI. + + :param qt.QWidget parent: Parent widget + """ + + def __init__(self, parent=None): + super(ProfileRoiEditorAction, self).__init__(parent) + self.__roiManager = None + self.__roi = None + self.__inhibiteReentance = None + + def createWidget(self, parent): + """Inherit the method to create a new editor""" + widget = qt.QWidget(parent) + layout = qt.QHBoxLayout(widget) + if isinstance(parent, qt.QMenu): + margins = layout.contentsMargins() + layout.setContentsMargins(margins.left(), 0, margins.right(), 0) + else: + layout.setContentsMargins(0, 0, 0, 0) + + editorClass = self.getEditorClass(self.__roi) + editor = editorClass(parent) + editor.setEditorData(self.__roi) + self.__setEditor(widget, editor) + return widget + + def deleteWidget(self, widget): + """Inherit the method to delete an editor""" + self.__setEditor(widget, None) + return qt.QWidgetAction.deleteWidget(self, widget) + + def _getEditor(self, widget): + """Returns the editor contained in the widget holder""" + layout = widget.layout() + if layout.count() == 0: + return None + return layout.itemAt(0).widget() + + def setRoiManager(self, roiManager): + """ + Connect this action to a ROI manager. + + :param RegionOfInterestManager roiManager: A ROI manager + """ + if self.__roiManager is roiManager: + return + if self.__roiManager is not None: + self.__roiManager.sigCurrentRoiChanged.disconnect(self.__currentRoiChanged) + self.__roiManager = roiManager + if self.__roiManager is not None: + self.__roiManager.sigCurrentRoiChanged.connect(self.__currentRoiChanged) + self.__currentRoiChanged(roiManager.getCurrentRoi()) + + def __currentRoiChanged(self, roi): + """Handle changes of the selected ROI""" + if roi is not None and not isinstance(roi, core.ProfileRoiMixIn): + return + self.setProfileRoi(roi) + + def setProfileRoi(self, roi): + """Set a profile ROI to edit. + + :param ProfileRoiMixIn roi: A profile ROI + """ + if self.__roi is roi: + return + if self.__roi is not None: + self.__roi.sigProfilePropertyChanged.disconnect(self.__roiPropertyChanged) + self.__roi = roi + if self.__roi is not None: + self.__roi.sigProfilePropertyChanged.connect(self.__roiPropertyChanged) + self._updateWidgets() + + def getRoiProfile(self): + """Returns the edited profile ROI. + + :rtype: ProfileRoiMixIn + """ + return self.__roi + + def __roiPropertyChanged(self): + """Handle changes on the property defining the ROI.""" + self._updateWidgetValues() + + def __setEditor(self, widget, editor): + """Set the editor to display. + + :param qt.QWidget editor: The editor to display + """ + previousEditor = self._getEditor(widget) + if previousEditor is editor: + return + layout = widget.layout() + if previousEditor is not None: + try: + previousEditor.sigDataCommited.disconnect(self._editorDataCommited) + except (RuntimeError, TypeError): + pass + layout.removeWidget(previousEditor) + previousEditor.deleteLater() + if editor is not None: + editor.sigDataCommited.connect(self._editorDataCommited) + layout.addWidget(editor) + + def getEditorClass(self, roi): + """Returns the editor class to use according to the ROI.""" + if roi is None: + editorClass = _NoProfileRoiEditor + elif isinstance( + roi, + (rois._DefaultImageStackProfileRoiMixIn, rois.ProfileImageStackCrossROI), + ): + # Must be done before the default image ROI + # Cause ImageStack ROIs inherit from Image ROIs + editorClass = _DefaultImageStackProfileRoiEditor + elif isinstance( + roi, (rois._DefaultImageProfileRoiMixIn, rois.ProfileImageCrossROI) + ): + editorClass = _DefaultImageProfileRoiEditor + elif isinstance( + roi, (rois._DefaultScatterProfileRoiMixIn, rois.ProfileScatterCrossROI) + ): + editorClass = _DefaultScatterProfileRoiEditor + else: + # Unsupported + editorClass = _NoProfileRoiEditor + return editorClass + + def _updateWidgets(self): + """Update the kind of editor to display, according to the selected + profile ROI.""" + parent = self.parent() + editorClass = self.getEditorClass(self.__roi) + for widget in self.createdWidgets(): + editor = editorClass(parent) + editor.setEditorData(self.__roi) + self.__setEditor(widget, editor) + + def _updateWidgetValues(self): + """Update the content of the displayed editor, according to the + selected profile ROI.""" + for widget in self.createdWidgets(): + editor = self._getEditor(widget) + if self.__inhibiteReentance is editor: + continue + editor.setEditorData(self.__roi) + + def _editorDataCommited(self): + """Handle changes from the editor.""" + editor = self.sender() + if self.__roi is not None: + self.__inhibiteReentance = editor + editor.setRoiData(self.__roi) + self.__inhibiteReentance = None |