class Led(PixmapWidget): """A LED (light-emitting diode) like widget""" #: constant defining default led image filename pattern DefaultLedPattern = NAMESPACE + ":/led/led_{color}_{status}.png" #: constant defining default led color (green) DefaultLedColor = LedColor.Green #: constant defining default led status (On) DefaultLedStatus = LedStatus.On #: constant defining default led status invertion (False) DefaultLedInverted = False def __init__(self, parent=None): self.__ledStatus = self.DefaultLedStatus self.__ledColor = self.DefaultLedColor self.__ledPatternName = self.DefaultLedPattern self.__ledInverted = self.DefaultLedInverted self.__ledName = self.toLedName() PixmapWidget.__init__(self, parent) self._refresh() def sizeHint(self): return PixmapWidget.sizeHint(self) def minimumSizeHint(self): """Overwrite the default minimum size hint (0,0) to be (8, 8) :return: the minimum size hint 8, 8 :rtype: QSize""" return QtCore.QSize(8, 8) def toLedName(self, status=None, color=None, inverted=None): """Gives the led name for the given status and color. If status or color are not given, the current led status or color are used. :param status: the status :type status: bool :param color: the color :type color: str :return: string containing the led name :rtype: str""" if status is None: status = self.__ledStatus if color is None: color = self.__ledColor if inverted is None: inverted = self.__ledInverted if inverted: if status is LedStatus.On: status = LedStatus.Off else: status = LedStatus.On status = status.name.lower() color = color.name.lower() return self.__ledPatternName.format(color=color, status=status) def isLedColorValid(self, name): """Determines if the given color name is valid. :param color: the color :type color: str :return: True is the given color name is valid or False otherwise :rtype: bool""" return hasattr(LedColor, name) def _refresh(self): """internal usage only""" self.__ledName = self.toLedName() pixmap = QtGui.QPixmap(self.__ledName) self.setPixmap(pixmap) return self.update() #-------------------------------------------------------------------------- # QT property definition #-------------------------------------------------------------------------- def getLedPatternName(self): """Returns the current led pattern name :return: led pattern name :rtype: str""" return self.__ledPatternName def setLedPatternName(self, name): """Sets the led pattern name. Should be a string containing a path to valid images. The string can contain the keywords: 1. {status} - transformed to 'on' of 'off' according to the status 2. {color} - transformed to the current led color Example: **:leds/images256/led_{color}_{status}.png** will be transformed to **:leds/images256/led_red_on.png** when the led status is True and the led color is red. :param name: new pattern :type name: str""" self.__ledPatternName = name self._refresh() def resetLedPatternName(self): """Resets the led pattern to **fwk4:/Leds/led_{color}_{status}.png**. """ self.setLedPatternName(self.DefaultLedPattern) def getLedStatus(self): """Returns the led status :return: led status :rtype: bool""" return self.__ledStatus.value def setLedStatus(self, status): """Sets the led status :param status: the new status :type status: bool""" self.__ledStatus = LedStatus(status) self._refresh() def resetLedStatus(self): """Resets the led status""" self.setLedStatus(self.DefaultLedStatus) def toggleLedStatus(self): """toggles the current status of the led""" if self.__ledStatus is LedStatus.On: self.setLedStatus(LedStatus.Off) else: self.setLedStatus(LedStatus.On) def getLedInverted(self): """Returns if the led is inverted. :return: inverted mode :rtype: bool""" return self.__ledInverted def setLedInverted(self, inverted): """Sets the led inverted mode :param status: the new inverted mode :type status: bool""" self.__ledInverted = bool(inverted) self._refresh() def resetLedInverted(self): """Resets the led inverted mode""" self.setLedInverted(self.DefaultLedInverted) def getLedColor(self): """Returns the led color :return: led color :rtype: LedColor""" return self.__ledColor.value def setLedColor(self, color): """Sets the led color :param status: the new color :type status: LedColor""" self.__ledColor = LedColor(color) self._refresh() def resetLedColor(self): """Resets the led color""" self.setLedColor(self.DefaultLedColor) @classmethod def getQtDesignerPluginInfo(cls): return dict(icon=":/designer/ledred.png",) #: This property holds the led status: False means OFF, True means ON #: #: **Access functions:** #: #: * :meth:`Led.getLedStatus` #: * :meth:`Led.setLedStatus` #: * :meth:`Led.resetLedStatus` ledStatus = QtCore.Property(int, getLedStatus, setLedStatus, resetLedStatus, doc="led status") #: This property holds the led color #: #: **Access functions:** #: #: * :meth:`Led.getLedColor` #: * :meth:`Led.setLedColor` #: * :meth:`Led.resetLedColor` ledColor = QtCore.Property(int, getLedColor, setLedColor, resetLedColor, doc="led color") #: This property holds the led inverted: False means do not invert the #. status, True means invert the status #: #: **Access functions:** #: #: * :meth:`Led.getLedInverted` #: * :meth:`Led.setLedInverted` #: * :meth:`Led.resetLedInverted` ledInverted = QtCore.Property(bool, getLedInverted, setLedInverted, resetLedInverted, doc="led inverted mode") #: This property holds the led pattern name #: #: **Access functions:** #: #: * :meth:`Led.getLedPatternName` #: * :meth:`Led.setLedPatternName` #: * :meth:`Led.resetLedPatternName` ledPattern = QtCore.Property(str, getLedPatternName, setLedPatternName, resetLedPatternName, doc="led pattern name")
class GroupBox(QtGui.QWidget): """An expandable/collapsible container widget""" DefaultContentVisible = True DefaultTitleBarVisible = True DefaultTitleBarHeight = 16 DefaultStyle = GROUPBOX_NEBULA_STYLESHEET_MAP def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.__titleVisible = self.DefaultTitleBarVisible self.__contentVisible = self.DefaultContentVisible self.__contentPanel = None self.__content = None self.__style = self.DefaultStyle self.__init() self.resetStyleMap() self.resetContentVisible() self.resetTitleHeight() self.resetTitleVisible() def __init(self): panelLayout = QtGui.QVBoxLayout() panelLayout.setSpacing(0) panelLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(panelLayout) self.__titleBar = titleBar = TitleBar() panelLayout.addWidget(titleBar, 0) l = QtGui.QHBoxLayout() l.setContentsMargins(2, 2, 2, 2) l.setSpacing(2) self.__titleBar.setLayout(l) self.__titleButton = QtGui.QToolButton() self.__titleButton.setStyleSheet("border: 0px") styleOption = QtGui.QStyleOption() styleOption.initFrom(self.__titleButton) style = Application().style() icon = style.standardIcon(QtGui.QStyle.SP_DesktopIcon, styleOption, self.__titleButton) self.__titleButton.setIcon(icon) self.__titleLabel = TitleLabel() self.__upDownButton = QtGui.QToolButton() self.__upDownButton.setStyleSheet("border: 0px") self.__upDownButton.clicked.connect(self.switchContentVisible) l.addWidget(self.__titleButton, 0) l.addWidget(self.__titleLabel, 1) l.addWidget(self.__upDownButton, 0) self.__contentPanel = contentPanel = ContentPanel() panelLayout.addWidget(contentPanel, 1) def _updateStyle(self): """Internal method that updates the style""" style = GROUPBOX_STYLESHEET_TEMPLATE.format(**self.__style) self.setStyleSheet(style) def content(self): """Returns the contents widget :return: the current content widget or None if no content is set :rtype: QWidget""" return self.__contentPanel.content() def setContent(self, qwidget): """Sets the content widget :param qwidget: the content widget or None :type qwidget: QWidget""" self.__contentPanel.setContent(qwidget) def titleBar(self): """Returns the title bar widget :return: the title bar widget :rtype: QFrame""" return self.__titleBar def titleButton(self): """Returns the title button widget :return: the title button widget :rtype: QToolButton""" return self.__titleButton def collapseButton(self): """Returns the collapse button widget :return: the collapse button widget :rtype: QToolButton""" return self.__upDownButton def setTitle(self, title): """Sets this widget's title :param title:the new widget title :type title: str""" self.__titleLabel.setText(title) self.setToolTip("<html>The <b>{0}</b>".format(title)) def getTitle(self): """Returns this widget's title :return: this widget's title :rtype: str""" return self.__titleLabel.text() def setTitleIcon(self, icon): """Sets this widget's title icon :param icon: (Qt.QIcon) the new widget title icon""" self.__titleButton.setIcon(icon) def getTitleIcon(self): """Returns this widget's title icon :return: this widget's title icon :rtype: QIcon""" return self.__titleButton.icon() def switchContentVisible(self): """Switches this widget's contents visibility""" self.setContentVisible(not self.isContentVisible()) def isContentVisible(self): """Returns this widget's contents visibility :return: this widget's contents visibility :rtype: bool""" return self.__contentVisible def resetContentVisible(self): """Resets this widget's contents visibility""" self.setContentVisible(self.DefaultContentVisible) def setContentVisible(self, show): """Sets this widget's contents visibility :param show: the new widget contents visibility :type show: bool""" self.__contentVisible = show if show: icon_name = QtGui.QStyle.SP_TitleBarShadeButton else: icon_name = QtGui.QStyle.SP_TitleBarUnshadeButton icon = self.style().standardIcon(icon_name) self.__upDownButton.setIcon(icon) self.__contentPanel.setVisible(show) self.adjustSize() def isTitleVisible(self): """Returns this widget's title visibility :return: this widget's title visibility :rtype: bool""" return self.__titleVisible def resetTitleVisible(self): """Resets this widget's title visibility""" self.setTitleVisible(self.DefaultTitleBarVisible) def setTitleVisible(self, show): """Sets this widget's title visibility :param show: the new widget title visibility :type show: bool""" self.__titleVisible = show self.__titleBar.setVisible(show) def getTitleHeight(self): """Returns this widget's title height :return: this widget's title height :rtype: int""" return self.titleButton().iconSize().height() def setTitleHeight(self, height): """Sets this widget's title height :param height: the new widget title height :type height: int""" s = QtCore.QSize(height, height) self.titleButton().setIconSize(s) self.collapseButton().setIconSize(s) def resetTitleHeight(self): """Resets this widget's title height""" self.setTitleHeight(self.DefaultTitleBarHeight) def getStyleMap(self): """Returns this widget's style :return: this widget's style :rtype: dict""" return self.__style def setStyleMap(self, style_map): """Sets this widget's title style Used key/values for style_map: - 'title_start_color' : brush (Ex.: '#E0E0E0') - 'title_stop_color' : brush (Ex.: '#E0E0E0') - 'title_font_color' : brush (Ex.: '#E0E0E0') - 'title_border_radius': radius (Ex.: '5px') - 'content_start_color' : brush (Ex.: '#E0E0E0') - 'content_stop_color' : brush (Ex.: '#E0E0E0') - 'content_border_radius': radius (Ex.: '5px') :param style_map: the new widget title style :type style_map: dict""" style = self.DefaultStyle.copy() style.update(style_map) self.__style = style self._updateStyle() def resetStyleMap(self): """Resets this widget's title style""" self.setStyleMap({}) @classmethod def getQtDesignerPluginInfo(cls): from qarbon.qt.designer.plugins.base import \ DesignerBaseSingleContainerExtension return dict(icon=":/designer/groupwidget.png", container=True, container_extension=DesignerBaseSingleContainerExtension) #: This property contains the widget's title #: #: **Access functions:** #: #: * :meth:`getTitle` #: * :meth:`setTitle` title = QtCore.Property(str, getTitle, setTitle) #: This property contains the widget's title icon #: #: **Access functions:** #: #: * :meth:`getTitleIcon` #: * :meth:`setTitleIcon` titleIcon = QtCore.Property("QIcon", getTitleIcon, setTitleIcon) #: This property contains the widget's title height #: #: **Access functions:** #: #: * :meth:`getTitleHeight` #: * :meth:`setTitleHeight` #: * :meth:`resetTitleHeight` titleHeight = QtCore.Property(int, getTitleHeight, setTitleHeight, resetTitleHeight) #: This property contains the widget's title visibility #: #: **Access functions:** #: #: * :meth:`isTitleVisible` #: * :meth:`setTitleVisible` titleVisible = QtCore.Property(bool, isTitleVisible, setTitleVisible) #: This property contains the widget's style map #: #: **Access functions:** #: #: * :meth:`getStyleMap` #: * :meth:`setStyleMap` #: * :meth:`resetStyleMap` styleMap = QtCore.Property(dict, getStyleMap, setStyleMap, resetStyleMap, designable=False) #: This property contains the widget's content's visibility #: #: **Access functions:** #: #: * :meth:`isContentVisible` #: * :meth:`setContentVisible` #: * :meth:`resetContentVisible` contentVisible = QtCore.Property(bool, isContentVisible, setContentVisible, resetContentVisible)
class PixmapWidget(QtGui.QWidget): """This widget displays an image (pixmap). By default the pixmap is scaled to the widget size and the aspect ratio is kept. The default alignment of the pixmap inside the widget space is horizontal left, vertical center.""" DefaultAlignment = QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter DefaultAspectRatioMode = QtCore.Qt.KeepAspectRatio DefaultTransformationMode = QtCore.Qt.SmoothTransformation #: Signal emited when pixmap source changes pixmapChanged = QtCore.Signal() def __init__(self, parent=None): self._pixmap = QtGui.QPixmap() self._pixmapDrawn = None self._alignment = self.DefaultAlignment self._pixmapAspectRatioMode = self.DefaultAspectRatioMode self._pixmapTransformationMode = self.DefaultTransformationMode QtGui.QWidget.__init__(self, parent) def _getPixmap(self): if self._pixmapDrawn is None: self._pixmapDrawn = self.recalculatePixmap() return self._pixmapDrawn def recalculatePixmap(self): origPixmap = self._pixmap if origPixmap.isNull(): return origPixmap return origPixmap.scaled(self.size(), self._pixmapAspectRatioMode, self._pixmapTransformationMode) def _setDirty(self): self._pixmapDrawn = None def paintEvent(self, paintEvent): """Overwrite the paintEvent from QWidget to draw the pixmap""" pixmap = self._getPixmap() w, h = self.width(), self.height() painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.Antialiasing) pw, ph = pixmap.width(), pixmap.height() align = self._alignment hAlign = align & QtCore.Qt.AlignHorizontal_Mask vAlign = align & QtCore.Qt.AlignVertical_Mask x, y = 0, 0 if hAlign & QtCore.Qt.AlignHCenter: x = (w - pw) / 2 elif hAlign & QtCore.Qt.AlignRight: x = w - pw if vAlign & QtCore.Qt.AlignVCenter: y = (h - ph) / 2 elif vAlign & QtCore.Qt.AlignBottom: y = h - ph x, y = max(0, x), max(0, y) painter.drawPixmap(x, y, pixmap) def resizeEvent(self, event): self._setDirty() return QtGui.QWidget.resizeEvent(self, event) def sizeHint(self): return self._pixmap.size() #-------------------------------------------------------------------------- # QT property definition #-------------------------------------------------------------------------- def getPixmap(self): """Returns the pixmap.Returns None if no pixmap is set. :return: the current pixmap :rtype: QtGui.QPixmap""" return self._pixmap def setPixmap(self, pixmap): """Sets the pixmap for this widget. Setting it to None disables pixmap :param pixmap: the new pixmap :type pixmap: QtGui.QPixmap""" # make sure to make a copy because of bug in PyQt 4.4. This is actually # not copying the internal bitmap, just the qpixmap, so there is no # performance penalty here self._pixmap = QtGui.QPixmap(pixmap) self._setDirty() self.update() self.pixmapChanged.emit() def resetPixmap(self): """Resets the pixmap for this widget.""" self.setPixmap(QtGui.QPixmap()) def getAspectRatioMode(self): """Returns the aspect ratio to apply when drawing the pixmap. :return: the current aspect ratio :rtype: QtCore.Qt.AspectRatioMode""" return self._pixmapAspectRatioMode def setAspectRatioMode(self, aspect): """Sets the aspect ratio mode to apply when drawing the pixmap. :param pixmap: the new aspect ratio mode :type pixmap: QtCore.Qt.AspectRatioMode""" self._pixmapAspectRatioMode = aspect self._setDirty() self.update() def resetAspectRatioMode(self): """Resets the aspect ratio mode to KeepAspectRatio""" self.setAspectRatioMode(self.DefaultAspectRatioMode) def getTransformationMode(self): """Returns the transformation mode to apply when drawing the pixmap. :return: the current transformation mode :rtype: QtCore.Qt.TransformationMode""" return self._pixmapTransformationMode def setTransformationMode(self, transformation): """Sets the transformation mode to apply when drawing the pixmap. :param pixmap: the new transformation mode :type pixmap: QtCore.Qt.TransformationMode""" self._pixmapTransformationMode = transformation self._setDirty() self.update() def resetTransformationMode(self): """Resets the transformation mode to SmoothTransformation""" self.setTransformationMode(self.DefaultTransformationMode) def getAlignment(self): """Returns the alignment to apply when drawing the pixmap. :return: the current alignment :rtype: QtCore.Qt.Alignment""" return self._alignment def setAlignment(self, alignment): """Sets the alignment to apply when drawing the pixmap. :param pixmap: the new alignment :type pixmap: QtCore.Qt.Alignment""" self._alignment = alignment self.update() def resetAlignment(self): """Resets the transformation mode to QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter""" self.setAlignment(self.DefaultAlignment) #: This property holds the widget's pixmap #: #: **Access functions:** #: #: * :meth:`PixmapWidget.getPixmap` #: * :meth:`PixmapWidget.setPixmap` #: * :meth:`PixmapWidget.resetPixmap` pixmap = QtCore.Property("QPixmap", getPixmap, setPixmap, resetPixmap, doc="the widget's pixmap") #: This property holds the widget's pixmap aspect ratio mode #: #: **Access functions:** #: #: * :meth:`PixmapWidget.getAspectRatioMode` #: * :meth:`PixmapWidget.setAspectRatioMode` #: * :meth:`PixmapWidget.resetAspectRatioMode` aspectRatioMode = QtCore.Property("Qt::AspectRatioMode", getAspectRatioMode, setAspectRatioMode, resetAspectRatioMode, doc="the widget's pixmap aspect ratio "\ "mode") #: This property holds the widget's pixmap transformation mode #: #: **Access functions:** #: #: * :meth:`PixmapWidget.getTransformationMode` #: * :meth:`PixmapWidget.setTransformationMode` #: * :meth:`PixmapWidget.resetTransformationMode` transformationMode = QtCore.Property("Qt::TransformationMode", getTransformationMode, setTransformationMode, resetTransformationMode, doc="the widget's pixmap "\ "transformation mode") #: This property holds the widget's pixmap alignment #: #: **Access functions:** #: #: * :meth:`PixmapWidget.getAlignment` #: * :meth:`PixmapWidget.setAlignment` #: * :meth:`PixmapWidget.resetAlignment` alignment = QtCore.Property("Qt::Alignment", getAlignment, setAlignment, resetAlignment, doc="the widget's pixmap alignment")
class XCommandWidget(QtGui.QWidget): """A widget displaying an X11 window inside from a command. Example:: from qarbon.external.qt import QtGui from qarbon.qt.gui.application import Application from qarbon.qt.gui.x11 import XCommandWidget app = Application() w = QtGui.QMainWindow() cmdWidget = XCommandWidget(parent=w) cmdWidget.command = 'xterm' cmdWidget.winIdParam = '-into' cmdWidget.start() w.setCentralWidget(cmdWidget) w.show() app.exec_()""" DefaultAutoRestart = False DefaultWinIdParam = '-into' def __init__(self, parent=None): super(XCommandWidget, self).__init__(parent) self.__process = QtCore.QProcess(self) self.__x11_widget = x11_widget = QtGui.QX11EmbedContainer(self) layout = QtGui.QVBoxLayout(self) layout.setMargin(0) layout.setSpacing(0) layout.addWidget(x11_widget) x11_widget.error.connect(self.__onError) self.resetCommand() self.resetAutoRestart() self.resetWinIdParam() self.resetExtraParams() def __onError(self, error): log.error("XEmbedContainer: Error") def __convert_wait(self, wait): if wait: if wait < 0: wait = -1 else: wait = int(wait * 1000) return wait def __finish(self, finish_func, wait=0): process = self.__process wait = self.__convert_wait(wait) finish_func() if wait: return process.waitForFinished(msecs=wait) def getX11WinId(self): return self.getX11Widget().winId() def getX11Widget(self): return self.__x11_widget def getProcess(self): return self.__process def getCommand(self): return self.__command def setCommand(self, command): self.__command = command if command is None: self.setWindowTitle("<None>") else: self.setWindowTitle(command) def resetCommand(self): self.setCommand(None) def getWinIdParam(self): return self.__winIdParam def setWinIdParam(self, winIdParam): self.__winIdParam = winIdParam def resetWinIdParam(self): self.setWinIdParam(self.DefaultWinIdParam) def setExtraParams(self, params): if params is None: params = [] self.__extraParams = params def getExtraParams(self): return self.__extraParams def resetExtraParams(self): self.setExtraParams(None) def setAutoRestart(self, yesno): self.__autoRestart = yesno def getAutoRestart(self): return self.__autoRestart def resetAutoRestart(self): return self.setAutoRestart(self.DefaultAutoRestart) def setWorkingDirectory(self, wd): if wd is not None: self.getProcess().setWorkingDirectory(wd) def getWorkingDirectory(self): return self.getProcess().workingDirectory() def start(self, wait=0): """wait < 0 -> wait forever, wait == 0 -> not wait, wait > 0 -> wait amount in seconds""" if self.__command is None: raise Exception("Cannot start: no command") if self.__winIdParam is None: raise Exception("Cannot start: no winIdParam") process = self.__process params = [self.__winIdParam, str(self.getX11WinId())] + \ self.__extraParams process.start(self.__command, params) wait = self.__convert_wait(wait) if wait: return process.waitForStarted(msecs=wait) def restart(self, wait=0): self.terminate(wait=-1) return self.start(wait=wait) def kill(self, wait=0): return self.__finish(self.__process.kill, wait=wait) def terminate(self, wait=0): return self.__finish(self.__process.terminate, wait=wait) def __del__(self): import sip if not sip.isdeleted(self.__process): log.debug("X11CommandWidget: __del__ terminates x11...") self.terminate() else: log.debug("X11CommandWidget: __del__ does nothing...") def deleteLater(self): log.debug("X11CommandWidget: deleteLater...") self.terminate(wait=-1) return super(XCommandWidget, self).deleteLater() @classmethod def getQtDesignerPluginInfo(cls): return dict(icon=":/designer/xorg.png", tooltip="XTerm widget") command = QtCore.Property(str, getCommand, setCommand, resetCommand) winIdParam = QtCore.Property(str, getWinIdParam, setWinIdParam, resetWinIdParam) extraParams = QtCore.Property("QStringList", getExtraParams, setExtraParams, resetExtraParams) autoRestart = QtCore.Property(bool, getAutoRestart, setAutoRestart, resetAutoRestart) workingDirectory = QtCore.Property(str, getWorkingDirectory, setWorkingDirectory)
class XCommandWindow(QtGui.QMainWindow): """The QMainWindow version of :class:`XCommandWidget`. Example:: from qarbon.external.qt import QtGui from qarbon.qt.gui.application import Application from qarbon.qt.gui.x11 import XCommandWindow app = Application() w = XCommandWindow() w.command = 'xterm' w.winIdParam = '-into' w.start() w.show() app.exec_()""" Widget = XCommandWidget def __init__(self, **kwargs): parent = kwargs.pop('parent', None) flags = kwargs.pop('flags', QtCore.Qt.WindowFlags()) super(XCommandWindow, self).__init__(parent=parent, flags=flags) x11 = self.Widget(parent=self, **kwargs) self.setCentralWidget(x11) toolBar = self.addToolBar("Actions") self.__actionsToolBar = weakref.ref(toolBar) self.__restartAction = Action("Restart", parent=self, icon=Icon("view-refresh"), tooltip="restart the current command", triggered=self.restart) toolBar.addAction(self.__restartAction) def XWidget(self): return self.centralWidget() def start(self, wait=0): self.XWidget().start(wait=wait) def restart(self, wait=0): self.XWidget().restart(wait=wait) def terminate(self, wait=0): self.XWidget().terminate(wait=wait) def getCommand(self): return self.XWidget().command def setCommand(self, command): self.XWidget().command = command def resetCommand(self): self.XWidget().resetCommand() def getWinIdParam(self): return self.XWidget().winIdParam def setWinIdParam(self, winIdParam): self.XWidget().winIdParam = winIdParam def resetWinIdParam(self): self.XWidget().resetWinIdParam() def setExtraParams(self, params): self.XWidget().extraParams = params def getExtraParams(self): return self.XWidget().extraParams def resetExtraParams(self): self.XWidget().resetExtraParams() def setAutoRestart(self, yesno): self.XWidget().autoRestart = yesno def getAutoRestart(self): return self.XWidget().autoRestart def resetAutoRestart(self): self.XWidget().resetAutoRestart() def setWorkingDirectory(self, wd): self.XWidget().workingDirectory = wd def getWorkingDirectory(self): return self.XWidget().workingDirectory command = QtCore.Property(str, getCommand, setCommand, resetCommand) winIdParam = QtCore.Property(str, getWinIdParam, setWinIdParam, resetWinIdParam) extraParams = QtCore.Property("QStringList", getExtraParams, setExtraParams, resetExtraParams) autoRestart = QtCore.Property(bool, getAutoRestart, setAutoRestart, resetAutoRestart) workingDirectory = QtCore.Property(str, getWorkingDirectory, setWorkingDirectory)
class AxesWidget(GroupBox): """A multiple axis widget.""" DefaultUpdateStatusBar = True def __init__(self, title=None, axes=None, parent=None): super(AxesWidget, self).__init__(parent) self._axes = {} contentWidget = QtGui.QWidget() layout = QtGui.QGridLayout() layout.setColumnStretch(Column.Position.value, 1) layout.setContentsMargins(2, 2, 2, 2) layout.setSpacing(2) contentWidget.setLayout(layout) self.setContent(contentWidget) self.setTitle(title) self.setTitleIcon(Icon(":/objects/motor.png")) self.setAxes(axes) self.resetUpdateStatusBar() def axes(self): return self._axes def setAxes(self, axes): for axis_id in self._axes: self.removeAxisID(axis_id) if axes is None: return for axis in axes: self.addAxis(axis) def addAxis(self, axis): self._axes[axis.name] = axis self.__buildAxisGUI(axis) axis.positionChanged.connect(self.onAxisPositionChanged) axis.stateChanged.connect(self.onAxisStateChanged) axis.labelChanged.connect(self.onAxisLabelChanged) axis.stepsChanged.connect(self.onAxisStepsChanged) axis.currentStepChanged.connect(self.onAxisCurrentStepChanged) axis.limitsChanged.connect(self.onAxisLimitsChanged) axis.unitChanged.connect(self.onAxisUnitChanged) def removeAxisID(self, axis_id): self.removeAxis(self.getAxis(axis_id)) def removeAxis(self, axis): axis.positionChanged.disconnect(self.onAxisPositionChanged) axis.stateChanged.disconnect(self.onAxisStateChanged) axis.labelChanged.disconnect(self.onAxisLabelChanged) axis.stepsChanged.disconnect(self.onAxisStepsChanged) axis.currentStepChanged.disconnect(self.onAxisCurrentStepChanged) axis.limitsChanged.disconnect(self.onAxisLimitsChanged) axis.unitChanged.disconnect(self.onAxisUnitChanged) layout = self.content().layout() for role in Column: w = self.axisColumnWidget(axis, role) layout.removeWidget(w) w.setParent(None) self._axes.pop(axis.id) def getAxis(self, name): return self._axes[name] def getAxisByRole(self, role): for axis in self._axes.values(): if axis.role == role: return axis raise KeyError(role) def __buildAxisGUI(self, axis): row = axis.index layout = self.content().layout() # create widgets label_widget = DisplayLabel(axis) position_widget = ValueSpinBox(axis) icon_widget = QtGui.QLabel() steps_widget = StepSize(axis) step_left_widget = StepLeftButton(axis) step_right_widget = StepRightButton(axis) stop_widget = StopButton(axis) # add widgets to container layout.addWidget(label_widget, row, Column.Label.value) layout.addWidget(position_widget, row, Column.Position.value) layout.addWidget(icon_widget, row, Column.Icon.value) layout.addWidget(steps_widget, row, Column.Steps.value) layout.addWidget(step_left_widget, row, Column.StepLeft.value) layout.addWidget(step_right_widget, row, Column.StepRight.value) layout.addWidget(stop_widget, row, Column.Stop.value) # initialize values label_widget.setValue(axis.label) position_widget.setValue(axis.position) position_widget.setState(axis.state) position_widget.setRange(*axis.limits) position_widget.setSingleStep(axis.currentStep) position_widget.setUnit(axis.unit) steps_widget.setSteps(axis.steps) steps_widget.setCurrentStep(axis.currentStep) # set buddy label_widget.setBuddy(position_widget) icon_widget.hide() # connect signals steps_widget.activated.connect(self.onUserCurrentStepsChanged) position_widget.valueApplied.connect(self.onUserPositionApplied) position_widget.valueChanged.connect(self.onUserPositionChanged) step_left_widget.clicked.connect(self.onUserStepLeft) step_right_widget.clicked.connect(self.onUserStepRight) stop_widget.clicked.connect(self.onUserStop) # initialize enable/disable and tooltips self.__updateAxis(axis) def axisColumnWidget(self, axis, role): layout = self.content().layout() return layout.itemAtPosition(axis.index, role.value).widget() def axisIDColumnWidget(self, name, role): return self.axisColumnWidget(self.getAxis(name), role) def setAxisColumnVisible(self, axis, role, show=True): self.axisColumnWidget(axis, role).setVisible(show) def setAxisIDColumnVisible(self, name, role, show=True): self.setAxisColumnVisible(self.getAxis(name), role, show=show) def setColumnVisible(self, role, show=True): for axis in self._axes.values(): self.setAxisColumnVisible(axis, role, show=show) def __updateAxis(self, axis): state = axis.state position = axis.position step = axis.currentStep min_value, max_value = axis.limits label_widget = self.axisColumnWidget(axis, Column.Label) position_widget = self.axisColumnWidget(axis, Column.Position) steps_widget = self.axisColumnWidget(axis, Column.Steps) step_left_widget = self.axisColumnWidget(axis, Column.StepLeft) step_right_widget = self.axisColumnWidget(axis, Column.StepRight) stop_widget = self.axisColumnWidget(axis, Column.Stop) if state is State.Moving: position_widget.setEnabled(False) step_left_widget.setEnabled(False) step_right_widget.setEnabled(False) label_widget.setModified(False) else: position_widget.setEnabled(True) step_left_widget.setEnabled((position - step) >= min_value) step_right_widget.setEnabled((position + step) <= max_value) label_widget.setModified(position_widget.value() != position) toolTip = axis.toolTip() label_widget.setToolTip(toolTip) position_widget.setToolTip(toolTip) steps_widget.setToolTip(toolTip) step_left_widget.setToolTip(toolTip) step_right_widget.setToolTip(toolTip) stop_widget.setToolTip(toolTip) def refreshAxes(self): for axis in self.axes().values(): axis.refresh() # # slots to react on user interaction # def onUserPositionApplied(self): widget = self.sender() axis = widget.axis position = widget.value() label = self.axisColumnWidget(axis, Column.Label) label.setModified(False) axis.move(position) def onUserPositionChanged(self, value): widget = self.sender() axis = widget.axis label_w = self.axisColumnWidget(axis, Column.Label) label_w.setModified(widget.value() != axis.position) def onUserCurrentStepsChanged(self, index): widget = self.sender() step = widget.itemData(index) axis = widget.axis axis.currentStep = step position_widget = self.axisColumnWidget(axis, Column.Position) position_widget.setSingleStep(step) self.__updateAxis(axis) def onUserStepLeft(self): widget = self.sender() axis = widget.axis axis.stepDown() def onUserStepRight(self): widget = self.sender() axis = widget.axis axis.stepUp() def onUserStop(self): widget = self.sender() axis = widget.axis axis.stop() # # axis has changed programatically through a signal coming from the model # def onAxisPositionChanged(self, name, position): axis = self.getAxis(name) position_widget = self.axisColumnWidget(axis, Column.Position) position_widget.setValue(position, emit=False) self.__updateAxis(axis) def onAxisStateChanged(self, name, old_state, state): axis = self.getAxis(name) position_widget = self.axisColumnWidget(axis, Column.Position) position_widget.setState(state) if self.updateStatusBar and old_state != state: icon = Icon(state) message = axis.label + " " if state == State.Moving: message += "started to move..." elif old_state == State.Moving: message += "stopped!" else: message += "changed from {0} to {1}".format( old_state.name, state.name) self.__setStatus(message, icon) self.__updateAxis(axis) def onAxisLabelChanged(self, name, label): axis = self.getAxis(name) label_widget = self.axisColumnWidget(axis, Column.Label) label_widget.setValue(label) def onAxisStepsChanged(self, name, steps): axis = self.getAxis(name) steps_widget = self.axisColumnWidget(axis, Column.Steps) steps_widget.setSteps(steps) self.__updateAxis(axis) def onAxisCurrentStepChanged(self, name, step): """Steps changed from the Axis model: - change the current step value on the combo box - change the step size on the position spin box - update (enable/disable the stepLeft and stepRight buttons according to the current axis limits) """ axis = self.getAxis(name) steps_widget = self.axisColumnWidget(axis, Column.Steps) position_widget = self.axisColumnWidget(axis, Column.Position) steps_widget.setCurrentStep(step) position_widget.setSingleStep(step) self.__updateAxis(axis) def onAxisLimitsChanged(self, name, limits): axis = self.getAxis(name) position_widget = self.axisColumnWidget(axis, Column.Position) if limits is None: limits = [float("-inf"), float("+inf")] position_widget.setRange(*limits) self.__updateAxis(axis) def onAxisUnitChanged(self, name, unit): axis = self.getAxis(name) position_widget = self.axisColumnWidget(axis, Column.Position) position_widget.setUnit(unit) self.__updateAxis(axis) def __setStatus(self, message, icon=QtGui.QStyle.SP_MessageBoxInformation): if not self.updateStatusBar: return statusBar = getStatusBar(self) if statusBar is None: return if hasattr(statusBar, 'setStatus'): statusBar.setStatus(message, icon) else: statusBar.showMessage(message) def setUpdateStatusBar(self, update): self.__updateStatusBar = update def getUdpateStatusBar(self): return self.__updateStatusBar def resetUpdateStatusBar(self): self.setUpdateStatusBar(self.DefaultUpdateStatusBar) @classmethod def getQtDesignerPluginInfo(cls): return dict(icon=":/designer/motor.png", tooltip="a multiple axis (axes) widget") #: This property sets if the widget should update stauts bar with messages #: #: **Access functions:** #: #: * :meth:`AxesWidget.getUdpateStatusBar` #: * :meth:`AxesWidget.setUpdateStatusBar` #: * :meth:`AxesWidget.resetUpdateStatusBar` updateStatusBar = QtCore.Property(bool, getUdpateStatusBar, setUpdateStatusBar, resetUpdateStatusBar)
class Axis(QtCore.QObject): # : position changed signal # : # : emitted when the axis position has changed # : the signal is emitted with the axis name and position value positionChanged = QtCore.Signal(str, float) # : position changed signal # : # : emitted when the axis minimum allowed position has changed # : the signal is emitted with the axis name and minimum position value limitsChanged = QtCore.Signal(str, list) # : step sizes changed signal # : # : emitted when the allowed axis step sizes has changed # : the signal is emitted with the axis name and step sizes stepsChanged = QtCore.Signal(str, object) # : current step changed signal # : # : emitted when the current step size has changed # : the signal is emitted with the axis name and current step size currentStepChanged = QtCore.Signal(str, float) # : state changed signal # : # : emitted when the axis state has changed # : the signal is emitted with the axis name, old state and new state stateChanged = QtCore.Signal(str, object, object) # : label changed signal # : # : emitted when the axis label has changed # : the signal is emitted with the axis name and label value labelChanged = QtCore.Signal(str, str) # : units changed signal # : # : emitted when the axis units has changed # : the signal is emitted with the axis name and units value unitChanged = QtCore.Signal(str, str) def __init__(self, axis_info, axes, parent=None): super(Axis, self).__init__(parent) self._axes = weakref.ref(axes) self.name = axis_info['name'] self.index = axis_info['index'] self.role = axis_info.get('role', str(self.index)) self._label = axis_info.get('username', self.name) self._position = None # float('nan') self._limits = None # float('-inf'), float('+inf') self._state = None self._steps = None self._current_step = None self._unit = None @property def axes(self): return self._axes() def refresh(self): self.state = self.getState(cache=False) self.limits = self.getLimits(cache=False) self.position = self.getPosition(cache=False) def getPosition(self, cache=True): if cache and self._position is not None: result = self._position else: self._position = result = self.axes.position(self.name) return result def setPosition(self, position, emit=True): self._position = position if emit: self.positionChanged.emit(self.name, position) #: This property contains the axis position #: #: **Access functions:** #: #: * :meth:`Axis.getPosition` #: * :meth:`Axis.setPosition` position = QtCore.Property(str, getPosition, setPosition) def getLimits(self, cache=True): if cache and self._limits is not None: result = self._limits else: self._limits = result = list(self.axes.limits(self.name)) return result def setLimits(self, limits, emit=True): self._limits = list(limits) if emit: self.limitsChanged.emit(self.name, limits) #: This property contains the axis limits #: #: **Access functions:** #: #: * :meth:`Axis.getLimits` #: * :meth:`Axis.setLimits` limits = QtCore.Property(list, getLimits, setLimits) def getState(self, cache=True): if cache and self._state is not None: result = self._state else: self._state = result = self.axes.state(self.name) return result def setState(self, state, emit=True): old_state = self._state if state is None: state = State._Invalid self._state = state if emit: self.stateChanged.emit(self.name, old_state, state) #: This property contains the axis state #: #: **Access functions:** #: #: * :meth:`Axis.getState` #: * :meth:`Axis.setState` state = QtCore.Property(object, getState, setState) def getLabel(self): return self._label def setLabel(self, label, emit=True): if label is None: label = "" self._label = label if emit: self.labelChanged.emit(self.name, label) #: This property contains the axis label #: #: **Access functions:** #: #: * :meth:`Axis.getLabel` #: * :meth:`Axis.setLabel` label = QtCore.Property(str, getLabel, setLabel) def getSteps(self): return self._steps def setSteps(self, steps, emit=True): if steps is None: steps = [] self._steps = steps if emit: self.stepsChanged.emit(self.name, steps) #: This property contains the axis steps #: #: **Access functions:** #: #: * :meth:`Axis.getSteps` #: * :meth:`Axis.setSteps` steps = QtCore.Property(str, getSteps, setSteps) def getCurrentStep(self): return self._current_step def setCurrentStep(self, current_step, emit=True): self._current_step = current_step if emit: self.currentStepChanged.emit(self.name, current_step) #: This property contains the axis current step size #: #: **Access functions:** #: #: * :meth:`Axis.getCurrentStep` #: * :meth:`Axis.setCurrentStep` currentStep = QtCore.Property(float, getCurrentStep, setCurrentStep) def getUnit(self): return self._unit def setUnit(self, unit, emit=True): if unit is None: unit = "" self._unit = unit if emit: self.unitChanged.emit(self.name, unit) #: This property contains the axis unit #: #: **Access functions:** #: #: * :meth:`Axis.getUnit` #: * :meth:`Axis.setUnit` unit = QtCore.Property(float, getUnit, setUnit) def move(self, absolute_position): self.axes.move(self.name, absolute_position) def moveRelative(self, relative_position): self.move(self.position + relative_position) def moveUp(self): self.moveRelative(+self.currentStep) def moveDown(self): self.moveRelative(-self.currentStep) stepUp = moveUp stepDown = moveDown def stop(self): self.axes.abort(self.name) ToolTipTemplate = """<html>axis <u>{axis.label}</u> is in \ <b>{axis.state.name}</b> state, at position <b>{axis.position}</b><br/> Limits set to <b>[{axis.limits[0]}, {axis.limits[1]}]</b><br/> (the hardware name for this axis is: <i>{axis.name}</i>)""" def toolTip(self): return self.ToolTipTemplate.format(axis=self)
class XEmbedCommandWidget(QtGui.QX11EmbedContainer): """A widget displaying an X11 window inside from a command. Example:: from qarbon.external.qt import QtGui from qarbon.qt.gui.application import Application from qarbon.qt.gui.x11 import XEmbedCommandWidget app = Application() w = QtGui.QMainWindow() cmdWidget = XEmbedCommandWidget(parent=w) cmdWidget.command = 'xterm' cmdWidget.winIdParam = '-into' cmdWidget.start() w.setCentralWidget(cmdWidget) w.show() app.exec_()""" DefaultAutoRestart = False DefaultWinIdParam = '-into' def __init__(self, parent=None): super(XEmbedCommandWidget, self).__init__(parent) self.error.connect(self.__onError) self.__process = process = QtCore.QProcess(self) self.resetCommand() self.resetAutoRestart() self.resetWinIdParam() self.resetExtraParams() def __onError(self, error): logging.error("XEmbedContainer: Error") def getProcess(self): return self.__process def getCommand(self): return self.__command def setCommand(self, command): self.__command = command if command is None: self.setWindowTitle("<None>") else: self.setWindowTitle(command) def resetCommand(self): self.setCommand(None) def getWinIdParam(self): return self.__winIdParam def setWinIdParam(self, winIdParam): self.__winIdParam = winIdParam def resetWinIdParam(self): self.setWinIdParam(self.DefaultWinIdParam) def setExtraParams(self, params): if params is None: params = [] self.__extraParams = params def getExtraParams(self): return self.__extraParams def resetExtraParams(self): self.setExtraParams(None) def setAutoRestart(self, yesno): self.__autoRestart = yesno def getAutoRestart(self): return self.__autoRestart def resetAutoRestart(self): return self.setAutoRestart(self.DefaultAutoRestart) def setWorkingDirectory(self, wd): if wd is not None: self.getProcess().setWorkingDirectory(wd) def getWorkingDirectory(self): return self.getProcess().workingDirectory() def __convert_wait(self, wait): if wait: if wait < 0: wait = -1 else: wait = int(wait * 1000) return wait def start(self, wait=0): """wait < 0 -> wait forever, wait == 0 -> not wait, wait > 0 -> wait amount in seconds""" if self.__command is None: raise Exception("Cannot start: no command") if self.__winIdParam is None: raise Exception("Cannot start: no winIdParam") process = self.__process params = [self.__winIdParam, str(self.winId())] + self.__extraParams process.start(self.__command, params) wait = self.__convert_wait(wait) if wait: return process.waitForStarted(msecs=wait) def restart(self, wait=0): self.terminate(wait=-1) return self.start(wait=wait) def __finish(self, finish_func, wait=0): process = self.__process wait = self.__convert_wait(wait) finish_func() if wait: return process.waitForFinished(msecs=wait) def kill(self, wait=0): return self.__finish(self.__process.kill, wait=wait) def terminate(self, wait=0): return self.__finish(self.__process.terminate, wait=wait) def event(self, event): ret = super(XEmbedCommandWidget, self).event(event) etype = event.type() if etype == QtCore.QEvent.ParentAboutToChange: if self.__process.state() != QtCore.QProcess.NotRunning: self.terminate() elif etype == QtCore.QEvent.ParentChange: if self.autoRestart: logging.info("restarting...") self.restart(wait=3) return ret def destroy(self, *args, **kwargs): print("Destroying embeded widget") logging.warning("Destroying embeded widget") self.terminate(wait=-1) return super(XEmbedCommandWidget, self).destroy(*args, **kwargs) command = QtCore.Property(str, getCommand, setCommand, resetCommand) winIdParam = QtCore.Property(str, getWinIdParam, setWinIdParam, resetWinIdParam) extraParams = QtCore.Property("QStringList", getExtraParams, setExtraParams, resetExtraParams) autoRestart = QtCore.Property(bool, getAutoRestart, setAutoRestart, resetAutoRestart) workingDirectory = QtCore.Property(str, getWorkingDirectory, setWorkingDirectory)