diff options
Diffstat (limited to 'silx/gui/widgets')
-rw-r--r-- | silx/gui/widgets/PrintPreview.py | 74 | ||||
-rw-r--r-- | silx/gui/widgets/RangeSlider.py | 198 | ||||
-rw-r--r-- | silx/gui/widgets/UrlSelectionTable.py | 164 |
3 files changed, 380 insertions, 56 deletions
diff --git a/silx/gui/widgets/PrintPreview.py b/silx/gui/widgets/PrintPreview.py index 94a8ed4..96af34b 100644 --- a/silx/gui/widgets/PrintPreview.py +++ b/silx/gui/widgets/PrintPreview.py @@ -285,7 +285,7 @@ class PrintPreviewDialog(qt.QDialog): def addSvgItem(self, item, title=None, comment=None, commentPosition=None, - viewBox=None): + viewBox=None, keepRatio=True): """Add a SVG item to the scene. :param QSvgRenderer item: SVG item to be added to the scene. @@ -295,6 +295,8 @@ class PrintPreviewDialog(qt.QDialog): :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.") @@ -331,35 +333,23 @@ class PrintPreviewDialog(qt.QDialog): svgItem.setFlag(qt.QGraphicsItem.ItemIsMovable, True) svgItem.setFlag(qt.QGraphicsItem.ItemIsFocusable, False) - rectItemResizeRect = _GraphicsResizeRectItem(svgItem, self.scene) + rectItemResizeRect = _GraphicsResizeRectItem(svgItem, self.scene, + keepratio=keepRatio) rectItemResizeRect.setZValue(2) self._svgItems.append(item) - if qt.qVersion() < '5.0': - textItem = qt.QGraphicsTextItem(title, svgItem, self.scene) - else: - textItem = qt.QGraphicsTextItem(title, svgItem) - textItem.setTextInteractionFlags(qt.Qt.TextEditorInteraction) - title_offset = 0.5 * textItem.boundingRect().width() - textItem.setZValue(1) - textItem.setFlag(qt.QGraphicsItem.ItemIsMovable, True) - + # 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() - comment_offset = 0.5 * commentItem.boundingRect().width() - if commentPosition.upper() == "LEFT": - x = 1 - else: - x = 0.5 * svgItem.boundingRect().width() - comment_offset * scale # fixme: centering - commentItem.moveBy(svgItem.boundingRect().x() + x, - svgItem.boundingRect().y() + svgItem.boundingRect().height()) + commentItem.setPlainText(comment) commentItem.setZValue(1) @@ -367,17 +357,46 @@ class PrintPreviewDialog(qt.QDialog): if qt.qVersion() < "5.0": commentItem.scale(scale, scale) else: - # the correct equivalent would be: - # rectItem.setTransform(qt.QTransform.fromScale(scalex, scaley)) 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: - # the correct equivalent would be: - # rectItem.setTransform(qt.QTransform.fromScale(scalex, scaley)) textItem.setScale(scale) def setup(self): @@ -601,7 +620,8 @@ class _GraphicsResizeRectItem(qt.QGraphicsRectItem): # following line prevents dragging along the previously selected # item when resizing another one scene.clearSelection() - rect = parent.rect() + + rect = parent.boundingRect() self._x = rect.x() self._y = rect.y() self._w = rect.width() @@ -655,12 +675,14 @@ class _GraphicsResizeRectItem(qt.QGraphicsRectItem): 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: - # the correct equivalent would be: - # rectItem.setTransform(qt.QTransform.fromScale(scalex, scaley)) - parent.setScale(scalex) + # 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 diff --git a/silx/gui/widgets/RangeSlider.py b/silx/gui/widgets/RangeSlider.py index 0b72e71..0cf195c 100644 --- a/silx/gui/widgets/RangeSlider.py +++ b/silx/gui/widgets/RangeSlider.py @@ -31,7 +31,7 @@ from __future__ import absolute_import, division __authors__ = ["D. Naudet", "T. Vincent"] __license__ = "MIT" -__date__ = "02/08/2018" +__date__ = "26/11/2018" import numpy as numpy @@ -40,6 +40,17 @@ 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. @@ -86,6 +97,8 @@ class RangeSlider(qt.QWidget): self.__secondValue = 1. self.__minValue = 0. self.__maxValue = 1. + self.__hoverRect = qt.QRect() + self.__hoverControl = None self.__focus = None self.__moving = None @@ -100,6 +113,7 @@ class RangeSlider(qt.QWidget): 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) @@ -107,6 +121,34 @@ class RangeSlider(qt.QWidget): # 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): @@ -469,10 +511,12 @@ class RangeSlider(qt.QWidget): super(RangeSlider, self).mouseMoveEvent(event) if self.__moving is not None: - position = self.__xPixelToPosition(event.pos().x()) + 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): @@ -564,13 +608,13 @@ class RangeSlider(qt.QWidget): def __sliderAreaRect(self): return self.__drawArea().adjusted(self._SLIDER_WIDTH / 2., 0, - -self._SLIDER_WIDTH / 2., + -self._SLIDER_WIDTH / 2. + 1, 0) def __pixMapRect(self): return self.__sliderAreaRect().adjusted(0, self._PIXMAP_VOFFSET, - 0, + -1, -self._PIXMAP_VOFFSET) def paintEvent(self, event): @@ -579,33 +623,55 @@ class RangeSlider(qt.QWidget): style = qt.QApplication.style() area = self.__drawArea() - pixmapRect = self.__pixMapRect() - - option = qt.QStyleOptionProgressBar() - option.initFrom(self) - option.rect = area - option.state = ((self.isEnabled() and qt.QStyle.State_Enabled) - or qt.QStyle.State_None) - style.drawControl(qt.QStyle.CE_ProgressBarGroove, - option, - painter, - self) + if self.__pixmap is not None: + pixmapRect = self.__pixMapRect() - 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, 1, 1)) - painter.restore() + 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) - if self.isEnabled() and self.__pixmap is not None: - painter.drawPixmap(area.adjusted(self._SLIDER_WIDTH / 2, - self._PIXMAP_VOFFSET, - -self._SLIDER_WIDTH / 2 + 1, - -self._PIXMAP_VOFFSET + 1), - self.__pixmap, - self.__pixmap.rect()) + # Avoid glitch when moving handles + hoverControl = self.__moving or self.__hoverControl for name in ('first', 'second'): rect = self.__sliderRect(name) @@ -613,7 +679,9 @@ class RangeSlider(qt.QWidget): option.initFrom(self) option.icon = self.__icons[name] option.iconSize = rect.size() * 0.7 - if option.state & qt.QStyle.State_MouseOver: + 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 @@ -625,3 +693,73 @@ class RangeSlider(qt.QWidget): 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() * 0.75, 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/UrlSelectionTable.py b/silx/gui/widgets/UrlSelectionTable.py new file mode 100644 index 0000000..4ac0381 --- /dev/null +++ b/silx/gui/widgets/UrlSelectionTable.py @@ -0,0 +1,164 @@ +# /*########################################################################## +# Copyright (C) 2017 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(__file__) + + +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 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("") |