diff options
Diffstat (limited to 'silx/gui/widgets')
32 files changed, 0 insertions, 6844 deletions
diff --git a/silx/gui/widgets/BoxLayoutDockWidget.py b/silx/gui/widgets/BoxLayoutDockWidget.py deleted file mode 100644 index 3d2b853..0000000 --- a/silx/gui/widgets/BoxLayoutDockWidget.py +++ /dev/null @@ -1,90 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018 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. -# -# ###########################################################################*/ -"""A QDockWidget that update the layout direction of its widget -""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "06/03/2018" - - -from .. import qt - - -class BoxLayoutDockWidget(qt.QDockWidget): - """QDockWidget adjusting its child widget QBoxLayout direction. - - The child widget layout direction is set according to dock widget area. - The child widget MUST use a QBoxLayout - - :param parent: See :class:`QDockWidget` - :param flags: See :class:`QDockWidget` - """ - - def __init__(self, parent=None, flags=qt.Qt.Widget): - super(BoxLayoutDockWidget, self).__init__(parent, flags) - self._currentArea = qt.Qt.NoDockWidgetArea - self.dockLocationChanged.connect(self._dockLocationChanged) - self.topLevelChanged.connect(self._topLevelChanged) - - def setWidget(self, widget): - """Set the widget of this QDockWidget - - See :meth:`QDockWidget.setWidget` - """ - super(BoxLayoutDockWidget, self).setWidget(widget) - # Update widget's layout direction - self._dockLocationChanged(self._currentArea) - - def _dockLocationChanged(self, area): - self._currentArea = area - - widget = self.widget() - if widget is not None: - layout = widget.layout() - if isinstance(layout, qt.QBoxLayout): - if area in (qt.Qt.LeftDockWidgetArea, qt.Qt.RightDockWidgetArea): - direction = qt.QBoxLayout.TopToBottom - else: - direction = qt.QBoxLayout.LeftToRight - layout.setDirection(direction) - self.resize(widget.minimumSize()) - self.adjustSize() - - def _topLevelChanged(self, topLevel): - widget = self.widget() - if widget is not None and topLevel: - layout = widget.layout() - if isinstance(layout, qt.QBoxLayout): - layout.setDirection(qt.QBoxLayout.LeftToRight) - self.resize(widget.minimumSize()) - self.adjustSize() - - def showEvent(self, event): - """Make sure this dock widget is raised when it is shown. - - This is useful for tabbed dock widgets. - """ - self.raise_() diff --git a/silx/gui/widgets/ColormapNameComboBox.py b/silx/gui/widgets/ColormapNameComboBox.py deleted file mode 100644 index fa8faf1..0000000 --- a/silx/gui/widgets/ColormapNameComboBox.py +++ /dev/null @@ -1,166 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-2018 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. -# -# ###########################################################################*/ -"""A QComboBox to display prefered colormaps -""" - -from __future__ import division - -__authors__ = ["V.A. Sole", "T. Vincent", "H. Payno"] -__license__ = "MIT" -__date__ = "27/11/2018" - - -import logging -import numpy - -from .. import qt -from .. import colors as colors_mdl - -_logger = logging.getLogger(__name__) - - -_colormapIconPreview = {} - - -class ColormapNameComboBox(qt.QComboBox): - def __init__(self, parent=None): - qt.QComboBox.__init__(self, parent) - self.__initItems() - - LUT_NAME = qt.Qt.UserRole + 1 - LUT_COLORS = qt.Qt.UserRole + 2 - - def __initItems(self): - for colormapName in colors_mdl.preferredColormaps(): - index = self.count() - self.addItem(str.title(colormapName)) - self.setItemIcon(index, self.getIconPreview(name=colormapName)) - self.setItemData(index, colormapName, role=self.LUT_NAME) - - def getIconPreview(self, name=None, colors=None): - """Return an icon preview from a LUT name. - - This icons are cached into a global structure. - - :param str name: Name of the LUT - :param numpy.ndarray colors: Colors identify the LUT - :rtype: qt.QIcon - """ - if name is not None: - iconKey = name - else: - iconKey = tuple(colors) - icon = _colormapIconPreview.get(iconKey, None) - if icon is None: - icon = self.createIconPreview(name, colors) - _colormapIconPreview[iconKey] = icon - return icon - - def createIconPreview(self, name=None, colors=None): - """Create and return an icon preview from a LUT name. - - This icons are cached into a global structure. - - :param str name: Name of the LUT - :param numpy.ndarray colors: Colors identify the LUT - :rtype: qt.QIcon - """ - colormap = colors_mdl.Colormap(name) - size = 32 - if name is not None: - lut = colormap.getNColors(size) - else: - lut = colors - if len(lut) > size: - # Down sample - step = int(len(lut) / size) - lut = lut[::step] - elif len(lut) < size: - # Over sample - indexes = numpy.arange(size) / float(size) * (len(lut) - 1) - indexes = indexes.astype("int") - lut = lut[indexes] - if lut is None or len(lut) == 0: - return qt.QIcon() - - pixmap = qt.QPixmap(size, size) - painter = qt.QPainter(pixmap) - for i in range(size): - rgb = lut[i] - r, g, b = rgb[0], rgb[1], rgb[2] - painter.setPen(qt.QColor(r, g, b)) - painter.drawPoint(qt.QPoint(i, 0)) - - painter.drawPixmap(0, 1, size, size - 1, pixmap, 0, 0, size, 1) - painter.end() - - return qt.QIcon(pixmap) - - def getCurrentName(self): - return self.itemData(self.currentIndex(), self.LUT_NAME) - - def getCurrentColors(self): - return self.itemData(self.currentIndex(), self.LUT_COLORS) - - def findLutName(self, name): - return self.findData(name, role=self.LUT_NAME) - - def findLutColors(self, lut): - for index in range(self.count()): - if self.itemData(index, role=self.LUT_NAME) is not None: - continue - colors = self.itemData(index, role=self.LUT_COLORS) - if colors is None: - continue - if numpy.array_equal(colors, lut): - return index - return -1 - - def setCurrentLut(self, colormap): - name = colormap.getName() - if name is not None: - self._setCurrentName(name) - else: - lut = colormap.getColormapLUT() - self._setCurrentLut(lut) - - def _setCurrentLut(self, lut): - index = self.findLutColors(lut) - if index == -1: - index = self.count() - self.addItem("Custom") - self.setItemIcon(index, self.getIconPreview(colors=lut)) - self.setItemData(index, None, role=self.LUT_NAME) - self.setItemData(index, lut, role=self.LUT_COLORS) - self.setCurrentIndex(index) - - def _setCurrentName(self, name): - index = self.findLutName(name) - if index < 0: - index = self.count() - self.addItem(str.title(name)) - self.setItemIcon(index, self.getIconPreview(name=name)) - self.setItemData(index, name, role=self.LUT_NAME) - self.setCurrentIndex(index) diff --git a/silx/gui/widgets/ElidedLabel.py b/silx/gui/widgets/ElidedLabel.py deleted file mode 100644 index fe53bb9..0000000 --- a/silx/gui/widgets/ElidedLabel.py +++ /dev/null @@ -1,137 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-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. -# -# ###########################################################################*/ -"""Module contains an elidable label -""" - -__license__ = "MIT" -__date__ = "07/12/2018" - -from silx.gui import qt - - -class ElidedLabel(qt.QLabel): - """QLabel with an edile property. - - By default if the text is too big, it is elided on the right. - - This mode can be changed with :func:`setElideMode`. - - In case the text is elided, the full content is displayed as part of the - tool tip. This behavior can be disabled with :func:`setTextAsToolTip`. - """ - - def __init__(self, parent=None): - super(ElidedLabel, self).__init__(parent) - self.__text = "" - self.__toolTip = "" - self.__textAsToolTip = True - self.__textIsElided = False - self.__elideMode = qt.Qt.ElideRight - self.__updateMinimumSize() - - def resizeEvent(self, event): - self.__updateText() - return qt.QLabel.resizeEvent(self, event) - - def setFont(self, font): - qt.QLabel.setFont(self, font) - self.__updateMinimumSize() - self.__updateText() - - def __updateMinimumSize(self): - metrics = self.fontMetrics() - width = metrics.width("...") - self.setMinimumWidth(width) - - def __updateText(self): - metrics = self.fontMetrics() - elidedText = metrics.elidedText(self.__text, self.__elideMode, self.width()) - qt.QLabel.setText(self, elidedText) - wasElided = self.__textIsElided - self.__textIsElided = elidedText != self.__text - if self.__textIsElided or wasElided != self.__textIsElided: - self.__updateToolTip() - - def __updateToolTip(self): - if self.__textIsElided and self.__textAsToolTip: - qt.QLabel.setToolTip(self, self.__text + "<br/>" + self.__toolTip) - else: - qt.QLabel.setToolTip(self, self.__toolTip) - - # Properties - - def setText(self, text): - self.__text = text - self.__updateText() - - def getText(self): - return self.__text - - text = qt.Property(str, getText, setText) - - def setToolTip(self, toolTip): - self.__toolTip = toolTip - self.__updateToolTip() - - def getToolTip(self): - return self.__toolTip - - toolTip = qt.Property(str, getToolTip, setToolTip) - - def setElideMode(self, elideMode): - """Set the elide mode. - - :param qt.Qt.TextElideMode elidMode: Elide mode to use - """ - self.__elideMode = elideMode - self.__updateText() - - def getElideMode(self): - """Returns the used elide mode. - - :rtype: qt.Qt.TextElideMode - """ - return self.__elideMode - - elideMode = qt.Property(qt.Qt.TextElideMode, getToolTip, setToolTip) - - def setTextAsToolTip(self, enabled): - """Enable displaying text as part of the tooltip if it is elided. - - :param bool enabled: Enable the behavior - """ - if self.__textAsToolTip == enabled: - return - self.__textAsToolTip = enabled - self.__updateToolTip() - - def getTextAsToolTip(self): - """True if an elided text is displayed as part of the tooltip. - - :rtype: bool - """ - return self.__textAsToolTip - - textAsToolTip = qt.Property(bool, getTextAsToolTip, setTextAsToolTip) diff --git a/silx/gui/widgets/FloatEdit.py b/silx/gui/widgets/FloatEdit.py deleted file mode 100644 index 36a39a7..0000000 --- a/silx/gui/widgets/FloatEdit.py +++ /dev/null @@ -1,65 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-2018 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. -# -# ###########################################################################*/ -"""Module contains a float editor -""" - -from __future__ import division - -__authors__ = ["V.A. Sole", "T. Vincent"] -__license__ = "MIT" -__date__ = "02/10/2017" - -from .. import qt - - -class FloatEdit(qt.QLineEdit): - """Field to edit a float value. - - :param parent: See :class:`QLineEdit` - :param float value: The value to set the QLineEdit to. - """ - def __init__(self, parent=None, value=None): - qt.QLineEdit.__init__(self, parent) - validator = qt.QDoubleValidator(self) - self.setValidator(validator) - self.setAlignment(qt.Qt.AlignRight) - if value is not None: - self.setValue(value) - - def value(self): - """Return the QLineEdit current value as a float.""" - text = self.text() - value, validated = self.validator().locale().toDouble(text) - if not validated: - self.setValue(value) - return value - - def setValue(self, value): - """Set the current value of the LineEdit - - :param float value: The value to set the QLineEdit to. - """ - text = self.validator().locale().toString(float(value)) - self.setText(text) diff --git a/silx/gui/widgets/FlowLayout.py b/silx/gui/widgets/FlowLayout.py deleted file mode 100644 index 3c4c9dd..0000000 --- a/silx/gui/widgets/FlowLayout.py +++ /dev/null @@ -1,177 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018 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 flow layout for QWidget: :class:`FlowLayout`. -""" - -from __future__ import division - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "20/07/2018" - - -from .. import qt - - -class FlowLayout(qt.QLayout): - """Layout widgets on (possibly) multiple lines in the available width. - - See Qt :class:`QLayout` for API documentation. - - Adapted from C++ `Qt FlowLayout example - <http://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html>`_ - - :param QWidget parent: See :class:`QLayout` - """ - - def __init__(self, parent=None): - super(FlowLayout, self).__init__(parent) - self._items = [] - self._horizontalSpacing = -1 - self._verticalSpacing = -1 - - def addItem(self, item): - self._items.append(item) - - def count(self): - return len(self._items) - - def itemAt(self, index): - if 0 <= index < len(self._items): - return self._items[index] - else: - return None - - def takeAt(self, index): - if 0 <= index < len(self._items): - return self._items.pop(index) - else: - return None - - def expandingDirections(self): - return qt.Qt.Orientations() - - def hasHeightForWidth(self): - return True - - def heightForWidth(self, width): - return self._layout(qt.QRect(0, 0, width, 0), test=True) - - def setGeometry(self, rect): - super(FlowLayout, self).setGeometry(rect) - self._layout(rect) - - def sizeHint(self): - return self.minimumSize() - - def minimumSize(self): - size = qt.QSize() - for item in self._items: - size = size.expandedTo(item.minimumSize()) - - left, top, right, bottom = self.getContentsMargins() - size += qt.QSize(left + right, top + bottom) - return size - - def _layout(self, rect, test=False): - left, top, right, bottom = self.getContentsMargins() - effectiveRect = rect.adjusted(left, top, -right, -bottom) - x, y = effectiveRect.x(), effectiveRect.y() - lineHeight = 0 - - for item in self._items: - widget = item.widget() - spaceX = self.horizontalSpacing() - if spaceX == -1: - spaceX = widget.style().layoutSpacing( - qt.QSizePolicy.PushButton, - qt.QSizePolicy.PushButton, - qt.Qt.Horizontal) - spaceY = self.verticalSpacing() - if spaceY == -1: - spaceY = widget.style().layoutSpacing( - qt.QSizePolicy.PushButton, - qt.QSizePolicy.PushButton, - qt.Qt.Vertical) - - nextX = x + item.sizeHint().width() + spaceX - if (nextX - spaceX) > effectiveRect.right() and lineHeight > 0: - x = effectiveRect.x() - y += lineHeight + spaceY - nextX = x + item.sizeHint().width() + spaceX - lineHeight = 0 - - if not test: - item.setGeometry(qt.QRect(qt.QPoint(x, y), item.sizeHint())) - - x = nextX - lineHeight = max(lineHeight, item.sizeHint().height()) - - return y + lineHeight - rect.y() + bottom - - def setHorizontalSpacing(self, spacing): - """Set the horizontal spacing between widgets laid out side by side - - :param int spacing: - """ - self._horizontalSpacing = spacing - self.update() - - def horizontalSpacing(self): - """Returns the horizontal spacing between widgets laid out side by side - - :rtype: int - """ - if self._horizontalSpacing >= 0: - return self._horizontalSpacing - else: - return self._smartSpacing(qt.QStyle.PM_LayoutHorizontalSpacing) - - def setVerticalSpacing(self, spacing): - """Set the vertical spacing between lines - - :param int spacing: - """ - self._verticalSpacing = spacing - self.update() - - def verticalSpacing(self): - """Returns the vertical spacing between lines - - :rtype: int - """ - if self._verticalSpacing >= 0: - return self._verticalSpacing - else: - return self._smartSpacing(qt.QStyle.PM_LayoutVerticalSpacing) - - def _smartSpacing(self, pm): - parent = self.parent() - if parent is None: - return -1 - if parent.isWidgetType(): - return parent.style().pixelMetric(pm, None, parent) - else: - return parent.spacing() diff --git a/silx/gui/widgets/FrameBrowser.py b/silx/gui/widgets/FrameBrowser.py deleted file mode 100644 index 671991f..0000000 --- a/silx/gui/widgets/FrameBrowser.py +++ /dev/null @@ -1,324 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2018 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 defines two main classes: - - - :class:`FrameBrowser`: a widget with 4 buttons (first, previous, next, - last) to browse between frames and a text entry to access a specific frame - by typing it's number) - - :class:`HorizontalSliderWithBrowser`: a FrameBrowser with an additional - slider. This class inherits :class:`qt.QAbstractSlider`. - -""" -from silx.gui import qt -from silx.gui import icons -from silx.utils import deprecation - -__authors__ = ["V.A. Sole", "P. Knobel"] -__license__ = "MIT" -__date__ = "16/01/2017" - - -class FrameBrowser(qt.QWidget): - """Frame browser widget, with 4 buttons/icons and a line edit to provide - a way of selecting a frame index in a stack of images. - - .. image:: img/FrameBrowser.png - - It can be used in more generic case to select an integer within a range. - - :param QWidget parent: Parent widget - :param int n: Number of frames. This will set the range - of frame indices to 0--n-1. - If None, the range is initialized to the default QSlider range (0--99). - """ - - sigIndexChanged = qt.pyqtSignal(object) - - def __init__(self, parent=None, n=None): - qt.QWidget.__init__(self, parent) - - # Use the font size as the icon size to avoid to create bigger buttons - fontMetric = self.fontMetrics() - iconSize = qt.QSize(fontMetric.height(), fontMetric.height()) - - self.mainLayout = qt.QHBoxLayout(self) - self.mainLayout.setContentsMargins(0, 0, 0, 0) - self.mainLayout.setSpacing(0) - self.firstButton = qt.QPushButton(self) - self.firstButton.setIcon(icons.getQIcon("first")) - self.firstButton.setIconSize(iconSize) - self.previousButton = qt.QPushButton(self) - self.previousButton.setIcon(icons.getQIcon("previous")) - self.previousButton.setIconSize(iconSize) - self._lineEdit = qt.QLineEdit(self) - - self._label = qt.QLabel(self) - self.nextButton = qt.QPushButton(self) - self.nextButton.setIcon(icons.getQIcon("next")) - self.nextButton.setIconSize(iconSize) - self.lastButton = qt.QPushButton(self) - self.lastButton.setIcon(icons.getQIcon("last")) - self.lastButton.setIconSize(iconSize) - - self.mainLayout.addWidget(self.firstButton) - self.mainLayout.addWidget(self.previousButton) - self.mainLayout.addWidget(self._lineEdit) - self.mainLayout.addWidget(self._label) - self.mainLayout.addWidget(self.nextButton) - self.mainLayout.addWidget(self.lastButton) - - if n is None: - first = qt.QSlider().minimum() - last = qt.QSlider().maximum() - else: - first, last = 0, n - - self._lineEdit.setFixedWidth(self._lineEdit.fontMetrics().boundingRect('%05d' % last).width()) - validator = qt.QIntValidator(first, last, self._lineEdit) - self._lineEdit.setValidator(validator) - self._lineEdit.setText("%d" % first) - self._label.setText("of %d" % last) - - self._index = first - """0-based index""" - - self.firstButton.clicked.connect(self._firstClicked) - self.previousButton.clicked.connect(self._previousClicked) - self.nextButton.clicked.connect(self._nextClicked) - self.lastButton.clicked.connect(self._lastClicked) - self._lineEdit.editingFinished.connect(self._textChangedSlot) - - def lineEdit(self): - """Returns the line edit provided by this widget. - - :rtype: qt.QLineEdit - """ - return self._lineEdit - - def limitWidget(self): - """Returns the widget displaying axes limits. - - :rtype: qt.QLabel - """ - return self._label - - def _firstClicked(self): - """Select first/lowest frame number""" - self.setValue(self.getRange()[0]) - - def _previousClicked(self): - """Select previous frame number""" - self.setValue(self.getValue() - 1) - - def _nextClicked(self): - """Select next frame number""" - self.setValue(self.getValue() + 1) - - def _lastClicked(self): - """Select last/highest frame number""" - self.setValue(self.getRange()[1]) - - def _textChangedSlot(self): - """Select frame number typed in the line edit widget""" - txt = self._lineEdit.text() - if not len(txt): - self._lineEdit.setText("%d" % self._index) - return - new_value = int(txt) - if new_value == self._index: - return - ddict = { - "event": "indexChanged", - "old": self._index, - "new": new_value, - "id": id(self) - } - self._index = new_value - self.sigIndexChanged.emit(ddict) - - def getRange(self): - """Returns frame range - - :return: (first_index, last_index) - """ - validator = self.lineEdit().validator() - return validator.bottom(), validator.top() - - def setRange(self, first, last): - """Set minimum and maximum frame indices. - - Initialize the frame index to *first*. - Update the label text to *" limits: first, last"* - - :param int first: Minimum frame index - :param int last: Maximum frame index""" - bottom = min(first, last) - top = max(first, last) - self._lineEdit.validator().setTop(top) - self._lineEdit.validator().setBottom(bottom) - self.setValue(bottom) - - # Update limits - self._label.setText(" limits: %d, %d " % (bottom, top)) - - @deprecation.deprecated(replacement="FrameBrowser.setRange", - since_version="0.8") - def setLimits(self, first, last): - return self.setRange(first, last) - - def setNFrames(self, nframes): - """Set minimum=0 and maximum=nframes-1 frame numbers. - - Initialize the frame index to 0. - Update the label text to *"1 of nframes"* - - :param int nframes: Number of frames""" - top = nframes - 1 - self.setRange(0, top) - # display 1-based index in label - self._label.setText(" of %d " % top) - - @deprecation.deprecated(replacement="FrameBrowser.getValue", - since_version="0.8") - def getCurrentIndex(self): - return self._index - - def getValue(self): - """Return current frame index""" - return self._index - - def setValue(self, value): - """Set 0-based frame index - - Value is clipped to current range. - - :param int value: Frame number""" - bottom = self.lineEdit().validator().bottom() - top = self.lineEdit().validator().top() - value = int(value) - - if value < bottom: - value = bottom - elif value > top: - value = top - - self._lineEdit.setText("%d" % value) - self._textChangedSlot() - - -class HorizontalSliderWithBrowser(qt.QAbstractSlider): - """ - Slider widget combining a :class:`QSlider` and a :class:`FrameBrowser`. - - .. image:: img/HorizontalSliderWithBrowser.png - - The data model is an integer within a range. - - The default value is the default :class:`QSlider` value (0), - and the default range is the default QSlider range (0 -- 99) - - The signal emitted when the value is changed is the usual QAbstractSlider - signal :attr:`valueChanged`. The signal carries the value (as an integer). - - :param QWidget parent: Optional parent widget - """ - def __init__(self, parent=None): - qt.QAbstractSlider.__init__(self, parent) - self.setOrientation(qt.Qt.Horizontal) - - self.mainLayout = qt.QHBoxLayout(self) - self.mainLayout.setContentsMargins(0, 0, 0, 0) - self.mainLayout.setSpacing(2) - - self._slider = qt.QSlider(self) - self._slider.setOrientation(qt.Qt.Horizontal) - - self._browser = FrameBrowser(self) - - self.mainLayout.addWidget(self._slider, 1) - self.mainLayout.addWidget(self._browser) - - self._slider.valueChanged[int].connect(self._sliderSlot) - self._browser.sigIndexChanged.connect(self._browserSlot) - - def lineEdit(self): - """Returns the line edit provided by this widget. - - :rtype: qt.QLineEdit - """ - return self._browser.lineEdit() - - def limitWidget(self): - """Returns the widget displaying axes limits. - - :rtype: qt.QLabel - """ - return self._browser.limitWidget() - - def setMinimum(self, value): - """Set minimum value - - :param int value: Minimum value""" - self._slider.setMinimum(value) - maximum = self._slider.maximum() - self._browser.setRange(value, maximum) - - def setMaximum(self, value): - """Set maximum value - - :param int value: Maximum value - """ - self._slider.setMaximum(value) - minimum = self._slider.minimum() - self._browser.setRange(minimum, value) - - def setRange(self, first, last): - """Set minimum/maximum values - - :param int first: Minimum value - :param int last: Maximum value""" - self._slider.setRange(first, last) - self._browser.setRange(first, last) - - def _sliderSlot(self, value): - """Emit selected value when slider is activated - """ - self._browser.setValue(value) - self.valueChanged.emit(value) - - def _browserSlot(self, ddict): - """Emit selected value when browser state is changed""" - self._slider.setValue(ddict['new']) - - def setValue(self, value): - """Set value - - :param int value: value""" - self._slider.setValue(value) - self._browser.setValue(value) - - def value(self): - """Get selected value""" - return self._slider.value() diff --git a/silx/gui/widgets/HierarchicalTableView.py b/silx/gui/widgets/HierarchicalTableView.py deleted file mode 100644 index 3ccf4c7..0000000 --- a/silx/gui/widgets/HierarchicalTableView.py +++ /dev/null @@ -1,172 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2017 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 define a hierarchical table view and model. - -It allows to define many headers in the middle of a table. - -The implementation hide the default header and allows to custom each cells -to became a header. - -Row and column span is a concept of the view in a QTableView. -This implementation also provide a span property as part of the model of the -cell. A role is define to custom this information. -The view is updated everytime the model is reset to take care of the -changes of this information. - -A default item delegate is used to redefine the paint of the cells. -""" -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "07/04/2017" - -from silx.gui import qt - - -class HierarchicalTableModel(qt.QAbstractTableModel): - """ - Abstract table model to provide more custom on row and column span and - headers. - - Default headers are ignored and each cells can define IsHeaderRole and - SpanRole using the `data` function. - """ - - SpanRole = qt.Qt.UserRole + 0 - """Role returning a tuple for number of row span then column span. - - None and (1, 1) are neutral for the rendering. - """ - - IsHeaderRole = qt.Qt.UserRole + 1 - """Role returning True is the identified cell is a header.""" - - UserRole = qt.Qt.UserRole + 2 - """First index of user defined roles""" - - def headerData(self, section, orientation, role=qt.Qt.DisplayRole): - """Returns the 0-based row or column index, for display in the - horizontal and vertical headers - - In this case the headers are just ignored. Header information is part - of each cells. - """ - return None - - -class HierarchicalItemDelegate(qt.QStyledItemDelegate): - """ - Delegate item to take care of the rendering of the default table cells and - also the header cells. - """ - - def __init__(self, parent=None): - """ - Constructor - - :param qt.QObject parent: Parent of the widget - """ - qt.QStyledItemDelegate.__init__(self, parent) - - def paint(self, painter, option, index): - """Override the paint function to inject the style of the header. - - :param qt.QPainter painter: Painter context used to displayed the cell - :param qt.QStyleOptionViewItem option: Control how the editor is shown - :param qt.QIndex index: Index of the data to display - """ - isHeader = index.data(role=HierarchicalTableModel.IsHeaderRole) - if isHeader: - span = index.data(role=HierarchicalTableModel.SpanRole) - span = 1 if span is None else span[1] - columnCount = index.model().columnCount() - if span == columnCount: - mainTitle = True - position = qt.QStyleOptionHeader.OnlyOneSection - else: - mainTitle = False - col = index.column() - if col == 0: - position = qt.QStyleOptionHeader.Beginning - elif col < columnCount - 1: - position = qt.QStyleOptionHeader.Middle - else: - position = qt.QStyleOptionHeader.End - opt = qt.QStyleOptionHeader() - opt.direction = option.direction - opt.text = index.data() - opt.textAlignment = qt.Qt.AlignCenter if mainTitle else qt.Qt.AlignVCenter - opt.direction = option.direction - opt.fontMetrics = option.fontMetrics - opt.palette = option.palette - opt.rect = option.rect - opt.state = option.state - opt.position = position - margin = -1 - style = qt.QApplication.instance().style() - opt.rect = opt.rect.adjusted(margin, margin, -margin, -margin) - style.drawControl(qt.QStyle.CE_HeaderSection, opt, painter, None) - margin = 3 - opt.rect = opt.rect.adjusted(margin, margin, -margin, -margin) - style.drawControl(qt.QStyle.CE_HeaderLabel, opt, painter, None) - else: - qt.QStyledItemDelegate.paint(self, painter, option, index) - - -class HierarchicalTableView(qt.QTableView): - """A TableView which allow to display a `HierarchicalTableModel`.""" - - def __init__(self, parent=None): - """ - Constructor - - :param qt.QWidget parent: Parent of the widget - """ - super(HierarchicalTableView, self).__init__(parent) - self.setItemDelegate(HierarchicalItemDelegate(self)) - self.verticalHeader().setVisible(False) - self.horizontalHeader().setVisible(False) - - def setModel(self, model): - """Override the default function to connect the model to update - function""" - if self.model() is not None: - model.modelReset.disconnect(self.__modelReset) - super(HierarchicalTableView, self).setModel(model) - if self.model() is not None: - model.modelReset.connect(self.__modelReset) - self.__modelReset() - - def __modelReset(self): - """Update the model to take care of the changes of the span - information""" - self.clearSpans() - model = self.model() - for row in range(model.rowCount()): - for column in range(model.columnCount()): - index = model.index(row, column, qt.QModelIndex()) - span = model.data(index, HierarchicalTableModel.SpanRole) - if span is not None and span != (1, 1): - self.setSpan(row, column, span[0], span[1]) diff --git a/silx/gui/widgets/LegendIconWidget.py b/silx/gui/widgets/LegendIconWidget.py deleted file mode 100755 index 1c95e41..0000000 --- a/silx/gui/widgets/LegendIconWidget.py +++ /dev/null @@ -1,514 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-2018 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. -# -# ###########################################################################*/ -"""Widget displaying a symbol (marker symbol, line style and color) to identify -an item displayed by a plot. -""" - -__authors__ = ["V.A. Sole", "T. Rueter", "T. Vincent"] -__license__ = "MIT" -__data__ = "11/11/2019" - - -import logging - -import numpy - -from .. import qt, colors - - -_logger = logging.getLogger(__name__) - - -# Build all symbols -# Courtesy of the pyqtgraph project - -_Symbols = None -""""Cache supported symbols as Qt paths""" - - -_NoSymbols = (None, 'None', 'none', '', ' ') -"""List of values resulting in no symbol being displayed for a curve""" - - -_LineStyles = { - None: qt.Qt.NoPen, - 'None': qt.Qt.NoPen, - 'none': qt.Qt.NoPen, - '': qt.Qt.NoPen, - ' ': qt.Qt.NoPen, - '-': qt.Qt.SolidLine, - '--': qt.Qt.DashLine, - ':': qt.Qt.DotLine, - '-.': qt.Qt.DashDotLine -} -"""Conversion from matplotlib-like linestyle to Qt""" - -_NoLineStyle = (None, 'None', 'none', '', ' ') -"""List of style values resulting in no line being displayed for a curve""" - - -_colormapImage = {} -"""Store cached pixmap""" -# FIXME: Could be better to use a LRU dictionary - -_COLORMAP_PIXMAP_SIZE = 32 -"""Size of the cached pixmaps for the colormaps""" - - -def _initSymbols(): - """Init the cached symbol structure if not yet done.""" - global _Symbols - if _Symbols is not None: - return - - symbols = dict([(name, qt.QPainterPath()) - for name in ['o', 's', 't', 'd', '+', 'x', '.', ',']]) - symbols['o'].addEllipse(qt.QRectF(.1, .1, .8, .8)) - symbols['.'].addEllipse(qt.QRectF(.3, .3, .4, .4)) - symbols[','].addEllipse(qt.QRectF(.4, .4, .2, .2)) - symbols['s'].addRect(qt.QRectF(.1, .1, .8, .8)) - - coords = { - 't': [(0.5, 0.), (.1, .8), (.9, .8)], - 'd': [(0.1, 0.5), (0.5, 0.), (0.9, 0.5), (0.5, 1.)], - '+': [(0.0, 0.40), (0.40, 0.40), (0.40, 0.), (0.60, 0.), - (0.60, 0.40), (1., 0.40), (1., 0.60), (0.60, 0.60), - (0.60, 1.), (0.40, 1.), (0.40, 0.60), (0., 0.60)], - 'x': [(0.0, 0.40), (0.40, 0.40), (0.40, 0.), (0.60, 0.), - (0.60, 0.40), (1., 0.40), (1., 0.60), (0.60, 0.60), - (0.60, 1.), (0.40, 1.), (0.40, 0.60), (0., 0.60)] - } - for s, c in coords.items(): - symbols[s].moveTo(*c[0]) - for x, y in c[1:]: - symbols[s].lineTo(x, y) - symbols[s].closeSubpath() - tr = qt.QTransform() - tr.rotate(45) - symbols['x'].translate(qt.QPointF(-0.5, -0.5)) - symbols['x'] = tr.map(symbols['x']) - symbols['x'].translate(qt.QPointF(0.5, 0.5)) - - _Symbols = symbols - - -class LegendIconWidget(qt.QWidget): - """Object displaying linestyle and symbol of plots. - - :param QWidget parent: See :class:`QWidget` - """ - - def __init__(self, parent=None): - super(LegendIconWidget, self).__init__(parent) - _initSymbols() - - # Visibilities - self.showLine = True - self.showSymbol = True - self.showColormap = True - - # Line attributes - self.lineStyle = qt.Qt.NoPen - self.lineWidth = 1. - self.lineColor = qt.Qt.green - - self.symbol = '' - # Symbol attributes - self.symbolStyle = qt.Qt.SolidPattern - self.symbolColor = qt.Qt.green - self.symbolOutlineBrush = qt.QBrush(qt.Qt.white) - self.symbolColormap = None - """Name or array of colors""" - - self.colormap = None - """Name or array of colors""" - - # Control widget size: sizeHint "is the only acceptable - # alternative, so the widget can never grow or shrink" - # (c.f. Qt Doc, enum QSizePolicy::Policy) - self.setSizePolicy(qt.QSizePolicy.Fixed, - qt.QSizePolicy.Fixed) - - def sizeHint(self): - return qt.QSize(50, 15) - - def setSymbol(self, symbol): - """Set the symbol""" - symbol = str(symbol) - if symbol not in _NoSymbols: - if symbol not in _Symbols: - raise ValueError("Unknown symbol: <%s>" % symbol) - self.symbol = symbol - self.update() - - def setSymbolColor(self, color): - """ - :param color: determines the symbol color - :type style: qt.QColor - """ - self.symbolColor = qt.QColor(color) - self.update() - - # Modify Line - - def setLineColor(self, color): - self.lineColor = qt.QColor(color) - self.update() - - def setLineWidth(self, width): - self.lineWidth = float(width) - self.update() - - def setLineStyle(self, style): - """Set the linestyle. - - Possible line styles: - - - '', ' ', 'None': No line - - '-': solid - - '--': dashed - - ':': dotted - - '-.': dash and dot - - :param str style: The linestyle to use - """ - if style not in _LineStyles: - raise ValueError('Unknown style: %s', style) - self.lineStyle = _LineStyles[style] - self.update() - - def _toLut(self, colormap): - """Returns an internal LUT object used by this widget to manage - a colormap LUT. - - If the argument is a `Colormap` object, only the current state will be - displayed. The object itself will not be stored, and further changes - of this `Colormap` will not update this widget. - - :param Union[str,numpy.ndarray,Colormap] colormap: The colormap to - display - :rtype: Union[None,str,numpy.ndarray] - """ - if isinstance(colormap, colors.Colormap): - # Helper to allow to support Colormap objects - c = colormap.getName() - if c is None: - c = colormap.getNColors() - colormap = c - - return colormap - - def setColormap(self, colormap): - """Set the colormap to display - - If the argument is a `Colormap` object, only the current state will be - displayed. The object itself will not be stored, and further changes - of this `Colormap` will not update this widget. - - :param Union[str,numpy.ndarray,Colormap] colormap: The colormap to - display - """ - colormap = self._toLut(colormap) - - if colormap is None: - if self.colormap is None: - return - self.colormap = None - self.update() - return - - if numpy.array_equal(self.colormap, colormap): - # This also works with strings - return - - self.colormap = colormap - self.update() - - def getColormap(self): - """Returns the used colormap. - - If the argument was set with a `Colormap` object, this function will - returns the LUT, represented by a string name or by an array or colors. - - :returns: Union[None,str,numpy.ndarray,Colormap] - """ - return self.colormap - - def setSymbolColormap(self, colormap): - """Set the colormap to display a symbol - - If the argument is a `Colormap` object, only the current state will be - displayed. The object itself will not be stored, and further changes - of this `Colormap` will not update this widget. - - :param Union[str,numpy.ndarray,Colormap] colormap: The colormap to - display - """ - colormap = self._toLut(colormap) - - if colormap is None: - if self.colormap is None: - return - self.symbolColormap = None - self.update() - return - - if numpy.array_equal(self.symbolColormap, colormap): - # This also works with strings - return - - self.symbolColormap = colormap - self.update() - - def getSymbolColormap(self): - """Returns the used symbol colormap. - - If the argument was set with a `Colormap` object, this function will - returns the LUT, represented by a string name or by an array or colors. - - :returns: Union[None,str,numpy.ndarray,Colormap] - """ - return self.colormap - - # Paint - - def paintEvent(self, event): - """ - :param event: event - :type event: QPaintEvent - """ - painter = qt.QPainter(self) - self.paint(painter, event.rect(), self.palette()) - - def paint(self, painter, rect, palette): - painter.save() - painter.setRenderHint(qt.QPainter.Antialiasing) - # Scale painter to the icon height - # current -> width = 2.5, height = 1.0 - scale = float(self.height()) - ratio = float(self.width()) / scale - symbolOffset = qt.QPointF(.5 * (ratio - 1.), 0.) - # Determine and scale offset - offset = qt.QPointF(float(rect.left()) / scale, float(rect.top()) / scale) - - # Override color when disabled - if self.isEnabled(): - overrideColor = None - else: - overrideColor = palette.color(qt.QPalette.Disabled, - qt.QPalette.WindowText) - - # Draw BG rectangle (for debugging) - # bottomRight = qt.QPointF( - # float(rect.right())/scale, - # float(rect.bottom())/scale) - # painter.fillRect(qt.QRectF(offset, bottomRight), - # qt.QBrush(qt.Qt.green)) - - if self.showColormap: - if self.colormap is not None: - if self.isEnabled(): - image = self.getColormapImage(self.colormap) - else: - image = self.getGrayedColormapImage(self.colormap) - pixmapRect = qt.QRect(0, 0, _COLORMAP_PIXMAP_SIZE, 1) - widthMargin = 0 - halfHeight = 4 - widgetRect = self.rect() - dest = qt.QRect( - widgetRect.left() + widthMargin, - widgetRect.center().y() - halfHeight + 1, - widgetRect.width() - widthMargin * 2, - halfHeight * 2, - ) - painter.drawImage(dest, image, pixmapRect) - - painter.scale(scale, scale) - - llist = [] - if self.showLine: - linePath = qt.QPainterPath() - linePath.moveTo(0., 0.5) - linePath.lineTo(ratio, 0.5) - # linePath.lineTo(2.5, 0.5) - lineBrush = qt.QBrush( - self.lineColor if overrideColor is None else overrideColor) - linePen = qt.QPen( - lineBrush, - (self.lineWidth / self.height()), - self.lineStyle, - qt.Qt.FlatCap - ) - llist.append((linePath, linePen, lineBrush)) - - isValidSymbol = (len(self.symbol) and - self.symbol not in _NoSymbols) - if self.showSymbol and isValidSymbol: - if self.symbolColormap is None: - # PITFALL ahead: Let this be a warning to others - # symbolPath = Symbols[self.symbol] - # Copy before translate! Dict is a mutable type - symbolPath = qt.QPainterPath(_Symbols[self.symbol]) - symbolPath.translate(symbolOffset) - symbolBrush = qt.QBrush( - self.symbolColor if overrideColor is None else overrideColor, - self.symbolStyle) - symbolPen = qt.QPen( - self.symbolOutlineBrush, # Brush - 1. / self.height(), # Width - qt.Qt.SolidLine # Style - ) - llist.append((symbolPath, - symbolPen, - symbolBrush)) - else: - nbSymbols = int(ratio + 2) - for i in range(nbSymbols): - if self.isEnabled(): - image = self.getColormapImage(self.symbolColormap) - else: - image = self.getGrayedColormapImage(self.symbolColormap) - pos = int((_COLORMAP_PIXMAP_SIZE / nbSymbols) * i) - pos = numpy.clip(pos, 0, _COLORMAP_PIXMAP_SIZE-1) - color = image.pixelColor(pos, 0) - delta = qt.QPointF(ratio * ((i - (nbSymbols-1)/2) / nbSymbols), 0) - - symbolPath = qt.QPainterPath(_Symbols[self.symbol]) - symbolPath.translate(symbolOffset + delta) - symbolBrush = qt.QBrush(color, self.symbolStyle) - symbolPen = qt.QPen( - self.symbolOutlineBrush, # Brush - 1. / self.height(), # Width - qt.Qt.SolidLine # Style - ) - llist.append((symbolPath, - symbolPen, - symbolBrush)) - - # Draw - for path, pen, brush in llist: - path.translate(offset) - painter.setPen(pen) - painter.setBrush(brush) - painter.drawPath(path) - - painter.restore() - - # Helpers - - @staticmethod - def isEmptySymbol(symbol): - """Returns True if this symbol description will result in an empty - symbol.""" - return symbol in _NoSymbols - - @staticmethod - def isEmptyLineStyle(lineStyle): - """Returns True if this line style description will result in an empty - line.""" - return lineStyle in _NoLineStyle - - @staticmethod - def _getColormapKey(colormap): - """ - Returns the key used to store the image in the data storage - """ - if isinstance(colormap, numpy.ndarray): - key = tuple(colormap) - else: - key = colormap - return key - - @staticmethod - def getGrayedColormapImage(colormap): - """Return a grayed version image preview from a LUT name. - - This images are cached into a global structure. - - :param Union[str,numpy.ndarray] colormap: Description of the LUT - :rtype: qt.QImage - """ - key = LegendIconWidget._getColormapKey(colormap) - grayKey = (key, "gray") - image = _colormapImage.get(grayKey, None) - if image is None: - image = LegendIconWidget.getColormapImage(colormap) - image = image.convertToFormat(qt.QImage.Format_Grayscale8) - _colormapImage[grayKey] = image - return image - - @staticmethod - def getColormapImage(colormap): - """Return an image preview from a LUT name. - - This images are cached into a global structure. - - :param Union[str,numpy.ndarray] colormap: Description of the LUT - :rtype: qt.QImage - """ - key = LegendIconWidget._getColormapKey(colormap) - image = _colormapImage.get(key, None) - if image is None: - image = LegendIconWidget.createColormapImage(colormap) - _colormapImage[key] = image - return image - - @staticmethod - def createColormapImage(colormap): - """Create and return an icon preview from a LUT name. - - This icons are cached into a global structure. - - :param Union[str,numpy.ndarray] colormap: Description of the LUT - :rtype: qt.QImage - """ - size = _COLORMAP_PIXMAP_SIZE - if isinstance(colormap, numpy.ndarray): - lut = colormap - if len(lut) > size: - # Down sample - step = int(len(lut) / size) - lut = lut[::step] - elif len(lut) < size: - # Over sample - indexes = numpy.arange(size) / float(size) * (len(lut) - 1) - indexes = indexes.astype("int") - lut = lut[indexes] - else: - colormap = colors.Colormap(colormap) - lut = colormap.getNColors(size) - - if lut is None or len(lut) == 0: - return qt.QIcon() - - pixmap = qt.QPixmap(size, 1) - painter = qt.QPainter(pixmap) - for i in range(size): - rgb = lut[i] - r, g, b = rgb[0], rgb[1], rgb[2] - painter.setPen(qt.QColor(r, g, b)) - painter.drawPoint(qt.QPoint(i, 0)) - painter.end() - return pixmap.toImage() diff --git a/silx/gui/widgets/MedianFilterDialog.py b/silx/gui/widgets/MedianFilterDialog.py deleted file mode 100644 index dd4a00d..0000000 --- a/silx/gui/widgets/MedianFilterDialog.py +++ /dev/null @@ -1,80 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2017-2018 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. -# -# ###########################################################################*/ -""" MedianFilterDialog -Classes -------- - -Widgets: - - - :class:`MedianFilterDialog` -""" - -__authors__ = ["H. Payno"] -__license__ = "MIT" -__date__ = "14/02/2017" - - -import logging - -from silx.gui import qt - - -_logger = logging.getLogger(__name__) - -class MedianFilterDialog(qt.QDialog): - """QDialog window featuring a :class:`BackgroundWidget`""" - sigFilterOptChanged = qt.Signal(int, bool) - - def __init__(self, parent=None): - qt.QDialog.__init__(self, parent) - - self.setWindowTitle("Median filter options") - self.mainLayout = qt.QHBoxLayout(self) - self.setLayout(self.mainLayout) - - # filter width GUI - self.mainLayout.addWidget(qt.QLabel('filter width:', parent = self)) - self._filterWidth = qt.QSpinBox(parent=self) - self._filterWidth.setMinimum(1) - self._filterWidth.setValue(1) - self._filterWidth.setSingleStep(2); - widthTooltip = """radius width of the pixel including in the filter - for each pixel""" - self._filterWidth.setToolTip(widthTooltip) - self._filterWidth.valueChanged.connect(self._filterOptionChanged) - self.mainLayout.addWidget(self._filterWidth) - - # filter option GUI - self._filterOption = qt.QCheckBox('conditional', parent=self) - conditionalTooltip = """if check, implement a conditional filter""" - self._filterOption.stateChanged.connect(self._filterOptionChanged) - self.mainLayout.addWidget(self._filterOption) - - def _filterOptionChanged(self): - """Call back used when the filter values are changed""" - if self._filterWidth.value()%2 == 0: - _logger.warning('median filter only accept odd values') - else: - self.sigFilterOptChanged.emit(self._filterWidth.value(), self._filterOption.isChecked())
\ No newline at end of file diff --git a/silx/gui/widgets/MultiModeAction.py b/silx/gui/widgets/MultiModeAction.py deleted file mode 100644 index 502275d..0000000 --- a/silx/gui/widgets/MultiModeAction.py +++ /dev/null @@ -1,83 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-2018 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. -# -# ###########################################################################*/ -"""Action to hold many mode actions, usually for a tool bar. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__data__ = "22/04/2020" - - -from silx.gui import qt - - -class MultiModeAction(qt.QWidgetAction): - """This action provides a default checkable action from a list of checkable - actions. - - The default action can be selected from a drop down list. The last one used - became the default one. - - The default action is directly usable without using the drop down list. - """ - - def __init__(self, parent=None): - assert isinstance(parent, qt.QWidget) - qt.QWidgetAction.__init__(self, parent) - button = qt.QToolButton(parent) - button.setPopupMode(qt.QToolButton.MenuButtonPopup) - self.setDefaultWidget(button) - self.__button = button - - def getMenu(self): - """Returns the menu. - - :rtype: qt.QMenu - """ - button = self.__button - menu = button.menu() - if menu is None: - menu = qt.QMenu(button) - button.setMenu(menu) - return menu - - def addAction(self, action): - """Add a new action to the list. - - :param qt.QAction action: New action - """ - menu = self.getMenu() - button = self.__button - menu.addAction(action) - if button.defaultAction() is None: - button.setDefaultAction(action) - if action.isCheckable(): - action.toggled.connect(self._toggled) - - def _toggled(self, checked): - if checked: - action = self.sender() - button = self.__button - button.setDefaultAction(action) diff --git a/silx/gui/widgets/PeriodicTable.py b/silx/gui/widgets/PeriodicTable.py deleted file mode 100644 index 0233e8c..0000000 --- a/silx/gui/widgets/PeriodicTable.py +++ /dev/null @@ -1,831 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-2018 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. -# -# ###########################################################################*/ -"""Periodic table widgets - -Classes -------- - -Widgets: - - - :class:`PeriodicTable` - - :class:`PeriodicList` - - :class:`PeriodicCombo` - -Data model: - - - :class:`PeriodicTableItem` - - :class:`ColoredPeriodicTableItem` - - -Example of usage ----------------- - -This example uses the widgets with the standard builtin elements list. - -.. code-block:: python - - from silx.gui import qt - from silx.gui.widgets.PeriodicTable import PeriodicTable, \ - PeriodicCombo, PeriodicList - - a = qt.QApplication([]) - - w = qt.QTabWidget() - - ptable = PeriodicTable(w, selectable=True) - pcombo = PeriodicCombo(w) - plist = PeriodicList(w) - - w.addTab(ptable, "PeriodicTable") - w.addTab(plist, "PeriodicList") - w.addTab(pcombo, "PeriodicCombo") - - ptable.setSelection(['H', 'Fe', 'Si']) - plist.setSelectedElements(['H', 'Be', 'F']) - pcombo.setSelection("Li") - - def change_list(items): - print("New list selection:", [item.symbol for item in items]) - - def change_combo(item): - print("New combo selection:", item.symbol) - - def click_table(item): - print("New table click:", item.symbol) - - def change_table(items): - print("New table selection:", [item.symbol for item in items]) - - ptable.sigElementClicked.connect(click_table) - ptable.sigSelectionChanged.connect(change_table) - plist.sigSelectionChanged.connect(change_list) - pcombo.sigSelectionChanged.connect(change_combo) - - w.show() - a.exec_() - - -The second example explains how to define custom elements. - -.. code-block:: python - - from silx.gui import qt - from silx.gui.widgets.PeriodicTable import PeriodicTable, \ - PeriodicCombo, PeriodicList - from silx.gui.widgets.PeriodicTable import PeriodicTableItem - - # subclass PeriodicTableItem - class MyPeriodicTableItem(PeriodicTableItem): - "New item with added mass number and number of protons" - def __init__(self, symbol, Z, A, col, row, name, mass, - subcategory=""): - PeriodicTableItem.__init__( - self, symbol, Z, col, row, name, mass, - subcategory) - - self.A = A - "Mass number (neutrons + protons)" - - self.num_neutrons = A - Z - "Number of neutrons" - - # build your list of elements - my_elements = [MyPeriodicTableItem("H", 1, 1, 1, 1, "hydrogen", - 1.00800, "diatomic nonmetal"), - MyPeriodicTableItem("He", 2, 4, 18, 1, "helium", - 4.0030, "noble gas"), - # etc ... - ] - - app = qt.QApplication([]) - - ptable = PeriodicTable(elements=my_elements, selectable=True) - ptable.show() - - def click_table(item): - "Callback function printing the mass number of clicked element" - print("New table click, mass number:", item.A) - - ptable.sigElementClicked.connect(click_table) - app.exec_() - -""" - -__authors__ = ["E. Papillon", "V.A. Sole", "P. Knobel"] -__license__ = "MIT" -__date__ = "26/01/2017" - -from collections import OrderedDict -import logging -from silx.gui import qt - -_logger = logging.getLogger(__name__) - -# Symbol Atomic Number col row name mass subcategory -_elements = [("H", 1, 1, 1, "hydrogen", 1.00800, "diatomic nonmetal"), - ("He", 2, 18, 1, "helium", 4.0030, "noble gas"), - ("Li", 3, 1, 2, "lithium", 6.94000, "alkali metal"), - ("Be", 4, 2, 2, "beryllium", 9.01200, "alkaline earth metal"), - ("B", 5, 13, 2, "boron", 10.8110, "metalloid"), - ("C", 6, 14, 2, "carbon", 12.0100, "polyatomic nonmetal"), - ("N", 7, 15, 2, "nitrogen", 14.0080, "diatomic nonmetal"), - ("O", 8, 16, 2, "oxygen", 16.0000, "diatomic nonmetal"), - ("F", 9, 17, 2, "fluorine", 19.0000, "diatomic nonmetal"), - ("Ne", 10, 18, 2, "neon", 20.1830, "noble gas"), - ("Na", 11, 1, 3, "sodium", 22.9970, "alkali metal"), - ("Mg", 12, 2, 3, "magnesium", 24.3200, "alkaline earth metal"), - ("Al", 13, 13, 3, "aluminium", 26.9700, "post transition metal"), - ("Si", 14, 14, 3, "silicon", 28.0860, "metalloid"), - ("P", 15, 15, 3, "phosphorus", 30.9750, "polyatomic nonmetal"), - ("S", 16, 16, 3, "sulphur", 32.0660, "polyatomic nonmetal"), - ("Cl", 17, 17, 3, "chlorine", 35.4570, "diatomic nonmetal"), - ("Ar", 18, 18, 3, "argon", 39.9440, "noble gas"), - ("K", 19, 1, 4, "potassium", 39.1020, "alkali metal"), - ("Ca", 20, 2, 4, "calcium", 40.0800, "alkaline earth metal"), - ("Sc", 21, 3, 4, "scandium", 44.9600, "transition metal"), - ("Ti", 22, 4, 4, "titanium", 47.9000, "transition metal"), - ("V", 23, 5, 4, "vanadium", 50.9420, "transition metal"), - ("Cr", 24, 6, 4, "chromium", 51.9960, "transition metal"), - ("Mn", 25, 7, 4, "manganese", 54.9400, "transition metal"), - ("Fe", 26, 8, 4, "iron", 55.8500, "transition metal"), - ("Co", 27, 9, 4, "cobalt", 58.9330, "transition metal"), - ("Ni", 28, 10, 4, "nickel", 58.6900, "transition metal"), - ("Cu", 29, 11, 4, "copper", 63.5400, "transition metal"), - ("Zn", 30, 12, 4, "zinc", 65.3800, "transition metal"), - ("Ga", 31, 13, 4, "gallium", 69.7200, "post transition metal"), - ("Ge", 32, 14, 4, "germanium", 72.5900, "metalloid"), - ("As", 33, 15, 4, "arsenic", 74.9200, "metalloid"), - ("Se", 34, 16, 4, "selenium", 78.9600, "polyatomic nonmetal"), - ("Br", 35, 17, 4, "bromine", 79.9200, "diatomic nonmetal"), - ("Kr", 36, 18, 4, "krypton", 83.8000, "noble gas"), - ("Rb", 37, 1, 5, "rubidium", 85.4800, "alkali metal"), - ("Sr", 38, 2, 5, "strontium", 87.6200, "alkaline earth metal"), - ("Y", 39, 3, 5, "yttrium", 88.9050, "transition metal"), - ("Zr", 40, 4, 5, "zirconium", 91.2200, "transition metal"), - ("Nb", 41, 5, 5, "niobium", 92.9060, "transition metal"), - ("Mo", 42, 6, 5, "molybdenum", 95.9500, "transition metal"), - ("Tc", 43, 7, 5, "technetium", 99.0000, "transition metal"), - ("Ru", 44, 8, 5, "ruthenium", 101.0700, "transition metal"), - ("Rh", 45, 9, 5, "rhodium", 102.9100, "transition metal"), - ("Pd", 46, 10, 5, "palladium", 106.400, "transition metal"), - ("Ag", 47, 11, 5, "silver", 107.880, "transition metal"), - ("Cd", 48, 12, 5, "cadmium", 112.410, "transition metal"), - ("In", 49, 13, 5, "indium", 114.820, "post transition metal"), - ("Sn", 50, 14, 5, "tin", 118.690, "post transition metal"), - ("Sb", 51, 15, 5, "antimony", 121.760, "metalloid"), - ("Te", 52, 16, 5, "tellurium", 127.600, "metalloid"), - ("I", 53, 17, 5, "iodine", 126.910, "diatomic nonmetal"), - ("Xe", 54, 18, 5, "xenon", 131.300, "noble gas"), - ("Cs", 55, 1, 6, "caesium", 132.910, "alkali metal"), - ("Ba", 56, 2, 6, "barium", 137.360, "alkaline earth metal"), - ("La", 57, 3, 6, "lanthanum", 138.920, "lanthanide"), - ("Ce", 58, 4, 9, "cerium", 140.130, "lanthanide"), - ("Pr", 59, 5, 9, "praseodymium", 140.920, "lanthanide"), - ("Nd", 60, 6, 9, "neodymium", 144.270, "lanthanide"), - ("Pm", 61, 7, 9, "promethium", 147.000, "lanthanide"), - ("Sm", 62, 8, 9, "samarium", 150.350, "lanthanide"), - ("Eu", 63, 9, 9, "europium", 152.000, "lanthanide"), - ("Gd", 64, 10, 9, "gadolinium", 157.260, "lanthanide"), - ("Tb", 65, 11, 9, "terbium", 158.930, "lanthanide"), - ("Dy", 66, 12, 9, "dysprosium", 162.510, "lanthanide"), - ("Ho", 67, 13, 9, "holmium", 164.940, "lanthanide"), - ("Er", 68, 14, 9, "erbium", 167.270, "lanthanide"), - ("Tm", 69, 15, 9, "thulium", 168.940, "lanthanide"), - ("Yb", 70, 16, 9, "ytterbium", 173.040, "lanthanide"), - ("Lu", 71, 17, 9, "lutetium", 174.990, "lanthanide"), - ("Hf", 72, 4, 6, "hafnium", 178.500, "transition metal"), - ("Ta", 73, 5, 6, "tantalum", 180.950, "transition metal"), - ("W", 74, 6, 6, "tungsten", 183.920, "transition metal"), - ("Re", 75, 7, 6, "rhenium", 186.200, "transition metal"), - ("Os", 76, 8, 6, "osmium", 190.200, "transition metal"), - ("Ir", 77, 9, 6, "iridium", 192.200, "transition metal"), - ("Pt", 78, 10, 6, "platinum", 195.090, "transition metal"), - ("Au", 79, 11, 6, "gold", 197.200, "transition metal"), - ("Hg", 80, 12, 6, "mercury", 200.610, "transition metal"), - ("Tl", 81, 13, 6, "thallium", 204.390, "post transition metal"), - ("Pb", 82, 14, 6, "lead", 207.210, "post transition metal"), - ("Bi", 83, 15, 6, "bismuth", 209.000, "post transition metal"), - ("Po", 84, 16, 6, "polonium", 209.000, "post transition metal"), - ("At", 85, 17, 6, "astatine", 210.000, "metalloid"), - ("Rn", 86, 18, 6, "radon", 222.000, "noble gas"), - ("Fr", 87, 1, 7, "francium", 223.000, "alkali metal"), - ("Ra", 88, 2, 7, "radium", 226.000, "alkaline earth metal"), - ("Ac", 89, 3, 7, "actinium", 227.000, "actinide"), - ("Th", 90, 4, 10, "thorium", 232.000, "actinide"), - ("Pa", 91, 5, 10, "proactinium", 231.03588, "actinide"), - ("U", 92, 6, 10, "uranium", 238.070, "actinide"), - ("Np", 93, 7, 10, "neptunium", 237.000, "actinide"), - ("Pu", 94, 8, 10, "plutonium", 239.100, "actinide"), - ("Am", 95, 9, 10, "americium", 243, "actinide"), - ("Cm", 96, 10, 10, "curium", 247, "actinide"), - ("Bk", 97, 11, 10, "berkelium", 247, "actinide"), - ("Cf", 98, 12, 10, "californium", 251, "actinide"), - ("Es", 99, 13, 10, "einsteinium", 252, "actinide"), - ("Fm", 100, 14, 10, "fermium", 257, "actinide"), - ("Md", 101, 15, 10, "mendelevium", 258, "actinide"), - ("No", 102, 16, 10, "nobelium", 259, "actinide"), - ("Lr", 103, 17, 10, "lawrencium", 262, "actinide"), - ("Rf", 104, 4, 7, "rutherfordium", 261, "transition metal"), - ("Db", 105, 5, 7, "dubnium", 262, "transition metal"), - ("Sg", 106, 6, 7, "seaborgium", 266, "transition metal"), - ("Bh", 107, 7, 7, "bohrium", 264, "transition metal"), - ("Hs", 108, 8, 7, "hassium", 269, "transition metal"), - ("Mt", 109, 9, 7, "meitnerium", 268)] - - -class PeriodicTableItem(object): - """Periodic table item, used as generic item in :class:`PeriodicTable`, - :class:`PeriodicCombo` and :class:`PeriodicList`. - - This implementation stores the minimal amount of information needed by the - widgets: - - - atomic symbol - - atomic number - - element name - - atomic mass - - column of element in periodic table - - row of element in periodic table - - You can subclass this class to add additional information. - - :param str symbol: Atomic symbol (e.g. H, He, Li...) - :param int Z: Proton number - :param int col: 1-based column index of element in periodic table - :param int row: 1-based row index of element in periodic table - :param str name: PeriodicTableItem name ("hydrogen", ...) - :param float mass: Atomic mass (gram per mol) - :param str subcategory: Subcategory, based on physical properties - (e.g. "alkali metal", "noble gas"...) - """ - def __init__(self, symbol, Z, col, row, name, mass, - subcategory=""): - self.symbol = symbol - """Atomic symbol (e.g. H, He, Li...)""" - self.Z = Z - """Atomic number (Proton number)""" - self.col = col - """1-based column index of element in periodic table""" - self.row = row - """1-based row index of element in periodic table""" - self.name = name - """PeriodicTableItem name ("hydrogen", ...)""" - self.mass = mass - """Atomic mass (gram per mol)""" - self.subcategory = subcategory - """Subcategory, based on physical properties - (e.g. "alkali metal", "noble gas"...)""" - - # pymca compatibility (elements used to be stored as a list of lists) - def __getitem__(self, idx): - if idx == 6: - _logger.warning("density not implemented in silx, returning 0.") - - ret = [self.symbol, self.Z, - self.col, self.row, - self.name, self.mass, - 0.] - return ret[idx] - - def __len__(self): - return 6 - - -class ColoredPeriodicTableItem(PeriodicTableItem): - """:class:`PeriodicTableItem` with an added :attr:`bgcolor`. - The background color can be passed as a parameter to the constructor. - If it is not specified, it will be defined based on - :attr:`subcategory`. - - :param str bgcolor: Custom background color for element in - periodic table, as a RGB string *#RRGGBB*""" - COLORS = { - "diatomic nonmetal": "#7FFF00", # chartreuse - "noble gas": "#00FFFF", # cyan - "alkali metal": "#FFE4B5", # Moccasin - "alkaline earth metal": "#FFA500", # orange - "polyatomic nonmetal": "#7FFFD4", # aquamarine - "transition metal": "#FFA07A", # light salmon - "metalloid": "#8FBC8F", # Dark Sea Green - "post transition metal": "#D3D3D3", # light gray - "lanthanide": "#FFB6C1", # light pink - "actinide": "#F08080", # Light Coral - "": "#FFFFFF" # white - } - """Dictionary defining RGB colors for each subcategory.""" - - def __init__(self, symbol, Z, col, row, name, mass, - subcategory="", bgcolor=None): - PeriodicTableItem.__init__(self, symbol, Z, col, row, name, mass, - subcategory) - - self.bgcolor = self.COLORS.get(subcategory, "#FFFFFF") - """Background color of element in the periodic table, - based on its subcategory. This should be a string of a hexadecimal - RGB code, with the format *#RRGGBB*. - If the subcategory is unknown, use white (*#FFFFFF*) - """ - - # possible custom color - if bgcolor is not None: - self.bgcolor = bgcolor - - -_defaultTableItems = [ColoredPeriodicTableItem(*info) for info in _elements] - - -class _ElementButton(qt.QPushButton): - """Atomic element button, used as a cell in the periodic table - """ - sigElementEnter = qt.pyqtSignal(object) - """Signal emitted as the cursor enters the widget""" - sigElementLeave = qt.pyqtSignal(object) - """Signal emitted as the cursor leaves the widget""" - sigElementClicked = qt.pyqtSignal(object) - """Signal emitted when the widget is clicked""" - - def __init__(self, item, parent=None): - """ - - :param parent: Parent widget - :param PeriodicTableItem item: :class:`PeriodicTableItem` object - """ - qt.QPushButton.__init__(self, parent) - - self.item = item - """:class:`PeriodicTableItem` object represented by this button""" - - self.setText(item.symbol) - self.setFlat(1) - self.setCheckable(0) - - self.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Expanding, - qt.QSizePolicy.Expanding)) - - self.selected = False - self.current = False - - # selection colors - self.selected_color = qt.QColor(qt.Qt.yellow) - self.current_color = qt.QColor(qt.Qt.gray) - self.selected_current_color = qt.QColor(qt.Qt.darkYellow) - - # element colors - - if hasattr(item, "bgcolor"): - self.bgcolor = qt.QColor(item.bgcolor) - else: - self.bgcolor = qt.QColor("#FFFFFF") - - self.brush = qt.QBrush() - self.__setBrush() - - self.clicked.connect(self.clickedSlot) - - def sizeHint(self): - return qt.QSize(40, 40) - - def setCurrent(self, b): - """Set this element button as current. - Multiple buttons can be selected. - - :param b: boolean - """ - self.current = b - self.__setBrush() - - def isCurrent(self): - """ - :return: True if element button is current - """ - return self.current - - def isSelected(self): - """ - :return: True if element button is selected - """ - return self.selected - - def setSelected(self, b): - """Set this element button as selected. - Only a single button can be selected. - - :param b: boolean - """ - self.selected = b - self.__setBrush() - - def __setBrush(self): - """Selected cells are yellow when not current. - The current cell is dark yellow when selected or grey when not - selected. - Other cells have no bg color by default, unless specified at - instantiation (:attr:`bgcolor`)""" - palette = self.palette() - # if self.current and self.selected: - # self.brush = qt.QBrush(self.selected_current_color) - # el - if self.selected: - self.brush = qt.QBrush(self.selected_color) - # elif self.current: - # self.brush = qt.QBrush(self.current_color) - elif self.bgcolor is not None: - self.brush = qt.QBrush(self.bgcolor) - else: - self.brush = qt.QBrush() - palette.setBrush(self.backgroundRole(), - self.brush) - self.setPalette(palette) - self.update() - - def paintEvent(self, pEvent): - # get button geometry - widgGeom = self.rect() - paintGeom = qt.QRect(widgGeom.left() + 1, - widgGeom.top() + 1, - widgGeom.width() - 2, - widgGeom.height() - 2) - - # paint background color - painter = qt.QPainter(self) - if self.brush is not None: - painter.fillRect(paintGeom, self.brush) - # paint frame - pen = qt.QPen(qt.Qt.black) - pen.setWidth(1 if not self.isCurrent() else 5) - painter.setPen(pen) - painter.drawRect(paintGeom) - painter.end() - qt.QPushButton.paintEvent(self, pEvent) - - def enterEvent(self, e): - """Emit a :attr:`sigElementEnter` signal and send a - :class:`PeriodicTableItem` object""" - self.sigElementEnter.emit(self.item) - - def leaveEvent(self, e): - """Emit a :attr:`sigElementLeave` signal and send a - :class:`PeriodicTableItem` object""" - self.sigElementLeave.emit(self.item) - - def clickedSlot(self): - """Emit a :attr:`sigElementClicked` signal and send a - :class:`PeriodicTableItem` object""" - self.sigElementClicked.emit(self.item) - - -class PeriodicTable(qt.QWidget): - """Periodic Table widget - - .. image:: img/PeriodicTable.png - - The following example shows how to connect clicking to selection:: - - from silx.gui import qt - from silx.gui.widgets.PeriodicTable import PeriodicTable - app = qt.QApplication([]) - pt = PeriodicTable() - pt.sigElementClicked.connect(pt.elementToggle) - pt.show() - app.exec_() - - To print all selected elements each time a new element is selected:: - - def my_slot(item): - pt.elementToggle(item) - selected_elements = pt.getSelection() - for e in selected_elements: - print(e.symbol) - - pt.sigElementClicked.connect(my_slot) - - """ - sigElementClicked = qt.pyqtSignal(object) - """When any element is clicked in the table, the widget emits - this signal and sends a :class:`PeriodicTableItem` object. - """ - - sigSelectionChanged = qt.pyqtSignal(object) - """When any element is selected/unselected in the table, the widget emits - this signal and sends a list of :class:`PeriodicTableItem` objects. - - .. note:: - - To enable selection of elements, you must set *selectable=True* - when you instantiate the widget. Alternatively, you can also connect - :attr:`sigElementClicked` to :meth:`elementToggle` manually:: - - pt = PeriodicTable() - pt.sigElementClicked.connect(pt.elementToggle) - - - :param parent: parent QWidget - :param str name: Widget window title - :param elements: List of items (:class:`PeriodicTableItem` objects) to - be represented in the table. By default, take elements from - a predefined list with minimal information (symbol, atomic number, - name, mass). - :param bool selectable: If *True*, multiple elements can be - selected by clicking with the mouse. If *False* (default), - selection is only possible with method :meth:`setSelection`. - """ - - def __init__(self, parent=None, name="PeriodicTable", elements=None, - selectable=False): - self.selectable = selectable - qt.QWidget.__init__(self, parent) - self.setWindowTitle(name) - self.gridLayout = qt.QGridLayout(self) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.addItem(qt.QSpacerItem(0, 5), 7, 0) - - for idx in range(10): - self.gridLayout.setRowStretch(idx, 3) - # row 8 (above lanthanoids is empty) - self.gridLayout.setRowStretch(7, 2) - - # Element information displayed when cursor enters a cell - self.eltLabel = qt.QLabel(self) - f = self.eltLabel.font() - f.setBold(1) - self.eltLabel.setFont(f) - self.eltLabel.setAlignment(qt.Qt.AlignHCenter) - self.gridLayout.addWidget(self.eltLabel, 1, 1, 3, 10) - - self._eltCurrent = None - """Current :class:`_ElementButton` (last clicked)""" - - self._eltButtons = OrderedDict() - """Dictionary of all :class:`_ElementButton`. Keys are the symbols - ("H", "He", "Li"...)""" - - if elements is None: - elements = _defaultTableItems - # fill cells with elements - for elmt in elements: - self.__addElement(elmt) - - def __addElement(self, elmt): - """Add one :class:`_ElementButton` widget into the grid, - connect its signals to interact with the cursor""" - b = _ElementButton(elmt, self) - b.setAutoDefault(False) - - self._eltButtons[elmt.symbol] = b - self.gridLayout.addWidget(b, elmt.row, elmt.col) - - b.sigElementEnter.connect(self.elementEnter) - b.sigElementLeave.connect(self._elementLeave) - b.sigElementClicked.connect(self._elementClicked) - - def elementEnter(self, item): - """Update label with element info (e.g. "Nb(41) - niobium") - when mouse cursor hovers an element. - - :param PeriodicTableItem item: Element entered by cursor - """ - self.eltLabel.setText("%s(%d) - %s" % (item.symbol, item.Z, item.name)) - - def _elementLeave(self, item): - """Clear label when the cursor leaves the cell - - :param PeriodicTableItem item: Element left - """ - self.eltLabel.setText("") - - def _elementClicked(self, item): - """Emit :attr:`sigElementClicked`, - toggle selected state of element - - :param PeriodicTableItem item: Element clicked - """ - if self._eltCurrent is not None: - self._eltCurrent.setCurrent(False) - self._eltButtons[item.symbol].setCurrent(True) - self._eltCurrent = self._eltButtons[item.symbol] - if self.selectable: - self.elementToggle(item) - self.sigElementClicked.emit(item) - - def getSelection(self): - """Return a list of selected elements, as a list of :class:`PeriodicTableItem` - objects. - - :return: Selected items - :rtype: List[PeriodicTableItem] - """ - return [b.item for b in self._eltButtons.values() if b.isSelected()] - - def setSelection(self, symbols): - """Set selected elements. - - This causes the sigSelectionChanged signal - to be emitted, even if the selection didn't actually change. - - :param List[str] symbols: List of symbols of elements to be selected - (e.g. *["Fe", "Hg", "Li"]*) - """ - # accept list of PeriodicTableItems as input, because getSelection - # returns these objects and it makes sense to have getter and setter - # use same type of data - if isinstance(symbols[0], PeriodicTableItem): - symbols = [elmt.symbol for elmt in symbols] - - for (e, b) in self._eltButtons.items(): - b.setSelected(e in symbols) - self.sigSelectionChanged.emit(self.getSelection()) - - def setElementSelected(self, symbol, state): - """Modify *selected* status of a single element (select or unselect) - - :param str symbol: PeriodicTableItem symbol to be selected - :param bool state: *True* to select, *False* to unselect - """ - self._eltButtons[symbol].setSelected(state) - self.sigSelectionChanged.emit(self.getSelection()) - - def isElementSelected(self, symbol): - """Return *True* if element is selected, else *False* - - :param str symbol: PeriodicTableItem symbol - :return: *True* if element is selected, else *False* - """ - return self._eltButtons[symbol].isSelected() - - def elementToggle(self, item): - """Toggle selected/unselected state for element - - :param item: PeriodicTableItem object - """ - b = self._eltButtons[item.symbol] - b.setSelected(not b.isSelected()) - self.sigSelectionChanged.emit(self.getSelection()) - - -class PeriodicCombo(qt.QComboBox): - """ - Combo list with all atomic elements of the periodic table - - .. image:: img/PeriodicCombo.png - - :param bool detailed: True (default) display element symbol, Z and name. - False display only element symbol and Z. - :param elements: List of items (:class:`PeriodicTableItem` objects) to - be represented in the table. By default, take elements from - a predefined list with minimal information (symbol, atomic number, - name, mass). - """ - sigSelectionChanged = qt.pyqtSignal(object) - """Signal emitted when the selection changes. Send - :class:`PeriodicTableItem` object representing selected - element - """ - - def __init__(self, parent=None, detailed=True, elements=None): - qt.QComboBox.__init__(self, parent) - - # add all elements from global list - if elements is None: - elements = _defaultTableItems - for i, elmt in enumerate(elements): - if detailed: - txt = "%2s (%d) - %s" % (elmt.symbol, elmt.Z, elmt.name) - else: - txt = "%2s (%d)" % (elmt.symbol, elmt.Z) - self.insertItem(i, txt) - - self.currentIndexChanged[int].connect(self.__selectionChanged) - - def __selectionChanged(self, idx): - """Emit :attr:`sigSelectionChanged`""" - self.sigSelectionChanged.emit(_defaultTableItems[idx]) - - def getSelection(self): - """Get selected element - - :return: Selected element - :rtype: PeriodicTableItem - """ - return _defaultTableItems[self.currentIndex()] - - def setSelection(self, symbol): - """Set selected item in combobox by giving the atomic symbol - - :param symbol: Symbol of element to be selected - """ - # accept PeriodicTableItem for getter/setter consistency - if isinstance(symbol, PeriodicTableItem): - symbol = symbol.symbol - symblist = [elmt.symbol for elmt in _defaultTableItems] - self.setCurrentIndex(symblist.index(symbol)) - - -class PeriodicList(qt.QTreeWidget): - """List of atomic elements in a :class:`QTreeView` - - .. image:: img/PeriodicList.png - - :param QWidget parent: Parent widget - :param bool detailed: True (default) display element symbol, Z and name. - False display only element symbol and Z. - :param single: *True* for single element selection with mouse click, - *False* for multiple element selection mode. - """ - sigSelectionChanged = qt.pyqtSignal(object) - """When any element is selected/unselected in the widget, it emits - this signal and sends a list of currently selected - :class:`PeriodicTableItem` objects. - """ - - def __init__(self, parent=None, detailed=True, single=False, elements=None): - qt.QTreeWidget.__init__(self, parent) - - self.detailed = detailed - - headers = ["Z", "Symbol"] - if detailed: - headers.append("Name") - self.setColumnCount(3) - else: - self.setColumnCount(2) - self.setHeaderLabels(headers) - self.header().setStretchLastSection(False) - - self.setRootIsDecorated(0) - self.itemClicked.connect(self.__selectionChanged) - self.setSelectionMode(qt.QAbstractItemView.SingleSelection if single - else qt.QAbstractItemView.ExtendedSelection) - self.__fill_widget(elements) - self.resizeColumnToContents(0) - self.resizeColumnToContents(1) - if detailed: - self.resizeColumnToContents(2) - - def __fill_widget(self, elements): - """Fill tree widget with elements """ - if elements is None: - elements = _defaultTableItems - - self.tree_items = [] - - previous_item = None - for elmt in elements: - if previous_item is None: - item = qt.QTreeWidgetItem(self) - else: - item = qt.QTreeWidgetItem(self, previous_item) - item.setText(0, str(elmt.Z)) - item.setText(1, elmt.symbol) - if self.detailed: - item.setText(2, elmt.name) - self.tree_items.append(item) - previous_item = item - - def __selectionChanged(self, treeItem, column): - """Emit a :attr:`sigSelectionChanged` and send a list of - :class:`PeriodicTableItem` objects.""" - self.sigSelectionChanged.emit(self.getSelection()) - - def getSelection(self): - """Get a list of selected elements, as a list of :class:`PeriodicTableItem` - objects. - - :return: Selected elements - :rtype: List[PeriodicTableItem]""" - return [_defaultTableItems[idx] for idx in range(len(self.tree_items)) - if self.tree_items[idx].isSelected()] - - # setSelection is a bad name (name of a QTreeWidget method) - def setSelectedElements(self, symbolList): - """ - - :param symbolList: List of atomic symbols ["H", "He", "Li"...] - to be selected in the widget - """ - # accept PeriodicTableItem for getter/setter consistency - if isinstance(symbolList[0], PeriodicTableItem): - symbolList = [elmt.symbol for elmt in symbolList] - for idx in range(len(self.tree_items)): - self.tree_items[idx].setSelected(_defaultTableItems[idx].symbol in symbolList) diff --git a/silx/gui/widgets/PrintGeometryDialog.py b/silx/gui/widgets/PrintGeometryDialog.py deleted file mode 100644 index db0f3b3..0000000 --- a/silx/gui/widgets/PrintGeometryDialog.py +++ /dev/null @@ -1,222 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2017 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. -# -# ###########################################################################*/ - - -from silx.gui import qt -from silx.gui.widgets.FloatEdit import FloatEdit - - -class PrintGeometryWidget(qt.QWidget): - """Widget to specify the size and aspect ratio of an item - before sending it to the print preview dialog. - - Use methods :meth:`setPrintGeometry` and :meth:`getPrintGeometry` - to interact with the widget. - """ - def __init__(self, parent=None): - super(PrintGeometryWidget, self).__init__(parent) - self.mainLayout = qt.QGridLayout(self) - self.mainLayout.setContentsMargins(0, 0, 0, 0) - self.mainLayout.setSpacing(2) - hbox = qt.QWidget(self) - hboxLayout = qt.QHBoxLayout(hbox) - hboxLayout.setContentsMargins(0, 0, 0, 0) - hboxLayout.setSpacing(2) - label = qt.QLabel(self) - label.setText("Units") - label.setAlignment(qt.Qt.AlignCenter) - self._pageButton = qt.QRadioButton() - self._pageButton.setText("Page") - self._inchButton = qt.QRadioButton() - self._inchButton.setText("Inches") - self._cmButton = qt.QRadioButton() - self._cmButton.setText("Centimeters") - self._buttonGroup = qt.QButtonGroup(self) - self._buttonGroup.addButton(self._pageButton) - self._buttonGroup.addButton(self._inchButton) - self._buttonGroup.addButton(self._cmButton) - self._buttonGroup.setExclusive(True) - - # units - self.mainLayout.addWidget(label, 0, 0, 1, 4) - hboxLayout.addWidget(self._pageButton) - hboxLayout.addWidget(self._inchButton) - hboxLayout.addWidget(self._cmButton) - self.mainLayout.addWidget(hbox, 1, 0, 1, 4) - self._pageButton.setChecked(True) - - # xOffset - label = qt.QLabel(self) - label.setText("X Offset:") - self.mainLayout.addWidget(label, 2, 0) - self._xOffset = FloatEdit(self, 0.1) - self.mainLayout.addWidget(self._xOffset, 2, 1) - - # yOffset - label = qt.QLabel(self) - label.setText("Y Offset:") - self.mainLayout.addWidget(label, 2, 2) - self._yOffset = FloatEdit(self, 0.1) - self.mainLayout.addWidget(self._yOffset, 2, 3) - - # width - label = qt.QLabel(self) - label.setText("Width:") - self.mainLayout.addWidget(label, 3, 0) - self._width = FloatEdit(self, 0.9) - self.mainLayout.addWidget(self._width, 3, 1) - - # height - label = qt.QLabel(self) - label.setText("Height:") - self.mainLayout.addWidget(label, 3, 2) - self._height = FloatEdit(self, 0.9) - self.mainLayout.addWidget(self._height, 3, 3) - - # aspect ratio - self._aspect = qt.QCheckBox(self) - self._aspect.setText("Keep screen aspect ratio") - self._aspect.setChecked(True) - self.mainLayout.addWidget(self._aspect, 4, 1, 1, 2) - - def getPrintGeometry(self): - """Return the print geometry dictionary. - - See :meth:`setPrintGeometry` for documentation about the - print geometry dictionary.""" - ddict = {} - if self._inchButton.isChecked(): - ddict['units'] = "inches" - elif self._cmButton.isChecked(): - ddict['units'] = "centimeters" - else: - ddict['units'] = "page" - - ddict['xOffset'] = self._xOffset.value() - ddict['yOffset'] = self._yOffset.value() - ddict['width'] = self._width.value() - ddict['height'] = self._height.value() - - if self._aspect.isChecked(): - ddict['keepAspectRatio'] = True - else: - ddict['keepAspectRatio'] = False - return ddict - - def setPrintGeometry(self, geometry=None): - """Set the print geometry. - - The geometry parameters must be provided as a dictionary with - the following keys: - - - *"xOffset"* (float) - - *"yOffset"* (float) - - *"width"* (float) - - *"height"* (float) - - *"units"*: possible values *"page", "inch", "cm"* - - *"keepAspectRatio"*: *True* or *False* - - If *units* is *"page"*, the values should be floats in [0, 1.] - and are interpreted as a fraction of the page width or height. - - :param dict geometry: Geometry parameters, as a dictionary.""" - if geometry is None: - geometry = {} - oldDict = self.getPrintGeometry() - for key in ["units", "xOffset", "yOffset", - "width", "height", "keepAspectRatio"]: - geometry[key] = geometry.get(key, oldDict[key]) - - if geometry['units'].lower().startswith("inc"): - self._inchButton.setChecked(True) - elif geometry['units'].lower().startswith("c"): - self._cmButton.setChecked(True) - else: - self._pageButton.setChecked(True) - - self._xOffset.setText("%s" % float(geometry['xOffset'])) - self._yOffset.setText("%s" % float(geometry['yOffset'])) - self._width.setText("%s" % float(geometry['width'])) - self._height.setText("%s" % float(geometry['height'])) - if geometry['keepAspectRatio']: - self._aspect.setChecked(True) - else: - self._aspect.setChecked(False) - - -class PrintGeometryDialog(qt.QDialog): - """Dialog embedding a :class:`PrintGeometryWidget`. - - Use methods :meth:`setPrintGeometry` and :meth:`getPrintGeometry` - to interact with the widget. - - Execute method :meth:`exec_` to run the dialog. - The return value of that method is *True* if the geometry was set - (*Ok* button clicked) or *False* if the user clicked the *Cancel* - button. - """ - - def __init__(self, parent=None): - qt.QDialog.__init__(self, parent) - self.setWindowTitle("Set print size preferences") - layout = qt.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - self.configurationWidget = PrintGeometryWidget(self) - hbox = qt.QWidget(self) - hboxLayout = qt.QHBoxLayout(hbox) - self.okButton = qt.QPushButton(hbox) - self.okButton.setText("Accept") - self.okButton.setAutoDefault(False) - self.rejectButton = qt.QPushButton(hbox) - self.rejectButton.setText("Dismiss") - self.rejectButton.setAutoDefault(False) - self.okButton.clicked.connect(self.accept) - self.rejectButton.clicked.connect(self.reject) - hboxLayout.setContentsMargins(0, 0, 0, 0) - hboxLayout.setSpacing(2) - # hboxLayout.addWidget(qt.HorizontalSpacer(hbox)) - hboxLayout.addWidget(self.okButton) - hboxLayout.addWidget(self.rejectButton) - # hboxLayout.addWidget(qt.HorizontalSpacer(hbox)) - layout.addWidget(self.configurationWidget) - layout.addWidget(hbox) - - def setPrintGeometry(self, geometry): - """Return the print geometry dictionary. - - See :meth:`PrintGeometryWidget.setPrintGeometry` for documentation on - print geometry dictionary. - - :param dict geometry: Print geometry parameters dictionary. - """ - self.configurationWidget.setPrintGeometry(geometry) - - def getPrintGeometry(self): - """Return the print geometry dictionary. - - See :meth:`PrintGeometryWidget.setPrintGeometry` for documentation on - print geometry dictionary.""" - return self.configurationWidget.getPrintGeometry() diff --git a/silx/gui/widgets/PrintPreview.py b/silx/gui/widgets/PrintPreview.py deleted file mode 100644 index 96af34b..0000000 --- a/silx/gui/widgets/PrintPreview.py +++ /dev/null @@ -1,728 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-2018 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 implements a print preview dialog. - -The dialog provides methods to send images, pixmaps and SVG -items to the page to be printed. - -The user can interactively move and resize the items. -""" -import sys -import logging -from silx.gui import qt, printer - - -__authors__ = ["V.A. Sole", "P. Knobel"] -__license__ = "MIT" -__date__ = "11/07/2017" - - -_logger = logging.getLogger(__name__) - - -class PrintPreviewDialog(qt.QDialog): - """Print preview dialog widget. - """ - def __init__(self, parent=None, printer=None): - - qt.QDialog.__init__(self, parent) - self.setWindowTitle("Print Preview") - self.setModal(False) - self.resize(400, 500) - - self.mainLayout = qt.QVBoxLayout(self) - self.mainLayout.setContentsMargins(0, 0, 0, 0) - self.mainLayout.setSpacing(0) - - self._buildToolbar() - - self.printer = printer - # :class:`QPrinter` (paint device that paints on a printer). - # :meth:`showEvent` has been reimplemented to enforce printer - # setup. - - self.printDialog = None - # :class:`QPrintDialog` (dialog for specifying the printer's - # configuration) - - self.scene = None - # :class:`QGraphicsScene` (surface for managing - # 2D graphical items) - - self.page = None - # :class:`QGraphicsRectItem` used as white background page on which - # to display the print preview. - - self.view = None - # :class:`QGraphicsView` widget for displaying :attr:`scene` - - self._svgItems = [] - # List storing :class:`QSvgRenderer` items to be printed, added in - # :meth:`addSvgItem`, cleared in :meth:`_clearAll`. - # This ensures that there is a reference pointing to the items, - # which ensures they are not destroyed before being printed. - - self._viewScale = 1.0 - # Zoom level (1.0 is 100%) - - self._toBeCleared = False - # Flag indicating that all items must be removed from :attr:`scene` - # and from :attr:`_svgItems`. - # Set to True after a successful printing. The widget is then hidden, - # and it will be cleared the next time it is shown. - # Reset to False after :meth:`_clearAll` has done its job. - - def _buildToolbar(self): - toolBar = qt.QWidget(self) - # a layout for the toolbar - toolsLayout = qt.QHBoxLayout(toolBar) - toolsLayout.setContentsMargins(0, 0, 0, 0) - toolsLayout.setSpacing(0) - - hideBut = qt.QPushButton("Hide", toolBar) - hideBut.setToolTip("Hide print preview dialog") - hideBut.clicked.connect(self.hide) - - cancelBut = qt.QPushButton("Clear All", toolBar) - cancelBut.setToolTip("Remove all items") - cancelBut.clicked.connect(self._clearAll) - - removeBut = qt.QPushButton("Remove", - toolBar) - removeBut.setToolTip("Remove selected item (use left click to select)") - removeBut.clicked.connect(self._remove) - - setupBut = qt.QPushButton("Setup", toolBar) - setupBut.setToolTip("Select and configure a printer") - setupBut.clicked.connect(self.setup) - - printBut = qt.QPushButton("Print", toolBar) - printBut.setToolTip("Print page and close print preview") - printBut.clicked.connect(self._print) - - zoomPlusBut = qt.QPushButton("Zoom +", toolBar) - zoomPlusBut.clicked.connect(self._zoomPlus) - - zoomMinusBut = qt.QPushButton("Zoom -", toolBar) - zoomMinusBut.clicked.connect(self._zoomMinus) - - toolsLayout.addWidget(hideBut) - toolsLayout.addWidget(printBut) - toolsLayout.addWidget(cancelBut) - toolsLayout.addWidget(removeBut) - toolsLayout.addWidget(setupBut) - # toolsLayout.addStretch() - # toolsLayout.addWidget(marginLabel) - # toolsLayout.addWidget(self.marginSpin) - toolsLayout.addStretch() - # toolsLayout.addWidget(scaleLabel) - # toolsLayout.addWidget(self.scaleCombo) - toolsLayout.addWidget(zoomPlusBut) - toolsLayout.addWidget(zoomMinusBut) - # toolsLayout.addStretch() - self.toolBar = toolBar - self.mainLayout.addWidget(self.toolBar) - - def _buildStatusBar(self): - """Create the status bar used to display the printer name - or output file name.""" - # status bar - statusBar = qt.QStatusBar(self) - self.targetLabel = qt.QLabel(statusBar) - self._updateTargetLabel() - statusBar.addWidget(self.targetLabel) - self.mainLayout.addWidget(statusBar) - - def _updateTargetLabel(self): - """Update printer name or file name shown in the status bar.""" - if self.printer is None: - self.targetLabel.setText("Undefined printer") - return - if self.printer.outputFileName(): - self.targetLabel.setText("File:" + - self.printer.outputFileName()) - else: - self.targetLabel.setText("Printer:" + - self.printer.printerName()) - - def _updatePrinter(self): - """Resize :attr:`page`, :attr:`scene` and :attr:`view` to :attr:`printer` - width and height.""" - printer = self.printer - assert printer is not None, \ - "_updatePrinter should not be called unless a printer is defined" - if self.scene is None: - self.scene = qt.QGraphicsScene() - self.scene.setBackgroundBrush(qt.QColor(qt.Qt.lightGray)) - self.scene.setSceneRect(qt.QRectF(0, 0, printer.width(), printer.height())) - - if self.page is None: - self.page = qt.QGraphicsRectItem(0, 0, printer.width(), printer.height()) - self.page.setBrush(qt.QColor(qt.Qt.white)) - self.scene.addItem(self.page) - - self.scene.setSceneRect(qt.QRectF(0, 0, printer.width(), printer.height())) - self.page.setPos(qt.QPointF(0.0, 0.0)) - self.page.setRect(qt.QRectF(0, 0, printer.width(), printer.height())) - - if self.view is None: - self.view = qt.QGraphicsView(self.scene) - self.mainLayout.addWidget(self.view) - self._buildStatusBar() - # self.view.scale(1./self._viewScale, 1./self._viewScale) - self.view.fitInView(self.page.rect(), qt.Qt.KeepAspectRatio) - self._viewScale = 1.00 - self._updateTargetLabel() - - # Public methods - def addImage(self, image, title=None, comment=None, commentPosition=None): - """Add an image to the print preview scene. - - :param QImage image: Image to be added to the scene - :param str title: Title shown above (centered) the image - :param str comment: Comment displayed below the image - :param commentPosition: "CENTER" or "LEFT" - """ - self.addPixmap(qt.QPixmap.fromImage(image), - title=title, comment=comment, - commentPosition=commentPosition) - - def addPixmap(self, pixmap, title=None, comment=None, commentPosition=None): - """Add a pixmap to the print preview scene - - :param QPixmap pixmap: Pixmap to be added to the scene - :param str title: Title shown above (centered) the pixmap - :param str comment: Comment displayed below the pixmap - :param commentPosition: "CENTER" or "LEFT" - """ - if self._toBeCleared: - self._clearAll() - self.ensurePrinterIsSet() - if self.printer is None: - _logger.error("printer is not set, cannot add pixmap to page") - return - if title is None: - title = ' ' * 88 - if comment is None: - comment = ' ' * 88 - if commentPosition is None: - commentPosition = "CENTER" - if qt.qVersion() < "5.0": - rectItem = qt.QGraphicsRectItem(self.page, self.scene) - else: - rectItem = qt.QGraphicsRectItem(self.page) - - rectItem.setRect(qt.QRectF(1, 1, - pixmap.width(), pixmap.height())) - - pen = rectItem.pen() - color = qt.QColor(qt.Qt.red) - color.setAlpha(1) - pen.setColor(color) - rectItem.setPen(pen) - rectItem.setZValue(1) - rectItem.setFlag(qt.QGraphicsItem.ItemIsSelectable, True) - rectItem.setFlag(qt.QGraphicsItem.ItemIsMovable, True) - rectItem.setFlag(qt.QGraphicsItem.ItemIsFocusable, False) - - rectItemResizeRect = _GraphicsResizeRectItem(rectItem, self.scene) - rectItemResizeRect.setZValue(2) - - if qt.qVersion() < "5.0": - pixmapItem = qt.QGraphicsPixmapItem(rectItem, self.scene) - else: - pixmapItem = qt.QGraphicsPixmapItem(rectItem) - pixmapItem.setPixmap(pixmap) - pixmapItem.setZValue(0) - - # I add the title - if qt.qVersion() < "5.0": - textItem = qt.QGraphicsTextItem(title, rectItem, self.scene) - else: - textItem = qt.QGraphicsTextItem(title, rectItem) - textItem.setTextInteractionFlags(qt.Qt.TextEditorInteraction) - offset = 0.5 * textItem.boundingRect().width() - textItem.moveBy(0.5 * pixmap.width() - offset, -20) - textItem.setZValue(2) - - # I add the comment - if qt.qVersion() < "5.0": - commentItem = qt.QGraphicsTextItem(comment, rectItem, self.scene) - else: - commentItem = qt.QGraphicsTextItem(comment, rectItem) - commentItem.setTextInteractionFlags(qt.Qt.TextEditorInteraction) - offset = 0.5 * commentItem.boundingRect().width() - if commentPosition.upper() == "LEFT": - x = 1 - else: - x = 0.5 * pixmap.width() - offset - commentItem.moveBy(x, pixmap.height() + 20) - commentItem.setZValue(2) - - rectItem.moveBy(20, 40) - - def addSvgItem(self, item, title=None, - comment=None, commentPosition=None, - viewBox=None, keepRatio=True): - """Add a SVG item to the scene. - - :param QSvgRenderer item: SVG item to be added to the scene. - :param str title: Title shown above (centered) the SVG item. - :param str comment: Comment displayed below the SVG item. - :param str commentPosition: "CENTER" or "LEFT" - :param QRectF viewBox: Bounding box for the item on the print page - (xOffset, yOffset, width, height). If None, use original - item size. - :param bool keepRatio: If True, resizing the item will preserve its - original aspect ratio. - """ - if not qt.HAS_SVG: - raise RuntimeError("Missing QtSvg library.") - if not isinstance(item, qt.QSvgRenderer): - raise TypeError("addSvgItem: QSvgRenderer expected") - if self._toBeCleared: - self._clearAll() - self.ensurePrinterIsSet() - if self.printer is None: - _logger.error("printer is not set, cannot add SvgItem to page") - return - - if title is None: - title = 50 * ' ' - if comment is None: - comment = 80 * ' ' - if commentPosition is None: - commentPosition = "CENTER" - - if viewBox is None: - if hasattr(item, "_viewBox"): - # PyMca compatibility: viewbox attached to item - viewBox = item._viewBox - else: - # try the original item viewbox - viewBox = item.viewBoxF() - - svgItem = _GraphicsSvgRectItem(viewBox, self.page) - svgItem.setSvgRenderer(item) - - svgItem.setCacheMode(qt.QGraphicsItem.NoCache) - svgItem.setZValue(0) - svgItem.setFlag(qt.QGraphicsItem.ItemIsSelectable, True) - svgItem.setFlag(qt.QGraphicsItem.ItemIsMovable, True) - svgItem.setFlag(qt.QGraphicsItem.ItemIsFocusable, False) - - rectItemResizeRect = _GraphicsResizeRectItem(svgItem, self.scene, - keepratio=keepRatio) - rectItemResizeRect.setZValue(2) - - self._svgItems.append(item) - - # Comment / legend - dummyComment = 80 * "1" - if qt.qVersion() < '5.0': - commentItem = qt.QGraphicsTextItem(dummyComment, svgItem, self.scene) - else: - commentItem = qt.QGraphicsTextItem(dummyComment, svgItem) - commentItem.setTextInteractionFlags(qt.Qt.TextEditorInteraction) - # we scale the text to have the legend box have the same width as the graph - scaleCalculationRect = qt.QRectF(commentItem.boundingRect()) - scale = svgItem.boundingRect().width() / scaleCalculationRect.width() - - commentItem.setPlainText(comment) - commentItem.setZValue(1) - - commentItem.setFlag(qt.QGraphicsItem.ItemIsMovable, True) - if qt.qVersion() < "5.0": - commentItem.scale(scale, scale) - else: - commentItem.setScale(scale) - - # align - if commentPosition.upper() == "CENTER": - alignment = qt.Qt.AlignCenter - elif commentPosition.upper() == "RIGHT": - alignment = qt.Qt.AlignRight - else: - alignment = qt.Qt.AlignLeft - commentItem.setTextWidth(commentItem.boundingRect().width()) - center_format = qt.QTextBlockFormat() - center_format.setAlignment(alignment) - cursor = commentItem.textCursor() - cursor.select(qt.QTextCursor.Document) - cursor.mergeBlockFormat(center_format) - cursor.clearSelection() - commentItem.setTextCursor(cursor) - if alignment == qt.Qt.AlignLeft: - deltax = 0 - else: - deltax = (svgItem.boundingRect().width() - commentItem.boundingRect().width()) / 2. - commentItem.moveBy(svgItem.boundingRect().x() + deltax, - svgItem.boundingRect().y() + svgItem.boundingRect().height()) - - # Title - if qt.qVersion() < '5.0': - textItem = qt.QGraphicsTextItem(title, svgItem, self.scene) - else: - textItem = qt.QGraphicsTextItem(title, svgItem) - textItem.setTextInteractionFlags(qt.Qt.TextEditorInteraction) - textItem.setZValue(1) - textItem.setFlag(qt.QGraphicsItem.ItemIsMovable, True) - - title_offset = 0.5 * textItem.boundingRect().width() - textItem.moveBy(svgItem.boundingRect().x() + - 0.5 * svgItem.boundingRect().width() - title_offset * scale, - svgItem.boundingRect().y()) - if qt.qVersion() < "5.0": - textItem.scale(scale, scale) - else: - textItem.setScale(scale) - - def setup(self): - """Open a print dialog to ensure the :attr:`printer` is set. - - If the setting fails or is cancelled, :attr:`printer` is reset to - *None*. - """ - if self.printer is None: - self.printer = printer.getDefaultPrinter() - if self.printDialog is None: - self.printDialog = qt.QPrintDialog(self.printer, self) - if self.printDialog.exec_(): - if self.printer.width() <= 0 or self.printer.height() <= 0: - self.message = qt.QMessageBox(self) - self.message.setIcon(qt.QMessageBox.Critical) - self.message.setText("Unknown library error \non printer initialization") - self.message.setWindowTitle("Library Error") - self.message.setModal(0) - self.printer = None - return - self.printer.setFullPage(True) - self._updatePrinter() - else: - # printer setup cancelled, check for a possible previous configuration - if self.page is None: - # not initialized - self.printer = None - - def ensurePrinterIsSet(self): - """If the printer is not already set, try to interactively - setup the printer using a QPrintDialog. - In case of failure, hide widget and log a warning. - - :return: True if printer was set. False if it failed or if the - selection dialog was canceled. - """ - if self.printer is None: - self.setup() - if self.printer is None: - self.hide() - _logger.warning("Printer setup failed or was cancelled, " + - "but printer is required.") - return self.printer is not None - - def setOutputFileName(self, name): - """Set output filename. - - Setting a non-empty name enables printing to file. - - :param str name: File name (path)""" - self.printer.setOutputFileName(name) - - # overloaded methods - def exec_(self): - if self._toBeCleared: - self._clearAll() - return qt.QDialog.exec_(self) - - def raise_(self): - if self._toBeCleared: - self._clearAll() - return qt.QDialog.raise_(self) - - def showEvent(self, event): - """Reimplemented to force printer setup. - In case of failure, hide the widget.""" - if self._toBeCleared: - self._clearAll() - self.ensurePrinterIsSet() - - return super(PrintPreviewDialog, self).showEvent(event) - - # button callbacks - def _print(self): - """Do the printing, hide the print preview dialog, - set :attr:`_toBeCleared` flag to True to trigger clearing the - next time the dialog is shown. - - If the printer is not setup, do it first.""" - printer = self.printer - - painter = qt.QPainter() - if not painter.begin(printer) or printer is None: - _logger.error("Cannot initialize printer") - return - try: - self.scene.render(painter, qt.QRectF(0, 0, printer.width(), printer.height()), - qt.QRectF(self.page.rect().x(), self.page.rect().y(), - self.page.rect().width(), self.page.rect().height()), - qt.Qt.KeepAspectRatio) - painter.end() - self.hide() - self.accept() - self._toBeCleared = True - except: # FIXME - painter.end() - qt.QMessageBox.critical(self, "ERROR", - 'Printing problem:\n %s' % sys.exc_info()[1]) - _logger.error('printing problem:\n %s' % sys.exc_info()[1]) - return - - def _zoomPlus(self): - self._viewScale *= 1.20 - self.view.scale(1.20, 1.20) - - def _zoomMinus(self): - self._viewScale *= 0.80 - self.view.scale(0.80, 0.80) - - def _clearAll(self): - """ - Clear the print preview window, remove all items - but keep the page. - """ - itemlist = self.scene.items() - keep = self.page - while len(itemlist) != 1: - if itemlist.index(keep) == 0: - self.scene.removeItem(itemlist[1]) - else: - self.scene.removeItem(itemlist[0]) - itemlist = self.scene.items() - self._svgItems = [] - self._toBeCleared = False - - def _remove(self): - """Remove selected item in :attr:`scene`. - """ - itemlist = self.scene.items() - - # this loop is not efficient if there are many items ... - for item in itemlist: - if item.isSelected(): - self.scene.removeItem(item) - - -class SingletonPrintPreviewDialog(PrintPreviewDialog): - """Singleton print preview dialog. - - All widgets in a program that instantiate this class will share - a single print preview dialog. This enables sending - multiple images to a single page to be printed. - """ - _instance = None - - def __new__(self, *var, **kw): - if self._instance is None: - self._instance = PrintPreviewDialog(*var, **kw) - return self._instance - - -class _GraphicsSvgRectItem(qt.QGraphicsRectItem): - """:class:`qt.QGraphicsRectItem` with an attached - :class:`qt.QSvgRenderer`, and with a painter redefined to render - the SVG item.""" - def setSvgRenderer(self, renderer): - """ - - :param QSvgRenderer renderer: svg renderer - """ - self._renderer = renderer - - def paint(self, painter, *var, **kw): - self._renderer.render(painter, self.boundingRect()) - - -class _GraphicsResizeRectItem(qt.QGraphicsRectItem): - """Resizable QGraphicsRectItem.""" - def __init__(self, parent=None, scene=None, keepratio=True): - if qt.qVersion() < '5.0': - qt.QGraphicsRectItem.__init__(self, parent, scene) - else: - qt.QGraphicsRectItem.__init__(self, parent) - rect = parent.boundingRect() - x = rect.x() - y = rect.y() - w = rect.width() - h = rect.height() - self._newRect = None - self.keepRatio = keepratio - self.setRect(qt.QRectF(x + w - 40, y + h - 40, 40, 40)) - self.setAcceptHoverEvents(True) - pen = qt.QPen() - color = qt.QColor(qt.Qt.white) - color.setAlpha(0) - pen.setColor(color) - pen.setStyle(qt.Qt.NoPen) - self.setPen(pen) - self.setBrush(color) - self.setFlag(self.ItemIsMovable, True) - self.show() - - def hoverEnterEvent(self, event): - if self.parentItem().isSelected(): - self.parentItem().setSelected(False) - if self.keepRatio: - self.setCursor(qt.QCursor(qt.Qt.SizeFDiagCursor)) - else: - self.setCursor(qt.QCursor(qt.Qt.SizeAllCursor)) - self.setBrush(qt.QBrush(qt.Qt.yellow, qt.Qt.SolidPattern)) - return qt.QGraphicsRectItem.hoverEnterEvent(self, event) - - def hoverLeaveEvent(self, event): - self.setCursor(qt.QCursor(qt.Qt.ArrowCursor)) - pen = qt.QPen() - color = qt.QColor(qt.Qt.white) - color.setAlpha(0) - pen.setColor(color) - pen.setStyle(qt.Qt.NoPen) - self.setPen(pen) - self.setBrush(color) - return qt.QGraphicsRectItem.hoverLeaveEvent(self, event) - - def mousePressEvent(self, event): - if self._newRect is not None: - self._newRect = None - self._point0 = self.pos() - parent = self.parentItem() - scene = self.scene() - # following line prevents dragging along the previously selected - # item when resizing another one - scene.clearSelection() - - rect = parent.boundingRect() - self._x = rect.x() - self._y = rect.y() - self._w = rect.width() - self._h = rect.height() - self._ratio = self._w / self._h - if qt.qVersion() < "5.0": - self._newRect = qt.QGraphicsRectItem(parent, scene) - else: - self._newRect = qt.QGraphicsRectItem(parent) - self._newRect.setRect(qt.QRectF(self._x, - self._y, - self._w, - self._h)) - qt.QGraphicsRectItem.mousePressEvent(self, event) - - def mouseMoveEvent(self, event): - point1 = self.pos() - deltax = point1.x() - self._point0.x() - deltay = point1.y() - self._point0.y() - if self.keepRatio: - r1 = (self._w + deltax) / self._w - r2 = (self._h + deltay) / self._h - if r1 < r2: - self._newRect.setRect(qt.QRectF(self._x, - self._y, - self._w + deltax, - (self._w + deltax) / self._ratio)) - else: - self._newRect.setRect(qt.QRectF(self._x, - self._y, - (self._h + deltay) * self._ratio, - self._h + deltay)) - else: - self._newRect.setRect(qt.QRectF(self._x, - self._y, - self._w + deltax, - self._h + deltay)) - qt.QGraphicsRectItem.mouseMoveEvent(self, event) - - def mouseReleaseEvent(self, event): - point1 = self.pos() - deltax = point1.x() - self._point0.x() - deltay = point1.y() - self._point0.y() - self.moveBy(-deltax, -deltay) - parent = self.parentItem() - - # deduce scale from rectangle - if (qt.qVersion() < "5.0") or self.keepRatio: - scalex = self._newRect.rect().width() / self._w - scaley = scalex - else: - scalex = self._newRect.rect().width() / self._w - scaley = self._newRect.rect().height() / self._h - - if qt.qVersion() < "5.0": - parent.scale(scalex, scaley) - else: - # apply the scale to the previous transformation matrix - previousTransform = parent.transform() - parent.setTransform( - previousTransform.scale(scalex, scaley)) - - self.scene().removeItem(self._newRect) - self._newRect = None - qt.QGraphicsRectItem.mouseReleaseEvent(self, event) - - -def main(): - """ - """ - if len(sys.argv) < 2: - print("give an image file as parameter please.") - sys.exit(1) - - if len(sys.argv) > 2: - print("only one parameter please.") - sys.exit(1) - - filename = sys.argv[1] - w = PrintPreviewDialog() - w.resize(400, 500) - - comment = "" - for i in range(20): - comment += "Line number %d: En un lugar de La Mancha de cuyo nombre ...\n" % i - - if filename[-3:] == "svg": - item = qt.QSvgRenderer(filename, w.page) - w.addSvgItem(item, title=filename, - comment=comment, commentPosition="CENTER") - else: - w.addPixmap(qt.QPixmap.fromImage(qt.QImage(filename)), - title=filename, - comment=comment, - commentPosition="CENTER") - w.addImage(qt.QImage(filename), comment=comment, commentPosition="LEFT") - - sys.exit(w.exec_()) - - -if __name__ == '__main__': - a = qt.QApplication(sys.argv) - main() - a.exec_() diff --git a/silx/gui/widgets/RangeSlider.py b/silx/gui/widgets/RangeSlider.py deleted file mode 100644 index 31dbd4e..0000000 --- a/silx/gui/widgets/RangeSlider.py +++ /dev/null @@ -1,765 +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 :class:`RangeSlider` widget. - -.. image:: img/RangeSlider.png - :align: center -""" -from __future__ import absolute_import, division - -__authors__ = ["D. Naudet", "T. Vincent"] -__license__ = "MIT" -__date__ = "26/11/2018" - - -import numpy as numpy - -from silx.gui import qt, icons, colors -from silx.gui.utils.image import convertArrayToQImage - - -class StyleOptionRangeSlider(qt.QStyleOption): - def __init__(self): - super(StyleOptionRangeSlider, self).__init__() - self.minimum = None - self.maximum = None - self.sliderPosition1 = None - self.sliderPosition2 = None - self.handlerRect1 = None - self.handlerRect2 = None - - -class RangeSlider(qt.QWidget): - """Range slider with 2 thumbs and an optional colored groove. - - The position of the slider thumbs can be retrieved either as values - in the slider range or as a number of steps or pixels. - - :param QWidget parent: See QWidget - """ - - _SLIDER_WIDTH = 10 - """Width of the slider rectangle""" - - _PIXMAP_VOFFSET = 7 - """Vertical groove pixmap offset""" - - sigRangeChanged = qt.Signal(float, float) - """Signal emitted when the value range has changed. - - It provides the new range (min, max). - """ - - sigValueChanged = qt.Signal(float, float) - """Signal emitted when the value of the sliders has changed. - - It provides the slider values (first, second). - """ - - sigPositionCountChanged = qt.Signal(object) - """This signal is emitted when the number of steps has changed. - - It provides the new position count. - """ - - sigPositionChanged = qt.Signal(int, int) - """Signal emitted when the position of the sliders has changed. - - It provides the slider positions in steps or pixels (first, second). - """ - - def __init__(self, parent=None): - self.__pixmap = None - self.__positionCount = None - self.__firstValue = 0. - self.__secondValue = 1. - self.__minValue = 0. - self.__maxValue = 1. - self.__hoverRect = qt.QRect() - self.__hoverControl = None - - self.__focus = None - self.__moving = None - - self.__icons = { - 'first': icons.getQIcon('previous'), - 'second': icons.getQIcon('next') - } - - # call the super constructor AFTER defining all members that - # are used in the "paint" method - super(RangeSlider, self).__init__(parent) - - self.setFocusPolicy(qt.Qt.ClickFocus) - self.setAttribute(qt.Qt.WA_Hover) - - self.setMinimumSize(qt.QSize(50, 20)) - self.setMaximumHeight(20) - - # Broadcast value changed signal - self.sigValueChanged.connect(self.__emitPositionChanged) - - def event(self, event): - t = event.type() - if t == qt.QEvent.HoverEnter or t == qt.QEvent.HoverLeave or t == qt.QEvent.HoverMove: - return self.__updateHoverControl(event.pos()) - else: - return super(RangeSlider, self).event(event) - - def __updateHoverControl(self, pos): - hoverControl, hoverRect = self.__findHoverControl(pos) - if hoverControl != self.__hoverControl: - self.update(self.__hoverRect) - self.update(hoverRect) - self.__hoverControl = hoverControl - self.__hoverRect = hoverRect - return True - return hoverControl is not None - - def __findHoverControl(self, pos): - """Returns the control at the position and it's rect location""" - for name in ["first", "second"]: - rect = self.__sliderRect(name) - if rect.contains(pos): - return name, rect - rect = self.__drawArea() - if rect.contains(pos): - return "groove", rect - return None, qt.QRect() - - # Position <-> Value conversion - - def __positionToValue(self, position): - """Returns value corresponding to position - - :param int position: - :rtype: float - """ - min_, max_ = self.getMinimum(), self.getMaximum() - maxPos = self.__getCurrentPositionCount() - 1 - return min_ + (max_ - min_) * int(position) / maxPos - - def __valueToPosition(self, value): - """Returns closest position corresponding to value - - :param float value: - :rtype: int - """ - min_, max_ = self.getMinimum(), self.getMaximum() - maxPos = self.__getCurrentPositionCount() - 1 - return int(0.5 + maxPos * (float(value) - min_) / (max_ - min_)) - - # Position (int) API - - def __getCurrentPositionCount(self): - """Return current count (either position count or widget width - - :rtype: int - """ - count = self.getPositionCount() - if count is not None: - return count - else: - return max(2, self.width() - self._SLIDER_WIDTH) - - def getPositionCount(self): - """Returns the number of positions. - - :rtype: Union[int,None]""" - return self.__positionCount - - def setPositionCount(self, count): - """Set the number of positions. - - Slider values are eventually adjusted. - - :param Union[int,None] count: - Either the number of possible positions or - None to allow any values. - :raise ValueError: If count <= 1 - """ - count = None if count is None else int(count) - if count != self.getPositionCount(): - if count is not None and count <= 1: - raise ValueError("Position count must be higher than 1") - self.__positionCount = count - emit = self.__setValues(*self.getValues()) - self.sigPositionCountChanged.emit(count) - if emit: - self.sigValueChanged.emit(*self.getValues()) - - def getFirstPosition(self): - """Returns first slider position - - :rtype: int - """ - return self.__valueToPosition(self.getFirstValue()) - - def setFirstPosition(self, position): - """Set the position of the first slider - - The position is adjusted to valid values - - :param int position: - """ - self.setFirstValue(self.__positionToValue(position)) - - def getSecondPosition(self): - """Returns second slider position - - :rtype: int - """ - return self.__valueToPosition(self.getSecondValue()) - - def setSecondPosition(self, position): - """Set the position of the second slider - - The position is adjusted to valid values - - :param int position: - """ - self.setSecondValue(self.__positionToValue(position)) - - def getPositions(self): - """Returns slider positions (first, second) - - :rtype: List[int] - """ - return self.getFirstPosition(), self.getSecondPosition() - - def setPositions(self, first, second): - """Set the position of both sliders at once - - First is clipped to the slider range: [0, max]. - Second is clipped to valid values: [first, max] - - :param int first: - :param int second: - """ - self.setValues(self.__positionToValue(first), - self.__positionToValue(second)) - - # Value (float) API - - def __emitPositionChanged(self, *args, **kwargs): - self.sigPositionChanged.emit(*self.getPositions()) - - def __rangeChanged(self): - """Handle change of value range""" - emit = self.__setValues(*self.getValues()) - self.sigRangeChanged.emit(*self.getRange()) - if emit: - self.sigValueChanged.emit(*self.getValues()) - - def getMinimum(self): - """Returns the minimum value of the slider range - - :rtype: float - """ - return self.__minValue - - def setMinimum(self, minimum): - """Set the minimum value of the slider range. - - It eventually adjusts maximum. - Slider positions remains unchanged and slider values are modified. - - :param float minimum: - """ - minimum = float(minimum) - if minimum != self.getMinimum(): - if minimum > self.getMaximum(): - self.__maxValue = minimum - self.__minValue = minimum - self.__rangeChanged() - - def getMaximum(self): - """Returns the maximum value of the slider range - - :rtype: float - """ - return self.__maxValue - - def setMaximum(self, maximum): - """Set the maximum value of the slider range - - It eventually adjusts minimum. - Slider positions remains unchanged and slider values are modified. - - :param float maximum: - """ - maximum = float(maximum) - if maximum != self.getMaximum(): - if maximum < self.getMinimum(): - self.__minValue = maximum - self.__maxValue = maximum - self.__rangeChanged() - - def getRange(self): - """Returns the range of values (min, max) - - :rtype: List[float] - """ - return self.getMinimum(), self.getMaximum() - - def setRange(self, minimum, maximum): - """Set the range of values. - - If maximum is lower than minimum, minimum is the only valid value. - Slider positions remains unchanged and slider values are modified. - - :param float minimum: - :param float maximum: - """ - minimum, maximum = float(minimum), float(maximum) - if minimum != self.getMinimum() or maximum != self.getMaximum(): - self.__minValue = minimum - self.__maxValue = max(maximum, minimum) - self.__rangeChanged() - - def getFirstValue(self): - """Returns the value of the first slider - - :rtype: float - """ - return self.__firstValue - - def __clipFirstValue(self, value, max_=None): - """Clip first value to range and steps - - :param float value: - :param float max_: Alternative maximum to use - """ - if max_ is None: - max_ = self.getSecondValue() - value = min(max(self.getMinimum(), float(value)), max_) - if self.getPositionCount() is not None: # Clip to steps - value = self.__positionToValue(self.__valueToPosition(value)) - return value - - def setFirstValue(self, value): - """Set the value of the first slider - - Value is clipped to valid values. - - :param float value: - """ - value = self.__clipFirstValue(value) - if value != self.getFirstValue(): - self.__firstValue = value - self.update() - self.sigValueChanged.emit(*self.getValues()) - - def getSecondValue(self): - """Returns the value of the second slider - - :rtype: float - """ - return self.__secondValue - - def __clipSecondValue(self, value): - """Clip second value to range and steps - - :param float value: - """ - value = min(max(self.getFirstValue(), float(value)), self.getMaximum()) - if self.getPositionCount() is not None: # Clip to steps - value = self.__positionToValue(self.__valueToPosition(value)) - return value - - def setSecondValue(self, value): - """Set the value of the second slider - - Value is clipped to valid values. - - :param float value: - """ - value = self.__clipSecondValue(value) - if value != self.getSecondValue(): - self.__secondValue = value - self.update() - self.sigValueChanged.emit(*self.getValues()) - - def getValues(self): - """Returns value of both sliders at once - - :return: (first value, second value) - :rtype: List[float] - """ - return self.getFirstValue(), self.getSecondValue() - - def setValues(self, first, second): - """Set values for both sliders at once - - First is clipped to the slider range: [minimum, maximum]. - Second is clipped to valid values: [first, maximum] - - :param float first: - :param float second: - """ - if self.__setValues(first, second): - self.sigValueChanged.emit(*self.getValues()) - - def __setValues(self, first, second): - """Set values for both sliders at once - - First is clipped to the slider range: [minimum, maximum]. - Second is clipped to valid values: [first, maximum] - - :param float first: - :param float second: - :return: True if values has changed, False otherwise - :rtype: bool - """ - first = self.__clipFirstValue(first, self.getMaximum()) - second = self.__clipSecondValue(second) - values = first, second - - if self.getValues() != values: - self.__firstValue, self.__secondValue = values - self.update() - return True - return False - - # Groove API - - def getGroovePixmap(self): - """Returns the pixmap displayed in the slider groove if any. - - :rtype: Union[QPixmap,None] - """ - return self.__pixmap - - def setGroovePixmap(self, pixmap): - """Set the pixmap displayed in the slider groove. - - :param Union[QPixmap,None] pixmap: The QPixmap to use or None to unset. - """ - assert pixmap is None or isinstance(pixmap, qt.QPixmap) - self.__pixmap = pixmap - self.update() - - def setGroovePixmapFromProfile(self, profile, colormap=None): - """Set the pixmap displayed in the slider groove from histogram values. - - :param Union[numpy.ndarray,None] profile: - 1D array of values to display - :param Union[~silx.gui.colors.Colormap,str] colormap: - The colormap name or object to convert profile values to colors - """ - if profile is None: - self.setSliderPixmap(None) - return - - profile = numpy.array(profile, copy=False) - - if profile.size == 0: - self.setSliderPixmap(None) - return - - if colormap is None: - colormap = colors.Colormap() - elif isinstance(colormap, str): - colormap = colors.Colormap(name=colormap) - assert isinstance(colormap, colors.Colormap) - - rgbImage = colormap.applyToData(profile.reshape(1, -1))[:, :, :3] - qimage = convertArrayToQImage(rgbImage) - qpixmap = qt.QPixmap.fromImage(qimage) - self.setGroovePixmap(qpixmap) - - # Handle interaction - - def mousePressEvent(self, event): - super(RangeSlider, self).mousePressEvent(event) - - if event.buttons() == qt.Qt.LeftButton: - picked = None - for name in ('first', 'second'): - area = self.__sliderRect(name) - if area.contains(event.pos()): - picked = name - break - - self.__moving = picked - self.__focus = picked - self.update() - - def mouseMoveEvent(self, event): - super(RangeSlider, self).mouseMoveEvent(event) - - if self.__moving is not None: - delta = self._SLIDER_WIDTH // 2 - if self.__moving == 'first': - position = self.__xPixelToPosition(event.pos().x() + delta) - self.setFirstPosition(position) - else: - position = self.__xPixelToPosition(event.pos().x() - delta) - self.setSecondPosition(position) - - def mouseReleaseEvent(self, event): - super(RangeSlider, self).mouseReleaseEvent(event) - - if event.button() == qt.Qt.LeftButton and self.__moving is not None: - self.__moving = None - self.update() - - def focusOutEvent(self, event): - if self.__focus is not None: - self.__focus = None - self.update() - super(RangeSlider, self).focusOutEvent(event) - - def keyPressEvent(self, event): - key = event.key() - if event.modifiers() == qt.Qt.NoModifier and self.__focus is not None: - if key in (qt.Qt.Key_Left, qt.Qt.Key_Down): - if self.__focus == 'first': - self.setFirstPosition(self.getFirstPosition() - 1) - else: - self.setSecondPosition(self.getSecondPosition() - 1) - return # accept event - elif key in (qt.Qt.Key_Right, qt.Qt.Key_Up): - if self.__focus == 'first': - self.setFirstPosition(self.getFirstPosition() + 1) - else: - self.setSecondPosition(self.getSecondPosition() + 1) - return # accept event - - super(RangeSlider, self).keyPressEvent(event) - - # Handle resize - - def resizeEvent(self, event): - super(RangeSlider, self).resizeEvent(event) - - # If no step, signal position update when width change - if (self.getPositionCount() is None and - event.size().width() != event.oldSize().width()): - self.sigPositionChanged.emit(*self.getPositions()) - - # Handle repaint - - def __xPixelToPosition(self, x): - """Convert position in pixel to slider position - - :param int x: X in pixel coordinates - :rtype: int - """ - sliderArea = self.__sliderAreaRect() - maxPos = self.__getCurrentPositionCount() - 1 - position = maxPos * (x - sliderArea.left()) / (sliderArea.width() - 1) - return int(position + 0.5) - - def __sliderRect(self, name): - """Returns rectangle corresponding to slider in pixels - - :param str name: 'first' or 'second' - :rtype: QRect - :raise ValueError: If wrong name - """ - assert name in ('first', 'second') - if name == 'first': - offset = - self._SLIDER_WIDTH - position = self.getFirstPosition() - elif name == 'second': - offset = 0 - position = self.getSecondPosition() - else: - raise ValueError('Unknown name') - - sliderArea = self.__sliderAreaRect() - - maxPos = self.__getCurrentPositionCount() - 1 - xOffset = int((sliderArea.width() - 1) * position / maxPos) - xPos = sliderArea.left() + xOffset + offset - - return qt.QRect(xPos, - sliderArea.top(), - self._SLIDER_WIDTH, - sliderArea.height()) - - def __drawArea(self): - return self.rect().adjusted(self._SLIDER_WIDTH, 0, - -self._SLIDER_WIDTH, 0) - - def __sliderAreaRect(self): - return self.__drawArea().adjusted(self._SLIDER_WIDTH // 2, - 0, - -self._SLIDER_WIDTH // 2 + 1, - 0) - - def __pixMapRect(self): - return self.__sliderAreaRect().adjusted(0, - self._PIXMAP_VOFFSET, - -1, - -self._PIXMAP_VOFFSET) - - def paintEvent(self, event): - painter = qt.QPainter(self) - - style = qt.QApplication.style() - - area = self.__drawArea() - if self.__pixmap is not None: - pixmapRect = self.__pixMapRect() - - option = qt.QStyleOptionProgressBar() - option.initFrom(self) - option.rect = area - option.state = (qt.QStyle.State_Enabled if self.isEnabled() - else qt.QStyle.State_None) - style.drawControl(qt.QStyle.CE_ProgressBarGroove, - option, - painter, - self) - - painter.save() - pen = painter.pen() - pen.setWidth(1) - pen.setColor(qt.Qt.black if self.isEnabled() else qt.Qt.gray) - painter.setPen(pen) - painter.drawRect(pixmapRect.adjusted(-1, -1, 0, 1)) - painter.restore() - - if self.isEnabled(): - rect = area.adjusted(self._SLIDER_WIDTH // 2, - self._PIXMAP_VOFFSET, - -self._SLIDER_WIDTH // 2, - -self._PIXMAP_VOFFSET + 1) - painter.drawPixmap(rect, - self.__pixmap, - self.__pixmap.rect()) - else: - option = StyleOptionRangeSlider() - option.initFrom(self) - option.rect = area - option.sliderPosition1 = self.__firstValue - option.sliderPosition2 = self.__secondValue - option.handlerRect1 = self.__sliderRect("first") - option.handlerRect2 = self.__sliderRect("second") - option.minimum = self.__minValue - option.maximum = self.__maxValue - option.state = (qt.QStyle.State_Enabled if self.isEnabled() - else qt.QStyle.State_None) - if self.__hoverControl == "groove": - option.state |= qt.QStyle.State_MouseOver - elif option.state & qt.QStyle.State_MouseOver: - option.state ^= qt.QStyle.State_MouseOver - self.drawRangeSliderBackground(painter, option, self) - - # Avoid glitch when moving handles - hoverControl = self.__moving or self.__hoverControl - - for name in ('first', 'second'): - rect = self.__sliderRect(name) - option = qt.QStyleOptionButton() - option.initFrom(self) - option.icon = self.__icons[name] - option.iconSize = rect.size() * 0.7 - if hoverControl == name: - option.state |= qt.QStyle.State_MouseOver - elif option.state & qt.QStyle.State_MouseOver: - option.state ^= qt.QStyle.State_MouseOver - if self.__focus == name: - option.state |= qt.QStyle.State_HasFocus - elif option.state & qt.QStyle.State_HasFocus: - option.state ^= qt.QStyle.State_HasFocus - option.rect = rect - style.drawControl( - qt.QStyle.CE_PushButton, option, painter, self) - - def sizeHint(self): - return qt.QSize(200, self.minimumHeight()) - - @classmethod - def drawRangeSliderBackground(cls, painter, option, widget): - """Draw the background of the RangeSlider widget into the painter. - - :param qt.QPainter painter: A painter - :param StyleOptionRangeSlider option: Options to draw the widget - :param qt.QWidget: The widget which have to be drawn - """ - painter.save() - painter.translate(0.5, 0.5) - - backgroundRect = qt.QRect(option.rect) - if backgroundRect.height() > 8: - center = backgroundRect.center() - backgroundRect.setHeight(8) - backgroundRect.moveCenter(center) - - selectedRangeRect = qt.QRect(backgroundRect) - selectedRangeRect.setLeft(option.handlerRect1.center().x()) - selectedRangeRect.setRight(option.handlerRect2.center().x()) - - highlight = option.palette.color(qt.QPalette.Highlight) - activeHighlight = highlight - selectedOutline = option.palette.color(qt.QPalette.Highlight) - - buttonColor = option.palette.button().color() - val = qt.qGray(buttonColor.rgb()) - buttonColor = buttonColor.lighter(100 + max(1, (180 - val) // 6)) - buttonColor.setHsv(buttonColor.hue(), (buttonColor.saturation() * 3) // 4, buttonColor.value()) - - grooveColor = qt.QColor() - grooveColor.setHsv(buttonColor.hue(), - min(255, (int)(buttonColor.saturation())), - min(255, (int)(buttonColor.value() * 0.9))) - - selectedInnerContrastLine = qt.QColor(255, 255, 255, 30) - - outline = option.palette.color(qt.QPalette.Background).darker(140) - if (option.state & qt.QStyle.State_HasFocus and option.state & qt.QStyle.State_KeyboardFocusChange): - outline = highlight.darker(125) - if outline.value() > 160: - outline.setHsl(highlight.hue(), highlight.saturation(), 160) - - # Draw background groove - painter.setRenderHint(qt.QPainter.Antialiasing, True) - gradient = qt.QLinearGradient() - gradient.setStart(backgroundRect.center().x(), backgroundRect.top()) - gradient.setFinalStop(backgroundRect.center().x(), backgroundRect.bottom()) - painter.setPen(qt.QPen(outline)) - gradient.setColorAt(0, grooveColor.darker(110)) - gradient.setColorAt(1, grooveColor.lighter(110)) - painter.setBrush(gradient) - painter.drawRoundedRect(backgroundRect.adjusted(1, 1, -2, -2), 1, 1) - - # Draw slider background for the value - gradient = qt.QLinearGradient() - gradient.setStart(selectedRangeRect.center().x(), selectedRangeRect.top()) - gradient.setFinalStop(selectedRangeRect.center().x(), selectedRangeRect.bottom()) - painter.setRenderHint(qt.QPainter.Antialiasing, True) - painter.setPen(qt.QPen(selectedOutline)) - gradient.setColorAt(0, activeHighlight) - gradient.setColorAt(1, activeHighlight.lighter(130)) - painter.setBrush(gradient) - painter.drawRoundedRect(selectedRangeRect.adjusted(1, 1, -2, -2), 1, 1) - painter.setPen(selectedInnerContrastLine) - painter.setBrush(qt.Qt.NoBrush) - painter.drawRoundedRect(selectedRangeRect.adjusted(2, 2, -3, -3), 1, 1) - - painter.restore() diff --git a/silx/gui/widgets/TableWidget.py b/silx/gui/widgets/TableWidget.py deleted file mode 100644 index 8167fec..0000000 --- a/silx/gui/widgets/TableWidget.py +++ /dev/null @@ -1,626 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-2017 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 table widgets handling cut, copy and paste for -multiple cell selections. These actions can be triggered using keyboard -shortcuts or through a context menu (right-click). - -:class:`TableView` is a subclass of :class:`QTableView`. The added features -are made available to users after a model is added to the widget, using -:meth:`TableView.setModel`. - -:class:`TableWidget` is a subclass of :class:`qt.QTableWidget`, a table view -with a built-in standard data model. The added features are available as soon as -the widget is initialized. - -The cut, copy and paste actions are implemented as QActions: - - - :class:`CopySelectedCellsAction` (*Ctrl+C*) - - :class:`CopyAllCellsAction` - - :class:`CutSelectedCellsAction` (*Ctrl+X*) - - :class:`CutAllCellsAction` - - :class:`PasteCellsAction` (*Ctrl+V*) - -The copy actions are enabled by default. The cut and paste actions must be -explicitly enabled, by passing parameters ``cut=True, paste=True`` when -creating the widgets, or later by calling their :meth:`enableCut` and -:meth:`enablePaste` methods. -""" - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "03/07/2017" - - -import sys -from .. import qt - - -if sys.platform.startswith("win"): - row_separator = "\r\n" -else: - row_separator = "\n" - -col_separator = "\t" - - -class CopySelectedCellsAction(qt.QAction): - """QAction to copy text from selected cells in a :class:`QTableWidget` - into the clipboard. - - If multiple cells are selected, the copied text will be a concatenation - of the texts in all selected cells, tabulated with tabulation and - newline characters. - - If the cells are sparsely selected, the structure is preserved by - representing the unselected cells as empty strings in between two - tabulation characters. - Beware of pasting this data in another table widget, because depending - on how the paste is implemented, the empty cells may cause data in the - target table to be deleted, even though you didn't necessarily select the - corresponding cell in the origin table. - - :param table: :class:`QTableView` to which this action belongs. - """ - def __init__(self, table): - if not isinstance(table, qt.QTableView): - raise ValueError('CopySelectedCellsAction must be initialised ' + - 'with a QTableWidget.') - super(CopySelectedCellsAction, self).__init__(table) - self.setText("Copy selection") - self.setToolTip("Copy selected cells into the clipboard.") - self.setShortcut(qt.QKeySequence.Copy) - self.setShortcutContext(qt.Qt.WidgetShortcut) - self.triggered.connect(self.copyCellsToClipboard) - self.table = table - self.cut = False - """:attr:`cut` can be set to True by classes inheriting this action, - to do a cut action.""" - - def copyCellsToClipboard(self): - """Concatenate the text content of all selected cells into a string - using tabulations and newlines to keep the table structure. - Put this text into the clipboard. - """ - selected_idx = self.table.selectedIndexes() - if not selected_idx: - return - selected_idx_tuples = [(idx.row(), idx.column()) for idx in selected_idx] - - selected_rows = [idx[0] for idx in selected_idx_tuples] - selected_columns = [idx[1] for idx in selected_idx_tuples] - - data_model = self.table.model() - - copied_text = "" - for row in range(min(selected_rows), max(selected_rows) + 1): - for col in range(min(selected_columns), max(selected_columns) + 1): - index = data_model.index(row, col) - cell_text = data_model.data(index) - flags = data_model.flags(index) - - if (row, col) in selected_idx_tuples and cell_text is not None: - copied_text += cell_text - if self.cut and (flags & qt.Qt.ItemIsEditable): - data_model.setData(index, "") - copied_text += col_separator - # remove the right-most tabulation - copied_text = copied_text[:-len(col_separator)] - # add a newline - copied_text += row_separator - # remove final newline - copied_text = copied_text[:-len(row_separator)] - - # put this text into clipboard - qapp = qt.QApplication.instance() - qapp.clipboard().setText(copied_text) - - -class CopyAllCellsAction(qt.QAction): - """QAction to copy text from all cells in a :class:`QTableWidget` - into the clipboard. - - The copied text will be a concatenation - of the texts in all cells, tabulated with tabulation and - newline characters. - - :param table: :class:`QTableView` to which this action belongs. - """ - def __init__(self, table): - if not isinstance(table, qt.QTableView): - raise ValueError('CopyAllCellsAction must be initialised ' + - 'with a QTableWidget.') - super(CopyAllCellsAction, self).__init__(table) - self.setText("Copy all") - self.setToolTip("Copy all cells into the clipboard.") - self.triggered.connect(self.copyCellsToClipboard) - self.table = table - self.cut = False - - def copyCellsToClipboard(self): - """Concatenate the text content of all cells into a string - using tabulations and newlines to keep the table structure. - Put this text into the clipboard. - """ - data_model = self.table.model() - copied_text = "" - for row in range(data_model.rowCount()): - for col in range(data_model.columnCount()): - index = data_model.index(row, col) - cell_text = data_model.data(index) - flags = data_model.flags(index) - if cell_text is not None: - copied_text += cell_text - if self.cut and (flags & qt.Qt.ItemIsEditable): - data_model.setData(index, "") - copied_text += col_separator - # remove the right-most tabulation - copied_text = copied_text[:-len(col_separator)] - # add a newline - copied_text += row_separator - # remove final newline - copied_text = copied_text[:-len(row_separator)] - - # put this text into clipboard - qapp = qt.QApplication.instance() - qapp.clipboard().setText(copied_text) - - -class CutSelectedCellsAction(CopySelectedCellsAction): - """QAction to cut text from selected cells in a :class:`QTableWidget` - into the clipboard. - - The text is deleted from the original table widget - (use :class:`CopySelectedCellsAction` to preserve the original data). - - If multiple cells are selected, the cut text will be a concatenation - of the texts in all selected cells, tabulated with tabulation and - newline characters. - - If the cells are sparsely selected, the structure is preserved by - representing the unselected cells as empty strings in between two - tabulation characters. - Beware of pasting this data in another table widget, because depending - on how the paste is implemented, the empty cells may cause data in the - target table to be deleted, even though you didn't necessarily select the - corresponding cell in the origin table. - - :param table: :class:`QTableView` to which this action belongs.""" - def __init__(self, table): - super(CutSelectedCellsAction, self).__init__(table) - self.setText("Cut selection") - self.setShortcut(qt.QKeySequence.Cut) - self.setShortcutContext(qt.Qt.WidgetShortcut) - # cutting is already implemented in CopySelectedCellsAction (but - # it is disabled), we just need to enable it - self.cut = True - - -class CutAllCellsAction(CopyAllCellsAction): - """QAction to cut text from all cells in a :class:`QTableWidget` - into the clipboard. - - The text is deleted from the original table widget - (use :class:`CopyAllCellsAction` to preserve the original data). - - The cut text will be a concatenation - of the texts in all cells, tabulated with tabulation and - newline characters. - - :param table: :class:`QTableView` to which this action belongs.""" - def __init__(self, table): - super(CutAllCellsAction, self).__init__(table) - self.setText("Cut all") - self.setToolTip("Cut all cells into the clipboard.") - self.cut = True - - -def _parseTextAsTable(text, row_separator=row_separator, col_separator=col_separator): - """Parse text into list of lists (2D sequence). - - The input text must be tabulated using tabulation characters and - newlines to separate columns and rows. - - :param text: text to be parsed - :param record_separator: String, or single character, to be interpreted - as a record/row separator. - :param field_separator: String, or single character, to be interpreted - as a field/column separator. - :return: 2D sequence of strings - """ - rows = text.split(row_separator) - table_data = [row.split(col_separator) for row in rows] - return table_data - - -class PasteCellsAction(qt.QAction): - """QAction to paste text from the clipboard into the table. - - If the text contains tabulations and - newlines, they are interpreted as column and row separators. - In such a case, the text is split into multiple texts to be pasted - into multiple cells. - - If a cell content is an empty string in the original text, it is - ignored: the destination cell's text will not be deleted. - - :param table: :class:`QTableView` to which this action belongs. - """ - def __init__(self, table): - if not isinstance(table, qt.QTableView): - raise ValueError('PasteCellsAction must be initialised ' + - 'with a QTableWidget.') - super(PasteCellsAction, self).__init__(table) - self.table = table - self.setText("Paste") - self.setShortcut(qt.QKeySequence.Paste) - self.setShortcutContext(qt.Qt.WidgetShortcut) - self.setToolTip("Paste data. The selected cell is the top-left" + - "corner of the paste area.") - self.triggered.connect(self.pasteCellFromClipboard) - - def pasteCellFromClipboard(self): - """Paste text from clipboard into the table. - - :return: *True* in case of success, *False* if pasting data failed. - """ - selected_idx = self.table.selectedIndexes() - if len(selected_idx) != 1: - msgBox = qt.QMessageBox(parent=self.table) - msgBox.setText("A single cell must be selected to paste data") - msgBox.exec_() - return False - - data_model = self.table.model() - - selected_row = selected_idx[0].row() - selected_col = selected_idx[0].column() - - qapp = qt.QApplication.instance() - clipboard_text = qapp.clipboard().text() - table_data = _parseTextAsTable(clipboard_text) - - protected_cells = 0 - out_of_range_cells = 0 - - # paste table data into cells, using selected cell as origin - for row_offset in range(len(table_data)): - for col_offset in range(len(table_data[row_offset])): - target_row = selected_row + row_offset - target_col = selected_col + col_offset - - if target_row >= data_model.rowCount() or\ - target_col >= data_model.columnCount(): - out_of_range_cells += 1 - continue - - index = data_model.index(target_row, target_col) - flags = data_model.flags(index) - - # ignore empty strings - if table_data[row_offset][col_offset] != "": - if not flags & qt.Qt.ItemIsEditable: - protected_cells += 1 - continue - data_model.setData(index, table_data[row_offset][col_offset]) - # item.setText(table_data[row_offset][col_offset]) - - if protected_cells or out_of_range_cells: - msgBox = qt.QMessageBox(parent=self.table) - msg = "Some data could not be inserted, " - msg += "due to out-of-range or write-protected cells." - msgBox.setText(msg) - msgBox.exec_() - return False - return True - - -class CopySingleCellAction(qt.QAction): - """QAction to copy text from a single cell in a modified - :class:`QTableWidget`. - - This action relies on the fact that the text in the last clicked cell - are stored in :attr:`_last_cell_clicked` of the modified widget. - - In most cases, :class:`CopySelectedCellsAction` handles single cells, - but if the selection mode of the widget has been set to NoSelection - it is necessary to use this class instead. - - :param table: :class:`QTableView` to which this action belongs. - """ - def __init__(self, table): - if not isinstance(table, qt.QTableView): - raise ValueError('CopySingleCellAction must be initialised ' + - 'with a QTableWidget.') - super(CopySingleCellAction, self).__init__(table) - self.setText("Copy cell") - self.setToolTip("Copy cell content into the clipboard.") - self.triggered.connect(self.copyCellToClipboard) - self.table = table - - def copyCellToClipboard(self): - """ - """ - cell_text = self.table._text_last_cell_clicked - if cell_text is None: - return - - # put this text into clipboard - qapp = qt.QApplication.instance() - qapp.clipboard().setText(cell_text) - - -class TableWidget(qt.QTableWidget): - """:class:`QTableWidget` with a context menu displaying up to 5 actions: - - - :class:`CopySelectedCellsAction` - - :class:`CopyAllCellsAction` - - :class:`CutSelectedCellsAction` - - :class:`CutAllCellsAction` - - :class:`PasteCellsAction` - - These actions interact with the clipboard and can be used to copy data - to or from an external application, or another widget. - - The cut and paste actions are disabled by default, due to the risk of - overwriting data (no *Undo* action is available). Use :meth:`enablePaste` - and :meth:`enableCut` to activate them. - - .. image:: img/TableWidget.png - - :param parent: Parent QWidget - :param bool cut: Enable cut action - :param bool paste: Enable paste action - """ - def __init__(self, parent=None, cut=False, paste=False): - super(TableWidget, self).__init__(parent) - self._text_last_cell_clicked = None - - self.copySelectedCellsAction = CopySelectedCellsAction(self) - self.copyAllCellsAction = CopyAllCellsAction(self) - self.copySingleCellAction = None - self.pasteCellsAction = None - self.cutSelectedCellsAction = None - self.cutAllCellsAction = None - - self.addAction(self.copySelectedCellsAction) - self.addAction(self.copyAllCellsAction) - if cut: - self.enableCut() - if paste: - self.enablePaste() - - self.setContextMenuPolicy(qt.Qt.ActionsContextMenu) - - def mousePressEvent(self, event): - item = self.itemAt(event.pos()) - if item is not None: - self._text_last_cell_clicked = item.text() - super(TableWidget, self).mousePressEvent(event) - - def enablePaste(self): - """Enable paste action, to paste data from the clipboard into the - table. - - .. warning:: - - This action can cause data to be overwritten. - There is currently no *Undo* action to retrieve lost data. - """ - self.pasteCellsAction = PasteCellsAction(self) - self.addAction(self.pasteCellsAction) - - def enableCut(self): - """Enable cut action. - - .. warning:: - - This action can cause data to be deleted. - There is currently no *Undo* action to retrieve lost data.""" - self.cutSelectedCellsAction = CutSelectedCellsAction(self) - self.cutAllCellsAction = CutAllCellsAction(self) - self.addAction(self.cutSelectedCellsAction) - self.addAction(self.cutAllCellsAction) - - def setSelectionMode(self, mode): - """Overloaded from QTableWidget to disable cut/copy selection - actions in case mode is NoSelection - - :param mode: - :return: - """ - if mode == qt.QTableView.NoSelection: - self.copySelectedCellsAction.setVisible(False) - self.copySelectedCellsAction.setEnabled(False) - if self.cutSelectedCellsAction is not None: - self.cutSelectedCellsAction.setVisible(False) - self.cutSelectedCellsAction.setEnabled(False) - if self.copySingleCellAction is None: - self.copySingleCellAction = CopySingleCellAction(self) - self.insertAction(self.copySelectedCellsAction, # before first action - self.copySingleCellAction) - self.copySingleCellAction.setVisible(True) - self.copySingleCellAction.setEnabled(True) - else: - self.copySelectedCellsAction.setVisible(True) - self.copySelectedCellsAction.setEnabled(True) - if self.cutSelectedCellsAction is not None: - self.cutSelectedCellsAction.setVisible(True) - self.cutSelectedCellsAction.setEnabled(True) - if self.copySingleCellAction is not None: - self.copySingleCellAction.setVisible(False) - self.copySingleCellAction.setEnabled(False) - super(TableWidget, self).setSelectionMode(mode) - - -class TableView(qt.QTableView): - """:class:`QTableView` with a context menu displaying up to 5 actions: - - - :class:`CopySelectedCellsAction` - - :class:`CopyAllCellsAction` - - :class:`CutSelectedCellsAction` - - :class:`CutAllCellsAction` - - :class:`PasteCellsAction` - - These actions interact with the clipboard and can be used to copy data - to or from an external application, or another widget. - - The cut and paste actions are disabled by default, due to the risk of - overwriting data (no *Undo* action is available). Use :meth:`enablePaste` - and :meth:`enableCut` to activate them. - - .. note:: - - These actions will be available only after a model is associated - with this view, using :meth:`setModel`. - - :param parent: Parent QWidget - :param bool cut: Enable cut action - :param bool paste: Enable paste action - """ - def __init__(self, parent=None, cut=False, paste=False): - super(TableView, self).__init__(parent) - self._text_last_cell_clicked = None - - self.cut = cut - self.paste = paste - - self.copySelectedCellsAction = None - self.copyAllCellsAction = None - self.copySingleCellAction = None - self.pasteCellsAction = None - self.cutSelectedCellsAction = None - self.cutAllCellsAction = None - - def mousePressEvent(self, event): - qindex = self.indexAt(event.pos()) - if self.copyAllCellsAction is not None: # model was set - self._text_last_cell_clicked = self.model().data(qindex) - super(TableView, self).mousePressEvent(event) - - def setModel(self, model): - """Set the data model for the table view, activate the actions - and the context menu. - - :param model: :class:`qt.QAbstractItemModel` object - """ - super(TableView, self).setModel(model) - - self.copySelectedCellsAction = CopySelectedCellsAction(self) - self.copyAllCellsAction = CopyAllCellsAction(self) - self.addAction(self.copySelectedCellsAction) - self.addAction(self.copyAllCellsAction) - if self.cut: - self.enableCut() - if self.paste: - self.enablePaste() - - self.setContextMenuPolicy(qt.Qt.ActionsContextMenu) - - def enablePaste(self): - """Enable paste action, to paste data from the clipboard into the - table. - - .. warning:: - - This action can cause data to be overwritten. - There is currently no *Undo* action to retrieve lost data. - """ - self.pasteCellsAction = PasteCellsAction(self) - self.addAction(self.pasteCellsAction) - - def enableCut(self): - """Enable cut action. - - .. warning:: - - This action can cause data to be deleted. - There is currently no *Undo* action to retrieve lost data. - """ - self.cutSelectedCellsAction = CutSelectedCellsAction(self) - self.cutAllCellsAction = CutAllCellsAction(self) - self.addAction(self.cutSelectedCellsAction) - self.addAction(self.cutAllCellsAction) - - def addAction(self, action): - # ensure the actions are not added multiple times: - # compare action type and parent widget with those of existing actions - for existing_action in self.actions(): - if type(action) == type(existing_action): - if hasattr(action, "table") and\ - action.table is existing_action.table: - return None - super(TableView, self).addAction(action) - - def setSelectionMode(self, mode): - """Overloaded from QTableView to disable cut/copy selection - actions in case mode is NoSelection - - :param mode: - :return: - """ - if mode == qt.QTableView.NoSelection: - self.copySelectedCellsAction.setVisible(False) - self.copySelectedCellsAction.setEnabled(False) - if self.cutSelectedCellsAction is not None: - self.cutSelectedCellsAction.setVisible(False) - self.cutSelectedCellsAction.setEnabled(False) - if self.copySingleCellAction is None: - self.copySingleCellAction = CopySingleCellAction(self) - self.insertAction(self.copySelectedCellsAction, # before first action - self.copySingleCellAction) - self.copySingleCellAction.setVisible(True) - self.copySingleCellAction.setEnabled(True) - else: - self.copySelectedCellsAction.setVisible(True) - self.copySelectedCellsAction.setEnabled(True) - if self.cutSelectedCellsAction is not None: - self.cutSelectedCellsAction.setVisible(True) - self.cutSelectedCellsAction.setEnabled(True) - if self.copySingleCellAction is not None: - self.copySingleCellAction.setVisible(False) - self.copySingleCellAction.setEnabled(False) - super(TableView, self).setSelectionMode(mode) - - -if __name__ == "__main__": - app = qt.QApplication([]) - - tablewidget = TableWidget() - tablewidget.setWindowTitle("TableWidget") - tablewidget.setColumnCount(10) - tablewidget.setRowCount(7) - tablewidget.enableCut() - tablewidget.enablePaste() - tablewidget.show() - - tableview = TableView(cut=True, paste=True) - tableview.setWindowTitle("TableView") - model = qt.QStandardItemModel() - model.setColumnCount(10) - model.setRowCount(7) - tableview.setModel(model) - tableview.show() - - app.exec_() diff --git a/silx/gui/widgets/ThreadPoolPushButton.py b/silx/gui/widgets/ThreadPoolPushButton.py deleted file mode 100644 index 949b6ef..0000000 --- a/silx/gui/widgets/ThreadPoolPushButton.py +++ /dev/null @@ -1,238 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2018 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. -# -# ###########################################################################*/ -"""ThreadPoolPushButton module -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "13/10/2016" - -import logging -from .. import qt -from .WaitingPushButton import WaitingPushButton - - -_logger = logging.getLogger(__name__) - - -class _Wrapper(qt.QRunnable): - """Wrapper to allow to call a function into a `QThreadPool` and - sending signals during the life cycle of the object""" - - def __init__(self, signalHolder, function, args, kwargs): - """Constructor""" - super(_Wrapper, self).__init__() - self.__signalHolder = signalHolder - self.__callable = function - self.__args = args - self.__kwargs = kwargs - - def run(self): - holder = self.__signalHolder - holder.started.emit() - try: - result = self.__callable(*self.__args, **self.__kwargs) - holder.succeeded.emit(result) - except Exception as e: - module = self.__callable.__module__ - name = self.__callable.__name__ - _logger.error("Error while executing callable %s.%s.", module, name, exc_info=True) - holder.failed.emit(e) - finally: - holder.finished.emit() - holder._sigReleaseRunner.emit(self) - - def autoDelete(self): - """Returns true to ask the QThreadPool to manage the life cycle of - this QRunner.""" - return True - - -class ThreadPoolPushButton(WaitingPushButton): - """ - ThreadPoolPushButton provides a simple push button to execute - a threaded task with user feedback when the task is running. - - The task can be defined with the method `setCallable`. It takes a python - function and arguments as parameters. - - WARNING: This task is run in a separate thread. - - Everytime the button is pushed a new runner is created to execute the - function with defined arguments. An animated waiting icon is displayed - to show the activity. By default the button is disabled when an execution - is requested. This behaviour can be disabled by using - `setDisabledWhenWaiting`. - - When the button is clicked a `beforeExecuting` signal is sent from the - Qt main thread. Then the task is started in a thread pool and the following - signals are emitted from the thread pool. Right before calling the - registered callable, the widget emits a `started` signal. - When the task ends, its result is emitted by the `succeeded` signal, but - if it fails the signal `failed` is emitted with the resulting exception. - At the end, the `finished` signal is emitted. - - The task can be programatically executed by using `executeCallable`. - - >>> # Compute a value - >>> import math - >>> button = ThreadPoolPushButton(text="Compute 2^16") - >>> button.setCallable(math.pow, 2, 16) - >>> button.succeeded.connect(print) # python3 - - .. image:: img/ThreadPoolPushButton.png - - >>> # Compute a wrong value - >>> import math - >>> button = ThreadPoolPushButton(text="Compute sqrt(-1)") - >>> button.setCallable(math.sqrt, -1) - >>> button.failed.connect(print) # python3 - """ - - def __init__(self, parent=None, text=None, icon=None): - """Constructor - - :param str text: Text displayed on the button - :param qt.QIcon icon: Icon displayed on the button - :param qt.QWidget parent: Parent of the widget - """ - WaitingPushButton.__init__(self, parent=parent, text=text, icon=icon) - self.__callable = None - self.__args = None - self.__kwargs = None - self.__runnerCount = 0 - self.__runnerSet = set([]) - self.clicked.connect(self.executeCallable) - self.finished.connect(self.__runnerFinished) - self._sigReleaseRunner.connect(self.__releaseRunner) - - beforeExecuting = qt.Signal() - """Signal emitted just before execution of the callable by the main Qt - thread. In synchronous mode (direct mode), it can be used to define - dynamically `setCallable`, or to execute something in the Qt thread before - the execution, or both.""" - - started = qt.Signal() - """Signal emitted from the thread pool when the defined callable is - started. - - WARNING: This signal is emitted from the thread performing the task, and - might be received after the registered callable has been called. If you - want to perform some initialisation or set the callable to run, use the - `beforeExecuting` signal instead. - """ - - finished = qt.Signal() - """Signal emitted from the thread pool when the defined callable is - finished""" - - succeeded = qt.Signal(object) - """Signal emitted from the thread pool when the callable exit with a - success. - - The parameter of the signal is the result returned by the callable. - """ - - failed = qt.Signal(object) - """Signal emitted emitted from the thread pool when the callable raises an - exception. - - The parameter of the signal is the raised exception. - """ - - _sigReleaseRunner = qt.Signal(object) - """Callback to release runners""" - - def __runnerStarted(self): - """Called when a runner is started. - - Count the number of executed tasks to change the state of the widget. - """ - self.__runnerCount += 1 - if self.__runnerCount > 0: - self.wait() - - def __runnerFinished(self): - """Called when a runner is finished. - - Count the number of executed tasks to change the state of the widget. - """ - self.__runnerCount -= 1 - if self.__runnerCount <= 0: - self.stopWaiting() - - @qt.Slot() - def executeCallable(self): - """Execute the defined callable in QThreadPool. - - First emit a `beforeExecuting` signal. - If callable is not defined, nothing append. - If a callable is defined, it will be started - as a new thread using the `QThreadPool` system. At start of the thread - the `started` will be emitted. When the callable returns a result it - is emitted by the `succeeded` signal. If the callable fail, the signal - `failed` is emitted with the resulting exception. Then the `finished` - signal is emitted. - """ - self.beforeExecuting.emit() - if self.__callable is None: - return - self.__runnerStarted() - runner = self._createRunner(self.__callable, self.__args, self.__kwargs) - qt.silxGlobalThreadPool().start(runner) - self.__runnerSet.add(runner) - - def __releaseRunner(self, runner): - self.__runnerSet.remove(runner) - - def hasPendingOperations(self): - return len(self.__runnerSet) > 0 - - def _createRunner(self, function, args, kwargs): - """Create a QRunnable from a callable object. - - :param callable function: A callable Python object. - :param List args: List of arguments to call the function. - :param dict kwargs: Dictionary of arguments used to call the function. - :rtpye: qt.QRunnable - """ - runnable = _Wrapper(self, function, args, kwargs) - return runnable - - def setCallable(self, function, *args, **kwargs): - """Define a callable which will be executed on QThreadPool everytime - the button is clicked. - - To retrieve the results, connect to the `succeeded` signal. - - WARNING: The callable will be called in a separate thread. - - :param callable function: A callable Python object - :param List args: List of arguments to call the function. - :param dict kwargs: Dictionary of arguments used to call the function. - """ - self.__callable = function - self.__args = args - self.__kwargs = kwargs diff --git a/silx/gui/widgets/UrlSelectionTable.py b/silx/gui/widgets/UrlSelectionTable.py deleted file mode 100644 index fb15edd..0000000 --- a/silx/gui/widgets/UrlSelectionTable.py +++ /dev/null @@ -1,172 +0,0 @@ -# /*########################################################################## -# Copyright (C) 2017-2021 European Synchrotron Radiation Facility -# -# This file is part of the PyMca X-ray Fluorescence Toolkit developed at -# the ESRF by the Software group. -# -# 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. -# -#############################################################################*/ -"""Some widget construction to check if a sample moved""" - -__author__ = ["H. Payno"] -__license__ = "MIT" -__date__ = "19/03/2018" - -from silx.gui import qt -from collections import OrderedDict -from silx.gui.widgets.TableWidget import TableWidget -from silx.io.url import DataUrl -import functools -import logging -import os - -logger = logging.getLogger(__name__) - - -class UrlSelectionTable(TableWidget): - """Table used to select the color channel to be displayed for each""" - - COLUMS_INDEX = OrderedDict([ - ('url', 0), - ('img A', 1), - ('img B', 2), - ]) - - sigImageAChanged = qt.Signal(str) - """Signal emitted when the image A change. Param is the image url path""" - - sigImageBChanged = qt.Signal(str) - """Signal emitted when the image B change. Param is the image url path""" - - def __init__(self, parent=None): - TableWidget.__init__(self, parent) - self.clear() - - def clear(self): - qt.QTableWidget.clear(self) - self.setRowCount(0) - self.setColumnCount(len(self.COLUMS_INDEX)) - self.setHorizontalHeaderLabels(list(self.COLUMS_INDEX.keys())) - self.verticalHeader().hide() - if hasattr(self.horizontalHeader(), 'setSectionResizeMode'): # Qt5 - self.horizontalHeader().setSectionResizeMode(0, - qt.QHeaderView.Stretch) - else: # Qt4 - self.horizontalHeader().setResizeMode(0, qt.QHeaderView.Stretch) - - self.setSortingEnabled(True) - self._checkBoxes = {} - - def setUrls(self, urls: list) -> None: - """ - - :param urls: urls to be displayed - """ - for url in urls: - self.addUrl(url=url) - - def addUrl(self, url, **kwargs): - """ - - :param url: - :param args: - :return: index of the created items row - :rtype int - """ - assert isinstance(url, DataUrl) - row = self.rowCount() - self.setRowCount(row + 1) - - _item = qt.QTableWidgetItem() - _item.setText(os.path.basename(url.path())) - _item.setFlags(qt.Qt.ItemIsEnabled | qt.Qt.ItemIsSelectable) - self.setItem(row, self.COLUMS_INDEX['url'], _item) - - widgetImgA = qt.QRadioButton(parent=self) - widgetImgA.setAutoExclusive(False) - self.setCellWidget(row, self.COLUMS_INDEX['img A'], widgetImgA) - callbackImgA = functools.partial(self._activeImgAChanged, url.path()) - widgetImgA.toggled.connect(callbackImgA) - - widgetImgB = qt.QRadioButton(parent=self) - widgetImgA.setAutoExclusive(False) - self.setCellWidget(row, self.COLUMS_INDEX['img B'], widgetImgB) - callbackImgB = functools.partial(self._activeImgBChanged, url.path()) - widgetImgB.toggled.connect(callbackImgB) - - self._checkBoxes[url.path()] = {'img A': widgetImgA, - 'img B': widgetImgB} - self.resizeColumnsToContents() - return row - - def _activeImgAChanged(self, name): - self._updatecheckBoxes('img A', name) - self.sigImageAChanged.emit(name) - - def _activeImgBChanged(self, name): - self._updatecheckBoxes('img B', name) - self.sigImageBChanged.emit(name) - - def _updatecheckBoxes(self, whichImg, name): - assert name in self._checkBoxes - assert whichImg in self._checkBoxes[name] - if self._checkBoxes[name][whichImg].isChecked(): - for radioUrl in self._checkBoxes: - if radioUrl != name: - self._checkBoxes[radioUrl][whichImg].blockSignals(True) - self._checkBoxes[radioUrl][whichImg].setChecked(False) - self._checkBoxes[radioUrl][whichImg].blockSignals(False) - - def getSelection(self): - """ - - :return: url selected for img A and img B. - """ - imgA = imgB = None - for radioUrl in self._checkBoxes: - if self._checkBoxes[radioUrl]['img A'].isChecked(): - imgA = radioUrl - if self._checkBoxes[radioUrl]['img B'].isChecked(): - imgB = radioUrl - return imgA, imgB - - def setSelection(self, url_img_a, url_img_b): - """ - - :param ddict: key: image url, values: list of active channels - """ - for radioUrl in self._checkBoxes: - for img in ('img A', 'img B'): - self._checkBoxes[radioUrl][img].blockSignals(True) - self._checkBoxes[radioUrl][img].setChecked(False) - self._checkBoxes[radioUrl][img].blockSignals(False) - - self._checkBoxes[radioUrl][img].blockSignals(True) - self._checkBoxes[url_img_a]['img A'].setChecked(True) - self._checkBoxes[radioUrl][img].blockSignals(False) - - self._checkBoxes[radioUrl][img].blockSignals(True) - self._checkBoxes[url_img_b]['img B'].setChecked(True) - self._checkBoxes[radioUrl][img].blockSignals(False) - self.sigImageAChanged.emit(url_img_a) - self.sigImageBChanged.emit(url_img_b) - - def removeUrl(self, url): - raise NotImplementedError("") diff --git a/silx/gui/widgets/WaitingPushButton.py b/silx/gui/widgets/WaitingPushButton.py deleted file mode 100644 index 499de1a..0000000 --- a/silx/gui/widgets/WaitingPushButton.py +++ /dev/null @@ -1,245 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-2017 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. -# -# ###########################################################################*/ -"""WaitingPushButton module -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "26/04/2017" - -from .. import qt -from .. import icons - - -class WaitingPushButton(qt.QPushButton): - """Button which allows to display a waiting status when, for example, - something is still computing. - - The component is graphically disabled when it is in waiting. Then we - overwrite the enabled method to dissociate the 2 concepts: - graphically enabled/disabled, and enabled/disabled - - .. image:: img/WaitingPushButton.png - """ - - def __init__(self, parent=None, text=None, icon=None): - """Constructor - - :param str text: Text displayed on the button - :param qt.QIcon icon: Icon displayed on the button - :param qt.QWidget parent: Parent of the widget - """ - if icon is not None: - qt.QPushButton.__init__(self, icon, text, parent) - elif text is not None: - qt.QPushButton.__init__(self, text, parent) - else: - qt.QPushButton.__init__(self, parent) - - self.__waiting = False - self.__enabled = True - self.__icon = icon - self.__disabled_when_waiting = True - self.__waitingIcon = icons.getWaitIcon() - - def sizeHint(self): - """Returns the recommended size for the widget. - - This implementation of the recommended size always consider there is an - icon. In this way it avoid to update the layout when the waiting icon - is displayed. - """ - self.ensurePolished() - - w = 0 - h = 0 - - opt = qt.QStyleOptionButton() - self.initStyleOption(opt) - - # Content with icon - # no condition, assume that there is an icon to avoid blinking - # when the widget switch to waiting state - ih = opt.iconSize.height() - iw = opt.iconSize.width() + 4 - w += iw - h = max(h, ih) - - # Content with text - text = self.text() - isEmpty = text == "" - if isEmpty: - text = "XXXX" - fm = self.fontMetrics() - textSize = fm.size(qt.Qt.TextShowMnemonic, text) - if not isEmpty or w == 0: - w += textSize.width() - if not isEmpty or h == 0: - h = max(h, textSize.height()) - - # Content with menu indicator - opt.rect.setSize(qt.QSize(w, h)) # PM_MenuButtonIndicator depends on the height - if self.menu() is not None: - w += self.style().pixelMetric(qt.QStyle.PM_MenuButtonIndicator, opt, self) - - contentSize = qt.QSize(w, h) - if qt.qVersion().startswith("4.8."): - # On PyQt4/PySide the method QCommonStyle sizeFromContents returns - # different size when the widget provides an icon or not. - # In Qt5 there is not this problem. - opt.icon = qt.QIcon() - sizeHint = self.style().sizeFromContents(qt.QStyle.CT_PushButton, opt, contentSize, self) - sizeHint = sizeHint.expandedTo(qt.QApplication.globalStrut()) - return sizeHint - - def setDisabledWhenWaiting(self, isDisabled): - """Enable or disable the auto disable behaviour when the button is waiting. - - :param bool isDisabled: Enable the auto-disable behaviour - """ - if self.__disabled_when_waiting == isDisabled: - return - self.__disabled_when_waiting = isDisabled - self.__updateVisibleEnabled() - - def isDisabledWhenWaiting(self): - """Returns true if the button is auto disabled when it is waiting. - - :rtype: bool - """ - return self.__disabled_when_waiting - - disabledWhenWaiting = qt.Property(bool, isDisabledWhenWaiting, setDisabledWhenWaiting) - """Property to enable/disable the auto disabled state when the button is waiting.""" - - def __setWaitingIcon(self, icon): - """Called when the waiting icon is updated. It is called every frames - of the animation. - - :param qt.QIcon icon: The new waiting icon - """ - qt.QPushButton.setIcon(self, icon) - - def setIcon(self, icon): - """Set the button icon. If the button is waiting, the icon is not - visible directly, but will be visible when the waiting state will be - removed. - - :param qt.QIcon icon: An icon - """ - self.__icon = icon - self.__updateVisibleIcon() - - def getIcon(self): - """Returns the icon set to the button. If the widget is waiting - it is not returning the visible icon, but the one requested by - the application (the one displayed when the widget is not in - waiting state). - - :rtype: qt.QIcon - """ - return self.__icon - - icon = qt.Property(qt.QIcon, getIcon, setIcon) - """Property providing access to the icon.""" - - def __updateVisibleIcon(self): - """Update the visible icon according to the state of the widget.""" - if not self.isWaiting(): - icon = self.__icon - else: - icon = self.__waitingIcon.currentIcon() - if icon is None: - icon = qt.QIcon() - qt.QPushButton.setIcon(self, icon) - - def setEnabled(self, enabled): - """Set the enabled state of the widget. - - :param bool enabled: The enabled state - """ - if self.__enabled == enabled: - return - self.__enabled = enabled - self.__updateVisibleEnabled() - - def isEnabled(self): - """Returns the enabled state of the widget. - - :rtype: bool - """ - return self.__enabled - - enabled = qt.Property(bool, isEnabled, setEnabled) - """Property providing access to the enabled state of the widget""" - - def __updateVisibleEnabled(self): - """Update the visible enabled state according to the state of the - widget.""" - if self.__disabled_when_waiting: - enabled = not self.isWaiting() and self.__enabled - else: - enabled = self.__enabled - qt.QPushButton.setEnabled(self, enabled) - - def setWaiting(self, waiting): - """Set the waiting state of the widget. - - :param bool waiting: Requested state""" - if self.__waiting == waiting: - return - self.__waiting = waiting - - if self.__waiting: - self.__waitingIcon.register(self) - self.__waitingIcon.iconChanged.connect(self.__setWaitingIcon) - else: - # unregister only if the object is registred - self.__waitingIcon.unregister(self) - self.__waitingIcon.iconChanged.disconnect(self.__setWaitingIcon) - - self.__updateVisibleEnabled() - self.__updateVisibleIcon() - - def isWaiting(self): - """Returns true if the widget is in waiting state. - - :rtype: bool""" - return self.__waiting - - @qt.Slot() - def wait(self): - """Enable the waiting state.""" - self.setWaiting(True) - - @qt.Slot() - def stopWaiting(self): - """Disable the waiting state.""" - self.setWaiting(False) - - @qt.Slot() - def swapWaiting(self): - """Swap the waiting state.""" - self.setWaiting(not self.isWaiting()) diff --git a/silx/gui/widgets/__init__.py b/silx/gui/widgets/__init__.py deleted file mode 100644 index 9d0299d..0000000 --- a/silx/gui/widgets/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2018 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 package provides a few simple Qt widgets that rely only on a Qt binding for Python. - -No other optional dependencies of *silx* should be required.""" diff --git a/silx/gui/widgets/setup.py b/silx/gui/widgets/setup.py deleted file mode 100644 index e96ac8d..0000000 --- a/silx/gui/widgets/setup.py +++ /dev/null @@ -1,41 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016 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. -# -# ###########################################################################*/ -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "11/10/2016" - - -from numpy.distutils.misc_util import Configuration - - -def configuration(parent_package='', top_path=None): - config = Configuration('widgets', parent_package, top_path) - config.add_subpackage('test') - return config - - -if __name__ == "__main__": - from numpy.distutils.core import setup - setup(configuration=configuration) diff --git a/silx/gui/widgets/test/__init__.py b/silx/gui/widgets/test/__init__.py deleted file mode 100644 index 9aaec76..0000000 --- a/silx/gui/widgets/test/__init__.py +++ /dev/null @@ -1,59 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ -import unittest - -from . import test_periodictable -from . import test_tablewidget -from . import test_threadpoolpushbutton -from . import test_hierarchicaltableview -from . import test_printpreview -from . import test_framebrowser -from . import test_boxlayoutdockwidget -from . import test_rangeslider -from . import test_flowlayout -from . import test_elidedlabel -from . import test_legendiconwidget - -__authors__ = ["V. Valls", "P. Knobel"] -__license__ = "MIT" -__date__ = "19/07/2017" - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTests( - [test_threadpoolpushbutton.suite(), - test_tablewidget.suite(), - test_periodictable.suite(), - test_printpreview.suite(), - test_hierarchicaltableview.suite(), - test_framebrowser.suite(), - test_boxlayoutdockwidget.suite(), - test_rangeslider.suite(), - test_flowlayout.suite(), - test_elidedlabel.suite(), - test_legendiconwidget.suite(), - ]) - return test_suite diff --git a/silx/gui/widgets/test/test_boxlayoutdockwidget.py b/silx/gui/widgets/test/test_boxlayoutdockwidget.py deleted file mode 100644 index 9a93ca1..0000000 --- a/silx/gui/widgets/test/test_boxlayoutdockwidget.py +++ /dev/null @@ -1,83 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018 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. -# -# ###########################################################################*/ -"""Tests for BoxLayoutDockWidget""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "06/03/2018" - -import unittest - -from silx.gui.widgets.BoxLayoutDockWidget import BoxLayoutDockWidget -from silx.gui import qt -from silx.gui.utils.testutils import TestCaseQt - - -class TestBoxLayoutDockWidget(TestCaseQt): - """Tests for BoxLayoutDockWidget""" - - def setUp(self): - """Create and show a main window""" - self.window = qt.QMainWindow() - self.qWaitForWindowExposed(self.window) - - def tearDown(self): - """Delete main window""" - self.window.setAttribute(qt.Qt.WA_DeleteOnClose) - self.window.close() - del self.window - self.qapp.processEvents() - - def test(self): - """Test update of layout direction according to dock area""" - # Create a widget with a QBoxLayout - layout = qt.QBoxLayout(qt.QBoxLayout.LeftToRight) - layout.addWidget(qt.QLabel('First')) - layout.addWidget(qt.QLabel('Second')) - widget = qt.QWidget() - widget.setLayout(layout) - - # Add it to a BoxLayoutDockWidget - dock = BoxLayoutDockWidget() - dock.setWidget(widget) - - self.window.addDockWidget(qt.Qt.BottomDockWidgetArea, dock) - self.qapp.processEvents() - self.assertEqual(layout.direction(), qt.QBoxLayout.LeftToRight) - - self.window.addDockWidget(qt.Qt.LeftDockWidgetArea, dock) - self.qapp.processEvents() - self.assertEqual(layout.direction(), qt.QBoxLayout.TopToBottom) - - -def suite(): - loader = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite = unittest.TestSuite() - test_suite.addTest(loader(TestBoxLayoutDockWidget)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_elidedlabel.py b/silx/gui/widgets/test/test_elidedlabel.py deleted file mode 100644 index 2856733..0000000 --- a/silx/gui/widgets/test/test_elidedlabel.py +++ /dev/null @@ -1,111 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ -"""Tests for ElidedLabel""" - -__license__ = "MIT" -__date__ = "08/06/2020" - -import unittest - -from silx.gui import qt -from silx.gui.widgets.ElidedLabel import ElidedLabel -from silx.gui.utils import testutils - - -class TestElidedLabel(testutils.TestCaseQt): - - def setUp(self): - self.label = ElidedLabel() - self.label.show() - self.qWaitForWindowExposed(self.label) - - def tearDown(self): - self.label.setAttribute(qt.Qt.WA_DeleteOnClose) - self.label.close() - del self.label - self.qapp.processEvents() - - def testElidedValue(self): - """Test elided text""" - raw = "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm" - self.label.setText(raw) - self.label.setFixedWidth(30) - displayedText = qt.QLabel.text(self.label) - self.assertNotEqual(raw, displayedText) - self.assertIn("…", displayedText) - self.assertIn("m", displayedText) - - def testNotElidedValue(self): - """Test elided text""" - raw = "mmmmmmm" - self.label.setText(raw) - self.label.setFixedWidth(200) - displayedText = qt.QLabel.text(self.label) - self.assertNotIn("…", displayedText) - self.assertEqual(raw, displayedText) - - def testUpdateFromElidedToNotElided(self): - """Test tooltip when not elided""" - raw1 = "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm" - raw2 = "nn" - self.label.setText(raw1) - self.label.setFixedWidth(30) - self.label.setText(raw2) - displayedTooltip = qt.QLabel.toolTip(self.label) - self.assertNotIn(raw1, displayedTooltip) - self.assertNotIn(raw2, displayedTooltip) - - def testUpdateFromNotElidedToElided(self): - """Test tooltip when elided""" - raw1 = "nn" - raw2 = "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm" - self.label.setText(raw1) - self.label.setFixedWidth(30) - self.label.setText(raw2) - displayedTooltip = qt.QLabel.toolTip(self.label) - self.assertNotIn(raw1, displayedTooltip) - self.assertIn(raw2, displayedTooltip) - - def testUpdateFromElidedToElided(self): - """Test tooltip when elided""" - raw1 = "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" - raw2 = "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm" - self.label.setText(raw1) - self.label.setFixedWidth(30) - self.label.setText(raw2) - displayedTooltip = qt.QLabel.toolTip(self.label) - self.assertNotIn(raw1, displayedTooltip) - self.assertIn(raw2, displayedTooltip) - - -def suite(): - loader = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite = unittest.TestSuite() - test_suite.addTest(loader(TestElidedLabel)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_flowlayout.py b/silx/gui/widgets/test/test_flowlayout.py deleted file mode 100644 index 1497945..0000000 --- a/silx/gui/widgets/test/test_flowlayout.py +++ /dev/null @@ -1,77 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018 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. -# -# ###########################################################################*/ -"""Tests for FlowLayout""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "02/08/2018" - -import unittest - -from silx.gui.widgets.FlowLayout import FlowLayout -from silx.gui import qt -from silx.gui.utils.testutils import TestCaseQt - - -class TestFlowLayout(TestCaseQt): - """Tests for FlowLayout""" - - def setUp(self): - """Create and show a widget""" - self.widget = qt.QWidget() - self.widget.show() - self.qWaitForWindowExposed(self.widget) - - def tearDown(self): - """Delete widget""" - self.widget.setAttribute(qt.Qt.WA_DeleteOnClose) - self.widget.close() - del self.widget - self.qapp.processEvents() - - def test(self): - """Basic tests""" - layout = FlowLayout() - self.widget.setLayout(layout) - - layout.addWidget(qt.QLabel('first')) - layout.addWidget(qt.QLabel('second')) - self.assertEqual(layout.count(), 2) - - layout.setHorizontalSpacing(10) - self.assertEqual(layout.horizontalSpacing(), 10) - layout.setVerticalSpacing(5) - self.assertEqual(layout.verticalSpacing(), 5) - - -def suite(): - loader = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite = unittest.TestSuite() - test_suite.addTest(loader(TestFlowLayout)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_framebrowser.py b/silx/gui/widgets/test/test_framebrowser.py deleted file mode 100644 index 2dfd302..0000000 --- a/silx/gui/widgets/test/test_framebrowser.py +++ /dev/null @@ -1,73 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018 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. -# -# ###########################################################################*/ -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "23/03/2018" - - -import unittest - -from silx.gui.utils.testutils import TestCaseQt -from silx.gui.widgets.FrameBrowser import FrameBrowser - - -class TestFrameBrowser(TestCaseQt): - """Test for FrameBrowser""" - - def test(self): - """Test FrameBrowser""" - widget = FrameBrowser() - widget.show() - self.qWaitForWindowExposed(widget) - - nFrames = 20 - widget.setNFrames(nFrames) - self.assertEqual(widget.getRange(), (0, nFrames - 1)) - self.assertEqual(widget.getValue(), 0) - - range_ = -100, 100 - widget.setRange(*range_) - self.assertEqual(widget.getRange(), range_) - self.assertEqual(widget.getValue(), range_[0]) - - widget.setValue(0) - self.assertEqual(widget.getValue(), 0) - - widget.setValue(range_[1] + 100) - self.assertEqual(widget.getValue(), range_[1]) - - widget.setValue(range_[0] - 100) - self.assertEqual(widget.getValue(), range_[0]) - - -def suite(): - loader = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite = unittest.TestSuite() - test_suite.addTest(loader(TestFrameBrowser)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_hierarchicaltableview.py b/silx/gui/widgets/test/test_hierarchicaltableview.py deleted file mode 100644 index 9fad54d..0000000 --- a/silx/gui/widgets/test/test_hierarchicaltableview.py +++ /dev/null @@ -1,117 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2017 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. -# -# ###########################################################################*/ -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "07/04/2017" - -import unittest - -from .. import HierarchicalTableView -from silx.gui.utils.testutils import TestCaseQt -from silx.gui import qt - - -class TableModel(HierarchicalTableView.HierarchicalTableModel): - - def __init__(self, parent): - HierarchicalTableView.HierarchicalTableModel.__init__(self, parent) - self.__content = {} - - def rowCount(self, parent=qt.QModelIndex()): - return 3 - - def columnCount(self, parent=qt.QModelIndex()): - return 3 - - def setData1(self): - if qt.qVersion() > "4.6": - self.beginResetModel() - else: - self.reset() - - content = {} - content[0, 0] = ("title", True, (1, 3)) - content[0, 1] = ("a", True, (2, 1)) - content[1, 1] = ("b", False, (1, 2)) - content[1, 2] = ("c", False, (1, 1)) - content[2, 2] = ("d", False, (1, 1)) - self.__content = content - if qt.qVersion() > "4.6": - self.endResetModel() - - def data(self, index, role=qt.Qt.DisplayRole): - if not index.isValid(): - return None - cell = self.__content.get((index.column(), index.row()), None) - if cell is None: - return None - - if role == self.SpanRole: - return cell[2] - elif role == self.IsHeaderRole: - return cell[1] - elif role == qt.Qt.DisplayRole: - return cell[0] - return None - - -class TestHierarchicalTableView(TestCaseQt): - """Test for HierarchicalTableView""" - - def testEmpty(self): - widget = HierarchicalTableView.HierarchicalTableView() - widget.show() - self.qWaitForWindowExposed(widget) - - def testModel(self): - widget = HierarchicalTableView.HierarchicalTableView() - model = TableModel(widget) - # set the data before using the model into the widget - model.setData1() - widget.setModel(model) - span = widget.rowSpan(0, 0), widget.columnSpan(0, 0) - self.assertEqual(span, (1, 3)) - widget.show() - self.qWaitForWindowExposed(widget) - - def testModelUpdate(self): - widget = HierarchicalTableView.HierarchicalTableView() - model = TableModel(widget) - widget.setModel(model) - # set the data after using the model into the widget - model.setData1() - span = widget.rowSpan(0, 0), widget.columnSpan(0, 0) - self.assertEqual(span, (1, 3)) - - -def suite(): - loader = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite = unittest.TestSuite() - test_suite.addTest(loader(TestHierarchicalTableView)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_legendiconwidget.py b/silx/gui/widgets/test/test_legendiconwidget.py deleted file mode 100644 index f845f75..0000000 --- a/silx/gui/widgets/test/test_legendiconwidget.py +++ /dev/null @@ -1,74 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ -"""Tests for LegendIconWidget""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "23/10/2020" - -import unittest - -from silx.gui import qt -from silx.gui.widgets.LegendIconWidget import LegendIconWidget -from silx.gui.utils.testutils import TestCaseQt -from silx.utils.testutils import ParametricTestCase - - -class TestLegendIconWidget(TestCaseQt, ParametricTestCase): - """Tests for TestRangeSlider""" - - def setUp(self): - self.widget = LegendIconWidget() - self.widget.show() - self.qWaitForWindowExposed(self.widget) - - def tearDown(self): - self.widget.setAttribute(qt.Qt.WA_DeleteOnClose) - self.widget.close() - del self.widget - self.qapp.processEvents() - - def testCreate(self): - self.qapp.processEvents() - - def testColormap(self): - self.widget.setColormap("viridis") - self.qapp.processEvents() - - def testSymbol(self): - self.widget.setSymbol("o") - self.widget.setSymbolColormap("viridis") - self.qapp.processEvents() - - -def suite(): - loader = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite = unittest.TestSuite() - test_suite.addTest(loader(TestLegendIconWidget)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_periodictable.py b/silx/gui/widgets/test/test_periodictable.py deleted file mode 100644 index 3e7eb16..0000000 --- a/silx/gui/widgets/test/test_periodictable.py +++ /dev/null @@ -1,163 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2017 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. -# -# ###########################################################################*/ -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "05/12/2016" - -import unittest - -from .. import PeriodicTable -from silx.gui.utils.testutils import TestCaseQt -from silx.gui import qt - - -class TestPeriodicTable(TestCaseQt): - """Basic test for ArrayTableWidget with a numpy array""" - - def testShow(self): - """basic test (instantiation done in setUp)""" - pt = PeriodicTable.PeriodicTable() - pt.show() - self.qWaitForWindowExposed(pt) - - def testSelectable(self): - """basic test (instantiation done in setUp)""" - pt = PeriodicTable.PeriodicTable(selectable=True) - self.assertTrue(pt.selectable) - - def testCustomElements(self): - PTI = PeriodicTable.ColoredPeriodicTableItem - my_items = [ - PTI("Xx", 42, 43, 44, "xaxatorium", 1002.2, - bgcolor="#FF0000"), - PTI("Yy", 25, 22, 44, "yoyotrium", 8.8) - ] - - pt = PeriodicTable.PeriodicTable(elements=my_items) - - pt.setSelection(["He", "Xx"]) - selection = pt.getSelection() - self.assertEqual(len(selection), 1) # "He" not found - self.assertEqual(selection[0].symbol, "Xx") - self.assertEqual(selection[0].Z, 42) - self.assertEqual(selection[0].col, 43) - self.assertAlmostEqual(selection[0].mass, 1002.2) - self.assertEqual(qt.QColor(selection[0].bgcolor), - qt.QColor(qt.Qt.red)) - - self.assertTrue(pt.isElementSelected("Xx")) - self.assertFalse(pt.isElementSelected("Yy")) - self.assertRaises(KeyError, pt.isElementSelected, "Yx") - - def testVeryCustomElements(self): - class MyPTI(PeriodicTable.PeriodicTableItem): - def __init__(self, *args): - PeriodicTable.PeriodicTableItem.__init__(self, *args[:6]) - self.my_feature = args[6] - - my_items = [ - MyPTI("Xx", 42, 43, 44, "xaxatorium", 1002.2, "spam"), - MyPTI("Yy", 25, 22, 44, "yoyotrium", 8.8, "eggs") - ] - - pt = PeriodicTable.PeriodicTable(elements=my_items) - - pt.setSelection(["Xx", "Yy"]) - selection = pt.getSelection() - self.assertEqual(len(selection), 2) - self.assertEqual(selection[1].symbol, "Yy") - self.assertEqual(selection[1].Z, 25) - self.assertEqual(selection[1].col, 22) - self.assertEqual(selection[1].row, 44) - self.assertAlmostEqual(selection[0].mass, 1002.2) - self.assertAlmostEqual(selection[0].my_feature, "spam") - - -class TestPeriodicCombo(TestCaseQt): - """Basic test for ArrayTableWidget with a numpy array""" - def setUp(self): - super(TestPeriodicCombo, self).setUp() - self.pc = PeriodicTable.PeriodicCombo() - - def tearDown(self): - del self.pc - super(TestPeriodicCombo, self).tearDown() - - def testShow(self): - """basic test (instantiation done in setUp)""" - self.pc.show() - self.qWaitForWindowExposed(self.pc) - - def testSelect(self): - self.pc.setSelection("Sb") - selection = self.pc.getSelection() - self.assertIsInstance(selection, - PeriodicTable.PeriodicTableItem) - self.assertEqual(selection.symbol, "Sb") - self.assertEqual(selection.Z, 51) - self.assertEqual(selection.name, "antimony") - - -class TestPeriodicList(TestCaseQt): - """Basic test for ArrayTableWidget with a numpy array""" - def setUp(self): - super(TestPeriodicList, self).setUp() - self.pl = PeriodicTable.PeriodicList() - - def tearDown(self): - del self.pl - super(TestPeriodicList, self).tearDown() - - def testShow(self): - """basic test (instantiation done in setUp)""" - self.pl.show() - self.qWaitForWindowExposed(self.pl) - - def testSelect(self): - self.pl.setSelectedElements(["Li", "He", "Au"]) - sel_elmts = self.pl.getSelection() - - self.assertEqual(len(sel_elmts), 3, - "Wrong number of elements selected") - for e in sel_elmts: - self.assertIsInstance(e, PeriodicTable.PeriodicTableItem) - self.assertIn(e.symbol, ["Li", "He", "Au"]) - self.assertIn(e.Z, [2, 3, 79]) - self.assertIn(e.name, ["lithium", "helium", "gold"]) - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(TestPeriodicTable)) - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(TestPeriodicList)) - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(TestPeriodicCombo)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_printpreview.py b/silx/gui/widgets/test/test_printpreview.py deleted file mode 100644 index 3c29171..0000000 --- a/silx/gui/widgets/test/test_printpreview.py +++ /dev/null @@ -1,74 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2017 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. -# -# ###########################################################################*/ -"""Test PrintPreview""" - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "19/07/2017" - - -import unittest -from silx.gui.utils.testutils import TestCaseQt -from silx.gui.widgets.PrintPreview import PrintPreviewDialog -from silx.gui import qt - -from silx.resources import resource_filename - - -class TestPrintPreview(TestCaseQt): - def testShow(self): - p = qt.QPrinter() - d = PrintPreviewDialog(printer=p) - d.show() - self.qapp.processEvents() - - def testAddImage(self): - p = qt.QPrinter() - d = PrintPreviewDialog(printer=p) - d.addImage(qt.QImage(resource_filename("gui/icons/clipboard.png"))) - self.qapp.processEvents() - - def testAddSvg(self): - p = qt.QPrinter() - d = PrintPreviewDialog(printer=p) - d.addSvgItem(qt.QSvgRenderer(resource_filename("gui/icons/clipboard.svg"), d.page)) - self.qapp.processEvents() - - def testAddPixmap(self): - p = qt.QPrinter() - d = PrintPreviewDialog(printer=p) - d.addPixmap(qt.QPixmap.fromImage(qt.QImage(resource_filename("gui/icons/clipboard.png")))) - self.qapp.processEvents() - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(TestPrintPreview)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_rangeslider.py b/silx/gui/widgets/test/test_rangeslider.py deleted file mode 100644 index 2829050..0000000 --- a/silx/gui/widgets/test/test_rangeslider.py +++ /dev/null @@ -1,114 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018 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. -# -# ###########################################################################*/ -"""Tests for RangeSlider""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "01/08/2018" - -import unittest - -from silx.gui import qt, colors -from silx.gui.widgets.RangeSlider import RangeSlider -from silx.gui.utils.testutils import TestCaseQt -from silx.utils.testutils import ParametricTestCase - - -class TestRangeSlider(TestCaseQt, ParametricTestCase): - """Tests for TestRangeSlider""" - - def setUp(self): - self.slider = RangeSlider() - self.slider.show() - self.qWaitForWindowExposed(self.slider) - - def tearDown(self): - self.slider.setAttribute(qt.Qt.WA_DeleteOnClose) - self.slider.close() - del self.slider - self.qapp.processEvents() - - def testRangeValue(self): - """Test slider range and values""" - - # Play with range - self.slider.setRange(1, 2) - self.assertEqual(self.slider.getRange(), (1., 2.)) - self.assertEqual(self.slider.getValues(), (1., 1.)) - - self.slider.setMinimum(-1) - self.assertEqual(self.slider.getRange(), (-1., 2.)) - self.assertEqual(self.slider.getValues(), (1., 1.)) - - self.slider.setMaximum(0) - self.assertEqual(self.slider.getRange(), (-1., 0.)) - self.assertEqual(self.slider.getValues(), (0., 0.)) - - # Play with values - self.slider.setFirstValue(-2.) - self.assertEqual(self.slider.getValues(), (-1., 0.)) - - self.slider.setFirstValue(-0.5) - self.assertEqual(self.slider.getValues(), (-0.5, 0.)) - - self.slider.setSecondValue(2.) - self.assertEqual(self.slider.getValues(), (-0.5, 0.)) - - self.slider.setSecondValue(-0.1) - self.assertEqual(self.slider.getValues(), (-0.5, -0.1)) - - def testStepCount(self): - """Test related to step count""" - self.slider.setPositionCount(11) - self.assertEqual(self.slider.getPositionCount(), 11) - self.slider.setFirstValue(0.32) - self.assertEqual(self.slider.getFirstValue(), 0.3) - self.assertEqual(self.slider.getFirstPosition(), 3) - - self.slider.setPositionCount(3) # Value is adjusted - self.assertEqual(self.slider.getValues(), (0.5, 1.)) - self.assertEqual(self.slider.getPositions(), (1, 2)) - - def testGroove(self): - """Test Groove pixmap""" - profile = list(range(100)) - - for cmap in ('jet', colors.Colormap('viridis')): - with self.subTest(str(cmap)): - self.slider.setGroovePixmapFromProfile(profile, cmap) - pixmap = self.slider.getGroovePixmap() - self.assertIsInstance(pixmap, qt.QPixmap) - self.assertEqual(pixmap.width(), len(profile)) - - -def suite(): - loader = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite = unittest.TestSuite() - test_suite.addTest(loader(TestRangeSlider)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_tablewidget.py b/silx/gui/widgets/test/test_tablewidget.py deleted file mode 100644 index 6822aef..0000000 --- a/silx/gui/widgets/test/test_tablewidget.py +++ /dev/null @@ -1,61 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016 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. -# -# ###########################################################################*/ -"""Test TableWidget""" - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "05/12/2016" - - -import unittest -from silx.gui.utils.testutils import TestCaseQt -from silx.gui.widgets.TableWidget import TableWidget - - -class TestTableWidget(TestCaseQt): - def setUp(self): - super(TestTableWidget, self).setUp() - self._result = [] - - def testShow(self): - table = TableWidget() - table.setColumnCount(10) - table.setRowCount(7) - table.enableCut() - table.enablePaste() - table.show() - table.hide() - self.qapp.processEvents() - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(TestTableWidget)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/widgets/test/test_threadpoolpushbutton.py b/silx/gui/widgets/test/test_threadpoolpushbutton.py deleted file mode 100644 index e92eb02..0000000 --- a/silx/gui/widgets/test/test_threadpoolpushbutton.py +++ /dev/null @@ -1,135 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016 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. -# -# ###########################################################################*/ -"""Test for silx.gui.hdf5 module""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "17/01/2018" - - -import unittest -import time -from silx.gui import qt -from silx.gui.utils.testutils import TestCaseQt -from silx.gui.utils.testutils import SignalListener -from silx.gui.widgets.ThreadPoolPushButton import ThreadPoolPushButton -from silx.utils.testutils import TestLogging - - -class TestThreadPoolPushButton(TestCaseQt): - - def setUp(self): - super(TestThreadPoolPushButton, self).setUp() - self._result = [] - - def waitForPendingOperations(self, object): - for i in range(50): - if not object.hasPendingOperations(): - break - self.qWait(10) - else: - raise RuntimeError("Still waiting for a pending operation") - - def _trace(self, name, delay=0): - self._result.append(name) - if delay != 0: - time.sleep(delay / 1000.0) - - def _compute(self): - return "result" - - def _computeFail(self): - raise Exception("exception") - - def testExecute(self): - button = ThreadPoolPushButton() - button.setCallable(self._trace, "a", 0) - button.executeCallable() - time.sleep(0.1) - self.assertListEqual(self._result, ["a"]) - self.waitForPendingOperations(button) - - def testMultiExecution(self): - button = ThreadPoolPushButton() - button.setCallable(self._trace, "a", 0) - number = qt.silxGlobalThreadPool().maxThreadCount() - for _ in range(number): - button.executeCallable() - self.waitForPendingOperations(button) - self.assertListEqual(self._result, ["a"] * number) - - def testSaturateThreadPool(self): - button = ThreadPoolPushButton() - button.setCallable(self._trace, "a", 100) - number = qt.silxGlobalThreadPool().maxThreadCount() * 2 - for _ in range(number): - button.executeCallable() - self.waitForPendingOperations(button) - self.assertListEqual(self._result, ["a"] * number) - - def testSuccess(self): - listener = SignalListener() - button = ThreadPoolPushButton() - button.setCallable(self._compute) - button.beforeExecuting.connect(listener.partial(test="be")) - button.started.connect(listener.partial(test="s")) - button.succeeded.connect(listener.partial(test="result")) - button.failed.connect(listener.partial(test="Unexpected exception")) - button.finished.connect(listener.partial(test="f")) - button.executeCallable() - self.qapp.processEvents() - time.sleep(0.1) - self.qapp.processEvents() - result = listener.karguments(argumentName="test") - self.assertListEqual(result, ["be", "s", "result", "f"]) - - def testFail(self): - listener = SignalListener() - button = ThreadPoolPushButton() - button.setCallable(self._computeFail) - button.beforeExecuting.connect(listener.partial(test="be")) - button.started.connect(listener.partial(test="s")) - button.succeeded.connect(listener.partial(test="Unexpected success")) - button.failed.connect(listener.partial(test="exception")) - button.finished.connect(listener.partial(test="f")) - with TestLogging('silx.gui.widgets.ThreadPoolPushButton', error=1): - button.executeCallable() - self.qapp.processEvents() - time.sleep(0.1) - self.qapp.processEvents() - result = listener.karguments(argumentName="test") - self.assertListEqual(result, ["be", "s", "exception", "f"]) - listener.clear() - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(TestThreadPoolPushButton)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') |