Aluode/PerceptionLabPortable
0
1"""2WidgetGroup.py - WidgetGroup class for easily managing lots of Qt widgets3Copyright 2010 Luke Campagnola4Distributed under MIT/X11 license. See license.txt for more information.5 6This class addresses the problem of having to save and restore the state7of a large group of widgets. 8"""9 10import inspect11import weakref12 13from .Qt import QtCore, QtWidgets14 15__all__ = ['WidgetGroup']16 17def splitterState(w):18 s = w.saveState().toPercentEncoding().data().decode()19 return s20 21def restoreSplitter(w, s):22 if type(s) is list:23 w.setSizes(s)24 elif type(s) is str:25 w.restoreState(QtCore.QByteArray.fromPercentEncoding(s.encode()))26 else:27 print("Can't configure QSplitter using object of type", type(s))28 if w.count() > 0: # make sure at least one item is not collapsed29 for i in w.sizes():30 if i > 0:31 return32 w.setSizes([50] * w.count())33 34def comboState(w):35 ind = w.currentIndex()36 data = w.itemData(ind)37 if data is not None:38 try:39 if not data.isValid():40 data = None41 else:42 data = data.toInt()[0]43 except AttributeError:44 pass45 if data is None:46 return str(w.itemText(ind))47 else:48 return data49 50def setComboState(w, v):51 if type(v) is int:52 ind = w.findData(v)53 if ind > -1:54 w.setCurrentIndex(ind)55 return56 w.setCurrentIndex(w.findText(str(v)))57 58 59class WidgetGroup(QtCore.QObject):60 """State manager for groups of widgets.61 62 WidgetGroup handles common problems that arise when dealing with groups of widgets like a control63 panel:64 - Provide a single place for saving / restoring the state of all widgets in the group65 - Provide a single signal for detecting when any of the widgets have changed66 """67 68 # List of widget types that can be handled by WidgetGroup.69 # The value for each type is a tuple (change signal function, get function, set function, [auto-add children])70 # The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just71 # when it is changed by user interaction. (for example, 'clicked' is not a valid signal here)72 # If the change signal is None, the value of the widget is not cached.73 # Custom widgets not in this list can be made to work with WidgetGroup by giving them a 'widgetGroupInterface' method74 # which returns the tuple.75 classes = {76 QtWidgets.QSpinBox: (77 lambda w: w.valueChanged,78 QtWidgets.QSpinBox.value, 79 QtWidgets.QSpinBox.setValue80 ),81 QtWidgets.QDoubleSpinBox: (82 lambda w: w.valueChanged,83 QtWidgets.QDoubleSpinBox.value, 84 QtWidgets.QDoubleSpinBox.setValue85 ),86 QtWidgets.QSplitter: (87 None,88 splitterState,89 restoreSplitter,90 True91 ),92 QtWidgets.QCheckBox: (93 lambda w: w.stateChanged,94 QtWidgets.QCheckBox.isChecked,95 QtWidgets.QCheckBox.setChecked96 ),97 QtWidgets.QComboBox: (98 lambda w: w.currentIndexChanged,99 comboState,100 setComboState101 ),102 QtWidgets.QGroupBox: (103 lambda w: w.toggled,104 QtWidgets.QGroupBox.isChecked,105 QtWidgets.QGroupBox.setChecked,106 True107 ),108 QtWidgets.QLineEdit: (109 lambda w: w.editingFinished,110 lambda w: str(w.text()),111 QtWidgets.QLineEdit.setText112 ),113 QtWidgets.QRadioButton: (114 lambda w: w.toggled,115 QtWidgets.QRadioButton.isChecked,116 QtWidgets.QRadioButton.setChecked117 ),118 QtWidgets.QSlider: (119 lambda w: w.valueChanged,120 QtWidgets.QSlider.value,121 QtWidgets.QSlider.setValue122 ),123 }124 125 sigChanged = QtCore.Signal(str, object)126 127 128 def __init__(self, widgetList=None):129 """Initialize WidgetGroup, adding specified widgets into this group.130 widgetList can be: 131 - a list of widget specifications (widget, [name], [scale])132 - a dict of name: widget pairs133 - any QObject, and all compatible child widgets will be added recursively.134 135 The 'scale' parameter for each widget allows QSpinBox to display a different value than the value recorded136 in the group state (for example, the program may set a spin box value to 100e-6 and have it displayed as 100 to the user)137 """138 QtCore.QObject.__init__(self)139 self.widgetList = weakref.WeakKeyDictionary() # Make sure widgets don't stick around just because they are listed here140 self.scales = weakref.WeakKeyDictionary()141 self.cache = {} # name:value pairs142 self.uncachedWidgets = weakref.WeakKeyDictionary()143 if isinstance(widgetList, QtCore.QObject):144 self.autoAdd(widgetList)145 elif isinstance(widgetList, list):146 for w in widgetList:147 self.addWidget(*w)148 elif isinstance(widgetList, dict):149 for name, w in widgetList.items():150 self.addWidget(w, name)151 elif widgetList is None:152 return153 else:154 raise Exception("Wrong argument type %s" % type(widgetList))155 156 def addWidget(self, w, name=None, scale=None):157 if not self.acceptsType(w):158 raise Exception("Widget type %s not supported by WidgetGroup" % type(w))159 if name is None:160 name = str(w.objectName())161 if name == '':162 raise Exception("Cannot add widget '%s' without a name." % str(w))163 self.widgetList[w] = name164 self.scales[w] = scale165 self.readWidget(w)166 167 if type(w) in WidgetGroup.classes:168 signal = WidgetGroup.classes[type(w)][0]169 else:170 signal = w.widgetGroupInterface()[0]171 172 if signal is not None:173 if inspect.isfunction(signal) or inspect.ismethod(signal):174 signal = signal(w)175 signal.connect(self.widgetChanged)176 else:177 self.uncachedWidgets[w] = None178 179 def findWidget(self, name):180 for w in self.widgetList:181 if self.widgetList[w] == name:182 return w183 return None184 185 def interface(self, obj):186 t = type(obj)187 if t in WidgetGroup.classes:188 return WidgetGroup.classes[t]189 else:190 return obj.widgetGroupInterface()191 192 def checkForChildren(self, obj):193 """Return true if we should automatically search the children of this object for more."""194 iface = self.interface(obj)195 return (len(iface) > 3 and iface[3])196 197 def autoAdd(self, obj):198 # Find all children of this object and add them if possible.199 accepted = self.acceptsType(obj)200 if accepted:201 self.addWidget(obj)202 203 if not accepted or self.checkForChildren(obj):204 for c in obj.children():205 self.autoAdd(c)206 207 def acceptsType(self, obj):208 for c in WidgetGroup.classes:209 if isinstance(obj, c):210 return True211 if hasattr(obj, 'widgetGroupInterface'):212 return True213 return False214 215 def setScale(self, widget, scale):216 val = self.readWidget(widget)217 self.scales[widget] = scale218 self.setWidget(widget, val)219 220 @QtCore.Slot()221 @QtCore.Slot(bool)222 @QtCore.Slot(int)223 def widgetChanged(self, *args):224 w = self.sender()225 n = self.widgetList[w]226 v1 = self.cache[n]227 v2 = self.readWidget(w)228 if v1 != v2:229 self.sigChanged.emit(self.widgetList[w], v2)230 231 def state(self):232 for w in self.uncachedWidgets:233 self.readWidget(w)234 return self.cache.copy()235 236 def setState(self, s):237 for w in self.widgetList:238 n = self.widgetList[w]239 if n not in s:240 continue241 self.setWidget(w, s[n])242 243 def readWidget(self, w):244 if type(w) in WidgetGroup.classes:245 getFunc = WidgetGroup.classes[type(w)][1]246 else:247 getFunc = w.widgetGroupInterface()[1]248 249 if getFunc is None:250 return None251 252 # if the getter function provided in the interface is a bound method,253 # then just call the method directly. Otherwise, pass in the widget as the first arg254 # to the function.255 if inspect.ismethod(getFunc) and getFunc.__self__ is not None: 256 val = getFunc()257 else:258 val = getFunc(w)259 260 if self.scales[w] is not None:261 val /= self.scales[w]262 n = self.widgetList[w]263 self.cache[n] = val264 return val265 266 def setWidget(self, w, v):267 if self.scales[w] is not None:268 v *= self.scales[w]269 270 if type(w) in WidgetGroup.classes:271 setFunc = WidgetGroup.classes[type(w)][2]272 else:273 setFunc = w.widgetGroupInterface()[2]274 275 # if the setter function provided in the interface is a bound method,276 # then just call the method directly. Otherwise, pass in the widget as the first arg277 # to the function.278 if inspect.ismethod(setFunc) and setFunc.__self__ is not None:279 setFunc(v)280 else:281 setFunc(w, v)282 