diff --git a/INSTALL b/INSTALL index 279c50a94f3d..771dd6acc7ce 100644 --- a/INSTALL +++ b/INSTALL @@ -233,7 +233,7 @@ backends and the capabilities they provide. Versions 8.6.0 and 8.6.1 are known to have issues that may result in segfaults when closing multiple windows in the wrong order. -:term:`pyqt` 4.0 or later +:term:`pyqt` 4.4 or later The Qt4 widgets library python wrappers for the Qt4Agg backend :term:`pygtk` 2.4 or later diff --git a/doc/conf.py b/doc/conf.py index 54273ca5bc6e..b5cc8847a0fc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -306,17 +306,99 @@ class Frame(object): class MyPyQt4(MagicMock): class QtGui(object): - class QToolBar(object): - pass - - class QDialog(object): - pass - - class QWidget(object): - pass - - class QMainWindow(object): - pass + # PyQt4.QtGui public classes. + # Generated with + # textwrap.fill([name for name in dir(PyQt4.QtGui) + # if isinstance(getattr(PyQt4.QtGui, name), type)]) + _QtGui_public_classes = """\ + Display QAbstractButton QAbstractGraphicsShapeItem + QAbstractItemDelegate QAbstractItemView QAbstractPrintDialog + QAbstractProxyModel QAbstractScrollArea QAbstractSlider + QAbstractSpinBox QAbstractTextDocumentLayout QAction QActionEvent + QActionGroup QApplication QBitmap QBoxLayout QBrush QButtonGroup + QCalendarWidget QCheckBox QClipboard QCloseEvent QColor QColorDialog + QColumnView QComboBox QCommandLinkButton QCommonStyle QCompleter + QConicalGradient QContextMenuEvent QCursor QDataWidgetMapper QDateEdit + QDateTimeEdit QDesktopServices QDesktopWidget QDial QDialog + QDialogButtonBox QDirModel QDockWidget QDoubleSpinBox QDoubleValidator + QDrag QDragEnterEvent QDragLeaveEvent QDragMoveEvent QDropEvent + QErrorMessage QFileDialog QFileIconProvider QFileOpenEvent + QFileSystemModel QFocusEvent QFocusFrame QFont QFontComboBox + QFontDatabase QFontDialog QFontInfo QFontMetrics QFontMetricsF + QFormLayout QFrame QGesture QGestureEvent QGestureRecognizer QGlyphRun + QGradient QGraphicsAnchor QGraphicsAnchorLayout QGraphicsBlurEffect + QGraphicsColorizeEffect QGraphicsDropShadowEffect QGraphicsEffect + QGraphicsEllipseItem QGraphicsGridLayout QGraphicsItem + QGraphicsItemAnimation QGraphicsItemGroup QGraphicsLayout + QGraphicsLayoutItem QGraphicsLineItem QGraphicsLinearLayout + QGraphicsObject QGraphicsOpacityEffect QGraphicsPathItem + QGraphicsPixmapItem QGraphicsPolygonItem QGraphicsProxyWidget + QGraphicsRectItem QGraphicsRotation QGraphicsScale QGraphicsScene + QGraphicsSceneContextMenuEvent QGraphicsSceneDragDropEvent + QGraphicsSceneEvent QGraphicsSceneHelpEvent QGraphicsSceneHoverEvent + QGraphicsSceneMouseEvent QGraphicsSceneMoveEvent + QGraphicsSceneResizeEvent QGraphicsSceneWheelEvent + QGraphicsSimpleTextItem QGraphicsTextItem QGraphicsTransform + QGraphicsView QGraphicsWidget QGridLayout QGroupBox QHBoxLayout + QHeaderView QHelpEvent QHideEvent QHoverEvent QIcon QIconDragEvent + QIconEngine QIconEngineV2 QIdentityProxyModel QImage QImageIOHandler + QImageReader QImageWriter QInputContext QInputContextFactory + QInputDialog QInputEvent QInputMethodEvent QIntValidator QItemDelegate + QItemEditorCreatorBase QItemEditorFactory QItemSelection + QItemSelectionModel QItemSelectionRange QKeyEvent QKeyEventTransition + QKeySequence QLCDNumber QLabel QLayout QLayoutItem QLineEdit + QLinearGradient QListView QListWidget QListWidgetItem QMainWindow + QMatrix QMatrix2x2 QMatrix2x3 QMatrix2x4 QMatrix3x2 QMatrix3x3 + QMatrix3x4 QMatrix4x2 QMatrix4x3 QMatrix4x4 QMdiArea QMdiSubWindow + QMenu QMenuBar QMessageBox QMimeSource QMouseEvent + QMouseEventTransition QMoveEvent QMovie QPageSetupDialog QPaintDevice + QPaintEngine QPaintEngineState QPaintEvent QPainter QPainterPath + QPainterPathStroker QPalette QPanGesture QPen QPicture QPictureIO + QPinchGesture QPixmap QPixmapCache QPlainTextDocumentLayout + QPlainTextEdit QPolygon QPolygonF QPrintDialog QPrintEngine + QPrintPreviewDialog QPrintPreviewWidget QPrinter QPrinterInfo + QProgressBar QProgressDialog QProxyModel QPushButton QPyTextObject + QQuaternion QRadialGradient QRadioButton QRawFont QRegExpValidator + QRegion QResizeEvent QRubberBand QScrollArea QScrollBar + QSessionManager QShortcut QShortcutEvent QShowEvent QSizeGrip + QSizePolicy QSlider QSortFilterProxyModel QSound QSpacerItem QSpinBox + QSplashScreen QSplitter QSplitterHandle QStackedLayout QStackedWidget + QStandardItem QStandardItemModel QStaticText QStatusBar + QStatusTipEvent QStringListModel QStyle QStyleFactory QStyleHintReturn + QStyleHintReturnMask QStyleHintReturnVariant QStyleOption + QStyleOptionButton QStyleOptionComboBox QStyleOptionComplex + QStyleOptionDockWidget QStyleOptionDockWidgetV2 QStyleOptionFocusRect + QStyleOptionFrame QStyleOptionFrameV2 QStyleOptionFrameV3 + QStyleOptionGraphicsItem QStyleOptionGroupBox QStyleOptionHeader + QStyleOptionMenuItem QStyleOptionProgressBar QStyleOptionProgressBarV2 + QStyleOptionRubberBand QStyleOptionSizeGrip QStyleOptionSlider + QStyleOptionSpinBox QStyleOptionTab QStyleOptionTabBarBase + QStyleOptionTabBarBaseV2 QStyleOptionTabV2 QStyleOptionTabV3 + QStyleOptionTabWidgetFrame QStyleOptionTabWidgetFrameV2 + QStyleOptionTitleBar QStyleOptionToolBar QStyleOptionToolBox + QStyleOptionToolBoxV2 QStyleOptionToolButton QStyleOptionViewItem + QStyleOptionViewItemV2 QStyleOptionViewItemV3 QStyleOptionViewItemV4 + QStylePainter QStyledItemDelegate QSwipeGesture QSyntaxHighlighter + QSystemTrayIcon QTabBar QTabWidget QTableView QTableWidget + QTableWidgetItem QTableWidgetSelectionRange QTabletEvent + QTapAndHoldGesture QTapGesture QTextBlock QTextBlockFormat + QTextBlockGroup QTextBlockUserData QTextBrowser QTextCharFormat + QTextCursor QTextDocument QTextDocumentFragment QTextDocumentWriter + QTextEdit QTextFormat QTextFragment QTextFrame QTextFrameFormat + QTextImageFormat QTextInlineObject QTextItem QTextLayout QTextLength + QTextLine QTextList QTextListFormat QTextObject QTextObjectInterface + QTextOption QTextTable QTextTableCell QTextTableCellFormat + QTextTableFormat QTimeEdit QToolBar QToolBox QToolButton QToolTip + QTouchEvent QTransform QTreeView QTreeWidget QTreeWidgetItem + QTreeWidgetItemIterator QUndoCommand QUndoGroup QUndoStack QUndoView + QVBoxLayout QValidator QVector2D QVector3D QVector4D QWhatsThis + QWhatsThisClickedEvent QWheelEvent QWidget QWidgetAction QWidgetItem + QWindowStateChangeEvent QWizard QWizardPage QWorkspace + QX11EmbedContainer QX11EmbedWidget QX11Info + """ + for _name in _QtGui_public_classes.split(): + locals()[_name] = type(_name, (), {}) + del _name class MySip(MagicMock): diff --git a/doc/users/whats_new/2016-04_qt_config-AL.rst b/doc/users/whats_new/2016-04_qt_config-AL.rst new file mode 100644 index 000000000000..434ec43c7cda --- /dev/null +++ b/doc/users/whats_new/2016-04_qt_config-AL.rst @@ -0,0 +1,14 @@ +Improvements for the Qt figure options editor +--------------------------------------------- + +Various usability improvements were implemented for the Qt figure options +editor, among which: +- Line style entries are now sorted without duplicates. +- The colormap and normalization limits can now be set for images. +- Line edits for floating values now display only as many digits as necessary + to avoid precision loss. An important bug was also fixed regarding input + validation using Qt5 and a locale where the decimal separator is ",". +- The axes selector now uses shorter, more user-friendly names for axes, and + does not crash if there are no axes. +- Line and image entries using the default labels ("_lineX", "_imageX") are now + sorted numerically even when there are more than 10 entries. diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 1e50188d14f0..05c4e43265d9 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1732,6 +1732,8 @@ def add_image(self, image): Returns the image. """ self._set_artist_props(image) + if not image.get_label(): + image.set_label('_image%d' % len(self.images)) self.images.append(image) image._remove_method = lambda h: self.images.remove(h) self.stale = True diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index d0facc419b56..0a8e68440178 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -22,12 +22,7 @@ from matplotlib._pylab_helpers import Gcf from matplotlib.figure import Figure - from matplotlib.widgets import SubplotTool -try: - import matplotlib.backends.qt_editor.figureoptions as figureoptions -except ImportError: - figureoptions = None from .qt_compat import QtCore, QtWidgets, _getSaveFileName, __version__ from matplotlib.backends.qt_editor.formsubplottool import UiSubplotTool diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 89e39a1dbd3e..c593bccb28ff 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -23,10 +23,7 @@ from matplotlib.figure import Figure from matplotlib.widgets import SubplotTool -try: - import matplotlib.backends.qt_editor.figureoptions as figureoptions -except ImportError: - figureoptions = None +import matplotlib.backends.qt_editor.figureoptions as figureoptions from .qt_compat import (QtCore, QtGui, QtWidgets, _getSaveFileName, __version__, is_pyqt5) @@ -589,7 +586,7 @@ def _init_toolbar(self): a.setCheckable(True) if tooltip_text is not None: a.setToolTip(tooltip_text) - if figureoptions is not None and text == 'Subplots': + if text == 'Subplots': a = self.addAction(self._icon("qt4_editor_options.png"), 'Customize', self.edit_parameters) a.setToolTip('Edit axis, curve and image parameters') @@ -620,43 +617,31 @@ def _init_toolbar(self): self.layout().setSpacing(12) self.setMinimumHeight(48) - if figureoptions is not None: - def edit_parameters(self): - allaxes = self.canvas.figure.get_axes() - if not allaxes: - QtWidgets.QMessageBox.warning( - self.parent, "Error", "There are no axes to edit.") - return - if len(allaxes) == 1: - axes = allaxes[0] + def edit_parameters(self): + allaxes = self.canvas.figure.get_axes() + if not allaxes: + QtWidgets.QMessageBox.warning( + self.parent, "Error", "There are no axes to edit.") + return + if len(allaxes) == 1: + axes = allaxes[0] + else: + titles = [] + for axes in allaxes: + name = (axes.get_title() or + " - ".join(filter(None, [axes.get_xlabel(), + axes.get_ylabel()])) or + "".format( + type(axes).__name__, id(axes))) + titles.append(name) + item, ok = QtWidgets.QInputDialog.getItem( + self.parent, 'Customize', 'Select axes:', titles, 0, False) + if ok: + axes = allaxes[titles.index(six.text_type(item))] else: - titles = [] - for axes in allaxes: - title = axes.get_title() - ylabel = axes.get_ylabel() - label = axes.get_label() - if title: - fmt = "%(title)s" - if ylabel: - fmt += ": %(ylabel)s" - fmt += " (%(axes_repr)s)" - elif ylabel: - fmt = "%(axes_repr)s (%(ylabel)s)" - elif label: - fmt = "%(axes_repr)s (%(label)s)" - else: - fmt = "%(axes_repr)s" - titles.append(fmt % dict(title=title, - ylabel=ylabel, label=label, - axes_repr=repr(axes))) - item, ok = QtWidgets.QInputDialog.getItem( - self.parent, 'Customize', 'Select axes:', titles, 0, False) - if ok: - axes = allaxes[titles.index(six.text_type(item))] - else: - return - - figureoptions.figure_edit(axes, self) + return + + figureoptions.figure_edit(axes, self) def _update_buttons_checked(self): # sync button checkstates to match active mode diff --git a/lib/matplotlib/backends/qt_editor/figureoptions.py b/lib/matplotlib/backends/qt_editor/figureoptions.py index 75abf9ddeeda..038073edd6a1 100644 --- a/lib/matplotlib/backends/qt_editor/figureoptions.py +++ b/lib/matplotlib/backends/qt_editor/figureoptions.py @@ -13,18 +13,19 @@ import six import os.path as osp +import re +import matplotlib +from matplotlib import cm, markers, colors as mcolors import matplotlib.backends.qt_editor.formlayout as formlayout from matplotlib.backends.qt_compat import QtGui -from matplotlib import markers -from matplotlib.colors import colorConverter, rgb2hex def get_icon(name): - import matplotlib basedir = osp.join(matplotlib.rcParams['datapath'], 'images') return QtGui.QIcon(osp.join(basedir, name)) + LINESTYLES = {'-': 'Solid', '--': 'Dashed', '-.': 'DashDot', @@ -43,8 +44,6 @@ def figure_edit(axes, parent=None): """Edit matplotlib figure options""" sep = (None, None) # separator - has_curve = len(axes.get_lines()) > 0 - # Get / General xmin, xmax = axes.get_xlim() ymin, ymax = axes.get_ylim() @@ -69,57 +68,115 @@ def figure_edit(axes, parent=None): xunits = axes.xaxis.get_units() yunits = axes.yaxis.get_units() - if has_curve: - # Get / Curves - linedict = {} - for line in axes.get_lines(): - label = line.get_label() - if label == '_nolegend_': - continue - linedict[label] = line - curves = [] - linestyles = list(six.iteritems(LINESTYLES)) - drawstyles = list(six.iteritems(DRAWSTYLES)) - markers = list(six.iteritems(MARKERS)) - curvelabels = sorted(linedict.keys()) - for label in curvelabels: - line = linedict[label] - color = rgb2hex(colorConverter.to_rgb(line.get_color())) - ec = rgb2hex(colorConverter.to_rgb(line.get_markeredgecolor())) - fc = rgb2hex(colorConverter.to_rgb(line.get_markerfacecolor())) - curvedata = [('Label', label), - sep, - (None, 'Line'), - ('Line Style', [line.get_linestyle()] + linestyles), - ('Draw Style', [line.get_drawstyle()] + drawstyles), - ('Width', line.get_linewidth()), - ('Color', color), - sep, - (None, 'Marker'), - ('Style', [line.get_marker()] + markers), - ('Size', line.get_markersize()), - ('Facecolor', fc), - ('Edgecolor', ec), - ] - curves.append([curvedata, label, ""]) - - # make sure that there is at least one displayed curve - has_curve = bool(curves) + # Sorting for default labels (_lineXXX, _imageXXX). + def cmp_key(label): + match = re.match(r"(_line|_image)(\d+)", label) + if match: + return match.group(1), int(match.group(2)) + else: + return label, 0 + + # Get / Curves + linedict = {} + for line in axes.get_lines(): + label = line.get_label() + if label == '_nolegend_': + continue + linedict[label] = line + curves = [] + + def prepare_data(d, init): + """Prepare entry for FormLayout. + + `d` is a mapping of shorthands to style names (a single style may + have multiple shorthands, in particular the shorthands `None`, + `"None"`, `"none"` and `""` are synonyms); `init` is one shorthand + of the initial style. + + This function returns an list suitable for initializing a + FormLayout combobox, namely `[initial_name, (shorthand, + style_name), (shorthand, style_name), ...]`. + """ + # Drop duplicate shorthands from dict (by overwriting them during + # the dict comprehension). + name2short = {name: short for short, name in d.items()} + # Convert back to {shorthand: name}. + short2name = {short: name for name, short in name2short.items()} + # Find the kept shorthand for the style specified by init. + canonical_init = name2short[d[init]] + # Sort by representation and prepend the initial value. + return ([canonical_init] + + sorted(short2name.items(), + key=lambda short_and_name: short_and_name[1])) + + curvelabels = sorted(linedict, key=cmp_key) + for label in curvelabels: + line = linedict[label] + color = mcolors.to_hex( + mcolors.to_rgba(line.get_color(), line.get_alpha()), + keep_alpha=True) + ec = mcolors.to_hex(line.get_markeredgecolor(), keep_alpha=True) + fc = mcolors.to_hex(line.get_markerfacecolor(), keep_alpha=True) + curvedata = [ + ('Label', label), + sep, + (None, 'Line'), + ('Line style', prepare_data(LINESTYLES, line.get_linestyle())), + ('Draw style', prepare_data(DRAWSTYLES, line.get_drawstyle())), + ('Width', line.get_linewidth()), + ('Color (RGBA)', color), + sep, + (None, 'Marker'), + ('Style', prepare_data(MARKERS, line.get_marker())), + ('Size', line.get_markersize()), + ('Face color (RGBA)', fc), + ('Edge color (RGBA)', ec)] + curves.append([curvedata, label, ""]) + # Is there a curve displayed? + has_curve = bool(curves) + + # Get / Images + imagedict = {} + for image in axes.get_images(): + label = image.get_label() + if label == '_nolegend_': + continue + imagedict[label] = image + imagelabels = sorted(imagedict, key=cmp_key) + images = [] + cmaps = [(cmap, name) for name, cmap in sorted(cm.cmap_d.items())] + for label in imagelabels: + image = imagedict[label] + cmap = image.get_cmap() + if cmap not in cm.cmap_d.values(): + cmaps = [(cmap, cmap.name)] + cmaps + low, high = image.get_clim() + imagedata = [ + ('Label', label), + ('Colormap', [cmap.name] + cmaps), + ('Min. value', low), + ('Max. value', high)] + images.append([imagedata, label, ""]) + # Is there an image displayed? + has_image = bool(images) datalist = [(general, "Axes", "")] - if has_curve: + if curves: datalist.append((curves, "Curves", "")) + if images: + datalist.append((images, "Images", "")) def apply_callback(data): """This function will be called to apply changes""" - if has_curve: - general, curves = data - else: - general, = data + general = data.pop(0) + curves = data.pop(0) if has_curve else [] + images = data.pop(0) if has_image else [] + if data: + raise ValueError("Unexpected field") # Set / General - title, xmin, xmax, xlabel, xscale, ymin, ymax, ylabel, yscale, \ - generate_legend = general + (title, xmin, xmax, xlabel, xscale, ymin, ymax, ylabel, yscale, + generate_legend) = general if axes.get_xscale() != xscale: axes.set_xscale(xscale) @@ -140,26 +197,33 @@ def apply_callback(data): axes.xaxis._update_axisinfo() axes.yaxis._update_axisinfo() - if has_curve: - # Set / Curves - for index, curve in enumerate(curves): - line = linedict[curvelabels[index]] - label, linestyle, drawstyle, linewidth, color, \ - marker, markersize, markerfacecolor, markeredgecolor \ - = curve - line.set_label(label) - line.set_linestyle(linestyle) - line.set_drawstyle(drawstyle) - line.set_linewidth(linewidth) - line.set_color(color) - if marker is not 'none': - line.set_marker(marker) - line.set_markersize(markersize) - line.set_markerfacecolor(markerfacecolor) - line.set_markeredgecolor(markeredgecolor) + # Set / Curves + for index, curve in enumerate(curves): + line = linedict[curvelabels[index]] + (label, linestyle, drawstyle, linewidth, color, marker, markersize, + markerfacecolor, markeredgecolor) = curve + line.set_label(label) + line.set_linestyle(linestyle) + line.set_drawstyle(drawstyle) + line.set_linewidth(linewidth) + rgba = mcolors.to_rgba(color) + line.set_color(rgba[:3]) + line.set_alpha(rgba[-1]) + if marker is not 'none': + line.set_marker(marker) + line.set_markersize(markersize) + line.set_markerfacecolor(markerfacecolor) + line.set_markeredgecolor(markeredgecolor) + + # Set / Images + for index, image_settings in enumerate(images): + image = imagedict[imagelabels[index]] + label, cmap, low, high = image_settings + image.set_label(label) + image.set_cmap(cm.get_cmap(cmap)) + image.set_clim(*sorted([low, high])) # re-generate legend, if checkbox is checked - if generate_legend: draggable = None ncol = 1 diff --git a/lib/matplotlib/backends/qt_editor/formlayout.py b/lib/matplotlib/backends/qt_editor/formlayout.py index a786a6105342..00a1a03a36ee 100644 --- a/lib/matplotlib/backends/qt_editor/formlayout.py +++ b/lib/matplotlib/backends/qt_editor/formlayout.py @@ -32,43 +32,33 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange # History: # 1.0.10: added float validator (disable "Ok" and "Apply" button when not valid) # 1.0.7: added support for "Apply" button # 1.0.6: code cleaning +from __future__ import (absolute_import, division, print_function, + unicode_literals) + __version__ = '1.0.10' __license__ = __doc__ DEBUG = False -import sys -STDERR = sys.stderr +import copy +import datetime +import warnings -from matplotlib.colors import is_color_like -from matplotlib.colors import rgb2hex -from matplotlib.colors import colorConverter +import six +from matplotlib import colors as mcolors from matplotlib.backends.qt_compat import QtGui, QtWidgets, QtCore -if not hasattr(QtWidgets, 'QFormLayout'): - raise ImportError("Warning: formlayout requires PyQt4 >v4.3 or PySide") -import datetime BLACKLIST = set(["title", "label"]) -def col2hex(color): - """Convert matplotlib color to hex before passing to Qt""" - return rgb2hex(colorConverter.to_rgb(color)) - - class ColorButton(QtWidgets.QPushButton): """ Color choosing push button @@ -83,7 +73,9 @@ def __init__(self, parent=None): self._color = QtGui.QColor() def choose_color(self): - color = QtWidgets.QColorDialog.getColor(self._color, self.parentWidget(), '') + color = QtWidgets.QColorDialog.getColor( + self._color, self.parentWidget(), "", + QtWidgets.QColorDialog.ShowAlphaChannel) if color.isValid(): self.set_color(color) @@ -101,21 +93,17 @@ def set_color(self, color): color = QtCore.Property(QtGui.QColor, get_color, set_color) -def col2hex(color): - """Convert matplotlib color to hex before passing to Qt""" - return rgb2hex(colorConverter.to_rgb(color)) def to_qcolor(color): """Create a QColor from a matplotlib color""" qcolor = QtGui.QColor() - color = str(color) try: - color = col2hex(color) + rgba = mcolors.to_rgba(color) except ValueError: - #print('WARNING: ignoring invalid color %r' % color) + warnings.warn('Ignoring invalid color %r' % color) return qcolor # return invalid QColor - qcolor.setNamedColor(color) # set using hex color - return qcolor # return valid QColor + qcolor.setRgbF(*rgba) + return qcolor class ColorLayout(QtWidgets.QHBoxLayout): @@ -123,7 +111,8 @@ class ColorLayout(QtWidgets.QHBoxLayout): def __init__(self, color, parent=None): QtWidgets.QHBoxLayout.__init__(self) assert isinstance(color, QtGui.QColor) - self.lineedit = QtWidgets.QLineEdit(color.name(), parent) + self.lineedit = QtWidgets.QLineEdit( + mcolors.to_hex(color.getRgbF(), keep_alpha=True), parent) self.lineedit.editingFinished.connect(self.update_color) self.addWidget(self.lineedit) self.colorbtn = ColorButton(parent) @@ -137,7 +126,7 @@ def update_color(self): self.colorbtn.color = qcolor # defaults to black if not qcolor.isValid() def update_text(self, color): - self.lineedit.setText(color.name()) + self.lineedit.setText(mcolors.to_hex(color.getRgbF(), keep_alpha=True)) def text(self): return self.lineedit.text() @@ -146,7 +135,7 @@ def text(self): def font_is_installed(font): """Check if font is installed""" return [fam for fam in QtGui.QFontDatabase().families() - if six.text_type(fam) == font] + if six.text_type(fam) == font] def tuple_to_qfont(tup): @@ -154,11 +143,11 @@ def tuple_to_qfont(tup): Create a QFont from tuple: (family [string], size [int], italic [bool], bold [bool]) """ - if not isinstance(tup, tuple) or len(tup) != 4 \ - or not font_is_installed(tup[0]) \ - or not isinstance(tup[1], int) \ - or not isinstance(tup[2], bool) \ - or not isinstance(tup[3], bool): + if not (isinstance(tup, tuple) and len(tup) == 4 + and font_is_installed(tup[0]) + and isinstance(tup[1], int) + and isinstance(tup[2], bool) + and isinstance(tup[3], bool)): return None font = QtGui.QFont() family, size, italic, bold = tup @@ -189,7 +178,7 @@ def __init__(self, value, parent=None): # Font size self.size = QtWidgets.QComboBox(parent) self.size.setEditable(True) - sizelist = list(xrange(6, 12)) + list(xrange(12, 30, 2)) + [36, 48, 72] + sizelist = list(range(6, 12)) + list(range(12, 30, 2)) + [36, 48, 72] size = font.pointSize() if size not in sizelist: sizelist.append(size) @@ -227,8 +216,7 @@ class FormWidget(QtWidgets.QWidget): update_buttons = QtCore.Signal() def __init__(self, data, comment="", parent=None): QtWidgets.QWidget.__init__(self, parent) - from copy import deepcopy - self.data = deepcopy(data) + self.data = copy.deepcopy(data) self.widgets = [] self.formlayout = QtWidgets.QFormLayout(self) if comment: @@ -264,7 +252,8 @@ def setup(self): continue elif tuple_to_qfont(value) is not None: field = FontLayout(value, self) - elif label.lower() not in BLACKLIST and is_color_like(value): + elif (label.lower() not in BLACKLIST + and mcolors.is_color_like(value)): field = ColorLayout(to_qcolor(value), self) elif isinstance(value, six.string_types): field = QtWidgets.QLineEdit(value, self) @@ -284,8 +273,9 @@ def setup(self): elif selindex in keys: selindex = keys.index(selindex) elif not isinstance(selindex, int): - print("Warning: '%s' index is invalid (label: " - "%s, value: %s)" % (selindex, label, value), file=STDERR) + warnings.warn( + "index '%s' is invalid (label: %s, value: %s)" % + (selindex, label, value)) selindex = 0 field.setCurrentIndex(selindex) elif isinstance(value, bool): @@ -326,7 +316,8 @@ def get(self): continue elif tuple_to_qfont(value) is not None: value = field.get_font() - elif isinstance(value, six.string_types) or is_color_like(value): + elif (isinstance(value, six.string_types) + or mcolors.is_color_like(value)): value = six.text_type(field.text()) elif isinstance(value, (list, tuple)): index = int(field.currentIndex()) @@ -431,8 +422,8 @@ def __init__(self, data, title="", comment="", self.formwidget.setup() # Button box - self.bbox = bbox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok - | QtWidgets.QDialogButtonBox.Cancel) + self.bbox = bbox = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) self.formwidget.update_buttons.connect(self.update_buttons) if self.apply_callback is not None: apply_btn = bbox.addButton(QtWidgets.QDialogButtonBox.Apply) @@ -457,7 +448,8 @@ def update_buttons(self): for field in self.float_fields: if not is_edit_valid(field): valid = False - for btn_type in (QtWidgets.QDialogButtonBox.Ok, QtWidgets.QDialogButtonBox.Apply): + for btn_type in (QtWidgets.QDialogButtonBox.Ok, + QtWidgets.QDialogButtonBox.Apply): btn = self.bbox.button(btn_type) if btn is not None: btn.setEnabled(valid)