class DamageCounterAdapter(QObject): """ Adapter for damage counter .. versionadded:: 0.6 """ def __init__(self, parent, object_to_animate): """ Default constructor """ super().__init__() self.object_to_animate = object_to_animate def __get_y_location(self): return self.object_to_animate.y() def __set_y_location(self, y): self.object_to_animate.setY(y) def __get_opacity(self): return self.object_to_animate.opacity() def __set_opacity(self, opacity): self.object_to_animate.setOpacity(opacity) y_location = pyqtProperty(int, __get_y_location, __set_y_location) opacity = pyqtProperty(float, __get_opacity, __set_opacity)
def __init__(self, parent): QWidget.__init__(parent) self.panelwidget = pyqtProperty(bool, self.getPW) self._singleRow = False self.panelwidget_singlerow = pyqtProperty(bool, self.isSingleRow, self.setSingleRow) self._lightColored = False self.lightColored = pyqtProperty(bool, self.isLightColored, self.setLightColored)
def prop_sig(type, name, default=None, doc=None): """ Creates a pyqtProperty/pyqtSignal pair from the given name and type. The signal is assumed to have the name of the property plus a "_changed" suffix; the member variable is stored as the name of the property. Example: class cls(object): length, length_changed = prop_sig(int, "length", 20) """ # """ # The new method should take one argument, the object that is assigned # to the property. It acts as a filter before the assigned object is # actually set. # # The delete method should take one argument, the object that is # to be deleted. It will be called on the property value when it is # about to be replaced by another value. # """ sig = name + "_changed" mem = "_" + name # Make sure that the type passed in was a python class, not # a C++ type-string. if (isinstance(type, basestring) or isinstance(type, QString) ) and default is None: raise PropertyException, "must give a default when type is a C++ type-string" fget = get_fget(mem, type, sig, default) fset = get_fset(mem, type, sig) return pyqtProperty(type, fget, fset, doc=doc), pyqtSignal(type)
class ColorButton(QPushButton): """ Color choosing push button """ __pyqtSignals__ = ("colorChanged(QColor)",) def __init__(self, parent=None): QPushButton.__init__(self, parent) self.setFixedSize(20, 20) self.setIconSize(QSize(12, 12)) self.connect(self, SIGNAL("clicked()"), self.choose_color) self._color = QColor() def choose_color(self): rgba, valid = QColorDialog.getRgba(self._color.rgba(), self.parentWidget()) if valid: color = QColor.fromRgba(rgba) self.set_color(color) def get_color(self): return self._color @pyqtSignature("QColor") def set_color(self, color): if color != self._color: self._color = color self.emit(SIGNAL("colorChanged(QColor)"), self._color) pixmap = QPixmap(self.iconSize()) pixmap.fill(color) self.setIcon(QIcon(pixmap)) color = pyqtProperty("QColor", get_color, set_color)
class ColorButton(QPushButton): """ Color choosing push button """ __pyqtSignals__ = ("colorChanged(QColor)", ) def __init__(self, parent=None): QPushButton.__init__(self, parent) self.setFixedSize(20, 20) self.setIconSize(QSize(12, 12)) self.connect(self, SIGNAL("clicked()"), self.choose_color) self._color = QColor() def choose_color(self): color = QColorDialog.getColor(self._color, self.parentWidget(), 'Select Color', QColorDialog.ShowAlphaChannel) if color.isValid(): self.set_color(color) def get_color(self): return self._color #@Slot(QColor) @pyqtSlot(QColor) def set_color(self, color): if color != self._color: self._color = color self.emit(SIGNAL("colorChanged(QColor)"), self._color) pixmap = QPixmap(self.iconSize()) pixmap.fill(color) self.setIcon(QIcon(pixmap)) #color = Property("QColor", get_color, set_color) color = pyqtProperty("QColor", get_color, set_color)
class FrameData(QObject): _name = '' def _get_name(self): return self._name def set_name(self, value): self._name = value name = pyqtProperty(str, fget=_get_name)
class PythonEditor(CommonScintilla): def __init__(self, parent): CommonScintilla.__init__(self, parent) # don't show the symbol margin self.setMarginWidth(1, 0) self.setLexer(QsciLexerPython()) def getUser(self): return self.text() def setUser(self, value): self.setText(value) user = pyqtProperty(str, getUser, setUser, user=True)
class UuidComboBox(QtGui.QComboBox): #TODO set the default tulpenmanie.commodity.model column to 1 def _get_current_uuid(self): return self.model().item(self.currentIndex(), 0).text() def _set_current_uuid(self, uuid): results = self.model().findItems(uuid) if results: self.setCurrentIndex(results[0].row()) else: self.setCurrentIndex(-1) currentUuid = pyqtProperty(str, _get_current_uuid, _set_current_uuid)
class Python(QObject): def __init__(self): QObject.__init__(self) self.setObjectName("python") # Does not work as expected :( self.setProperty("app", QVariant(self)) self.t = QTimer(self) self.t.setObjectName("timer") @pyqtSignature("QString") def hello(self, name): print "Hello,", name def get_test(self): return 123 test = pyqtProperty("int", get_test)
def deal(key, default): def get(self): return self.settings.value(key, default) get.__name__ = getter_name(key) def set(self, value): if getattr(self, key) == value: return self.settings.setValue(key, value) getattr(self, signal_name(key)).emit(value) set.__name__ = setter_name(key) setattr(Settings, key, pyqtProperty(type(default), get, set)) # cannot directly use pyqtSignal here, cause python crash setattr(Settings, signal_name(key), signal(type(default))) setattr(Settings, setter_name(key), set) setattr(Settings, setter_name(key), getattr(Settings, setter_name(key)))
class QLed(QWidget): Circle = 1 Round = 2 Square = 3 Triangle = 4 Red = 1 Green = 2 Yellow = 3 Grey = 4 Orange = 5 Purple = 6 Blue = 7 shapes={ Circle:""" <svg height="50.000000px" id="svg9493" width="50.000000px" xmlns="http://www.w3.org/2000/svg"> <defs id="defs9495"> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient6650" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient id="linearGradient6494"> <stop id="stop6496" offset="0.0000000" style="stop-color:%s;stop-opacity:1.0000000;"/> <stop id="stop6498" offset="1.0000000" style="stop-color:%s;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient6648" x1="23.213980" x2="23.201290" xlink:href="#linearGradient6494" y1="42.754631" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient6646" x1="23.349695" x2="23.440580" xlink:href="#linearGradient5756" y1="42.767944" y2="43.710873"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient6644" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient id="linearGradient6506"> <stop id="stop6508" offset="0.0000000" style="stop-color:#ffffff;stop-opacity:0.0000000;"/> <stop id="stop6510" offset="1.0000000" style="stop-color:#ffffff;stop-opacity:0.87450981;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7498" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient id="linearGradient7464"> <stop id="stop7466" offset="0.0000000" style="stop-color:#00039a;stop-opacity:1.0000000;"/> <stop id="stop7468" offset="1.0000000" style="stop-color:#afa5ff;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7496" x1="23.213980" x2="23.201290" xlink:href="#linearGradient7464" y1="42.754631" y2="43.892632"/> <linearGradient id="linearGradient5756"> <stop id="stop5758" offset="0.0000000" style="stop-color:#828282;stop-opacity:1.0000000;"/> <stop id="stop5760" offset="1.0000000" style="stop-color:#929292;stop-opacity:0.35294119;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9321" x1="22.935030" x2="23.662106" xlink:href="#linearGradient5756" y1="42.699776" y2="43.892632"/> <linearGradient id="linearGradient5742"> <stop id="stop5744" offset="0.0000000" style="stop-color:#adadad;stop-opacity:1.0000000;"/> <stop id="stop5746" offset="1.0000000" style="stop-color:#f0f0f0;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7492" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9527" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9529" x1="22.935030" x2="23.662106" xlink:href="#linearGradient5756" y1="42.699776" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9531" x1="23.213980" x2="23.201290" xlink:href="#linearGradient7464" y1="42.754631" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9533" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> </defs> <g id="layer1"> <g id="g9447" style="overflow:visible" transform="matrix(31.25000,0.000000,0.000000,31.25000,-625.0232,-1325.000)"> <path d="M 24.000001,43.200001 C 24.000001,43.641601 23.641601,44.000001 23.200001,44.000001 C 22.758401,44.000001 22.400001,43.641601 22.400001,43.200001 C 22.400001,42.758401 22.758401,42.400001 23.200001,42.400001 C 23.641601,42.400001 24.000001,42.758401 24.000001,43.200001 z " id="path6596" style="fill:url(#linearGradient6644);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible" transform="translate(-2.399258,-1.000000e-6)"/> <path d="M 23.906358,43.296204 C 23.906358,43.625433 23.639158,43.892633 23.309929,43.892633 C 22.980700,43.892633 22.713500,43.625433 22.713500,43.296204 C 22.713500,42.966975 22.980700,42.699774 23.309929,42.699774 C 23.639158,42.699774 23.906358,42.966975 23.906358,43.296204 z " id="path6598" style="fill:url(#linearGradient6646);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible" transform="matrix(1.082474,0.000000,0.000000,1.082474,-4.431649,-3.667015)"/> <path d="M 23.906358,43.296204 C 23.906358,43.625433 23.639158,43.892633 23.309929,43.892633 C 22.980700,43.892633 22.713500,43.625433 22.713500,43.296204 C 22.713500,42.966975 22.980700,42.699774 23.309929,42.699774 C 23.639158,42.699774 23.906358,42.966975 23.906358,43.296204 z " id="path6600" style="fill:url(#linearGradient6648);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible" transform="matrix(0.969072,0.000000,0.000000,0.969072,-1.788256,1.242861)"/> <path d="M 23.906358,43.296204 C 23.906358,43.625433 23.639158,43.892633 23.309929,43.892633 C 22.980700,43.892633 22.713500,43.625433 22.713500,43.296204 C 22.713500,42.966975 22.980700,42.699774 23.309929,42.699774 C 23.639158,42.699774 23.906358,42.966975 23.906358,43.296204 z " id="path6602" style="fill:url(#linearGradient6650);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible" transform="matrix(0.773196,0.000000,0.000000,0.597938,2.776856,17.11876)"/> </g> </g> </svg> """, Round:""" <svg height="50.000000px" id="svg9493" width="100.00000px" xmlns="http://www.w3.org/2000/svg"> <defs id="defs9495"> <linearGradient gradientTransform="matrix(0.928127,0.000000,0.000000,0.639013,13.55634,12.87587)" gradientUnits="userSpaceOnUse" id="linearGradient13424" x1="21.593750" x2="21.593750" xlink:href="#linearGradient6506" y1="47.917328" y2="46.774261"/> <linearGradient id="linearGradient6494"> <stop id="stop6496" offset="0.0000000" style="stop-color:%s;stop-opacity:1.0000000;"/> <stop id="stop6498" offset="1.0000000" style="stop-color:%s;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientTransform="translate(12.00000,-4.000002)" gradientUnits="userSpaceOnUse" id="linearGradient13427" x1="21.591305" x2="21.593750" xlink:href="#linearGradient6494" y1="46.617390" y2="47.781250"/> <linearGradient gradientTransform="translate(12.00000,-4.000002)" gradientUnits="userSpaceOnUse" id="linearGradient13430" x1="21.408695" x2="21.834784" xlink:href="#linearGradient5756" y1="46.556522" y2="47.843750"/> <linearGradient gradientTransform="translate(12.00000,-4.000002)" gradientUnits="userSpaceOnUse" id="linearGradient13433" x1="21.594427" x2="21.600000" xlink:href="#linearGradient5742" y1="46.376728" y2="48.000000"/> <linearGradient gradientTransform="matrix(0.928127,0.000000,0.000000,0.639013,21.55634,15.27587)" gradientUnits="userSpaceOnUse" id="linearGradient13472" x1="21.593750" x2="21.593750" xlink:href="#linearGradient6506" y1="47.917328" y2="46.774261"/> <linearGradient gradientTransform="translate(20.00000,-1.600002)" gradientUnits="userSpaceOnUse" id="linearGradient13475" x1="21.591305" x2="21.593750" xlink:href="#linearGradient9163" y1="46.617390" y2="47.781250"/> <linearGradient gradientTransform="translate(20.00000,-1.600002)" gradientUnits="userSpaceOnUse" id="linearGradient13478" x1="21.408695" x2="21.834784" xlink:href="#linearGradient5756" y1="46.556522" y2="47.843750"/> <linearGradient gradientTransform="translate(20.00000,-1.600002)" gradientUnits="userSpaceOnUse" id="linearGradient13481" x1="21.594427" x2="21.600000" xlink:href="#linearGradient5742" y1="46.376728" y2="48.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9199" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient id="linearGradient9163"> <stop id="stop9165" offset="0.0000000" style="stop-color:#000000;stop-opacity:1.0000000;"/> <stop id="stop9167" offset="1.0000000" style="stop-color:#8c8c8c;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9197" x1="23.213980" x2="23.201290" xlink:href="#linearGradient9163" y1="42.754631" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9195" x1="23.349695" x2="23.440580" xlink:href="#linearGradient5756" y1="42.767944" y2="43.710873"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9193" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient id="linearGradient6506"> <stop id="stop6508" offset="0.0000000" style="stop-color:#ffffff;stop-opacity:0.0000000;"/> <stop id="stop6510" offset="1.0000000" style="stop-color:#ffffff;stop-opacity:0.87450981;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7498" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient id="linearGradient7464"> <stop id="stop7466" offset="0.0000000" style="stop-color:#00039a;stop-opacity:1.0000000;"/> <stop id="stop7468" offset="1.0000000" style="stop-color:#afa5ff;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7496" x1="23.213980" x2="23.201290" xlink:href="#linearGradient7464" y1="42.754631" y2="43.892632"/> <linearGradient id="linearGradient5756"> <stop id="stop5758" offset="0.0000000" style="stop-color:#828282;stop-opacity:1.0000000;"/> <stop id="stop5760" offset="1.0000000" style="stop-color:#929292;stop-opacity:0.35294119;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9321" x1="22.935030" x2="23.662106" xlink:href="#linearGradient5756" y1="42.699776" y2="43.892632"/> <linearGradient id="linearGradient5742"> <stop id="stop5744" offset="0.0000000" style="stop-color:#adadad;stop-opacity:1.0000000;"/> <stop id="stop5746" offset="1.0000000" style="stop-color:#f0f0f0;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7492" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9527" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9529" x1="22.935030" x2="23.662106" xlink:href="#linearGradient5756" y1="42.699776" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9531" x1="23.213980" x2="23.201290" xlink:href="#linearGradient7464" y1="42.754631" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9533" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient gradientTransform="matrix(24.16238,0.000000,0.000000,18.68556,-538.2464,-790.0391)" gradientUnits="userSpaceOnUse" id="linearGradient1336" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient gradientTransform="matrix(30.28350,0.000000,0.000000,30.28350,-680.9062,-1286.161)" gradientUnits="userSpaceOnUse" id="linearGradient1339" x1="23.213980" x2="23.201290" xlink:href="#linearGradient9163" y1="42.754631" y2="43.892632"/> <linearGradient gradientTransform="matrix(33.82731,0.000000,0.000000,33.82731,-763.5122,-1439.594)" gradientUnits="userSpaceOnUse" id="linearGradient1342" x1="23.349695" x2="23.440580" xlink:href="#linearGradient5756" y1="42.767944" y2="43.710873"/> <linearGradient gradientTransform="matrix(31.25000,0.000000,0.000000,31.25000,-700.0000,-1325.000)" gradientUnits="userSpaceOnUse" id="linearGradient1345" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> </defs> <g id="layer1"> <g id="g13543" style="overflow:visible" transform="matrix(31.25000,0.000000,0.000000,31.25000,-999.9999,-1325.000)"> <path d="M 32.799998,42.400000 L 34.399998,42.400000 C 34.843198,42.400000 35.199998,42.756800 35.199998,43.200000 C 35.199998,43.643200 34.843198,44.000000 34.399998,44.000000 L 32.799998,44.000000 C 32.356798,44.000000 31.999998,43.643200 31.999998,43.200000 C 31.999998,42.756800 32.356798,42.400000 32.799998,42.400000 z " id="path13335" style="fill:url(#linearGradient13433);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"/> <path d="M 32.812498,42.562498 C 32.447387,42.562498 32.156248,42.829606 32.156248,43.187498 C 32.156248,43.545390 32.454607,43.843750 32.812498,43.843748 L 34.406248,43.843748 C 34.764141,43.843748 35.031248,43.552611 35.031248,43.187498 C 35.031248,42.822387 34.771358,42.562498 34.406248,42.562498 L 32.812498,42.562498 z " id="path13337" style="fill:url(#linearGradient13430);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible"/> <path d="M 32.812498,42.624998 C 32.485887,42.624998 32.218748,42.871665 32.218748,43.187498 C 32.218748,43.503332 32.496667,43.781249 32.812498,43.781248 L 34.406248,43.781248 C 34.722082,43.781248 34.968748,43.514111 34.968748,43.187498 C 34.968748,42.860887 34.732858,42.624998 34.406248,42.624998 L 32.812498,42.624998 z " id="path13339" style="fill:url(#linearGradient13427);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible"/> <path d="M 32.872983,42.669849 C 32.569847,42.669849 32.321908,42.827473 32.321908,43.029294 C 32.321908,43.231116 32.579852,43.408709 32.872983,43.408708 L 34.352185,43.408708 C 34.645320,43.408708 34.874257,43.238004 34.874257,43.029294 C 34.874257,42.820585 34.655321,42.669849 34.352185,42.669849 L 32.872983,42.669849 z " id="path13341" style="fill:url(#linearGradient13424);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible"/> </g> </g> </svg> """, Square:""" <svg height="50.000000px" id="svg9493" width="50.000000px" xmlns="http://www.w3.org/2000/svg"> <defs id="defs9495"> <linearGradient gradientTransform="matrix(0.388435,0.000000,0.000000,0.618097,2.806900,2.626330)" gradientUnits="userSpaceOnUse" id="linearGradient31681" x1="21.593750" x2="21.593750" xlink:href="#linearGradient6506" y1="47.917328" y2="46.774261"/> <linearGradient id="linearGradient6494"> <stop id="stop6496" offset="0.0000000" style="stop-color:%s;stop-opacity:1.0000000;"/> <stop id="stop6498" offset="1.0000000" style="stop-color:%s;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient31704" x1="18.390625" x2="18.390625" xlink:href="#linearGradient6494" y1="43.400002" y2="44.593750"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient31624" x1="17.728125" x2="19.031250" xlink:href="#linearGradient5756" y1="43.337502" y2="44.656250"/> <linearGradient gradientTransform="matrix(0.500000,0.000000,0.000000,1.000000,-3.600000,-8.800000)" gradientUnits="userSpaceOnUse" id="linearGradient31686" x1="29.600000" x2="29.600000" xlink:href="#linearGradient5742" y1="39.991302" y2="41.599998"/> <linearGradient gradientTransform="matrix(0.388435,0.000000,0.000000,0.618097,7.606900,5.026330)" gradientUnits="userSpaceOnUse" id="linearGradient31649" x1="21.593750" x2="21.593750" xlink:href="#linearGradient6506" y1="47.917328" y2="46.774261"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient31710" x1="18.390625" x2="18.390625" xlink:href="#linearGradient9163" y1="43.400002" y2="44.593750"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient31570" x1="17.728125" x2="19.031250" xlink:href="#linearGradient5756" y1="43.337502" y2="44.656250"/> <linearGradient gradientTransform="matrix(0.500000,0.000000,0.000000,1.000000,1.200000,-6.400000)" gradientUnits="userSpaceOnUse" id="linearGradient31654" x1="29.600000" x2="29.600000" xlink:href="#linearGradient5742" y1="39.991302" y2="41.599998"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9199" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient id="linearGradient9163"> <stop id="stop9165" offset="0.0000000" style="stop-color:#000000;stop-opacity:1.0000000;"/> <stop id="stop9167" offset="1.0000000" style="stop-color:#8c8c8c;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9197" x1="23.213980" x2="23.201290" xlink:href="#linearGradient9163" y1="42.754631" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9195" x1="23.349695" x2="23.440580" xlink:href="#linearGradient5756" y1="42.767944" y2="43.710873"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9193" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient id="linearGradient6506"> <stop id="stop6508" offset="0.0000000" style="stop-color:#ffffff;stop-opacity:0.0000000;"/> <stop id="stop6510" offset="1.0000000" style="stop-color:#ffffff;stop-opacity:0.87450981;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7498" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient id="linearGradient7464"> <stop id="stop7466" offset="0.0000000" style="stop-color:#00039a;stop-opacity:1.0000000;"/> <stop id="stop7468" offset="1.0000000" style="stop-color:#afa5ff;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7496" x1="23.213980" x2="23.201290" xlink:href="#linearGradient7464" y1="42.754631" y2="43.892632"/> <linearGradient id="linearGradient5756"> <stop id="stop5758" offset="0.0000000" style="stop-color:#828282;stop-opacity:1.0000000;"/> <stop id="stop5760" offset="1.0000000" style="stop-color:#929292;stop-opacity:0.35294119;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9321" x1="22.935030" x2="23.662106" xlink:href="#linearGradient5756" y1="42.699776" y2="43.892632"/> <linearGradient id="linearGradient5742"> <stop id="stop5744" offset="0.0000000" style="stop-color:#adadad;stop-opacity:1.0000000;"/> <stop id="stop5746" offset="1.0000000" style="stop-color:#f0f0f0;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7492" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9527" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9529" x1="22.935030" x2="23.662106" xlink:href="#linearGradient5756" y1="42.699776" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9531" x1="23.213980" x2="23.201290" xlink:href="#linearGradient7464" y1="42.754631" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9533" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient gradientTransform="matrix(24.16238,0.000000,0.000000,18.68556,-538.2464,-790.0391)" gradientUnits="userSpaceOnUse" id="linearGradient1336" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient gradientTransform="matrix(30.28350,0.000000,0.000000,30.28350,-680.9062,-1286.161)" gradientUnits="userSpaceOnUse" id="linearGradient1339" x1="23.213980" x2="23.201290" xlink:href="#linearGradient9163" y1="42.754631" y2="43.892632"/> <linearGradient gradientTransform="matrix(33.82731,0.000000,0.000000,33.82731,-763.5122,-1439.594)" gradientUnits="userSpaceOnUse" id="linearGradient1342" x1="23.349695" x2="23.440580" xlink:href="#linearGradient5756" y1="42.767944" y2="43.710873"/> <linearGradient gradientTransform="matrix(31.25000,0.000000,0.000000,31.25000,-700.0000,-1325.000)" gradientUnits="userSpaceOnUse" id="linearGradient1345" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> </defs> <g id="layer1"> <g id="g31718" style="overflow:visible" transform="matrix(31.25000,0.000000,0.000000,31.25000,-325.0000,-975.0000)"> <path d="M 10.400000,31.200000 L 12.000000,31.200000 L 12.000000,32.800000 L 10.400000,32.800000 L 10.400000,31.200000 z " id="path31614" style="fill:url(#linearGradient31686);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"/> <path d="M 17.750000,43.343750 L 17.750000,44.656250 L 19.031250,44.656250 L 19.031250,43.343750 L 17.750000,43.343750 z " id="path31616" style="fill:url(#linearGradient31624);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible" transform="translate(-7.190625,-12.00000)"/> <path d="M 17.812500,43.406250 L 17.812500,44.593750 L 18.968750,44.593750 L 18.968750,43.406250 L 17.812500,43.406250 z " id="path31618" style="fill:url(#linearGradient31704);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible" transform="translate(-7.190625,-12.00000)"/> <path d="M 10.891195,31.445120 C 10.630356,31.445967 10.660563,31.393294 10.660563,31.792800 C 10.660563,31.988016 10.768517,32.159796 10.891195,32.159795 L 11.510263,32.159795 C 11.632945,32.159795 11.728757,31.994678 11.728757,31.792800 C 11.728757,31.389990 11.754584,31.441761 11.510263,31.445120 L 10.891195,31.445120 z " id="path31620" sodipodi:nodetypes="csccscc" style="fill:url(#linearGradient31681);fill-opacity:1.0000000;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible"/> </g> </g> </svg> """, Triangle:""" <svg height="50.000000px" id="svg9493" width="50.000000px" xmlns="http://www.w3.org/2000/svg" > <defs id="defs9495"> <linearGradient gradientTransform="matrix(0.389994,0.000000,0.000000,0.403942,4.557010,29.83582)" gradientUnits="userSpaceOnUse" id="linearGradient28861" x1="23.187498" x2="23.187498" xlink:href="#linearGradient6506" y1="28.449617" y2="26.670279"/> <linearGradient id="linearGradient6494"> <stop id="stop6496" offset="0.0000000" style="stop-color:%s;stop-opacity:1.0000000;"/> <stop id="stop6498" offset="1.0000000" style="stop-color:%s;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientTransform="translate(-9.587500,13.60000)" gradientUnits="userSpaceOnUse" id="linearGradient28864" x1="23.181250" x2="23.187500" xlink:href="#linearGradient6494" y1="26.793751" y2="27.843750"/> <linearGradient gradientTransform="translate(-9.587500,13.60000)" gradientUnits="userSpaceOnUse" id="linearGradient28867" x1="22.762501" x2="23.812500" xlink:href="#linearGradient5756" y1="26.687500" y2="27.906250"/> <linearGradient gradientTransform="translate(-9.600000,13.60000)" gradientUnits="userSpaceOnUse" id="linearGradient28870" x1="23.187500" x2="23.200001" xlink:href="#linearGradient5742" y1="26.400000" y2="28.000000"/> <linearGradient gradientTransform="matrix(0.389994,0.000000,0.000000,0.403942,9.357010,32.23582)" gradientUnits="userSpaceOnUse" id="linearGradient28801" x1="23.187498" x2="23.187498" xlink:href="#linearGradient6506" y1="28.449617" y2="26.670279"/> <linearGradient gradientTransform="translate(-4.787500,16.00000)" gradientUnits="userSpaceOnUse" id="linearGradient28804" x1="23.181250" x2="23.187500" xlink:href="#linearGradient9163" y1="26.793751" y2="27.843750"/> <linearGradient gradientTransform="translate(-4.787500,16.00000)" gradientUnits="userSpaceOnUse" id="linearGradient28807" x1="22.762501" x2="23.812500" xlink:href="#linearGradient5756" y1="26.687500" y2="27.906250"/> <linearGradient gradientTransform="translate(-4.800000,16.00000)" gradientUnits="userSpaceOnUse" id="linearGradient28810" x1="23.187500" x2="23.200001" xlink:href="#linearGradient5742" y1="26.400000" y2="28.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9199" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient id="linearGradient9163"> <stop id="stop9165" offset="0.0000000" style="stop-color:#000000;stop-opacity:1.0000000;"/> <stop id="stop9167" offset="1.0000000" style="stop-color:#8c8c8c;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9197" x1="23.213980" x2="23.201290" xlink:href="#linearGradient9163" y1="42.754631" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9195" x1="23.349695" x2="23.440580" xlink:href="#linearGradient5756" y1="42.767944" y2="43.710873"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9193" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient id="linearGradient6506"> <stop id="stop6508" offset="0.0000000" style="stop-color:#ffffff;stop-opacity:0.0000000;"/> <stop id="stop6510" offset="1.0000000" style="stop-color:#ffffff;stop-opacity:0.87450981;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7498" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient id="linearGradient7464"> <stop id="stop7466" offset="0.0000000" style="stop-color:#00039a;stop-opacity:1.0000000;"/> <stop id="stop7468" offset="1.0000000" style="stop-color:#afa5ff;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7496" x1="23.213980" x2="23.201290" xlink:href="#linearGradient7464" y1="42.754631" y2="43.892632"/> <linearGradient id="linearGradient5756"> <stop id="stop5758" offset="0.0000000" style="stop-color:#828282;stop-opacity:1.0000000;"/> <stop id="stop5760" offset="1.0000000" style="stop-color:#929292;stop-opacity:0.35294119;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9321" x1="22.935030" x2="23.662106" xlink:href="#linearGradient5756" y1="42.699776" y2="43.892632"/> <linearGradient id="linearGradient5742"> <stop id="stop5744" offset="0.0000000" style="stop-color:#adadad;stop-opacity:1.0000000;"/> <stop id="stop5746" offset="1.0000000" style="stop-color:#f0f0f0;stop-opacity:1.0000000;"/> </linearGradient> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7492" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9527" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9529" x1="22.935030" x2="23.662106" xlink:href="#linearGradient5756" y1="42.699776" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9531" x1="23.213980" x2="23.201290" xlink:href="#linearGradient7464" y1="42.754631" y2="43.892632"/> <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9533" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient gradientTransform="matrix(24.16238,0.000000,0.000000,18.68556,-538.2464,-790.0391)" gradientUnits="userSpaceOnUse" id="linearGradient1336" x1="23.402565" x2="23.389874" xlink:href="#linearGradient6506" y1="44.066776" y2="42.883698"/> <linearGradient gradientTransform="matrix(30.28350,0.000000,0.000000,30.28350,-680.9062,-1286.161)" gradientUnits="userSpaceOnUse" id="linearGradient1339" x1="23.213980" x2="23.201290" xlink:href="#linearGradient9163" y1="42.754631" y2="43.892632"/> <linearGradient gradientTransform="matrix(33.82731,0.000000,0.000000,33.82731,-763.5122,-1439.594)" gradientUnits="userSpaceOnUse" id="linearGradient1342" x1="23.349695" x2="23.440580" xlink:href="#linearGradient5756" y1="42.767944" y2="43.710873"/> <linearGradient gradientTransform="matrix(31.25000,0.000000,0.000000,31.25000,-700.0000,-1325.000)" gradientUnits="userSpaceOnUse" id="linearGradient1345" x1="23.193102" x2="23.200001" xlink:href="#linearGradient5742" y1="42.429230" y2="44.000000"/> </defs> <g id="layer1"> <g id="g28884" style="overflow:visible" transform="matrix(31.25000,0.000000,0.000000,31.25000,-400.0000,-1250.000)"> <path d="M 14.400000,41.600000 L 12.800000,41.600000 L 13.600000,40.000000 L 14.400000,41.600000 z " id="path28664" sodipodi:nodetypes="cccc" style="fill:url(#linearGradient28870);fill-opacity:1.0000000;stroke:none;stroke-width:0.064000003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"/> <path d="M 13.600000,40.256250 L 12.975000,41.506250 L 14.225000,41.506250 L 13.600000,40.256250 z " id="path28666" style="fill:url(#linearGradient28867);fill-opacity:1.0000000;stroke:none;stroke-width:0.064000003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible"/> <path d="M 13.600000,40.381250 L 13.068750,41.443750 L 14.131250,41.443750 L 13.600000,40.381250 z " id="path28668" style="fill:url(#linearGradient28864);fill-opacity:1.0000000;stroke:none;stroke-width:0.064000003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible"/> <path d="M 13.575621,40.552906 C 13.555816,40.559679 13.538695,40.572979 13.526872,40.590776 C 13.522451,40.594595 13.518372,40.598819 13.514685,40.603399 L 13.307500,41.032587 C 13.299161,41.047990 13.294953,41.065424 13.295313,41.083080 C 13.296850,41.096430 13.300996,41.109315 13.307500,41.120950 C 13.310377,41.129925 13.314481,41.138427 13.319688,41.146196 C 13.323375,41.150775 13.327454,41.155000 13.331875,41.158819 C 13.339376,41.164212 13.347584,41.168462 13.356250,41.171442 C 13.367483,41.178179 13.379923,41.182474 13.392812,41.184066 L 13.807180,41.184066 C 13.835802,41.183428 13.862639,41.169530 13.880304,41.146196 C 13.884725,41.142377 13.888804,41.138152 13.892491,41.133573 C 13.898995,41.121938 13.903142,41.109053 13.904679,41.095703 C 13.905039,41.078047 13.900831,41.060614 13.892491,41.045211 C 13.892751,41.041007 13.892751,41.036791 13.892491,41.032587 L 13.685307,40.603399 C 13.681620,40.598819 13.677541,40.594595 13.673120,40.590776 C 13.650701,40.559305 13.612491,40.544463 13.575621,40.552906 z " id="path28670" style="fill:url(#linearGradient28861);fill-opacity:1.0000000;stroke:none;stroke-width:0.064000003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000;overflow:visible"/> </g> </g> </svg> """ } colours={Red : (0xCF, 0x00, 0x00), Green : (0x0f, 0x69, 0x00), Yellow : (0xd2, 0xcd, 0x00), Grey : (0x5a, 0x5a, 0x5a), Orange : (0xda, 0x46, 0x15), Purple : (0x87, 0x00, 0x83), Blue : (0x00, 0x03, 0x9a)} clicked=pyqtSignal() def __init__(self, parent=None, **kwargs): self.m_value=False self.m_onColour=QLed.Red self.m_offColour=QLed.Grey self.m_shape=QLed.Circle QWidget.__init__(self, parent, **kwargs) self._pressed=False self.renderer=QSvgRenderer() def value(self): return self.m_value def setValue(self, value): self.m_value=value self.update() value=pyqtProperty(bool, value, setValue) def onColour(self): return self.m_onColour def setOnColour(self, newColour): self.m_onColour=newColour self.update() onColour=pyqtProperty(int, onColour, setOnColour) def offColour(self): return self.m_offColour def setOffColour(self, newColour): self.m_offColour=newColour self.update() offColour=pyqtProperty(int, offColour, setOffColour) def shape(self): return self.m_shape def setShape(self, newShape): self.m_shape=newShape self.update() shape=pyqtProperty(int, shape, setShape) def sizeHint(self): if self.m_shape==QLed.Triangle: return QSize(64,48) elif self.m_shape==QLed.Round: return QSize(96, 48) return QSize(48,48) def adjust(self, r, g, b): def normalise(x): return x/255.0 def denormalise(x): return int(x*255.0) (h,l,s)=rgb_to_hls(normalise(r),normalise(g),normalise(b)) (nr,ng,nb)=hls_to_rgb(h,l*1.5,s) return (denormalise(nr),denormalise(ng),denormalise(nb)) def paintEvent(self, event): option=QStyleOption() option.initFrom(self) h=option.rect.height() w=option.rect.width() if self.m_shape in (QLed.Triangle, QLed.Round): aspect=(4/3.0) if self.m_shape==QLed.Triangle else 2.0 ah=w/aspect aw=w if ah>h: ah=h aw=h*aspect x=abs(aw-w)/2.0 y=abs(ah-h)/2.0 bounds=QRectF(x,y,aw,ah) else: size=min(w,h) x=abs(size-w)/2.0 y=abs(size-h)/2.0 bounds=QRectF(x,y,size,size) painter=QPainter(self); painter.setRenderHint(QPainter.Antialiasing, True); (dark_r,dark_g,dark_b)=self.colours[self.m_onColour if self.m_value else self.m_offColour] dark_str="rgb(%d,%d,%d)" % (dark_r,dark_g,dark_b) light_str="rgb(%d,%d,%d)" % self.adjust(dark_r,dark_g,dark_b) self.renderer.load(QByteArray(self.shapes[self.m_shape] % (dark_str,light_str))) self.renderer.render(painter, bounds) def mousePressEvent(self, event): self._pressed=True QWidget.mousePressEvent(self, event) def mouseReleaseEvent(self, event): if self._pressed: self._pressed=False self.clicked.emit() QWidget.mouseReleaseEvent(self, event) def toggleValue(self): self.m_value=not self.m_value; self.update()
class OWWidget(QDialog, Report, metaclass=WidgetMetaClass): """Base widget class""" # Global widget count widget_id = 0 # Widget Meta Description # ----------------------- #: Widget name (:class:`str`) as presented in the Canvas name = None id = None category = None version = None #: Short widget description (:class:`str` optional), displayed in #: canvas help tooltips. description = None #: A longer widget description (:class:`str` optional) long_description = None #: Widget icon path relative to the defining module icon = "icons/Unknown.png" #: Widget priority used for sorting within a category #: (default ``sys.maxsize``). priority = sys.maxsize help = None help_ref = None url = None keywords = [] background = None replaces = None #: A list of published input definitions inputs = [] #: A list of published output definitions outputs = [] # Default widget GUI layout settings # ---------------------------------- #: Should the widget have basic layout #: (If this flag is false then the `want_main_area` and #: `want_control_area` are ignored). want_basic_layout = True #: Should the widget construct a `mainArea` (this is a resizable #: area to the right of the `controlArea`). want_main_area = True #: Should the widget construct a `controlArea`. want_control_area = True #: Orientation of the buttonsArea box; valid only if # `want_control_area` is `True`. Possible values are Qt.Horizontal, # Qt.Vertical and None for no buttons area buttons_area_orientation = Qt.Horizontal #: Widget painted by `Save graph" button graph_name = None graph_writers = FileFormat.img_writers save_position = True #: If false the widget will receive fixed size constraint #: (derived from it's layout). Use for widgets which have simple #: static size contents. resizing_enabled = True widgetStateChanged = Signal(str, int, str) blockingStateChanged = Signal(bool) progressBarValueChanged = Signal(float) processingStateChanged = Signal(int) settingsHandler = None """:type: SettingsHandler""" savedWidgetGeometry = settings.Setting(None) #: A list of advice messages (:class:`Message`) to display to the user. #: When a widget is first shown a message from this list is selected #: for display. If a user accepts (clicks 'Ok. Got it') the choice is #: recorded and the message is never shown again (closing the message #: will not mark it as seen). Messages can be displayed again by pressing #: Shift + F1 #: #: :type: list of :class:`Message` UserAdviceMessages = [] def __new__(cls, *args, **kwargs): self = super().__new__(cls, None, cls.get_flags()) QDialog.__init__(self, None, self.get_flags()) stored_settings = kwargs.get('stored_settings', None) if self.settingsHandler: self.settingsHandler.initialize(self, stored_settings) self.signalManager = kwargs.get('signal_manager', None) self.__env = _asmappingproxy(kwargs.get("env", {})) setattr(self, gui.CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self)) self.graphButton = None self.report_button = None OWWidget.widget_id += 1 self.widget_id = OWWidget.widget_id if self.name: self.setCaption(self.name) self.setFocusPolicy(Qt.StrongFocus) self.startTime = time.time() # used in progressbar self.widgetState = {"Info": {}, "Warning": {}, "Error": {}} self.__blocking = False # flag indicating if the widget's position was already restored self.__was_restored = False self.__progressBarValue = -1 self.__progressState = 0 self.__statusMessage = "" self.__msgwidget = None self.__msgchoice = 0 self.left_side = None self.controlArea = self.mainArea = self.buttonsArea = None self.splitter = None self.warning_bar = self.warning_label = self.warning_icon = None if self.want_basic_layout: self.set_basic_layout() sc = QShortcut(QKeySequence(Qt.ShiftModifier | Qt.Key_F1), self) sc.activated.connect(self.__quicktip) return self def __init__(self, *args, **kwargs): """QDialog __init__ was already called in __new__, please do not call it here.""" @classmethod def get_flags(cls): return (Qt.Window if cls.resizing_enabled else Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) class _Splitter(QSplitter): def createHandle(self): """Create splitter handle""" return self._Handle(self.orientation(), self, cursor=Qt.PointingHandCursor) class _Handle(QSplitterHandle): def mouseReleaseEvent(self, event): """Resize on left button""" if event.button() == Qt.LeftButton: splitter = self.splitter() splitter.setSizes([int(splitter.sizes()[0] == 0), 1000]) super().mouseReleaseEvent(event) def mouseMoveEvent(self, event): """Prevent moving; just show/hide""" return def _insert_splitter(self): self.splitter = self._Splitter(Qt.Horizontal, self) self.layout().addWidget(self.splitter) def _insert_warning_bar(self): self.warning_bar = gui.hBox(self, spacing=0) self.warning_icon = gui.widgetLabel(self.warning_bar, "") self.warning_label = gui.widgetLabel(self.warning_bar, "") self.warning_label.setStyleSheet("padding-top: 5px") self.warning_bar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum) gui.rubber(self.warning_bar) self.warning_bar.setVisible(False) def _insert_control_area(self): self.left_side = gui.vBox(self.splitter, spacing=0) self.splitter.setSizes([1]) # Smallest size allowed by policy if self.buttons_area_orientation is not None: self.controlArea = gui.vBox(self.left_side, addSpace=0) self._insert_buttons_area() else: self.controlArea = self.left_side if self.want_main_area: self.controlArea.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) m = 0 else: m = 4 self.controlArea.layout().setContentsMargins(m, m, m, m) def _insert_buttons_area(self): self.buttonsArea = gui.widgetBox( self.left_side, addSpace=0, spacing=9, orientation=self.buttons_area_orientation) if self.graphButton is not None: self.buttonsArea.layout().addWidget(self.graphButton) if self.report_button is not None: self.buttonsArea.layout().addWidget(self.report_button) def _insert_main_area(self): self.mainArea = gui.vBox(self.splitter, margin=4, sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding)) self.splitter.setCollapsible(1, False) self.mainArea.layout().setContentsMargins( 0 if self.want_control_area else 4, 4, 4, 4) def _create_default_buttons(self): # These buttons are inserted in buttons_area, if it exists # Otherwise it is up to the widget to add them to some layout if self.graph_name is not None: self.graphButton = QPushButton("&Save Image", autoDefault=False) self.graphButton.clicked.connect(self.save_graph) if hasattr(self, "send_report"): self.report_button = QPushButton("&Report", autoDefault=False) self.report_button.clicked.connect(self.show_report) def set_basic_layout(self): """Provide the basic widget layout Which parts are created is regulated by class attributes `want_main_area`, `want_control_area` and `buttons_area_orientation`, the presence of method `send_report` and attribute `graph_name`. """ self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(2, 2, 2, 2) if not self.resizing_enabled: self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) self.want_main_area = self.want_main_area or self.graph_name self._create_default_buttons() self._insert_warning_bar() self._insert_splitter() if self.want_control_area: self._insert_control_area() if self.want_main_area: self._insert_main_area() def save_graph(self): """Save the graph with the name given in class attribute `graph_name`. The method is called by the *Save graph* button, which is created automatically if the `graph_name` is defined. """ graph_obj = getdeepattr(self, self.graph_name, None) if graph_obj is None: return saveplot.save_plot(graph_obj, self.graph_writers) def __restoreWidgetGeometry(self): def _fullscreen_to_maximized(geometry): """Don't restore windows into full screen mode because it loses decorations and can't be de-fullscreened at least on some platforms. Use Maximized state insted.""" w = QWidget(visible=False) w.restoreGeometry(QByteArray(geometry)) if w.isFullScreen(): w.setWindowState(w.windowState() & ~Qt.WindowFullScreen | Qt.WindowMaximized) return w.saveGeometry() restored = False if self.save_position: geometry = self.savedWidgetGeometry if geometry is not None: geometry = _fullscreen_to_maximized(geometry) restored = self.restoreGeometry(geometry) if restored and not self.windowState() & \ (Qt.WindowMaximized | Qt.WindowFullScreen): space = qApp.desktop().availableGeometry(self) frame, geometry = self.frameGeometry(), self.geometry() #Fix the widget size to fit inside the available space width = space.width() - (frame.width() - geometry.width()) width = min(width, geometry.width()) height = space.height() - (frame.height() - geometry.height()) height = min(height, geometry.height()) self.resize(width, height) # Move the widget to the center of available space if it is # currently outside it if not space.contains(self.frameGeometry()): x = max(0, space.width() / 2 - width / 2) y = max(0, space.height() / 2 - height / 2) self.move(x, y) return restored def __updateSavedGeometry(self): if self.__was_restored and self.isVisible(): # Update the saved geometry only between explicit show/hide # events (i.e. changes initiated by the user not by Qt's default # window management). self.savedWidgetGeometry = self.saveGeometry() # when widget is resized, save the new width and height def resizeEvent(self, event): """Overloaded to save the geometry (width and height) when the widget is resized. """ QDialog.resizeEvent(self, event) # Don't store geometry if the widget is not visible # (the widget receives a resizeEvent (with the default sizeHint) # before first showEvent and we must not overwrite the the # savedGeometry with it) if self.save_position and self.isVisible(): self.__updateSavedGeometry() def moveEvent(self, event): """Overloaded to save the geometry when the widget is moved """ QDialog.moveEvent(self, event) if self.save_position and self.isVisible(): self.__updateSavedGeometry() def hideEvent(self, event): """Overloaded to save the geometry when the widget is hidden """ if self.save_position: self.__updateSavedGeometry() QDialog.hideEvent(self, event) def closeEvent(self, event): """Overloaded to save the geometry when the widget is closed """ if self.save_position and self.isVisible(): self.__updateSavedGeometry() QDialog.closeEvent(self, event) def showEvent(self, event): """Overloaded to restore the geometry when the widget is shown """ QDialog.showEvent(self, event) if self.save_position and not self.__was_restored: # Restore saved geometry on show self.__restoreWidgetGeometry() self.__was_restored = True self.__quicktipOnce() def wheelEvent(self, event): """Silently accept the wheel event. This is to ensure combo boxes and other controls that have focus don't receive this event unless the cursor is over them. """ event.accept() def setCaption(self, caption): # we have to save caption title in case progressbar will change it self.captionTitle = str(caption) self.setWindowTitle(caption) def reshow(self): """Put the widget on top of all windows """ self.show() self.raise_() self.activateWindow() def send(self, signalName, value, id=None): """ Send a `value` on the `signalName` widget output. An output with `signalName` must be defined in the class ``outputs`` list. """ if not any(s.name == signalName for s in self.outputs): raise ValueError( '{} is not a valid output signal for widget {}'.format( signalName, self.name)) if self.signalManager is not None: self.signalManager.send(self, signalName, value, id) def __setattr__(self, name, value): """Set value to members of this instance or any of its members. If member is used in a gui control, notify the control about the change. name: name of the member, dot is used for nesting ("graph.point.size"). value: value to set to the member. """ names = name.rsplit(".") field_name = names.pop() obj = reduce(lambda o, n: getattr(o, n, None), names, self) if obj is None: raise AttributeError("Cannot set '{}' to {} ".format(name, value)) if obj is self: super().__setattr__(field_name, value) else: setattr(obj, field_name, value) notify_changed(obj, field_name, value) if self.settingsHandler: self.settingsHandler.fast_save(self, name, value) def openContext(self, *a): """Open a new context corresponding to the given data. The settings handler first checks the stored context for a suitable match. If one is found, it becomes the current contexts and the widgets settings are initialized accordingly. If no suitable context exists, a new context is created and data is copied from the widget's settings into the new context. Widgets that have context settings must call this method after reinitializing the user interface (e.g. combo boxes) with the new data. The arguments given to this method are passed to the context handler. Their type depends upon the handler. For instance, `DomainContextHandler` expects `Orange.data.Table` or `Orange.data.Domain`. """ self.settingsHandler.open_context(self, *a) def closeContext(self): """Save the current settings and close the current context. Widgets that have context settings must call this method before reinitializing the user interface (e.g. combo boxes) with the new data. """ self.settingsHandler.close_context(self) def retrieveSpecificSettings(self): """ Retrieve data that is not registered as setting. This method is called by `Orange.widgets.settings.ContextHandler.settings_to_widget`. Widgets may define it to retrieve any data that is not stored in widget attributes. See :obj:`Orange.widgets.data.owcolor.OWColor` for an example. """ pass def storeSpecificSettings(self): """ Store data that is not registered as setting. This method is called by `Orange.widgets.settings.ContextHandler.settings_from_widget`. Widgets may define it to store any data that is not stored in widget attributes. See :obj:`Orange.widgets.data.owcolor.OWColor` for an example. """ pass def saveSettings(self): """ Writes widget instance's settings to class defaults. Usually called when the widget is deleted. """ self.settingsHandler.update_defaults(self) def onDeleteWidget(self): """ Invoked by the canvas to notify the widget it has been deleted from the workflow. If possible, subclasses should gracefully cancel any currently executing tasks. """ pass def handleNewSignals(self): """ Invoked by the workflow signal propagation manager after all signals handlers have been called. Reimplement this method in order to coalesce updates from multiple updated inputs. """ pass # ############################################ # PROGRESS BAR FUNCTIONS def progressBarInit(self, processEvents=QEventLoop.AllEvents): """ Initialize the widget's progress (i.e show and set progress to 0%). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") if self.__progressState != 1: self.__progressState = 1 self.processingStateChanged.emit(1) self.progressBarSet(0, processEvents) def progressBarSet(self, value, processEvents=QEventLoop.AllEvents): """ Set the current progress bar to `value`. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param float value: Progress value :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ old = self.__progressBarValue self.__progressBarValue = value if value > 0: if self.__progressState != 1: warnings.warn( "progressBarSet() called without a " "preceding progressBarInit()", stacklevel=2) self.__progressState = 1 self.processingStateChanged.emit(1) usedTime = max(1, time.time() - self.startTime) totalTime = 100.0 * usedTime / value remainingTime = max(0, int(totalTime - usedTime)) hrs = remainingTime // 3600 mins = (remainingTime % 3600) // 60 secs = remainingTime % 60 if hrs > 0: text = "{}:{:02}:{:02}".format(hrs, mins, secs) else: text = "{}:{}:{:02}".format(hrs, mins, secs) self.setWindowTitle( "{} ({:.2f}% complete, remaining time: {})".format( self.captionTitle, value, text)) else: self.setWindowTitle(self.captionTitle + " (0% complete)") if old != value: self.progressBarValueChanged.emit(value) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) def progressBarValue(self): """Return the state of the progress bar """ return self.__progressBarValue progressBarValue = pyqtProperty(float, fset=progressBarSet, fget=progressBarValue) processingState = pyqtProperty(int, fget=lambda self: self.__progressState) def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents): """ Advance the progress bar. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. Args: value (int): progress value processEvents (`QEventLoop.ProcessEventsFlags` or `None`): process events flag """ self.progressBarSet(self.progressBarValue + value, processEvents) def progressBarFinished(self, processEvents=QEventLoop.AllEvents): """ Stop the widget's progress (i.e hide the progress bar). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.setWindowTitle(self.captionTitle) if self.__progressState != 0: self.__progressState = 0 self.processingStateChanged.emit(0) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) @contextlib.contextmanager def progressBar(self, iterations=0): """ Context manager for progress bar. Using it ensures that the progress bar is removed at the end without needing the `finally` blocks. Usage: with self.progressBar(20) as progress: ... progress.advance() or with self.progressBar() as progress: ... progress.advance(0.15) or with self.progressBar(): ... self.progressBarSet(50) :param iterations: the number of iterations (optional) :type iterations: int """ progress_bar = gui.ProgressBar(self, iterations) yield progress_bar progress_bar.finish() # Let us not rely on garbage collector #: Widget's status message has changed. statusMessageChanged = Signal(str) def setStatusMessage(self, text): """ Set widget's status message. This is a short status string to be displayed inline next to the instantiated widget icon in the canvas. """ if self.__statusMessage != text: self.__statusMessage = text self.statusMessageChanged.emit(text) def statusMessage(self): """ Return the widget's status message. """ return self.__statusMessage def keyPressEvent(self, e): """Handle default key actions or pass the event to the inherited method """ if (int(e.modifiers()), e.key()) in OWWidget.defaultKeyActions: OWWidget.defaultKeyActions[int(e.modifiers()), e.key()](self) else: QDialog.keyPressEvent(self, e) def information(self, id=0, text=None): """ Set/clear a widget information message (for `id`). Args: id (int or list): The id of the message text (str): Text of the message. """ self._set_state("Info", id, text) def warning(self, id=0, text=""): """ Set/clear a widget warning message (for `id`). Args: id (int or list): The id of the message text (str): Text of the message. """ self._set_state("Warning", id, text) def error(self, id=0, text=""): """ Set/clear a widget error message (for `id`). Args: id (int or list): The id of the message text (str): Text of the message. """ self._set_state("Error", id, text) def _set_state(self, state_type, id, text): changed = 0 if isinstance(id, list): for val in id: if val in self.widgetState[state_type]: self.widgetState[state_type].pop(val) changed = 1 else: if isinstance(id, str): text = id id = 0 if not text: if id in self.widgetState[state_type]: self.widgetState[state_type].pop(id) changed = 1 else: self.widgetState[state_type][id] = text changed = 1 if changed: if isinstance(id, list): for i in id: self.widgetStateChanged.emit(state_type, i, "") else: self.widgetStateChanged.emit(state_type, id, text or "") tooltip_lines = [] highest_type = None for a_type in ("Error", "Warning", "Info"): msgs_for_ids = self.widgetState.get(a_type) if not msgs_for_ids: continue msgs_for_ids = list(msgs_for_ids.values()) if not msgs_for_ids: continue tooltip_lines += msgs_for_ids if highest_type is None: highest_type = a_type if highest_type is None: self._set_warning_bar(None) elif len(tooltip_lines) == 1: msg = tooltip_lines[0] if "\n" in msg: self._set_warning_bar(highest_type, msg[:msg.index("\n")] + " (...)", msg) else: self._set_warning_bar(highest_type, tooltip_lines[0], tooltip_lines[0]) else: self._set_warning_bar( highest_type, "{} problems during execution".format(len(tooltip_lines)), "\n".join(tooltip_lines)) return changed def _set_warning_bar(self, state_type, text=None, tooltip=None): colors = { "Error": ("#ffc6c6", "black", QStyle.SP_MessageBoxCritical), "Warning": ("#ffffc9", "black", QStyle.SP_MessageBoxWarning), "Info": ("#ceceff", "black", QStyle.SP_MessageBoxInformation) } current_height = self.height() if state_type is None: if not self.warning_bar.isHidden(): new_height = current_height - self.warning_bar.height() self.warning_bar.setVisible(False) self.resize(self.width(), new_height) return background, foreground, icon = colors[state_type] style = QApplication.instance().style() self.warning_icon.setPixmap(style.standardIcon(icon).pixmap(14, 14)) self.warning_bar.setStyleSheet( "background-color: {}; color: {};" "padding: 3px; padding-left: 6px; vertical-align: center".format( background, foreground)) self.warning_label.setText(text) self.warning_bar.setToolTip(tooltip) if self.warning_bar.isHidden(): self.warning_bar.setVisible(True) new_height = current_height + self.warning_bar.height() self.resize(self.width(), new_height) def widgetStateToHtml(self, info=True, warning=True, error=True): """Create HTML code with images and status messages describing the current widget state. """ iconpaths = { "Info": gui.resource_filename("icons/information.png"), "Warning": gui.resource_filename("icons/warning.png"), "Error": gui.resource_filename("icons/error.png") } items = [] for show, what in [(info, "Info"), (warning, "Warning"), (error, "Error")]: if show and self.widgetState[what]: items.append('<img src="%s" style="float: left;"> %s' % (iconpaths[what], "\n".join( self.widgetState[what].values()))) return "<br>".join(items) @classmethod def getWidgetStateIcons(cls): """Return a (potentially cached) dictionary with icons for info (key `Info`), warning (`Warning`) and error (`Error`) """ if not hasattr(cls, "_cached__widget_state_icons"): info = QPixmap(gui.resource_filename("icons/information.png")) warning = QPixmap(gui.resource_filename("icons/warning.png")) error = QPixmap(gui.resource_filename("icons/error.png")) cls._cached__widget_state_icons = \ {"Info": info, "Warning": warning, "Error": error} return cls._cached__widget_state_icons defaultKeyActions = {} if sys.platform == "darwin": defaultKeyActions = { (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(), (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible()) } def setBlocking(self, state=True): """ Set blocking flag for this widget. While this flag is set this widget and all its descendants will not receive any new signals from the workflow signal manager. This is useful for instance if the widget does it's work in a separate thread or schedules processing from the event queue. In this case it can set the blocking flag in it's processNewSignals method schedule the task and return immediately. After the task has completed the widget can clear the flag and send the updated outputs. .. note:: Failure to clear this flag will block dependent nodes forever. """ if self.__blocking != state: self.__blocking = state self.blockingStateChanged.emit(state) def isBlocking(self): """Is this widget blocking signal processing.""" return self.__blocking def resetSettings(self): """Reset the widget settings to default""" self.settingsHandler.reset_settings(self) def workflowEnv(self): """ Return (a view to) the workflow runtime environment. Returns ------- env : types.MappingProxyType """ return self.__env def workflowEnvChanged(self, key, value, oldvalue): """ A workflow environment variable `key` has changed to value. Called by the canvas framework to notify widget of a change in the workflow runtime environment. The default implementation does nothing. """ pass def __showMessage(self, message): if self.__msgwidget is not None: self.__msgwidget.hide() self.__msgwidget.deleteLater() self.__msgwidget = None if message is None: return buttons = MessageOverlayWidget.Ok | MessageOverlayWidget.Close if message.moreurl is not None: buttons |= MessageOverlayWidget.Help if message.icon is not None: icon = message.icon else: icon = Message.Information self.__msgwidget = MessageOverlayWidget(parent=self, text=message.text, icon=icon, wordWrap=True, standardButtons=buttons) btn = self.__msgwidget.button(MessageOverlayWidget.Ok) btn.setText("Ok, got it") self.__msgwidget.setStyleSheet(""" MessageOverlayWidget { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #666, stop:0.3 #6D6D6D, stop:1 #666) } MessageOverlayWidget QLabel#text-label { color: white; }""") if message.moreurl is not None: helpbutton = self.__msgwidget.button(MessageOverlayWidget.Help) helpbutton.setText("Learn more\N{HORIZONTAL ELLIPSIS}") self.__msgwidget.helpRequested.connect( lambda: QDesktopServices.openUrl(QUrl(message.moreurl))) self.__msgwidget.setWidget(self) self.__msgwidget.show() def __quicktip(self): messages = list(self.UserAdviceMessages) if messages: message = messages[self.__msgchoice % len(messages)] self.__msgchoice += 1 self.__showMessage(message) def __quicktipOnce(self): filename = os.path.join(settings.widget_settings_dir(), "user-session-state.ini") namespace = ( "user-message-history/{0.__module__}.{0.__qualname__}".format( type(self))) session_hist = QSettings(filename, QSettings.IniFormat) session_hist.beginGroup(namespace) messages = self.UserAdviceMessages def _ispending(msg): return not session_hist.value("{}/confirmed".format( msg.persistent_id), defaultValue=False, type=bool) messages = [msg for msg in messages if _ispending(msg)] if not messages: return message = messages[self.__msgchoice % len(messages)] self.__msgchoice += 1 self.__showMessage(message) def _userconfirmed(): session_hist = QSettings(filename, QSettings.IniFormat) session_hist.beginGroup(namespace) session_hist.setValue("{}/confirmed".format(message.persistent_id), True) session_hist.sync() self.__msgwidget.accepted.connect(_userconfirmed)
class GeoLocationWidget(QWidget): """GeoLocationWidget(QWidget) Provides a custom geographical location widget. """ __pyqtSignals__ = ("latitudeChanged(double)", "longitudeChanged(double)", "elevationChanged(double)") def __init__(self, parent=None): super().__init__(parent) latitudeLabel = QLabel(self.tr("Latitude:")) self.latitudeSpinBox = QDoubleSpinBox() self.latitudeSpinBox.setRange(-90.0, 90.0) self.latitudeSpinBox.setDecimals(5) self.latitudeSpinBox.setSuffix(" degrees") self.latitudeSpinBox.setToolTip( "How far north or sourth you are from the equator. Must be between -90 and 90." ) longitudeLabel = QLabel(self.tr("Longitude:")) self.longitudeSpinBox = QDoubleSpinBox() self.longitudeSpinBox.setRange(-180.0, 180.0) self.longitudeSpinBox.setDecimals(5) self.longitudeSpinBox.setSuffix(" degrees") self.longitudeSpinBox.setToolTip( "How far west or east you are from the meridian. Must be between -180 and 180." ) elevationLabel = QLabel(self.tr("Elevation")) self.elevationSpinBox = QDoubleSpinBox() self.elevationSpinBox.setRange(-418.0, 8850.0) self.elevationSpinBox.setDecimals(5) self.elevationSpinBox.setSuffix(" m") self.elevationSpinBox.setToolTip( "The distance from sea level in meters. Must be between -418 and 8850." ) self.connect(self.latitudeSpinBox, SIGNAL("valueChanged(double)"), self, SIGNAL("latitudeChanged(double)")) self.connect(self.longitudeSpinBox, SIGNAL("valueChanged(double)"), self, SIGNAL("longitudeChanged(double)")) self.connect(self.elevationSpinBox, SIGNAL("valueChanged(double)"), self, SIGNAL("elevationChanged(double)")) layout = QGridLayout(self) layout.addWidget(latitudeLabel, 0, 0) layout.addWidget(self.latitudeSpinBox, 1, 0) layout.addWidget(longitudeLabel, 0, 1) layout.addWidget(self.longitudeSpinBox, 1, 1) layout.addWidget(elevationLabel, 0, 2) layout.addWidget(self.elevationSpinBox, 1, 2) # The latitude property is implemented with the latitude() and setLatitude() # methods, and contains the latitude of the user. def latitude(self): return self.latitudeSpinBox.value() @pyqtSignature("setLatitude(double)") def setLatitude(self, latitude): if latitude != self.latitudeSpinBox.value(): self.latitudeSpinBox.setValue(latitude) self.emit(SIGNAL("latitudeChanged(double)"), latitude) latitude = pyqtProperty("double", latitude, setLatitude) # The longitude property is implemented with the longitude() and setlongitude() # methods, and contains the longitude of the user. def longitude(self): return self.longitudeSpinBox.value() @pyqtSignature("setLongitude(double)") def setLongitude(self, longitude): if longitude != self.longitudeSpinBox.value(): self.longitudeSpinBox.setValue(longitude) self.emit(SIGNAL("longitudeChanged(double)"), longitude) longitude = pyqtProperty("double", longitude, setLongitude) def elevation(self): return self.elevationSpinBox.value() @pyqtSignature("setElevation(double)") def setElevation(self, elevation): if elevation != self.elevationSpinBox.value(): self.elevationSpinBox.setValue(elevation) self.emit(SIGNAL("elevationChanged(double)"), elevation) elevation = pyqtProperty("double", elevation, setElevation)
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin): """ Shell base widget """ def __init__(self, parent, history_filename, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert isinstance(history_filename, (str, unicode)) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.connect(self.__flushtimer, SIGNAL('timeout()'), self.flush) # Give focus to widget self.setFocus() # Calltips calltip_size = CONF.get('shell_appearance', 'calltips/size') calltip_font = get_font('shell_appearance', 'calltips') self.setup_calltips(calltip_size, calltip_font) # Completion completion_size = CONF.get('shell_appearance', 'completion/size') completion_font = get_font('shell_appearance', 'completion') self.completion_widget.setup_appearance(completion_size, completion_font) # Cursor width self.setCursorWidth(CONF.get('shell_appearance', 'cursor/width')) def toggle_wrap_mode(self, enable): """Enable/disable wrap mode""" self.set_wrap_mode('character' if enable else None) def set_font(self, font): """Set shell styles font""" self.set_pythonshell_font(font) cursor = self.textCursor() cursor.select(QTextCursor.Document) charformat = QTextCharFormat() charformat.setFontFamily(font.family()) charformat.setFontPointSize(font.pointSize()) cursor.mergeCharFormat(charformat) #------ Context menu def setup_context_menu(self): """Setup shell context menu""" self.menu = QMenu(self) self.cut_action = create_action(self, _("Cut"), shortcut=keybinding('Cut'), icon=get_icon('editcut.png'), triggered=self.cut) self.copy_action = create_action(self, _("Copy"), shortcut=keybinding('Copy'), icon=get_icon('editcopy.png'), triggered=self.copy) paste_action = create_action(self, _("Paste"), shortcut=keybinding('Paste'), icon=get_icon('editpaste.png'), triggered=self.paste) save_action = create_action(self, _("Save history log..."), icon=get_icon('filesave.png'), tip=_( "Save current history log (i.e. all " "inputs and outputs) in a text file"), triggered=self.save_historylog) self.delete_action = create_action(self, _("Delete"), shortcut=keybinding('Delete'), icon=get_icon('editdelete.png'), triggered=self.delete) selectall_action = create_action(self, _("Select All"), shortcut=keybinding('SelectAll'), icon=get_icon('selectall.png'), triggered=self.selectAll) add_actions( self.menu, (self.cut_action, self.copy_action, paste_action, self.delete_action, None, selectall_action, None, save_action)) def contextMenuEvent(self, event): """Reimplement Qt method""" state = self.has_selected_text() self.copy_action.setEnabled(state) self.cut_action.setEnabled(state) self.delete_action.setEnabled(state) self.menu.popup(event.globalPos()) event.accept() #------ Input buffer def get_current_line_from_cursor(self): return self.get_text('cursor', 'eof') def _select_input(self): """Select current line (without selecting console prompt)""" line, index = self.get_position('eof') if self.current_prompt_pos is None: pline, pindex = line, index else: pline, pindex = self.current_prompt_pos self.setSelection(pline, pindex, line, index) def clear_line(self): """Clear current line (without clearing console prompt)""" if self.current_prompt_pos is not None: self.remove_text(self.current_prompt_pos, 'eof') def clear_terminal(self): """ Clear terminal window Child classes reimplement this method to write prompt """ self.clear() # The buffer being edited def _set_input_buffer(self, text): """Set input buffer""" if self.current_prompt_pos is not None: self.replace_text(self.current_prompt_pos, 'eol', text) else: self.insert(text) self.set_cursor_position('eof') def _get_input_buffer(self): """Return input buffer""" input_buffer = '' if self.current_prompt_pos is not None: input_buffer = self.get_text(self.current_prompt_pos, 'eol') input_buffer = input_buffer.replace(os.linesep, '\n') return input_buffer input_buffer = pyqtProperty("QString", _get_input_buffer, _set_input_buffer) #------ Prompt def new_prompt(self, prompt): """ Print a new prompt and save its (line, index) position """ if self.get_cursor_line_column()[1] != 0: self.write('\n') self.write(prompt, prompt=True) # now we update our cursor giving end of prompt self.current_prompt_pos = self.get_position('cursor') self.ensureCursorVisible() self.new_input_line = False def check_selection(self): """ Check if selected text is r/w, otherwise remove read-only parts of selection """ if self.current_prompt_pos is None: self.set_cursor_position('eof') else: self.truncate_selection(self.current_prompt_pos) #------ Copy / Keyboard interrupt def copy(self): """Copy text to clipboard... or keyboard interrupt""" if self.has_selected_text(): ConsoleBaseWidget.copy(self) elif not sys.platform == 'darwin': self.interrupt() def interrupt(self): """Keyboard interrupt""" self.emit(SIGNAL("keyboard_interrupt()")) def cut(self): """Cut text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.cut(self) def delete(self): """Remove selected text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.remove_selected_text(self) def save_historylog(self): """Save current history log (all text in console)""" title = _("Save history log") self.emit(SIGNAL('redirect_stdio(bool)'), False) filename, _selfilter = getsavefilename( self, title, self.historylog_filename, "%s (*.log)" % _("History logs")) self.emit(SIGNAL('redirect_stdio(bool)'), True) if filename: filename = osp.normpath(filename) try: encoding.write(unicode(self.get_text_with_eol()), filename) self.historylog_filename = filename CONF.set('main', 'historylog_filename', filename) except EnvironmentError, error: QMessageBox.critical( self, title, _("<b>Unable to save file '%s'</b>" "<br><br>Error message:<br>%s") % (osp.basename(filename), unicode(error)))
class AccordionWidget(QScrollArea): itemCollapsed = pyqtSignal(AccordionItem) itemMenuRequested = pyqtSignal(AccordionItem) itemDragFailed = pyqtSignal(AccordionItem) itemsReordered = pyqtSignal() Boxed = 1 Rounded = 2 Square = 3 Maya = 4 NoDragDrop = 0 InternalMove = 1 def __init__(self, parent): QScrollArea.__init__(self, parent) self.setFrameShape(QScrollArea.NoFrame) self.setAutoFillBackground(False) self.setWidgetResizable(True) self.setMouseTracking(True) #self.verticalScrollBar().setMaximumWidth(10) widget = QWidget(self) # define custom properties self._rolloutStyle = AccordionWidget.Rounded self._dragDropMode = AccordionWidget.NoDragDrop self._scrolling = False self._scrollInitY = 0 self._scrollInitVal = 0 self._itemClass = AccordionItem self._items = {} layout = QVBoxLayout() layout.setContentsMargins(2, 2, 2, 2) layout.setSpacing(2) layout.addStretch(1) widget.setLayout(layout) self.setWidget(widget) def setSpacing(self, spaceInt): self.widget().layout().setSpacing(spaceInt) def addItem(self, title: object, widget: object, collapsed: object = False, index: object = None) -> object: self.setUpdatesEnabled(False) item = self._itemClass(self, title, widget) item.setRolloutStyle(self.rolloutStyle()) item.setDragDropMode(self.dragDropMode()) layout = self.widget().layout() if index is None: # append if not specified index index = layout.count() - 1 layout.insertWidget(index, item) layout.setStretchFactor(item, 0) if collapsed: item.setCollapsed(collapsed) self.setUpdatesEnabled(True) return item def clear(self): self.setUpdatesEnabled(False) layout = self.widget().layout() while layout.count() > 1: item = layout.itemAt(0) # remove the item from the layout w = item.widget() layout.removeItem(item) # close the widget and delete it w.close() w.deleteLater() self.setUpdatesEnabled(True) def eventFilter(self, object, event): if event.type() == QEvent.MouseButtonPress: self.mousePressEvent(event) return True elif event.type() == QEvent.MouseMove: self.mouseMoveEvent(event) return True elif event.type() == QEvent.MouseButtonRelease: self.mouseReleaseEvent(event) return True return False def canScroll(self): return self.verticalScrollBar().maximum() > 0 def count(self): return self.widget().layout().count() - 1 def dragDropMode(self): return self._dragDropMode def indexOf(self, widget): """ \remarks Searches for widget(not including child layouts). Returns the index of widget, or -1 if widget is not found \return <int> """ layout = self.widget().layout() for index in range(layout.count()): if layout.itemAt(index).widget().widget() == widget: return index return -1 def indexOfTitle(self, title): layout = self.widget().layout() for index in range(layout.count()): if layout.itemAt(index).widget().title() == title: return index return -1 def isBoxedMode(self): return self._rolloutStyle == AccordionWidget.Boxed def itemClass(self): return self._itemClass def itemAt(self, index): layout = self.widget().layout() if 0 <= index and index < layout.count() - 1: return layout.itemAt(index).widget() return None def emitItemCollapsed(self, item): if not self.signalsBlocked(): self.itemCollapsed.emit(item) def emitItemDragFailed(self, item): if not self.signalsBlocked(): self.itemDragFailed.emit(item) def emitItemMenuRequested(self, item): if not self.signalsBlocked(): self.itemMenuRequested.emit(item) def emitItemsReordered(self): if not self.signalsBlocked(): self.itemsReordered.emit() def enterEvent(self, event): if self.canScroll(): QApplication.setOverrideCursor(Qt.OpenHandCursor) def leaveEvent(self, event): if self.canScroll(): QApplication.restoreOverrideCursor() def mouseMoveEvent(self, event): if self._scrolling: sbar = self.verticalScrollBar() smax = sbar.maximum() # calculate the distance moved for the moust point dy = event.globalY() - self._scrollInitY # calculate the percentage that is of the scroll bar dval = smax * (dy / float(sbar.height())) # calculate the new value sbar.setValue(self._scrollInitVal - dval) event.accept() def mousePressEvent(self, event): # handle a scroll event if event.button() == Qt.LeftButton and self.canScroll(): self._scrolling = True self._scrollInitY = event.globalY() self._scrollInitVal = self.verticalScrollBar().value() QApplication.setOverrideCursor(Qt.ClosedHandCursor) event.accept() def mouseReleaseEvent(self, event): if self._scrolling: QApplication.restoreOverrideCursor() self._scrolling = False self._scrollInitY = 0 self._scrollInitVal = 0 event.accept() def moveItemDown(self, index): layout = self.widget().layout() if (layout.count() - 1) > (index + 1): widget = layout.takeAt(index).widget() layout.insertWidget(index + 1, widget) def moveItemUp(self, index): if index > 0: layout = self.widget().layout() widget = layout.takeAt(index).widget() layout.insertWidget(index - 1, widget) def setBoxedMode(self, state): if state: self._rolloutStyle = AccordionWidget.Boxed else: self._rolloutStyle = AccordionWidget.Rounded def setDragDropMode(self, dragDropMode): self._dragDropMode = dragDropMode for item in self.findChildren(AccordionItem): item.setDragDropMode(self._dragDropMode) def setItemClass(self, itemClass): self._itemClass = itemClass def setRolloutStyle(self, rolloutStyle): self._rolloutStyle = rolloutStyle for item in self.findChildren(AccordionItem): item.setRolloutStyle(self._rolloutStyle) def rolloutStyle(self): return self._rolloutStyle def takeAt(self, index): self.setUpdatesEnabled(False) layout = self.widget().layout() widget = None if 0 <= index and index < layout.count() - 1: item = layout.itemAt(index) widget = item.widget() layout.removeItem(item) widget.close() self.setUpdatesEnabled(True) return widget def widgetAt(self, index): item = self.itemAt(index) if item: return item.widget() return None pyBoxedMode = pyqtProperty('bool', isBoxedMode, setBoxedMode)
class OWWidget(QDialog, metaclass=WidgetMetaClass): # Global widget count widget_id = 0 # Widget description name = None id = None category = None version = None description = None long_description = None icon = "icons/Unknown.png" priority = sys.maxsize author = None author_email = None maintainer = None maintainer_email = None help = None help_ref = None url = None keywords = [] background = None replaces = None inputs = [] outputs = [] # Default widget layout settings want_basic_layout = True want_main_area = True want_control_area = True want_graph = False show_save_graph = True want_status_bar = False no_report = False save_position = True resizing_enabled = True widgetStateChanged = Signal(str, int, str) blockingStateChanged = Signal(bool) asyncCallsStateChange = Signal() progressBarValueChanged = Signal(float) processingStateChanged = Signal(int) settingsHandler = None """:type: SettingsHandler""" savedWidgetGeometry = settings.Setting(None) def __new__(cls, parent=None, *args, **kwargs): self = super().__new__(cls, None, cls.get_flags()) QDialog.__init__(self, None, self.get_flags()) stored_settings = kwargs.get('stored_settings', None) if self.settingsHandler: self.settingsHandler.initialize(self, stored_settings) self.signalManager = kwargs.get('signal_manager', None) setattr(self, gui.CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self)) self._guiElements = [] # used for automatic widget debugging self.__reportData = None # TODO: position used to be saved like this. Reimplement. #if save_position: # self.settingsList = getattr(self, "settingsList", []) + \ # ["widgetShown", "savedWidgetGeometry"] OWWidget.widget_id += 1 self.widget_id = OWWidget.widget_id if self.name: self.setCaption(self.name) self.setFocusPolicy(Qt.StrongFocus) self.startTime = time.time() # used in progressbar self.widgetState = {"Info": {}, "Warning": {}, "Error": {}} self.__blocking = False # flag indicating if the widget's position was already restored self.__was_restored = False self.__progressBarValue = -1 self.__progressState = 0 self.__statusMessage = "" if self.want_basic_layout: self.insertLayout() return self def __init__(self, *args, **kwargs): """QDialog __init__ was already called in __new__, please do not call it here.""" @classmethod def get_flags(cls): return (Qt.Window if cls.resizing_enabled else Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) # noinspection PyAttributeOutsideInit def insertLayout(self): def createPixmapWidget(self, parent, iconName): w = QLabel(parent) parent.layout().addWidget(w) w.setFixedSize(16, 16) w.hide() if os.path.exists(iconName): w.setPixmap(QPixmap(iconName)) return w self.setLayout(QVBoxLayout()) self.layout().setMargin(2) self.warning_bar = gui.widgetBox(self, orientation="horizontal", margin=0, spacing=0) self.warning_icon = gui.widgetLabel(self.warning_bar, "") self.warning_label = gui.widgetLabel(self.warning_bar, "") self.warning_label.setStyleSheet("padding-top: 5px") self.warning_bar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum) gui.rubber(self.warning_bar) self.warning_bar.setVisible(False) self.topWidgetPart = gui.widgetBox(self, orientation="horizontal", margin=0) self.leftWidgetPart = gui.widgetBox(self.topWidgetPart, orientation="vertical", margin=0) if self.want_main_area: self.leftWidgetPart.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) self.leftWidgetPart.updateGeometry() self.mainArea = gui.widgetBox(self.topWidgetPart, orientation="vertical", sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding), margin=0) self.mainArea.layout().setMargin(4) self.mainArea.updateGeometry() if self.want_control_area: self.controlArea = gui.widgetBox(self.leftWidgetPart, orientation="vertical", margin=4) if self.want_graph and self.show_save_graph: graphButtonBackground = gui.widgetBox(self.leftWidgetPart, orientation="horizontal", margin=4) self.graphButton = gui.button(graphButtonBackground, self, "&Save Graph") self.graphButton.setAutoDefault(0) if self.want_status_bar: self.widgetStatusArea = QFrame(self) self.statusBarIconArea = QFrame(self) self.widgetStatusBar = QStatusBar(self) self.layout().addWidget(self.widgetStatusArea) self.widgetStatusArea.setLayout(QHBoxLayout(self.widgetStatusArea)) self.widgetStatusArea.layout().addWidget(self.statusBarIconArea) self.widgetStatusArea.layout().addWidget(self.widgetStatusBar) self.widgetStatusArea.layout().setMargin(0) self.widgetStatusArea.setFrameShape(QFrame.StyledPanel) self.statusBarIconArea.setLayout(QHBoxLayout()) self.widgetStatusBar.setSizeGripEnabled(0) self.statusBarIconArea.hide() self._warningWidget = createPixmapWidget( self.statusBarIconArea, os.path.join(environ.widget_install_dir, "icons/triangle-orange.png")) self._errorWidget = createPixmapWidget( self.statusBarIconArea, os.path.join(environ.widget_install_dir, "icons/triangle-red.png")) # status bar handler functions def setState(self, stateType, id, text): stateChanged = super().setState(stateType, id, text) if not stateChanged or not hasattr(self, "widgetStatusArea"): return iconsShown = 0 warnings = [("Warning", self._warningWidget, self._owWarning), ("Error", self._errorWidget, self._owError)] for state, widget, use in warnings: if not widget: continue if use and self.widgetState[state]: widget.setToolTip("\n".join(self.widgetState[state].values())) widget.show() iconsShown = 1 else: widget.setToolTip("") widget.hide() if iconsShown: self.statusBarIconArea.show() else: self.statusBarIconArea.hide() if (stateType == "Warning" and self._owWarning) or \ (stateType == "Error" and self._owError): if text: self.setStatusBarText(stateType + ": " + text) else: self.setStatusBarText("") self.updateStatusBarState() def updateWidgetStateInfo(self, stateType, id, text): html = self.widgetStateToHtml(self._owInfo, self._owWarning, self._owError) if html: self.widgetStateInfoBox.show() self.widgetStateInfo.setText(html) self.widgetStateInfo.setToolTip(html) else: if not self.widgetStateInfoBox.isVisible(): dHeight = -self.widgetStateInfoBox.height() else: dHeight = 0 self.widgetStateInfoBox.hide() self.widgetStateInfo.setText("") self.widgetStateInfo.setToolTip("") width, height = self.width(), self.height() + dHeight self.resize(width, height) def updateStatusBarState(self): if not hasattr(self, "widgetStatusArea"): return if self.widgetState["Warning"] or self.widgetState["Error"]: self.widgetStatusArea.show() else: self.widgetStatusArea.hide() def setStatusBarText(self, text, timeout=5000): if hasattr(self, "widgetStatusBar"): self.widgetStatusBar.showMessage(" " + text, timeout) # TODO add! def prepareDataReport(self, data): pass # ############################################## """ def isDataWithClass(self, data, wantedVarType=None, checkMissing=False): self.error([1234, 1235, 1236]) if not data: return 0 if not data.domain.classVar: self.error(1234, "A data set with a class attribute is required.") return 0 if wantedVarType and data.domain.classVar.varType != wantedVarType: self.error(1235, "Unable to handle %s class." % str(data.domain.class_var.var_type).lower()) return 0 if checkMissing and not orange.Preprocessor_dropMissingClasses(data): self.error(1236, "Unable to handle data set with no known classes") return 0 return 1 """ def restoreWidgetPosition(self): restored = False if self.save_position: geometry = self.savedWidgetGeometry if geometry is not None: restored = self.restoreGeometry(QByteArray(geometry)) if restored: space = qApp.desktop().availableGeometry(self) frame, geometry = self.frameGeometry(), self.geometry() #Fix the widget size to fit inside the available space width = space.width() - (frame.width() - geometry.width()) width = min(width, geometry.width()) height = space.height() - (frame.height() - geometry.height()) height = min(height, geometry.height()) self.resize(width, height) # Move the widget to the center of available space if it is # currently outside it if not space.contains(self.frameGeometry()): x = max(0, space.width() / 2 - width / 2) y = max(0, space.height() / 2 - height / 2) self.move(x, y) return restored def __updateSavedGeometry(self): if self.__was_restored: # Update the saved geometry only between explicit show/hide # events (i.e. changes initiated by the user not by Qt's default # window management). self.savedWidgetGeometry = self.saveGeometry() # when widget is resized, save the new width and height def resizeEvent(self, ev): QDialog.resizeEvent(self, ev) # Don't store geometry if the widget is not visible # (the widget receives a resizeEvent (with the default sizeHint) # before showEvent and we must not overwrite the the savedGeometry # with it) if self.save_position and self.isVisible(): self.__updateSavedGeometry() def moveEvent(self, ev): QDialog.moveEvent(self, ev) if self.save_position and self.isVisible(): self.__updateSavedGeometry() # set widget state to hidden def hideEvent(self, ev): if self.save_position: self.__updateSavedGeometry() self.__was_restored = False QDialog.hideEvent(self, ev) def closeEvent(self, ev): if self.save_position and self.isVisible(): self.__updateSavedGeometry() self.__was_restored = False QDialog.closeEvent(self, ev) def showEvent(self, ev): QDialog.showEvent(self, ev) if self.save_position: # Restore saved geometry on show self.restoreWidgetPosition() self.__was_restored = True def wheelEvent(self, event): """ Silently accept the wheel event. This is to ensure combo boxes and other controls that have focus don't receive this event unless the cursor is over them. """ event.accept() def setCaption(self, caption): # we have to save caption title in case progressbar will change it self.captionTitle = str(caption) self.setWindowTitle(caption) # put this widget on top of all windows def reshow(self): self.show() self.raise_() self.activateWindow() def send(self, signalName, value, id=None): if not any(s.name == signalName for s in self.outputs): raise ValueError( '{} is not a valid output signal for widget {}'.format( signalName, self.name)) if self.signalManager is not None: self.signalManager.send(self, signalName, value, id) def __setattr__(self, name, value): """Set value to members of this instance or any of its members. If member is used in a gui control, notify the control about the change. name: name of the member, dot is used for nesting ("graph.point.size"). value: value to set to the member. """ names = name.rsplit(".") field_name = names.pop() obj = reduce(lambda o, n: getattr(o, n, None), names, self) if obj is None: raise AttributeError("Cannot set '{}' to {} ".format(name, value)) if obj is self: super().__setattr__(field_name, value) else: setattr(obj, field_name, value) notify_changed(obj, field_name, value) if self.settingsHandler: self.settingsHandler.fast_save(self, name, value) def openContext(self, *a): self.settingsHandler.open_context(self, *a) def closeContext(self): self.settingsHandler.close_context(self) def retrieveSpecificSettings(self): pass def storeSpecificSettings(self): pass def saveSettings(self): self.settingsHandler.update_defaults(self) # this function is only intended for derived classes to send appropriate # signals when all settings are loaded def activate_loaded_settings(self): pass # reimplemented in other widgets def onDeleteWidget(self): pass def handleNewSignals(self): # this is called after all new signals have been handled # implement this in your widget if you want to process something only # after you received multiple signals pass # ############################################ # PROGRESS BAR FUNCTIONS def progressBarInit(self, processEvents=QEventLoop.AllEvents): """ Initialize the widget's progress (i.e show and set progress to 0%). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") if self.__progressState != 1: self.__progressState = 1 self.processingStateChanged.emit(1) self.progressBarSet(0, processEvents) def progressBarSet(self, value, processEvents=QEventLoop.AllEvents): """ Set the current progress bar to `value`. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param float value: Progress value :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ old = self.__progressBarValue self.__progressBarValue = value if value > 0: if self.__progressState != 1: warnings.warn( "progressBarSet() called without a " "preceding progressBarInit()", stacklevel=2) self.__progressState = 1 self.processingStateChanged.emit(1) usedTime = max(1, time.time() - self.startTime) totalTime = (100.0 * usedTime) / float(value) remainingTime = max(0, totalTime - usedTime) h = int(remainingTime / 3600) min = int((remainingTime - h * 3600) / 60) sec = int(remainingTime - h * 3600 - min * 60) if h > 0: text = "%(h)d:%(min)02d:%(sec)02d" % vars() else: text = "%(min)d:%(sec)02d" % vars() self.setWindowTitle( self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars()) else: self.setWindowTitle(self.captionTitle + " (0% complete)") if old != value: self.progressBarValueChanged.emit(value) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) def progressBarValue(self): return self.__progressBarValue progressBarValue = pyqtProperty(float, fset=progressBarSet, fget=progressBarValue) processingState = pyqtProperty(int, fget=lambda self: self.__progressState) def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents): self.progressBarSet(self.progressBarValue + value, processEvents) def progressBarFinished(self, processEvents=QEventLoop.AllEvents): """ Stop the widget's progress (i.e hide the progress bar). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.setWindowTitle(self.captionTitle) if self.__progressState != 0: self.__progressState = 0 self.processingStateChanged.emit(0) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) #: Widget's status message has changed. statusMessageChanged = Signal(str) def setStatusMessage(self, text): if self.__statusMessage != text: self.__statusMessage = text self.statusMessageChanged.emit(text) def statusMessage(self): return self.__statusMessage def keyPressEvent(self, e): if (int(e.modifiers()), e.key()) in OWWidget.defaultKeyActions: OWWidget.defaultKeyActions[int(e.modifiers()), e.key()](self) else: QDialog.keyPressEvent(self, e) def information(self, id=0, text=None): self.setState("Info", id, text) def warning(self, id=0, text=""): self.setState("Warning", id, text) def error(self, id=0, text=""): self.setState("Error", id, text) def setState(self, state_type, id, text): changed = 0 if type(id) == list: for val in id: if val in self.widgetState[state_type]: self.widgetState[state_type].pop(val) changed = 1 else: if type(id) == str: text = id id = 0 if not text: if id in self.widgetState[state_type]: self.widgetState[state_type].pop(id) changed = 1 else: self.widgetState[state_type][id] = text changed = 1 if changed: if type(id) == list: for i in id: self.widgetStateChanged.emit(state_type, i, "") else: self.widgetStateChanged.emit(state_type, id, text or "") tooltip_lines = [] highest_type = None for a_type in ("Error", "Warning", "Info"): msgs_for_ids = self.widgetState.get(a_type) if not msgs_for_ids: continue msgs_for_ids = list(msgs_for_ids.values()) if not msgs_for_ids: continue tooltip_lines += msgs_for_ids if highest_type is None: highest_type = a_type if highest_type is None: self.set_warning_bar(None) elif len(tooltip_lines) == 1: msg = tooltip_lines[0] if "\n" in msg: self.set_warning_bar(highest_type, msg[:msg.index("\n")] + " (...)", msg) else: self.set_warning_bar(highest_type, tooltip_lines[0], tooltip_lines[0]) else: self.set_warning_bar( highest_type, "{} problems during execution".format(len(tooltip_lines)), "\n".join(tooltip_lines)) return changed def set_warning_bar(self, state_type, text=None, tooltip=None): colors = { "Error": ("#ffc6c6", "black", QStyle.SP_MessageBoxCritical), "Warning": ("#ffffc9", "black", QStyle.SP_MessageBoxWarning), "Info": ("#ceceff", "black", QStyle.SP_MessageBoxInformation) } current_height = self.height() if state_type is None: if not self.warning_bar.isHidden(): new_height = current_height - self.warning_bar.height() self.warning_bar.setVisible(False) self.resize(self.width(), new_height) return background, foreground, icon = colors[state_type] style = QApplication.instance().style() self.warning_icon.setPixmap(style.standardIcon(icon).pixmap(14, 14)) self.warning_bar.setStyleSheet( "background-color: {}; color: {};" "padding: 3px; padding-left: 6px; vertical-align: center".format( background, foreground)) self.warning_label.setText(text) self.warning_bar.setToolTip(tooltip) if self.warning_bar.isHidden(): self.warning_bar.setVisible(True) new_height = current_height + self.warning_bar.height() self.resize(self.width(), new_height) def widgetStateToHtml(self, info=True, warning=True, error=True): pixmaps = self.getWidgetStateIcons() items = [] iconPath = { "Info": "canvasIcons:information.png", "Warning": "canvasIcons:warning.png", "Error": "canvasIcons:error.png" } for show, what in [(info, "Info"), (warning, "Warning"), (error, "Error")]: if show and self.widgetState[what]: items.append('<img src="%s" style="float: left;"> %s' % (iconPath[what], "\n".join( self.widgetState[what].values()))) return "<br>".join(items) @classmethod def getWidgetStateIcons(cls): if not hasattr(cls, "_cached__widget_state_icons"): iconsDir = os.path.join(environ.canvas_install_dir, "icons") QDir.addSearchPath( "canvasIcons", os.path.join(environ.canvas_install_dir, "icons/")) info = QPixmap("canvasIcons:information.png") warning = QPixmap("canvasIcons:warning.png") error = QPixmap("canvasIcons:error.png") cls._cached__widget_state_icons = \ {"Info": info, "Warning": warning, "Error": error} return cls._cached__widget_state_icons defaultKeyActions = {} if sys.platform == "darwin": defaultKeyActions = { (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(), (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible()) } def setBlocking(self, state=True): """ Set blocking flag for this widget. While this flag is set this widget and all its descendants will not receive any new signals from the signal manager """ if self.__blocking != state: self.__blocking = state self.blockingStateChanged.emit(state) def isBlocking(self): """ Is this widget blocking signal processing. """ return self.__blocking def resetSettings(self): self.settingsHandler.reset_settings(self)
class ProgressBarMixin: # Set these here so we avoid having to call `__init__` fromm classes # that use this mix-in __progressBarValue = -1 __progressState = 0 startTime = time.time() # used in progressbar def progressBarInit(self, processEvents=QEventLoop.AllEvents): """ Initialize the widget's progress (i.e show and set progress to 0%). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") if self.__progressState != 1: self.__progressState = 1 self.processingStateChanged.emit(1) self.progressBarSet(0, processEvents) def progressBarSet(self, value, processEvents=QEventLoop.AllEvents): """ Set the current progress bar to `value`. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param float value: Progress value :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ old = self.__progressBarValue self.__progressBarValue = value if value > 0: if self.__progressState != 1: warnings.warn("progressBarSet() called without a " "preceding progressBarInit()", stacklevel=2) self.__progressState = 1 self.processingStateChanged.emit(1) usedTime = max(1, time.time() - self.startTime) totalTime = 100.0 * usedTime / value remainingTime = max(0, int(totalTime - usedTime)) hrs = remainingTime // 3600 mins = (remainingTime % 3600) // 60 secs = remainingTime % 60 if hrs > 0: text = "{}:{:02}:{:02}".format(hrs, mins, secs) else: text = "{}:{}:{:02}".format(hrs, mins, secs) self.setWindowTitle("{} ({:d}%, ETA: {})" .format(self.captionTitle, int(value), text)) else: self.setWindowTitle(self.captionTitle + " (0% complete)") if old != value: self.progressBarValueChanged.emit(value) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) def progressBarValue(self): """Return the state of the progress bar """ return self.__progressBarValue progressBarValue = pyqtProperty( float, fset=progressBarSet, fget=progressBarValue) processingState = pyqtProperty(int, fget=lambda self: self.__progressState) def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents): """ Advance the progress bar. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. Args: value (int): progress value processEvents (`QEventLoop.ProcessEventsFlags` or `None`): process events flag """ self.progressBarSet(self.progressBarValue + value, processEvents) def progressBarFinished(self, processEvents=QEventLoop.AllEvents): """ Stop the widget's progress (i.e hide the progress bar). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.setWindowTitle(self.captionTitle) if self.__progressState != 0: self.__progressState = 0 self.processingStateChanged.emit(0) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) @contextlib.contextmanager def progressBar(self, iterations=0): """ Context manager for progress bar. Using it ensures that the progress bar is removed at the end without needing the `finally` blocks. Usage: with self.progressBar(20) as progress: ... progress.advance() or with self.progressBar() as progress: ... progress.advance(0.15) or with self.progressBar(): ... self.progressBarSet(50) :param iterations: the number of iterations (optional) :type iterations: int """ progress_bar = gui.ProgressBar(self, iterations) yield progress_bar progress_bar.finish() # Let us not rely on garbage collector
class QxtSpanSlider(QSlider): NoHandle = None LowerHandle = 1 UpperHandle = 2 FreeMovement = None NoCrossing = 1 NoOverlapping = 2 __pyqtSignals__ = ("spanChanged(int, int)", "lowerValueChanged(int)", "upperValueChanged(int)", "lowerPositionChanged(int)", "upperPositionChanged(int)", "sliderPressed(PyQt_PyObject)") __pyqtSlots__ = ("setLowerValue(int)", "setUpperValue(int)", "setSpan(int, int)", "setLowerPosition(int)", "setUpperPosition(int)", "setGradientLeftColor(PyQt_PyObject)", "setGradientRightColor(PyQt_PyObject)") def __init__(self, parent=None): QSlider.__init__(self, QtCore.Qt.Horizontal, parent) self.connect(self, SIGNAL("rangeChanged(int, int)"), self.updateRange) self.connect(self, SIGNAL("sliderReleased()"), self.movePressedHandle) self.setStyle(QStyleFactory.create('Plastique')) self.lower = 0 self.upper = 0 self.lowerPos = 0 self.upperPos = 0 self.offset = 0 self.position = 0 self.lastPressed = QxtSpanSlider.NoHandle self.upperPressed = QStyle.SC_None self.lowerPressed = QStyle.SC_None self.movement = QxtSpanSlider.FreeMovement self.mainControl = QxtSpanSlider.LowerHandle self.firstMovement = False self.blockTracking = False self.gradientLeft = self.palette().color(QPalette.Dark).light(110) self.gradientRight = self.palette().color(QPalette.Dark).light(110) self.colorOutsideRange = QColor(0, 0, 0) def lowerValue(self): return min(self.lower, self.upper) def setLowerValue(self, lower): self.setSpan(lower, self.upper) def upperValue(self): return max(self.lower, self.upper) def setUpperValue(self, upper): self.setSpan(self.lower, upper) def handleMovementMode(self): return self.movement def setHandleMovementMode(self, mode): self.movement = mode def setSpan(self, lower, upper): low = clamp(min(lower, upper), self.minimum(), self.maximum()) upp = clamp(max(lower, upper), self.minimum(), self.maximum()) changed = False if low != self.lower: self.lower = low self.lowerPos = low changed = True if upp != self.upper: self.upper = upp self.upperPos = upp changed = True if changed: self.emit(SIGNAL("spanChanged(int, int)"), self.lower, self.upper) self.update() def lowerPosition(self): return self.lowerPos def setLowerPosition(self, lower): if self.lowerPos != lower: self.lowerPos = lower if not self.hasTracking(): self.update() if self.isSliderDown(): self.emit(SIGNAL("lowerPositionChanged(int)"), lower) if self.hasTracking() and not self.blockTracking: main = (self.mainControl == QxtSpanSlider.LowerHandle) self.triggerAction(QxtSpanSlider.SliderMove, main) def upperPosition(self): return self.upperPos def setUpperPosition(self, upper): if self.upperPos != upper: self.upperPos = upper if not self.hasTracking(): self.update() if self.isSliderDown(): self.emit(SIGNAL("upperPositionChanged(int)"), upper) if self.hasTracking() and not self.blockTracking: main = (self.mainControl == QxtSpanSlider.UpperHandle) self.triggerAction(QxtSpanSlider.SliderMove, main) def gradientLeftColor(self): return self.gradientLeft def setGradientLeftColor(self, color): self.gradientLeft = color self.update() def gradientRightColor(self): return self.gradientRight def setGradientRightColor(self, color): self.gradientRight = color self.update() def colorOutsideRange(self): return self.colorOutsideRange def setColorOutsideRange(self, color): self.colorOutsideRange = color def movePressedHandle(self): if self.lastPressed == QxtSpanSlider.LowerHandle: if self.lowerPos != self.lower: main = (self.mainControl == QxtSpanSlider.LowerHandle) self.triggerAction(QAbstractSlider.SliderMove, main) elif self.lastPressed == QxtSpanSlider.UpperHandle: if self.upperPos != self.upper: main = (self.mainControl == QxtSpanSlider.UpperHandle) self.triggerAction(QAbstractSlider.SliderMove, main) def pick(self, p): if self.orientation() == QtCore.Qt.Horizontal: return p.x() else: return p.y() def triggerAction(self, action, main): value = 0 no = False up = False my_min = self.minimum() my_max = self.maximum() altControl = QxtSpanSlider.LowerHandle if self.mainControl == QxtSpanSlider.LowerHandle: altControl = QxtSpanSlider.UpperHandle self.blockTracking = True isUpperHandle = (main and self.mainControl == QxtSpanSlider.UpperHandle ) or (not main and altControl == QxtSpanSlider.UpperHandle) if action == QAbstractSlider.SliderSingleStepAdd: if isUpperHandle: value = clamp(self.upper + self.singleStep(), my_min, my_max) up = True else: value = clamp(self.lower + self.singleStep(), my_min, my_max) elif action == QAbstractSlider.SliderSingleStepSub: if isUpperHandle: value = clamp(self.upper - self.singleStep(), my_min, my_max) up = True else: value = clamp(self.lower - self.singleStep(), my_min, my_max) elif action == QAbstractSlider.SliderToMinimum: value = my_min if isUpperHandle: up = True elif action == QAbstractSlider.SliderToMaximum: value = my_max if isUpperHandle: up = True elif action == QAbstractSlider.SliderMove: if isUpperHandle: up = True no = True elif action == QAbstractSlider.SliderNoAction: no = True if not no and not up: if self.movement == QxtSpanSlider.NoCrossing: value = min(value, self.upper) elif self.movement == QxtSpanSlider.NoOverlapping: value = min(value, self.upper - 1) if self.movement == QxtSpanSlider.FreeMovement and value > self.upper: self.swapControls() self.setUpperPosition(value) else: self.setLowerPosition(value) elif not no: if self.movement == QxtSpanSlider.NoCrossing: value = max(value, self.lower) elif self.movement == QxtSpanSlider.NoOverlapping: value = max(value, self.lower + 1) if self.movement == QxtSpanSlider.FreeMovement and value < self.lower: self.swapControls() self.setLowerPosition(value) else: self.setUpperPosition(value) self.blockTracking = False self.setLowerValue(self.lowerPos) self.setUpperValue(self.upperPos) def swapControls(self): self.lower, self.upper = self.upper, self.lower self.lowerPressed, self.upperPressed = self.upperPressed, self.lowerPressed if self.lastPressed == QxtSpanSlider.LowerHandle: self.lastPressed = QxtSpanSlider.UpperHandle else: self.lastPressed = QxtSpanSlider.LowerHandle if self.mainControl == QxtSpanSlider.LowerHandle: self.mainControl = QxtSpanSlider.UpperHandle else: self.mainControl = QxtSpanSlider.LowerHandle def updateRange(self, min, max): # setSpan() takes care of keeping span in range self.setSpan(self.lower, self.upper) def paintEvent(self, event): painter = QStylePainter(self) # ticks opt = QStyleOptionSlider() self.initStyleOption(opt) opt.subControls = QStyle.SC_SliderTickmarks painter.drawComplexControl(QStyle.CC_Slider, opt) # groove opt.sliderPosition = 20 opt.sliderValue = 0 opt.subControls = QStyle.SC_SliderGroove painter.drawComplexControl(QStyle.CC_Slider, opt) # handle rects opt.sliderPosition = self.lowerPos lr = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self) lrv = self.pick(lr.center()) opt.sliderPosition = self.upperPos ur = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self) urv = self.pick(ur.center()) # span minv = min(lrv, urv) maxv = max(lrv, urv) c = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderGroove, self).center() spanRect = QRect(QPoint(c.x() - 2, minv), QPoint(c.x() + 1, maxv)) if self.orientation() == QtCore.Qt.Horizontal: spanRect = QRect(QPoint(minv, c.y() - 2), QPoint(maxv, c.y() + 1)) self.drawSpan(painter, spanRect) # handles if self.lastPressed == QxtSpanSlider.LowerHandle: self.drawHandle(painter, QxtSpanSlider.UpperHandle) self.drawHandle(painter, QxtSpanSlider.LowerHandle) else: self.drawHandle(painter, QxtSpanSlider.LowerHandle) self.drawHandle(painter, QxtSpanSlider.UpperHandle) def setupPainter(self, painter, orientation, x1, y1, x2, y2): highlight = self.palette().color(QPalette.Highlight) gradient = QLinearGradient(x1, y1, x2, y2) gradient.setColorAt(0, highlight.dark(120)) gradient.setColorAt(1, highlight.light(108)) painter.setBrush(gradient) if orientation == QtCore.Qt.Horizontal: painter.setPen(QPen(highlight.dark(130), 0)) else: painter.setPen(QPen(highlight.dark(150), 0)) def drawSpan(self, painter, rect): opt = QStyleOptionSlider() QSlider.initStyleOption(self, opt) # area groove = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderGroove, self) if opt.orientation == QtCore.Qt.Horizontal: groove.adjust(0, 0, -1, 0) else: groove.adjust(0, 0, 0, -1) # pen & brush painter.setPen(QPen(self.gradientLeftColor, 0)) if opt.orientation == QtCore.Qt.Horizontal: self.setupPainter(painter, opt.orientation, groove.center().x(), groove.top(), groove.center().x(), groove.bottom()) else: self.setupPainter(painter, opt.orientation, groove.left(), groove.center().y(), groove.right(), groove.center().y()) # draw groove pointTopLeftOutsideLeft = QPoint(groove.topLeft().x(), rect.topLeft().y()) pointBottomRightOutsideLeft = rect.bottomLeft() pointTopLeftOutsideRight = rect.topLeft() pointBottomRightOutsideRight = QPoint(groove.bottomRight().x(), rect.bottomRight().y()) rectOutsideRangeLeft = QtCore.QRect(pointTopLeftOutsideLeft, pointBottomRightOutsideLeft) rectOutsideRangeRight = QtCore.QRect(pointTopLeftOutsideRight, pointBottomRightOutsideRight) intersected = QtCore.QRectF(rect.intersected(groove)) gradient = QLinearGradient(intersected.topLeft(), intersected.topRight()) gradient.setColorAt(0, self.gradientLeft) gradient.setColorAt(1, self.gradientRight) painter.fillRect(rectOutsideRangeLeft, self.colorOutsideRange) painter.fillRect(rectOutsideRangeRight, self.colorOutsideRange) painter.fillRect(intersected, gradient) def drawHandle(self, painter, handle): opt = QStyleOptionSlider() self._initStyleOption(opt, handle) opt.subControls = QStyle.SC_SliderHandle pressed = self.upperPressed if handle == QxtSpanSlider.LowerHandle: pressed = self.lowerPressed if pressed == QStyle.SC_SliderHandle: opt.activeSubControls = pressed opt.state |= QStyle.State_Sunken painter.drawComplexControl(QStyle.CC_Slider, opt) def _initStyleOption(self, option, handle): self.initStyleOption(option) option.sliderPosition = self.upperPos if handle == QxtSpanSlider.LowerHandle: option.sliderPosition = self.lowerPos option.sliderValue = self.upper if handle == QxtSpanSlider.LowerHandle: option.sliderPosition = self.lower def handleMousePress(self, pos, control, value, handle): opt = QStyleOptionSlider() self._initStyleOption(opt, handle) oldControl = control control = self.style().hitTestComplexControl(QStyle.CC_Slider, opt, pos, self) sr = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self) if control == QStyle.SC_SliderHandle: self.position = value self.offset = self.pick(pos - sr.topLeft()) self.lastPressed = handle self.setSliderDown(True) self.emit(SIGNAL("sliderPressed(PyQt_PyObject)"), handle) if control != oldControl: self.update(sr) return control def mousePressEvent(self, event): if self.minimum() == self.maximum( ) or event.buttons() ^ event.button(): event.ignore() return self.upperPressed = self.handleMousePress(event.pos(), self.upperPressed, self.upper, QxtSpanSlider.UpperHandle) if self.upperPressed != QStyle.SC_SliderHandle: self.lowerPressed = self.handleMousePress( event.pos(), self.lowerPressed, self.lower, QxtSpanSlider.LowerHandle) self.firstMovement = True event.accept() def mouseMoveEvent(self, event): if self.lowerPressed != QStyle.SC_SliderHandle and self.upperPressed != QStyle.SC_SliderHandle: event.ignore() return opt = QStyleOptionSlider() self.initStyleOption(opt) m = self.style().pixelMetric(QStyle.PM_MaximumDragDistance, opt, self) newPosition = self.pixelPosToRangeValue( self.pick(event.pos()) - self.offset) if m >= 0: r = self.rect().adjusted(-m, -m, m, m) if not r.contains(event.pos()): newPosition = self.position # pick the preferred handle on the first movement if self.firstMovement: if self.lower == self.upper: if newPosition < self.lowerValue: self.swapControls() self.firstMovement = False else: self.firstMovement = False if self.lowerPressed == QStyle.SC_SliderHandle: if self.movement == QxtSpanSlider.NoCrossing: newPosition = min(newPosition, self.upper) elif self.movement == QxtSpanSlider.NoOverlapping: newPosition = min(newPosition, self.upper - 1) if self.movement == QxtSpanSlider.FreeMovement and newPosition > self.upper: self.swapControls() self.setUpperPosition(newPosition) else: self.setLowerPosition(newPosition) elif self.upperPressed == QStyle.SC_SliderHandle: if self.movement == QxtSpanSlider.NoCrossing: newPosition = max(newPosition, self.lowerValue) elif self.movement == QxtSpanSlider.NoOverlapping: newPosition = max(newPosition, self.lowerValue + 1) if self.movement == QxtSpanSlider.FreeMovement and newPosition < self.lower: self.swapControls() self.setLowerPosition(newPosition) else: self.setUpperPosition(newPosition) event.accept() def mouseReleaseEvent(self, event): QSlider.mouseReleaseEvent(self, event) self.setSliderDown(False) self.lowerPressed = QStyle.SC_None self.upperPressed = QStyle.SC_None self.update() def pixelPosToRangeValue(self, pos): opt = QStyleOptionSlider() self.initStyleOption(opt) sliderMin = 0 sliderMax = 0 sliderLength = 0 gr = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderGroove, self) sr = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self) if self.orientation() == QtCore.Qt.Horizontal: sliderLength = sr.width() sliderMin = gr.x() sliderMax = gr.right() - sliderLength + 1 else: sliderLength = sr.height() sliderMin = gr.y() sliderMax = gr.bottom() - sliderLength + 1 return QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), pos - sliderMin, sliderMax - sliderMin, opt.upsideDown) lowerValue = pyqtProperty("int", lowerValue, setLowerValue) upperValue = pyqtProperty("int", upperValue, setUpperValue) upperPosition = pyqtProperty("int", upperPosition, setUpperPosition) lowerPosition = pyqtProperty("int", lowerPosition, setLowerPosition) handleMovementMode = pyqtProperty("PyQt_PyObject", handleMovementMode, setHandleMovementMode) gradientLeftColor = pyqtProperty("PyQt_PyObject", gradientLeftColor, setGradientLeftColor) gradientRightColor = pyqtProperty("PyQt_PyObject", gradientRightColor, setGradientRightColor)
class OWWidget(QDialog, metaclass=WidgetMetaClass): # Global widget count widget_id = 0 # Widget description name = None id = None category = None version = None description = None long_description = None icon = "icons/Unknown.png" priority = sys.maxsize author = None author_email = None maintainer = None maintainer_email = None help = None help_ref = None url = None keywords = [] background = None replaces = None inputs = [] outputs = [] # Default widget layout settings want_basic_layout = True want_main_area = True want_control_area = True want_graph = False show_save_graph = True want_status_bar = False no_report = False save_position = True resizing_enabled = True widgetStateChanged = Signal(str, int, str) blockingStateChanged = Signal(bool) progressBarValueChanged = Signal(float) processingStateChanged = Signal(int) settingsHandler = None """:type: SettingsHandler""" savedWidgetGeometry = settings.Setting(None) #: A list of user advice messages to display to the user. #: When a widget is first shown a message tom this list is selected #: for display. If a user accepts (clicks 'Ok. Got it') the choice is #: recorded and the message is never shown again (closing the message #: will not mark it as seen). Messages can be displayed again by pressing #: Shift + F1 #: :type: List[Message] UserAdviceMessages = [] def __new__(cls, *args, **kwargs): self = super().__new__(cls, None, cls.get_flags()) QDialog.__init__(self, None, self.get_flags()) stored_settings = kwargs.get('stored_settings', None) if self.settingsHandler: self.settingsHandler.initialize(self, stored_settings) self.signalManager = kwargs.get('signal_manager', None) self.__env = _asmappingproxy(kwargs.get("env", {})) setattr(self, gui.CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self)) self.__reportData = None OWWidget.widget_id += 1 self.widget_id = OWWidget.widget_id if self.name: self.setCaption(self.name) self.setFocusPolicy(Qt.StrongFocus) self.startTime = time.time() # used in progressbar self.widgetState = {"Info": {}, "Warning": {}, "Error": {}} self.__blocking = False # flag indicating if the widget's position was already restored self.__was_restored = False self.__progressBarValue = -1 self.__progressState = 0 self.__statusMessage = "" self.__msgwidget = None self.__msgchoice = 0 if self.want_basic_layout: self.insertLayout() sc = QShortcut(QKeySequence(Qt.ShiftModifier | Qt.Key_F1), self) sc.activated.connect(self.__quicktip) return self def __init__(self, *args, **kwargs): """QDialog __init__ was already called in __new__, please do not call it here.""" @classmethod def get_flags(cls): return (Qt.Window if cls.resizing_enabled else Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) # noinspection PyAttributeOutsideInit def insertLayout(self): def createPixmapWidget(self, parent, iconName): w = QLabel(parent) parent.layout().addWidget(w) w.setFixedSize(16, 16) w.hide() if os.path.exists(iconName): w.setPixmap(QPixmap(iconName)) return w self.setLayout(QVBoxLayout()) self.layout().setMargin(2) self.warning_bar = gui.widgetBox(self, orientation="horizontal", margin=0, spacing=0) self.warning_icon = gui.widgetLabel(self.warning_bar, "") self.warning_label = gui.widgetLabel(self.warning_bar, "") self.warning_label.setStyleSheet("padding-top: 5px") self.warning_bar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum) gui.rubber(self.warning_bar) self.warning_bar.setVisible(False) self.topWidgetPart = gui.widgetBox(self, orientation="horizontal", margin=0) self.leftWidgetPart = gui.widgetBox(self.topWidgetPart, orientation="vertical", margin=0) if self.want_main_area: self.leftWidgetPart.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) self.leftWidgetPart.updateGeometry() self.mainArea = gui.widgetBox(self.topWidgetPart, orientation="vertical", sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding), margin=0) self.mainArea.layout().setMargin(4) self.mainArea.updateGeometry() if self.want_control_area: self.controlArea = gui.widgetBox(self.leftWidgetPart, orientation="vertical", margin=4) if self.want_graph and self.show_save_graph: graphButtonBackground = gui.widgetBox(self.leftWidgetPart, orientation="horizontal", margin=4) self.graphButton = gui.button(graphButtonBackground, self, "&Save Graph") self.graphButton.setAutoDefault(0) if self.want_status_bar: self.widgetStatusArea = QFrame(self) self.statusBarIconArea = QFrame(self) self.widgetStatusBar = QStatusBar(self) self.layout().addWidget(self.widgetStatusArea) self.widgetStatusArea.setLayout(QHBoxLayout(self.widgetStatusArea)) self.widgetStatusArea.layout().addWidget(self.statusBarIconArea) self.widgetStatusArea.layout().addWidget(self.widgetStatusBar) self.widgetStatusArea.layout().setMargin(0) self.widgetStatusArea.setFrameShape(QFrame.StyledPanel) self.statusBarIconArea.setLayout(QHBoxLayout()) self.widgetStatusBar.setSizeGripEnabled(0) self.statusBarIconArea.hide() self._warningWidget = createPixmapWidget( self.statusBarIconArea, gui.resource_filename("icons/triangle-orange.png")) self._errorWidget = createPixmapWidget( self.statusBarIconArea, gui.resource_filename("icons/triangle-red.png")) if not self.resizing_enabled: self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) def updateStatusBarState(self): if not hasattr(self, "widgetStatusArea"): return if self.widgetState["Warning"] or self.widgetState["Error"]: self.widgetStatusArea.show() else: self.widgetStatusArea.hide() def setStatusBarText(self, text, timeout=5000): if hasattr(self, "widgetStatusBar"): self.widgetStatusBar.showMessage(" " + text, timeout) # TODO add! def prepareDataReport(self, data): pass def __restoreWidgetGeometry(self): restored = False if self.save_position: geometry = self.savedWidgetGeometry if geometry is not None: restored = self.restoreGeometry(QByteArray(geometry)) if restored and not self.windowState() & \ (Qt.WindowMaximized | Qt.WindowFullScreen): space = qApp.desktop().availableGeometry(self) frame, geometry = self.frameGeometry(), self.geometry() #Fix the widget size to fit inside the available space width = space.width() - (frame.width() - geometry.width()) width = min(width, geometry.width()) height = space.height() - (frame.height() - geometry.height()) height = min(height, geometry.height()) self.resize(width, height) # Move the widget to the center of available space if it is # currently outside it if not space.contains(self.frameGeometry()): x = max(0, space.width() / 2 - width / 2) y = max(0, space.height() / 2 - height / 2) self.move(x, y) return restored def __updateSavedGeometry(self): if self.__was_restored and self.isVisible(): # Update the saved geometry only between explicit show/hide # events (i.e. changes initiated by the user not by Qt's default # window management). self.savedWidgetGeometry = self.saveGeometry() # when widget is resized, save the new width and height def resizeEvent(self, ev): QDialog.resizeEvent(self, ev) # Don't store geometry if the widget is not visible # (the widget receives a resizeEvent (with the default sizeHint) # before first showEvent and we must not overwrite the the # savedGeometry with it) if self.save_position and self.isVisible(): self.__updateSavedGeometry() def moveEvent(self, ev): QDialog.moveEvent(self, ev) if self.save_position and self.isVisible(): self.__updateSavedGeometry() # set widget state to hidden def hideEvent(self, ev): if self.save_position: self.__updateSavedGeometry() QDialog.hideEvent(self, ev) def closeEvent(self, ev): if self.save_position and self.isVisible(): self.__updateSavedGeometry() QDialog.closeEvent(self, ev) def showEvent(self, ev): QDialog.showEvent(self, ev) if self.save_position and not self.__was_restored: # Restore saved geometry on show self.__restoreWidgetGeometry() self.__was_restored = True self.__quicktipOnce() def wheelEvent(self, event): """ Silently accept the wheel event. This is to ensure combo boxes and other controls that have focus don't receive this event unless the cursor is over them. """ event.accept() def setCaption(self, caption): # we have to save caption title in case progressbar will change it self.captionTitle = str(caption) self.setWindowTitle(caption) # put this widget on top of all windows def reshow(self): self.show() self.raise_() self.activateWindow() def send(self, signalName, value, id=None): if not any(s.name == signalName for s in self.outputs): raise ValueError( '{} is not a valid output signal for widget {}'.format( signalName, self.name)) if self.signalManager is not None: self.signalManager.send(self, signalName, value, id) def __setattr__(self, name, value): """Set value to members of this instance or any of its members. If member is used in a gui control, notify the control about the change. name: name of the member, dot is used for nesting ("graph.point.size"). value: value to set to the member. """ names = name.rsplit(".") field_name = names.pop() obj = reduce(lambda o, n: getattr(o, n, None), names, self) if obj is None: raise AttributeError("Cannot set '{}' to {} ".format(name, value)) if obj is self: super().__setattr__(field_name, value) else: setattr(obj, field_name, value) notify_changed(obj, field_name, value) if self.settingsHandler: self.settingsHandler.fast_save(self, name, value) def openContext(self, *a): self.settingsHandler.open_context(self, *a) def closeContext(self): self.settingsHandler.close_context(self) def retrieveSpecificSettings(self): pass def storeSpecificSettings(self): pass def saveSettings(self): self.settingsHandler.update_defaults(self) def onDeleteWidget(self): """ Invoked by the canvas to notify the widget it has been deleted from the workflow. If possible, subclasses should gracefully cancel any currently executing tasks. """ pass def handleNewSignals(self): """ Invoked by the workflow signal propagation manager after all signals handlers have been called. Reimplement this method in order to coalesce updates from multiple updated inputs. """ pass # ############################################ # PROGRESS BAR FUNCTIONS def progressBarInit(self, processEvents=QEventLoop.AllEvents): """ Initialize the widget's progress (i.e show and set progress to 0%). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") if self.__progressState != 1: self.__progressState = 1 self.processingStateChanged.emit(1) self.progressBarSet(0, processEvents) def progressBarSet(self, value, processEvents=QEventLoop.AllEvents): """ Set the current progress bar to `value`. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param float value: Progress value :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ old = self.__progressBarValue self.__progressBarValue = value if value > 0: if self.__progressState != 1: warnings.warn( "progressBarSet() called without a " "preceding progressBarInit()", stacklevel=2) self.__progressState = 1 self.processingStateChanged.emit(1) usedTime = max(1, time.time() - self.startTime) totalTime = (100.0 * usedTime) / float(value) remainingTime = max(0, totalTime - usedTime) h = int(remainingTime / 3600) min = int((remainingTime - h * 3600) / 60) sec = int(remainingTime - h * 3600 - min * 60) if h > 0: text = "%(h)d:%(min)02d:%(sec)02d" % vars() else: text = "%(min)d:%(sec)02d" % vars() self.setWindowTitle( self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars()) else: self.setWindowTitle(self.captionTitle + " (0% complete)") if old != value: self.progressBarValueChanged.emit(value) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) def progressBarValue(self): return self.__progressBarValue progressBarValue = pyqtProperty(float, fset=progressBarSet, fget=progressBarValue) processingState = pyqtProperty(int, fget=lambda self: self.__progressState) def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents): self.progressBarSet(self.progressBarValue + value, processEvents) def progressBarFinished(self, processEvents=QEventLoop.AllEvents): """ Stop the widget's progress (i.e hide the progress bar). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.setWindowTitle(self.captionTitle) if self.__progressState != 0: self.__progressState = 0 self.processingStateChanged.emit(0) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) #: Widget's status message has changed. statusMessageChanged = Signal(str) def setStatusMessage(self, text): if self.__statusMessage != text: self.__statusMessage = text self.statusMessageChanged.emit(text) def statusMessage(self): return self.__statusMessage def keyPressEvent(self, e): if (int(e.modifiers()), e.key()) in OWWidget.defaultKeyActions: OWWidget.defaultKeyActions[int(e.modifiers()), e.key()](self) else: QDialog.keyPressEvent(self, e) def information(self, id=0, text=None): self.setState("Info", id, text) def warning(self, id=0, text=""): self.setState("Warning", id, text) def error(self, id=0, text=""): self.setState("Error", id, text) def setState(self, state_type, id, text): changed = 0 if type(id) == list: for val in id: if val in self.widgetState[state_type]: self.widgetState[state_type].pop(val) changed = 1 else: if type(id) == str: text = id id = 0 if not text: if id in self.widgetState[state_type]: self.widgetState[state_type].pop(id) changed = 1 else: self.widgetState[state_type][id] = text changed = 1 if changed: if type(id) == list: for i in id: self.widgetStateChanged.emit(state_type, i, "") else: self.widgetStateChanged.emit(state_type, id, text or "") tooltip_lines = [] highest_type = None for a_type in ("Error", "Warning", "Info"): msgs_for_ids = self.widgetState.get(a_type) if not msgs_for_ids: continue msgs_for_ids = list(msgs_for_ids.values()) if not msgs_for_ids: continue tooltip_lines += msgs_for_ids if highest_type is None: highest_type = a_type if highest_type is None: self.set_warning_bar(None) elif len(tooltip_lines) == 1: msg = tooltip_lines[0] if "\n" in msg: self.set_warning_bar(highest_type, msg[:msg.index("\n")] + " (...)", msg) else: self.set_warning_bar(highest_type, tooltip_lines[0], tooltip_lines[0]) else: self.set_warning_bar( highest_type, "{} problems during execution".format(len(tooltip_lines)), "\n".join(tooltip_lines)) return changed def set_warning_bar(self, state_type, text=None, tooltip=None): colors = { "Error": ("#ffc6c6", "black", QStyle.SP_MessageBoxCritical), "Warning": ("#ffffc9", "black", QStyle.SP_MessageBoxWarning), "Info": ("#ceceff", "black", QStyle.SP_MessageBoxInformation) } current_height = self.height() if state_type is None: if not self.warning_bar.isHidden(): new_height = current_height - self.warning_bar.height() self.warning_bar.setVisible(False) self.resize(self.width(), new_height) return background, foreground, icon = colors[state_type] style = QApplication.instance().style() self.warning_icon.setPixmap(style.standardIcon(icon).pixmap(14, 14)) self.warning_bar.setStyleSheet( "background-color: {}; color: {};" "padding: 3px; padding-left: 6px; vertical-align: center".format( background, foreground)) self.warning_label.setText(text) self.warning_bar.setToolTip(tooltip) if self.warning_bar.isHidden(): self.warning_bar.setVisible(True) new_height = current_height + self.warning_bar.height() self.resize(self.width(), new_height) def widgetStateToHtml(self, info=True, warning=True, error=True): iconpaths = { "Info": gui.resource_filename("icons/information.png"), "Warning": gui.resource_filename("icons/warning.png"), "Error": gui.resource_filename("icons/error.png") } items = [] for show, what in [(info, "Info"), (warning, "Warning"), (error, "Error")]: if show and self.widgetState[what]: items.append('<img src="%s" style="float: left;"> %s' % (iconpaths[what], "\n".join( self.widgetState[what].values()))) return "<br>".join(items) @classmethod def getWidgetStateIcons(cls): if not hasattr(cls, "_cached__widget_state_icons"): info = QPixmap(gui.resource_filename("icons/information.png")) warning = QPixmap(gui.resource_filename("icons/warning.png")) error = QPixmap(gui.resource_filename("icons/error.png")) cls._cached__widget_state_icons = \ {"Info": info, "Warning": warning, "Error": error} return cls._cached__widget_state_icons defaultKeyActions = {} if sys.platform == "darwin": defaultKeyActions = { (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(), (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible()) } def setBlocking(self, state=True): """ Set blocking flag for this widget. While this flag is set this widget and all its descendants will not receive any new signals from the workflow signal manager. This is useful for instance if the widget does it's work in a separate thread or schedules processing from the event queue. In this case it can set the blocking flag in it's processNewSignals method schedule the task and return immediately. After the task has completed the widget can clear the flag and send the updated outputs. .. note:: Failure to clear this flag will block dependent nodes forever. """ if self.__blocking != state: self.__blocking = state self.blockingStateChanged.emit(state) def isBlocking(self): """ Is this widget blocking signal processing. """ return self.__blocking def resetSettings(self): self.settingsHandler.reset_settings(self) def workflowEnv(self): """ Return (a view to) the workflow runtime environment. Returns ------- env : types.MappingProxyType """ return self.__env def workflowEnvChanged(self, key, value, oldvalue): """ A workflow environment variable `key` has changed to value. Called by the canvas framework to notify widget of a change in the workflow runtime environment. The default implementation does nothing. """ pass def __showMessage(self, message): if self.__msgwidget is not None: self.__msgwidget.hide() self.__msgwidget.deleteLater() self.__msgwidget = None if message is None: return buttons = MessageOverlayWidget.Ok | MessageOverlayWidget.Close if message.moreurl is not None: buttons |= MessageOverlayWidget.Help if message.icon is not None: icon = message.icon else: icon = Message.Information self.__msgwidget = MessageOverlayWidget(parent=self, text=message.text, icon=icon, wordWrap=True, standardButtons=buttons) b = self.__msgwidget.button(MessageOverlayWidget.Ok) b.setText("Ok, got it") self.__msgwidget.setStyleSheet(""" MessageOverlayWidget { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #666, stop:0.3 #6D6D6D, stop:1 #666) } MessageOverlayWidget QLabel#text-label { color: white; }""") if message.moreurl is not None: helpbutton = self.__msgwidget.button(MessageOverlayWidget.Help) helpbutton.setText("Learn more\N{HORIZONTAL ELLIPSIS}") self.__msgwidget.helpRequested.connect( lambda: QDesktopServices.openUrl(QUrl(message.moreurl))) self.__msgwidget.setWidget(self) self.__msgwidget.show() def __quicktip(self): messages = list(self.UserAdviceMessages) if messages: message = messages[self.__msgchoice % len(messages)] self.__msgchoice += 1 self.__showMessage(message) def __quicktipOnce(self): filename = os.path.join(settings.widget_settings_dir(), "user-session-state.ini") namespace = ( "user-message-history/{0.__module__}.{0.__qualname__}".format( type(self))) session_hist = QSettings(filename, QSettings.IniFormat) session_hist.beginGroup(namespace) messages = self.UserAdviceMessages def ispending(msg): return not session_hist.value("{}/confirmed".format( msg.persistent_id), defaultValue=False, type=bool) messages = list(filter(ispending, messages)) if not messages: return message = messages[self.__msgchoice % len(messages)] self.__msgchoice += 1 self.__showMessage(message) def userconfirmed(): session_hist = QSettings(filename, QSettings.IniFormat) session_hist.beginGroup(namespace) session_hist.setValue("{}/confirmed".format(message.persistent_id), True) session_hist.sync() self.__msgwidget.accepted.connect(userconfirmed)
class CompassWidget(QWidget): angleChanged = pyqtSignal(float) angle2Changed = pyqtSignal(float) def __init__(self, parent=None): QWidget.__init__(self, parent) self._angle = -9999.9 self._angle2 = -9999.9 self._margins = 10 self._pointText = { 0: "N", 45: "45", 90: "90", 135: "135", 180: "S", 225: "225", 270: "270", 315: "315" } def paintEvent(self, event): painter = QPainter() painter.begin(self) painter.setRenderHint(QPainter.Antialiasing) painter.fillRect(event.rect(), self.palette().brush(QPalette.Window)) self.drawMarkings(painter) self.drawNeedle(painter) self.drawNeedle2(painter) painter.end() def drawMarkings(self, painter): painter.save() painter.translate(self.width() / 2, self.height() / 2) scale = min((self.width() - self._margins) / 120.0, (self.height() - self._margins) / 120.0) painter.scale(scale, scale) font = QFont(self.font()) font.setPixelSize(10) metrics = QFontMetricsF(font) painter.setFont(font) painter.setPen(self.palette().color(QPalette.Shadow)) i = 0 while i < 360: if i % 45 == 0: painter.drawLine(0, -40, 0, -50) painter.drawText(-metrics.width(self._pointText[i]) / 2.0, -52, self._pointText[i]) else: painter.drawLine(0, -45, 0, -50) painter.rotate(15) i += 15 painter.restore() def drawNeedle(self, painter): if self._angle < -9999: return painter.save() painter.translate(self.width() / 2, self.height() / 2) painter.rotate(self._angle) scale = min((self.width() - self._margins) / 120.0, (self.height() - self._margins) / 120.0) painter.scale(scale, scale) painter.setPen(QPen(Qt.NoPen)) # painter.setBrush(self.palette().brush(QPalette.Shadow)) # # painter.drawPolygon( # QPolygon([QPoint(-10, 0), QPoint(0, -45), QPoint(10, 0), # QPoint(0, 45), QPoint(-10, 0)]) # ) painter.setBrush(self.palette().brush(QPalette.Highlight)) painter.drawPolygon( QPolygon([ QPoint(-5, -25), QPoint(0, -45), QPoint(5, -25), QPoint(0, -30), QPoint(-5, -25) ])) painter.restore() def drawNeedle2(self, painter): if self._angle2 < -9999: return painter.save() painter.translate(self.width() / 2, self.height() / 2) painter.rotate(self._angle2) scale = min((self.width() - self._margins) / 120.0, (self.height() - self._margins) / 120.0) painter.scale(scale, scale) painter.setPen(QPen(Qt.NoPen)) # painter.setBrush(self.palette().brush(QPalette.Dark)) # # painter.drawPolygon( # QPolygon([QPoint(-7, 0), QPoint(0, -25), QPoint(7, 0), # QPoint(0, 25), QPoint(-7, 0)]) # ) painter.setBrush(self.palette().brush(QPalette.Foreground)) painter.drawPolygon( QPolygon([ QPoint(-5, -10), QPoint(0, -25), QPoint(5, -10), QPoint(0, -13), QPoint(-5, -10) ])) painter.restore() def sizeHint(self): return QSize(150, 150) def angle(self): return self._angle def angle2(self): return self._angle2 @pyqtSlot(float) def setAngle(self, angle): if angle != self._angle: self._angle = angle self.angleChanged.emit(angle) self.update() angle = pyqtProperty(float, angle, setAngle) @pyqtSlot(float) def setAngle2(self, angle): if angle != self._angle2: self._angle2 = angle self.angle2Changed.emit(angle) self.update() angle2 = pyqtProperty(float, angle2, setAngle2) def reset(self, no=None): if no == 1: self._angle = -9999.9 elif no == 2: self._angle2 = -9999.9 else: self._angle = -9999.9 self._angle2 = -9999.9 self.update()
class IconWidget(QWidget): QT_BLACK = Qt.color1 QT_WHITE = Qt.color0 updated = pyqtSignal() def __init__(self, parent=None, size=(8, 8), zoom=8): QWidget.__init__(self, parent) self.setAttribute(Qt.WA_StaticContents) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.curColor = Qt.color0 self.zoom = zoom self.size = QSize(*size) self.newCleanImage() def penColor(self): return self.curColor def setPenColor(self, penColor): self.curColor = penColor def newCleanImage(self, size=None): if size: self.size = QSize(*size) self.image = QImage(self.size, QImage.Format_Mono) self.clearIconImage() def iconImage(self): return self.image def setIconImage(self, new_image): if new_image != self.image: self.image = new_image self.update() self.updateGeometry() def clearIconImage(self): self.image.fill(1) self.update() self.updateGeometry() def zoomFactor(self): return self.zoom def setZoomFactor(self, zoomFactor): self.zoom = zoomFactor # Qt Designer attributes penColor = pyqtProperty("QColor", penColor, setPenColor) iconImage = pyqtProperty("QImage", iconImage, setIconImage) zoomFactor = pyqtProperty("int", zoomFactor, setZoomFactor) def sizeHint(self): size = self.zoom * self.image.size() if self.zoom >= 3: size += QSize(1, 1) return size def getIconData(self): width = self.image.width() height = self.image.height() matrix = list() for y in range(height): row = list() for x in range(width): opaque = 1 if QColor.fromRgba( self.image.pixel(QPoint(x, y))).red() != 255 else 0 row.append(opaque) matrix.append(row) return np.array(matrix) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.setImagePixel(event.pos(), True) elif event.button() == Qt.RightButton: self.setImagePixel(event.pos(), False) elif event.button() == Qt.MiddleButton: # self.image.fill(0) self.clearIconImage() def mouseReleaseEvent(self, event): self.updated.emit() def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton: self.setImagePixel(event.pos(), True) elif event.buttons() == Qt.RightButton: self.setImagePixel(event.pos(), False) elif event.buttons() == Qt.MiddleButton: self.clearIconImage() def setImagePixel(self, pos, opaque): i = pos.x() / self.zoom j = pos.y() / self.zoom if self.image.rect().contains(i, j): self.image.setPixel(i, j, 0) self.update(self.pixelRect(i, j)) def paintEvent(self, event): self.painter = QPainter() self.painter.begin(self) if self.zoom >= 3: self.painter.setPen(QPalette().foreground().color()) # draw horizontal lines for i in range(self.image.width() + 1): self.painter.drawLine(self.zoom * i, 0, self.zoom * i, self.zoom * self.image.height()) # draw vertical lines for j in range(self.image.height() + 1): self.painter.drawLine(0, self.zoom * j, self.zoom * self.image.width(), self.zoom * j) for i in range(self.image.width()): for j in range(self.image.height()): rect = self.pixelRect(i, j) if not event.region().intersected(rect).isEmpty(): color = QColor.fromRgba(self.image.pixel(QPoint(i, j))) if color.red() < 255: self.painter.fillRect(rect, self.QT_WHITE) self.painter.fillRect(rect, color) self.painter.end() def pixelRect(self, i, j): if self.zoom >= 3: return QRect(self.zoom * i + 1, self.zoom * j + 1, self.zoom - 1, self.zoom - 1) else: return QRect(self.zoom * i, self.zoom * j, self.zoom, self.zoom) def getImageData(self): pass
class GdalToolsInOutSelector(QWidget, Ui_GdalToolsInOutSelector): FILE = 0x1 LAYER = 0x2 MULTIFILE = 0x4 # NOT IMPLEMENTED YET FILE_LAYER = 0x1 | 0x2 FILES = 0x1 | 0x4 # NOT IMPLEMENTED YET FILES_LAYER = 0x3 | 0x4 # NOT IMPLEMENTED YET __pyqtSignals__ = ("selectClicked()", "filenameChanged(), layerChanged()") def __init__(self, parent=None, type=None): QWidget.__init__(self, parent) self.setupUi(self) self.setFocusPolicy(Qt.StrongFocus) self.combo.setInsertPolicy(QComboBox.NoInsert) self.clear() self.typ = None if type is None: self.resetType() else: self.setType(type) self.connect(self.selectBtn, SIGNAL("clicked()"), self.selectButtonClicked) self.connect(self.fileEdit, SIGNAL("textChanged(const QString &)"), self.textChanged) self.connect(self.combo, SIGNAL("editTextChanged(const QString &)"), self.textChanged) self.connect(self.combo, SIGNAL("currentIndexChanged(int)"), self.indexChanged) def clear(self): self.filenames = [] self.fileEdit.clear() self.clearComboState() self.combo.clear() def textChanged(self): if self.getType() & self.MULTIFILE: self.filenames = self.fileEdit.text().split(",") if self.getType() & self.LAYER: index = self.combo.currentIndex() if index >= 0: text = self.combo.currentText() if text != self.combo.itemText(index): return self.setFilename(text) self.filenameChanged() def indexChanged(self): self.layerChanged() self.filenameChanged() def selectButtonClicked(self): self.emit(SIGNAL("selectClicked()")) def filenameChanged(self): self.emit(SIGNAL("filenameChanged()")) def layerChanged(self): self.emit(SIGNAL("layerChanged()")) def setType(self, type): if type == self.typ: return if type & self.MULTIFILE: # MULTITYPE IS NOT IMPLEMENTED YET type = type & ~self.MULTIFILE self.typ = type self.selectBtn.setVisible(self.getType() & self.FILE) self.combo.setVisible(self.getType() & self.LAYER) self.fileEdit.setVisible(not (self.getType() & self.LAYER)) self.combo.setEditable(self.getType() & self.FILE) if self.getType() & self.FILE: self.setFocusProxy(self.selectBtn) else: self.setFocusProxy(self.combo) # send signals to refresh connected widgets self.filenameChanged() self.layerChanged() def getType(self): return self.typ def resetType(self): self.setType(self.FILE_LAYER) selectorType = pyqtProperty("int", getType, setType, resetType) def setFilename(self, fn=None): self.blockSignals(True) prevFn, prevLayer = self.filename(), self.layer() if isinstance(fn, QgsMapLayer): fn = fn.source() elif isinstance(fn, str) or isinstance(fn, unicode): fn = unicode(fn) # TODO test elif isinstance(fn, list): if len(fn) > 0: if self.getType() & self.MULTIFILE: self.filenames = fn #fn = "".join( fn, "," ) fn = ",".join(fn) else: fn = '' else: fn = '' if not (self.getType() & self.LAYER): self.fileEdit.setText(fn) else: self.combo.setCurrentIndex(-1) self.combo.setEditText(fn) self.blockSignals(False) if self.filename() != prevFn: self.filenameChanged() if self.layer() != prevLayer: self.layerChanged() def setLayer(self, layer=None): if not (self.getType() & self.LAYER): return self.setFilename(layer) self.blockSignals(True) prevFn, prevLayer = self.filename(), self.layer() if isinstance(layer, QgsMapLayer): if self.combo.findData(layer.id()) >= 0: index = self.combo.findData(layer.id()) self.combo.setCurrentIndex(index) else: self.combo.setCurrentIndex(-1) self.combo.setEditText(layer.source()) elif isinstance(layer, int) and layer >= 0 and layer < self.combo.count(): self.combo.setCurrentIndex(layer) else: self.combo.clearEditText() self.combo.setCurrentIndex(-1) self.blockSignals(False) if self.filename() != prevFn: self.filenameChanged() if self.layer() != prevLayer: self.layerChanged() def setLayers(self, layers=None): if layers is None or not hasattr(layers, '__iter__') or len(layers) <= 0: self.combo.clear() return self.blockSignals(True) prevFn, prevLayer = self.filename(), self.layer() self.saveComboState() self.combo.clear() for l in layers: self.combo.addItem(l.name(), l.id()) self.restoreComboState() self.blockSignals(False) if self.filename() != prevFn: self.filenameChanged() if self.layer() != prevLayer: self.layerChanged() def clearComboState(self): self.prevState = None def saveComboState(self): index = self.combo.currentIndex() text = self.combo.currentText() layerID = self.combo.itemData(index) if index >= 0 else "" self.prevState = (index, text, layerID) def restoreComboState(self): if self.prevState is None: return index, text, layerID = self.prevState if index < 0: if text == '' and self.combo.count() > 0: index = 0 elif self.combo.findData(layerID) < 0: index = -1 text = "" else: index = self.combo.findData(layerID) self.combo.setCurrentIndex(index) if index >= 0: text = self.combo.itemText(index) self.combo.setEditText(text) def layer(self): if self.getType() != self.FILE and self.combo.currentIndex() >= 0: layerID = self.combo.itemData(self.combo.currentIndex()) return QgsMapLayerRegistry.instance().mapLayer(layerID) return None def filename(self): if not (self.getType() & self.LAYER): if self.getType() & self.MULTIFILE: return self.filenames return self.fileEdit.text() if self.combo.currentIndex() < 0: if self.getType() & self.MULTIFILE: return self.filenames return self.combo.currentText() layer = self.layer() if layer is not None: return layer.source() return ''
class BouncingWidget(QWidget): def __init__(self, parent = None): QWidget.__init__(self, parent) self.balls = [] random.seed() def number(self): return len(self.balls) def setNumber(self, number): if 0 <= number < len(self.balls): self.balls = self.balls[:number] elif number > len(self.balls): while len(self.balls) < number: self.balls.append( (random.random(), random.random(), random.choice((-0.025, 0.025)), random.choice((-0.025, 0.025))) ) number = pyqtProperty("int", number, setNumber) def paintEvent(self, event): w = self.width() h = self.height() painter = QPainter() painter.begin(self) painter.setRenderHint(QPainter.Antialiasing) painter.fillRect(event.rect(), QColor(0, 0, 100)) painter.setBrush(QBrush(QColor(200, 255, 200))) painter.setPen(QPen(QColor(0, 32, 0))) for x, y, dx, dy in self.balls: painter.drawEllipse((x - 0.1) * w, (y - 0.1) * h, w/10.0, h/10.0) painter.end() def showEvent(self, event): self.timer_id = self.startTimer(40) def hideEvent(self, event): self.killTimer(self.timer_id) def timerEvent(self, event): x1 = 0.1 x2 = 0.9 y1 = 0.1 y2 = 0.9 i = 0 while i < len(self.balls): x, y, dx, dy = self.balls[i] x = min(max(x1, x + dx), x2) y = min(max(y1, y + dy), y2) if x <= x1: dx = 0.025 elif x >= x2: dx = -0.025 if y <= y1: dy = 0.025 elif y >= y2: dy = -0.025 self.balls[i] = x, y, dx, dy i += 1 self.update()
class QLedIndicator(QAbstractButton): def __init__(self, parent): QAbstractButton.__init__(self) self.scaledSize = 1000 self.setMinimumSize(24, 24) self.setCheckable(True) self._onColor1 = QColor(0, 255, 0) self._onColor2 = QColor(0, 192, 0) self._offColor1 = QColor(0, 28, 0) self._offColor2 = QColor(0, 128, 0) self.show() def setOnColor1(self, color): self._onColor1 = color def getOnColor1(self): return (self.onColor1) def setOnColor2(self, color): self._onColor2 = color def getOnColor2(self): return (self.onColor2) def setOffColor1(self, color): self._offColor1 = color def getOffColor1(): return (self.offColor1) def setOffColor2(self, color): self._offColor2 = color def getOffColor2(self): return (self._offColor2) onColor1 = pyqtProperty(QColor, fget=getOnColor1, fset=setOnColor1) onColor2 = pyqtProperty(QColor, fget=getOnColor2, fset=setOnColor2) offColor1 = pyqtProperty(QColor, fget=getOffColor1, fset=setOffColor1) offColor2 = pyqtProperty(QColor, fget=getOffColor2, fset=setOffColor2) def resizeEvent(self, event): self.update() def paintEvent(self, event): realSize = min(self.width(), self.height()) painter = QPainter() painter.begin(self) pen = QPen(Qt.black) pen.setWidth(1) painter.setPen(Qt.black) painter.setRenderHint(QPainter.Antialiasing) painter.translate(self.width() / 2, self.height() / 2) painter.scale( float(realSize) / self.scaledSize, float(realSize) / self.scaledSize) gradient = QRadialGradient(QPointF(-500, -500), 1500, QPointF(-500, -500)) gradient.setColorAt(0, QColor(224, 224, 224)) gradient.setColorAt(1, QColor(28, 28, 28)) painter.setPen(pen) painter.setBrush(QBrush(gradient)) painter.drawEllipse(QPointF(0, 0), 500, 500) gradient = QRadialGradient(QPointF(500, 500), 1500, QPointF(500, 500)) gradient.setColorAt(0, QColor(224, 224, 224)) gradient.setColorAt(1, QColor(28, 28, 28)) painter.setPen(pen) painter.setBrush(QBrush(gradient)) painter.drawEllipse(QPointF(0, 0), 450, 450) painter.setPen(pen) if (self.isChecked()): gradient = QRadialGradient(QPointF(-500, -500), 1500, QPointF(-500, -500)) gradient.setColorAt(0, self._onColor1) gradient.setColorAt(1, self._onColor2) else: gradient = QRadialGradient(QPointF(500, 500), 1500, QPointF(500, 500)) gradient.setColorAt(0, self._offColor1) gradient.setColorAt(1, self._offColor2) painter.setBrush(gradient) painter.drawEllipse(QPointF(0, 0), 400, 400) painter.end()
class ShellBaseWidget(ConsoleBaseWidget): """ Shell base widget """ INITHISTORY = None SEPARATOR = None def __init__(self, parent, history_filename, debug=False, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert isinstance(history_filename, (str, unicode)) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Debug mode self.debug = debug # Simple profiling test self.profile = profile # write/flush self.__buffer = [] self.__timestamp = 0.0 # Give focus to widget self.setFocus() def setup(self): """Reimplement ConsoleBaseWidget method""" ConsoleBaseWidget.setup(self) self.set_caret(color=Qt.darkGray, width=2) self.remove_margins() # Suppressing Scintilla margins def toggle_wrap_mode(self, enable): """Reimplement ConsoleBaseWidget method: 'word' -> 'character'""" self.set_wrap_mode('character' if enable else None) def set_font(self, font): """Set shell styles font""" self.set_pythonshell_font(font) #------ Context menu def setup_context_menu(self): """Setup shell context menu""" self.menu = QMenu(self) self.cut_action = create_action(self, translate("ShellBaseWidget", "Cut"), shortcut=keybinding('Cut'), icon=get_icon('editcut.png'), triggered=self.cut) self.copy_action = create_action(self, translate("ShellBaseWidget", "Copy"), shortcut=keybinding('Copy'), icon=get_icon('editcopy.png'), triggered=self.copy) paste_action = create_action(self, translate("ShellBaseWidget", "Paste"), shortcut=keybinding('Paste'), icon=get_icon('editpaste.png'), triggered=self.paste) save_action = create_action(self, translate("ShellBaseWidget", "Save history log..."), icon=get_icon('filesave.png'), tip=translate( "ShellBaseWidget", "Save current history log (i.e. all " "inputs and outputs) in a text file"), triggered=self.save_historylog) add_actions(self.menu, (self.cut_action, self.copy_action, paste_action, None, save_action)) def contextMenuEvent(self, event): """Reimplement Qt method""" state = self.hasSelectedText() self.copy_action.setEnabled(state) self.cut_action.setEnabled(state) self.menu.popup(event.globalPos()) event.accept() #------ Input buffer def get_current_line_to_cursor(self): return self.get_text(self.current_prompt_pos, 'cursor') def get_current_line_from_cursor(self): return self.get_text('cursor', 'eof') def _select_input(self): """Select current line (without selecting console prompt)""" line, index = self.get_position('eof') if self.current_prompt_pos is None: pline, pindex = line, index else: pline, pindex = self.current_prompt_pos self.setSelection(pline, pindex, line, index) def clear_line(self): """Clear current line (without clearing console prompt)""" if self.current_prompt_pos is not None: self.remove_text(self.current_prompt_pos, 'eof') def clear_terminal(self): """ Clear terminal window Child classes reimplement this method to write prompt """ self.clear() # The buffer being edited def _set_input_buffer(self, text): """Set input buffer""" if self.current_prompt_pos is not None: self.replace_text(self.current_prompt_pos, 'eol', text) else: self.insert(text) self.set_cursor_position('eof') def _get_input_buffer(self): """Return input buffer""" input_buffer = '' if self.current_prompt_pos is not None: input_buffer = self.get_text(self.current_prompt_pos, 'eol') input_buffer = input_buffer.replace(os.linesep, '\n') return input_buffer input_buffer = pyqtProperty("QString", _get_input_buffer, _set_input_buffer) #------ Prompt def new_prompt(self, prompt): """ Print a new prompt and save its (line, index) position """ self.write(prompt, prompt=True) # now we update our cursor giving end of prompt self.current_prompt_pos = self.get_position('cursor') self.ensureCursorVisible() self.new_input_line = False def check_selection(self): """ Check if selected text is r/w, otherwise remove read-only parts of selection """ if self.current_prompt_pos is None: self.set_cursor_position('eof') else: self.truncate_selection(self.current_prompt_pos) #------ Copy / Keyboard interrupt def copy(self): """Copy text to clipboard... or keyboard interrupt""" if self.hasSelectedText(): text = unicode(self.selectedText()).replace(u"\u2029", os.linesep) QApplication.clipboard().setText(text) else: self.emit(SIGNAL("keyboard_interrupt()")) def cut(self): """Cut text""" self.check_selection() if self.hasSelectedText(): ConsoleBaseWidget.cut(self) def delete(self): """Remove selected text""" self.check_selection() if self.hasSelectedText(): ConsoleBaseWidget.removeSelectedText(self) def save_historylog(self): """Save current history log (all text in console)""" title = translate("ShellBaseWidget", "Save history log") self.emit(SIGNAL('redirect_stdio(bool)'), False) filename = QFileDialog.getSaveFileName(self, title, self.historylog_filename, "History logs (*.log)") self.emit(SIGNAL('redirect_stdio(bool)'), True) if filename: filename = osp.normpath(unicode(filename)) try: encoding.write(unicode(self.text()), filename) self.historylog_filename = filename CONF.set('main', 'historylog_filename', filename) except EnvironmentError, error: QMessageBox.critical(self, title, translate("ShellBaseWidget", "<b>Unable to save file '%1'</b>" "<br><br>Error message:<br>%2") \ .arg(osp.basename(filename)).arg(str(error)))
def install_style_properties(cls): # Diff GUI colors -- this is controllable via the style sheet if pyqtProperty is None: return for name in default_colors: setattr(cls, name, pyqtProperty('QColor', *accessors(name)))
class TextTreeNode(QGraphicsTextItem, GraphNode): def setBackgroundBrush(self, brush): if self._background_brush != brush: self._background_brush = QBrush(brush) color = brush.color() r, g, b, _ = color.getRgb() lum = 0.2126 * r + 0.7152 * g + 0.0722 * b if lum > 100: self.setDefaultTextColor(Qt.black) else: self.setDefaultTextColor(Qt.white) self.update() def backgroundBrush(self): brush = getattr(self, "_background_brush") if brush is None: brush = getattr(self.scene(), "defaultItemBrush", Qt.NoBrush) return QBrush(brush) backgroundBrush = pyqtProperty("QBrush", fget=backgroundBrush, fset=setBackgroundBrush, doc="Background brush") def __init__(self, parent, *args, **kwargs): QGraphicsTextItem.__init__(self, *args) GraphNode.__init__(self, **kwargs) self._background_brush = None self._rect = None self.parent = parent font = self.font() font.setPointSize(10) self.setFont(font) self.droplet = GraphicsDroplet(-5, 0, 10, 10, self) self.droplet.setPos(self.rect().center().x(), self.rect().height()) self.document().contentsChanged.connect(self.update_contents) self.isOpen = True self.setFlag(QGraphicsItem.ItemIsSelectable, True) def setHtml(self, html): return super().setHtml("<body>" + html + "</body>") def update_contents(self): self.setTextWidth(-1) self.setTextWidth(self.document().idealWidth()) self.droplet.setPos(self.rect().center().x(), self.rect().height()) self.droplet.setVisible(bool(self.branches)) def set_rect(self, rect): self.prepareGeometryChange() rect = QRectF() if rect is None else rect self._rect = rect self.update_contents() self.update() def shape(self): path = QPainterPath() path.addRect(self.boundingRect()) return path def rect(self): if getattr(self, "_rect", QRectF()).isValid(): return self._rect else: return QRectF(QPointF(0, 0), self.document().size()) | \ getattr(self, "_rect", QRectF(0, 0, 1, 1)) def boundingRect(self): return self._rect if getattr(self, "_rect", QRectF()).isValid() \ else super().boundingRect() @property def branches(self): return [ edge.node2 for edge in self.graph_edges() if edge.node1 is self ] def paint(self, painter, option, widget=0): painter.save() painter.setBrush(self.backgroundBrush) painter.setPen(QPen(Qt.gray)) rect = self.rect() painter.drawRoundedRect(rect, 4, 4) painter.restore() painter.setClipRect(rect) return QGraphicsTextItem.paint(self, painter, option, widget)
class UITile(QObject, Tile): """A tile visible on the screen. Every tile is only allocated once and then reshuffled and reused for every game.""" def __init__(self, element, xoffset=0.0, yoffset=0.0, level=0): QObject.__init__(self) Tile.__init__(self, element) self.__board = None self.graphics = GraphicsTileItem(self) self.__xoffset = xoffset self.__yoffset = yoffset self.__dark = False self.level = level self.activeAnimation = dict() # key is the property name self.queuedAnimations = [] def hide(self): """proxy for self.graphics""" if self.graphics: self.graphics.hide() def setBoard(self, board, xoffset=None, yoffset=None, level=None): """change Position of tile in board""" placeDirty = False if self.__board != board: oldBoard = self.__board self.__board = board if oldBoard: oldBoard.tiles.remove(self) if board: board.tiles.append(self) placeDirty = True if level is not None and self.level != level: self.level = level placeDirty = True if xoffset is not None and xoffset != self.__xoffset: self.__xoffset = xoffset placeDirty = True if yoffset is not None and yoffset != self.__yoffset: self.__yoffset = yoffset placeDirty = True if board and placeDirty: board.placeTile(self) @property def element(self): """tileName""" return Tile.element.fget(self) @element.setter def element(self, value): # pylint: disable=W0221 """set element and update display""" if value != self.element: Tile.element.fset(self, value) self.graphics.setDrawingOrder() self.graphics.update() @property def dark(self): """show face?""" return self.__dark @dark.setter def dark(self, value): """toggle and update display""" if value != self.__dark: self.__dark = value self.graphics.update() def _get_pos(self): """getter for property pos""" return self.graphics.pos() def _set_pos(self, pos): """setter for property pos""" self.graphics.setPos(pos) pos = pyqtProperty('QPointF', fget=_get_pos, fset=_set_pos) def _get_scale(self): """getter for property scale""" return self.graphics.scale() def _set_scale(self, scale): """setter for property scale""" self.graphics.setScale(scale) scale = pyqtProperty(float, fget=_get_scale, fset=_set_scale) def _get_rotation(self): """getter for property rotation""" return self.graphics.rotation() def _set_rotation(self, rotation): """setter for property rotation""" self.graphics.setRotation(rotation) rotation = pyqtProperty(float, fget=_get_rotation, fset=_set_rotation) def queuedAnimation(self, propertyName): """return the last queued animation for this tile and propertyName""" for item in reversed(self.queuedAnimations): if item.pName() == propertyName: return item def shortcutAnimation(self, animation): """directly set the end value of the animation""" setattr(self, animation.pName(), animation.unpackValue(animation.endValue())) self.queuedAnimations = [] self.graphics.setDrawingOrder() def getValue(self, pName): """gets a property value by not returning a QVariant""" return { 'pos': self.pos, 'rotation': self.rotation, 'scale': self.scale }[pName] def setActiveAnimation(self, animation): """the tile knows which of its properties are currently animated""" self.queuedAnimations = [] propName = animation.pName() assert propName not in self.activeAnimation or not isAlive( self.activeAnimation[propName]) self.activeAnimation[propName] = animation self.graphics.setCacheMode(QGraphicsItem.ItemCoordinateCache) def clearActiveAnimation(self, animation): """an animation for this tile has ended. Finalize tile in its new position""" del self.activeAnimation[animation.pName()] self.graphics.setDrawingOrder() if not len(self.activeAnimation): self.graphics.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.graphics.update() @property def focusable(self): """redirect to self.graphics.""" if not self.graphics: return False return bool(self.graphics.flags() & QGraphicsItem.ItemIsFocusable) @focusable.setter def focusable(self, value): """redirect to self.graphics and generate Debug output""" if self.element in Debug.focusable: newStr = 'focusable' if value else 'unfocusable' logDebug('%s: %s from %s' % (newStr, self.element, stack('')[-2])) self.graphics.setFlag(QGraphicsItem.ItemIsFocusable, value) @property def board(self): """get current board of this tile. Readonly.""" return self.__board @property def xoffset(self): """in logical board coordinates""" return self.__xoffset @xoffset.setter def xoffset(self, value): """in logical board coordinates""" if value != self.__xoffset: self.__xoffset = value if self.__board: self.__board.placeTile(self) @property def yoffset(self): """in logical board coordinates""" return self.__yoffset @yoffset.setter def yoffset(self, value): """in logical board coordinates. Update board display.""" if value != self.__yoffset: self.__yoffset = value if self.__board: self.__board.placeTile(self) def __str__(self): """printable string with tile""" if self.graphics: return self.graphics.__str__() else: return '%s: %s' % (id(self), self.element) def __repr__(self): return 'UITile(%s)' % str(self)
def Jx__Prop(func): '''A decorator function for easy property creation. >>> class CLS(object): ... def __init__(self): ... self._name='Runsun Pan' ... self._mod='panprop' ... self.CLS_crazy = 'Customized internal name' ... ... @prop ... def name(): pass # Simply pass ... ... @prop ... def mod(): # Read-only, customized get ... return {'fset':None, ... 'fget': lambda self: "{%s}"%self._mod } ... ... @prop ... def crazy(): # Doc string and customized prefix ... return {'prefix': 'CLS_', ... 'doc':'I can be customized!'} >>> cls = CLS() ---------------------------- default >>> cls.name 'Runsun Pan' >>> cls.name = "Pan" >>> cls.name 'Pan' --------------------------- Read-only >>> cls.mod '{panprop}' Trying to set cls.mod=??? will get: AttributeError: can't set attribute --------------------------- Customized prefix for internal name >>> cls.crazy 'Customized internal name' >>> cls.CLS_crazy 'Customized internal name' --------------------------- docstring >>> CLS.name.__doc__ '' >>> CLS.mod.__doc__ '' >>> CLS.crazy.__doc__ 'I can be customized!' --------------------------- delete >>> del cls.crazy Trying to get cls.crazy will get: AttributeError: 'CLS' object has no attribute 'CLS_crazy' ''' ops = func() or {} name=ops.get('prefix','_')+func.__name__ # property name fget=ops.get('fget',lambda self:getattr(self, name)) fset=ops.get('fset',lambda self,value:setattr(self,name,value)) fdel=ops.get('fdel',lambda self:delattr(self,name)) return pyqtProperty ('QString', fget, fset, fdel, ops.get('doc','') )
class Proxy(QObject): _expects = None _data = None _delay_timer = None onTrigger = pyqtSignal(str, 'QVariantMap', FrameData) def _trigger(self, trigger_name, trigger_args, frame_data): self.onTrigger.emit(trigger_name, trigger_args, frame_data) @pyqtSlot(str, 'QVariantMap', int, FrameData) def trigger(self, trigger_name, trigger_args, trigger_delay, frame_data): self._active = False if trigger_delay > 0: try: self._delay_timer.timeout.disconnect() except: pass self._delay_timer.timeout.connect( partial(self._trigger, trigger_name=trigger_name, trigger_args=trigger_args, frame_data=frame_data)) self._delay_timer.start(trigger_delay * 1000) else: self._trigger(trigger_name, trigger_args, frame_data) onCallHandler = pyqtSignal(str, 'QVariantMap', FrameData) @pyqtSlot(str, 'QVariantMap', FrameData) def call(self, handler_name, handler_args, frame_data): self.onCallHandler.emit(handler_name, handler_args, frame_data) _active = False def _get_active(self): return self._active def _set_active(self, value): self._active = value active = pyqtProperty(bool, fget=_get_active, fset=_set_active) _trigger_wait_page_load = False def _get_trigger_wait_page_load(self): return self._trigger_wait_page_load def _set_trigger_wait_page_load(self, value): self._trigger_wait_page_load = value trigger_wait_page_load = pyqtProperty(bool, fget=_get_trigger_wait_page_load, fset=_set_trigger_wait_page_load) def _get_expects(self): return self._expects def set_expects(self, value): self._expects = value self._active = True expects = pyqtProperty('QVariantList', fget=_get_expects) def _get_data(self): return self._data def _set_data(self, value): self._data = value data = pyqtProperty('QVariantMap', fget=_get_data, fset=_set_data) onAddQueue = pyqtSignal('QVariantMap') @pyqtSlot('QVariantMap') def add_queue(self, task): self.onQueue.emit(task) onLog = pyqtSignal(int, str) @pyqtSlot(str) def info(self, message): self.onLog.emit(INFO, message) @pyqtSlot(str) def debug(self, message): self.onLog.emit(DEBUG, message) @pyqtSlot(str) def error(self, message): self.onLog.emit(ERROR, message) @pyqtSlot(str) def warn(self, message): self.onLog.emit(WARNING, message) def __init__(self): super(Proxy, self).__init__() self._delay_timer = QTimer()