From 291c7c304cf175d6566ce29a8f4768bc8a7c38a1 Mon Sep 17 00:00:00 2001 From: Nils Nemitz Date: Thu, 11 Mar 2021 02:45:02 +0900 Subject: [PATCH] restructured to reduce circular dependencies --- pyqtgraph/Qt.py | 2 +- pyqtgraph/__init__.py | 1 + pyqtgraph/functions.py | 29 ++++++----- pyqtgraph/graphicsItems/GraphicsObject.py | 2 +- pyqtgraph/graphicsItems/GraphicsWidget.py | 2 +- pyqtgraph/namedBrush.py | 28 ++++++----- pyqtgraph/namedColorManager.py | 9 ++-- pyqtgraph/namedPen.py | 60 ++++++++++++++--------- pyqtgraph/palette.py | 2 +- pyqtgraph/widgets/GraphicsView.py | 2 +- 10 files changed, 79 insertions(+), 58 deletions(-) diff --git a/pyqtgraph/Qt.py b/pyqtgraph/Qt.py index 6d15a578..61557266 100644 --- a/pyqtgraph/Qt.py +++ b/pyqtgraph/Qt.py @@ -317,7 +317,7 @@ if QT_LIB in [PYQT5, PYQT6]: loadUiType = uic.loadUiType QtCore.Signal = QtCore.pyqtSignal - + QtCore.Slot = QtCore.pyqtSlot if QT_LIB == PYSIDE6: # PySide6 6.0 has a missing binding diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py index 63f5219c..ecb40d8b 100644 --- a/pyqtgraph/__init__.py +++ b/pyqtgraph/__init__.py @@ -287,6 +287,7 @@ from .ThreadsafeTimer import * from .namedPen import * from .namedBrush import * from .palette import * +from . import namedColorManager ############################################################## diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 4f7a9795..00547602 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -28,6 +28,12 @@ from .metaarray import MetaArray from collections import OrderedDict from .python2_3 import asUnicode, basestring +# legacy color definitions: +# NamedColorManager now maintains the primary list of palette colors, +# accessible through functions.NAMED_COLOR_MANAGER.colors(). +# For backwards compatibility, this dictionary is updated to contain the same information. +# +# For the user, colors and color palettes are most conveniently accessed through a Palette object. Colors = { 'b': QtGui.QColor(0,0,255,255), 'g': QtGui.QColor(0,255,0,255), @@ -41,11 +47,9 @@ Colors = { 'l': QtGui.QColor(200,200,200,255), 's': QtGui.QColor(100,100,150,255) } -# instantiate singleton NamedColorManager with a reference to Colors +print(' functions loaded, colors initiated.') NAMED_COLOR_MANAGER = NamedColorManager( Colors ) -# populates the Colors dictionary with default functional colors. -# print('updated(?) colors:', Colors) - +print(' namedColorManager loaded, colors updated.') SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY') SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY' @@ -243,7 +247,7 @@ def parseNamedColorSpecification(*args): return None # hexadecimal string not handled as NamedColor if arg in Colors: return (arg, None) # valid name, no alpha given - if isinstance(arg, tuple) or isinstance(arg, list): + if isinstance(arg, (tuple, list)): args = arg # promote to top level else: return None #numerical values not handled as NamedColor @@ -350,7 +354,7 @@ def mkBrush(*args, **kargs): | Calling mkBrush(None) returns an invisible brush. """ while ( # unravel single element sublists - ( isinstance(args, tuple) or isinstance(args,list) ) + isinstance(args, (tuple, list) ) and len(args) == 1 ): args = args[0] @@ -375,14 +379,14 @@ def mkBrush(*args, **kargs): return QtGui.QBrush( QtCore.Qt.NoBrush ) # explicit None means "no brush" if args == () or args == []: print(' functions: returning default color NamedBrush') - qpen = NamedBrush( 'gr_fg' ) # default foreground color + qpen = NamedBrush( 'gr_fg', manager=NAMED_COLOR_MANAGER ) # default foreground color else: result = parseNamedColorSpecification(args) if result is not None: # make a NamedBrush name, alpha = result if name == '': return QtGui.QBrush( QtCore.Qt.NoBrush ) # empty string means "no brush" - qbrush = NamedBrush(name, alpha=alpha) + qbrush = NamedBrush(name, manager=NAMED_COLOR_MANAGER, alpha=alpha) else: # make a QBrush qcol = mkColor(args) qbrush = QtGui.QBrush(qcol) @@ -404,7 +408,7 @@ def mkPen(*args, **kargs): In these examples, *color* may be replaced with any arguments accepted by :func:`mkColor() ` """ # print('mkPen called:',args,kargs) while ( # unravel single element sublists - ( isinstance(args, tuple) or isinstance(args,list) ) + isinstance(args, (tuple, list) ) and len(args) == 1 ): args = args[0] @@ -429,14 +433,14 @@ def mkPen(*args, **kargs): if args is None: return QtGui.QPen( QtCore.Qt.NoPen ) # explicit None means "no pen" if args == () or args == []: - qpen = NamedPen( 'gr_fg', width=width ) # default foreground color + qpen = NamedPen( 'gr_fg', manager=NAMED_COLOR_MANAGER, width=width ) # default foreground color else: result = parseNamedColorSpecification(args) if result is not None: # make a NamedPen name, alpha = result if name == '': return QtGui.QPen( QtCore.Qt.NoPen ) # empty string means "no pen" - qpen = NamedPen( name, alpha=alpha, width=width ) + qpen = NamedPen( name, manager=NAMED_COLOR_MANAGER, alpha=alpha, width=width ) else: # make a QPen qcol = mkColor(args) qpen = QtGui.QPen(QtGui.QBrush(qcol), width) @@ -445,7 +449,8 @@ def mkPen(*args, **kargs): width = kargs.get('width', 1) # collect remaining kargs to define properties dash = kargs.get('dash', None) cosmetic = kargs.get('cosmetic', True) - assert qpen is not None + if qpen is None: + raise ValueError('Failed to construct QPen from arguments '+str(args)+','+str(kargs) ) qpen.setCosmetic(cosmetic) if style is not None: qpen.setStyle(style) diff --git a/pyqtgraph/graphicsItems/GraphicsObject.py b/pyqtgraph/graphicsItems/GraphicsObject.py index f0fde5eb..80c410fc 100644 --- a/pyqtgraph/graphicsItems/GraphicsObject.py +++ b/pyqtgraph/graphicsItems/GraphicsObject.py @@ -54,7 +54,7 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject): # """ stub function called after Palette.apply(), specific reactions to palette redefinitions execute here """ # print('style change request:', self, type(color_dict)) - @QT_CORE_SLOT() + @QtCore.Slot() # qt.py equates this to pyqtSlot for PyQt def styleHasChanged(self): """ called to trigger redraw after all named colors have been updated """ # self._boundingRect = None diff --git a/pyqtgraph/graphicsItems/GraphicsWidget.py b/pyqtgraph/graphicsItems/GraphicsWidget.py index 6df50962..d4cfb184 100644 --- a/pyqtgraph/graphicsItems/GraphicsWidget.py +++ b/pyqtgraph/graphicsItems/GraphicsWidget.py @@ -64,7 +64,7 @@ class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget): #print "shape:", p.boundingRect() return p - @QT_CORE_SLOT() + @QtCore.Slot() # qt.py equates this to pyqtSlot for PyQt def styleHasChanged(self): """ called to trigger redraw after all named colors have been updated """ # self._boundingRect = None diff --git a/pyqtgraph/namedBrush.py b/pyqtgraph/namedBrush.py index 22896405..ae944f14 100644 --- a/pyqtgraph/namedBrush.py +++ b/pyqtgraph/namedBrush.py @@ -1,24 +1,26 @@ -# from ..Qt import QtGui from .Qt import QtGui, QtCore - -from . import functions as fn +from .namedColorManager import NamedColorManager __all__ = ['NamedBrush'] DEBUG = False class NamedBrush(QtGui.QBrush): """ Extends QPen to retain a functional color description """ - def __init__(self, name, alpha=None ): + def __init__(self, name, manager=None, alpha=None ): """ Creates a new NamedBrush object. - 'name' should be in 'functions.Colors' - 'alpha' controls opacity which persists over palette changes + 'name' should be in 'functions.Colors' + 'manager' is a reference to the controlling NamedColorManager + 'alpha' controls opacity which persists over palette changes """ if DEBUG: print(' NamedBrush created as',name,alpha) super().__init__(QtCore.Qt.SolidPattern) # Initialize QBrush superclass self._identifier = (name, alpha) + if manager is None or not isinstance(manager, NamedColorManager): + raise ValueError("NamedPen requires NamedColorManager to be provided in 'manager' argument!") + self._manager = manager self._updateQColor(self._identifier) - fn.NAMED_COLOR_MANAGER.register( self ) # manually register for callbacks + self._manager.register( self ) # manually register for callbacks def __eq__(self, other): # make this a hashable object # return other is self @@ -51,13 +53,17 @@ class NamedBrush(QtGui.QBrush): self._identifier = (self._identifier[0], alpha) self._updateQColor(self._identifier) + def identifier(self): + """ return current color identifier """ + return self._identifier + def _updateQColor(self, identifier, color_dict=None): """ update super-class QColor """ name, alpha = identifier if color_dict is None: - color_dict = fn.NAMED_COLOR_MANAGER.colors() + color_dict = self._manager.colors() try: - qcol = fn.Colors[name] + qcol = color_dict[name] except ValueError as exc: raise ValueError("Color '{:s}' is not in list of defined colors".format(str(name)) ) from exc if alpha is not None: @@ -65,10 +71,6 @@ class NamedBrush(QtGui.QBrush): if DEBUG: print(' NamedBrush '+name+' updated to QColor ('+str(qcol.name())+', alpha='+str(alpha)+')') super().setColor( qcol ) - def identifier(self): - """ return current color identifier """ - return self._identifier - def paletteChange(self, color_dict): """ refresh QColor according to lookup of identifier in functions.Colors """ if DEBUG: print(' NamedBrush: style change request:', self, type(color_dict)) diff --git a/pyqtgraph/namedColorManager.py b/pyqtgraph/namedColorManager.py index f9447c05..59385cbf 100644 --- a/pyqtgraph/namedColorManager.py +++ b/pyqtgraph/namedColorManager.py @@ -4,7 +4,7 @@ from .Qt import QtCore, QtGui import weakref __all__ = ['NamedColorManager'] -DEBUG = True +DEBUG = False DEFAULT_COLORS = { 'b': QtGui.QColor( 0, 0,255,255), @@ -18,7 +18,7 @@ DEFAULT_COLORS = { 'd': QtGui.QColor(150,150,150,255), 'l': QtGui.QColor(200,200,200,255), 's': QtGui.QColor(100,100,150,255), - 'gr_acc':QtGui.QColor(200,200,100,255), # graphical accent color: pastel yellow + 'gr_acc':QtGui.QColor(200,200,100,255), # graphical accent color: pastel yellow 'gr_reg':QtGui.QColor( 0, 0,255, 50) # graphical region marker: translucent blue } for key, col in [ # add functional colors @@ -35,8 +35,9 @@ for idx, col in enumerate( ( # twelve predefined plot colors key = 'p{:X}'.format(idx) DEFAULT_COLORS[key] = DEFAULT_COLORS[col] -# define and instantiate a SignalSource object to pass signals to all pyqtgraph elements -class NamedColorManager(QtCore.QObject): # this needs to emit QEvents +# An instantiated QObject is required to emit QSignals. +# functions.py initializes and maintains NAMED_COLOR_MANAGER for this purpose. +class NamedColorManager(QtCore.QObject): """ Singleton QObject that provides palette change signals Instantiated by 'functions.py' and retrievable as functions.NAMED_COLOR_MANAGER diff --git a/pyqtgraph/namedPen.py b/pyqtgraph/namedPen.py index 195c0809..6a8d1ac1 100644 --- a/pyqtgraph/namedPen.py +++ b/pyqtgraph/namedPen.py @@ -1,27 +1,30 @@ -# from ..Qt import QtGui from .Qt import QtGui, QtCore - -from . import functions as fn +from .namedColorManager import NamedColorManager __all__ = ['NamedPen'] DEBUG = False class NamedPen(QtGui.QPen): """ Extends QPen to retain a functional color description """ - def __init__(self, name, width=1, alpha=None ): + def __init__(self, name, manager=None, width=1, alpha=None ): """ Creates a new NamedPen object. - 'name' should be included in 'functions.Colors' - 'width' specifies linewidth and defaults to 1 - 'alpha' controls opacity which persists over palette changes + 'name' should be included in 'functions.Colors' + 'manager' is a reference to the controlling NamedColorManager + 'width' specifies linewidth and defaults to 1 + 'alpha' controls opacity which persists over palette changes """ - if DEBUG: print(' NamedBrush created as',name,alpha) + if DEBUG: print(' NamedPen created as',name,alpha) super().__init__(QtCore.Qt.SolidLine) # Initialize QPen superclass super().setWidth(width) super().setCosmetic(True) self._identifier = (name, alpha) + if manager is None or not isinstance(manager, NamedColorManager): + raise ValueError("NamedPen requires NamedColorManager to be provided in 'manager' argument!") + self._manager = manager self._updateQColor(self._identifier) - fn.NAMED_COLOR_MANAGER.register( self ) # manually register for callbacks + if self._manager is not None: + self._manager.register( self ) # manually register for callbacks def __eq__(self, other): # make this a hashable object # return other is self @@ -55,13 +58,17 @@ class NamedPen(QtGui.QPen): self._identifier = (self._identifier[0], alpha) self._updateQColor(self._identifier) + def identifier(self): + """ return current color identifier """ + return self._identifier + def _updateQColor(self, identifier, color_dict=None): """ update super-class QColor """ name, alpha = identifier if color_dict is None: - color_dict = fn.NAMED_COLOR_MANAGER.colors() + color_dict = self._manager.colors() try: - qcol = fn.Colors[name] + qcol = color_dict[name] except ValueError as exc: raise ValueError("Color '{:s}' is not in list of defined colors".format(str(name)) ) from exc if alpha is not None: @@ -69,19 +76,24 @@ class NamedPen(QtGui.QPen): if DEBUG: print(' NamedPen updated to QColor ('+str(qcol.name())+')') super().setColor( qcol ) - - def paletteChange(self, color_dict): """ refresh QColor according to lookup of identifier in functions.Colors """ if DEBUG: print(' NamedPen: style change request:', self, type(color_dict)) - name, alpha = self._identifier - if color_dict is None: # manually retrieve color manager palette - color_dict = fn.NAMED_COLOR_MANAGER.colors() - try: - qcol = color_dict[name] - if DEBUG: print(' NamedPen: retrieved new QColor (', qcol.getRgb(), ') for name', name) - except ValueError as exc: - raise ValueError("Color {:s} is not in list of defined colors".format(str(name)) ) from exc - if alpha is not None: - qcol.setAlpha( alpha ) - super().setColor(qcol) + self._updateQColor(self._identifier, color_dict=color_dict) + if DEBUG: + qcol = super().color() + name, alpha = self._identifier + print(' NamedPen: retrieved new QColor ('+str(qcol.name())+') ' + + 'for name '+str(name)+' ('+str(alpha)+')' ) + + # name, alpha = self._identifier + # if color_dict is None: # manually retrieve color manager palette + # color_dict = self._manager.colors() + # try: + # qcol = color_dict[name] + # if DEBUG: print(' NamedPen: retrieved new QColor (', qcol.getRgb(), ') for name', name) + # except ValueError as exc: + # raise ValueError("Color {:s} is not in list of defined colors".format(str(name)) ) from exc + # if alpha is not None: + # qcol.setAlpha( alpha ) + # super().setColor(qcol) diff --git a/pyqtgraph/palette.py b/pyqtgraph/palette.py index f1b97451..def1cc46 100644 --- a/pyqtgraph/palette.py +++ b/pyqtgraph/palette.py @@ -1,6 +1,6 @@ from .Qt import QtGui, QtCore -from . import functions as fn +from . import functions as fn # namedColorManager __all__ = ['Palette'] diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py index f543a30a..a47dbea7 100644 --- a/pyqtgraph/widgets/GraphicsView.py +++ b/pyqtgraph/widgets/GraphicsView.py @@ -418,7 +418,7 @@ class GraphicsView(QtGui.QGraphicsView): def dragEnterEvent(self, ev): ev.ignore() ## not sure why, but for some reason this class likes to consume drag events - @QT_CORE_SLOT() + @QtCore.Slot() # qt.py equates this to pyqtSlot for PyQt def styleHasChanged(self): """ called to trigger redraw after all named colors have been updated """ self.setBackgroundBrush( self._bgBrush )