Example #1
0
class XTabBar(QTabBar):
    resized = qt.Signal()

    def resizeEvent(self, event):
        """
        Updates the position of the additional buttons when this widget \
        resizes.
        
        :param      event | <QResizeEvet>
        """
        super(XTabBar, self).resizeEvent(event)
        self.resized.emit()
Example #2
0
class XPopupWidget(QWidget):
    """ """
    Direction = enum('North', 'South', 'East', 'West')
    Mode = enum('Popup', 'Dialog', 'ToolTip')
    Anchor = enum('TopLeft', 'TopCenter', 'TopRight', 'LeftTop', 'LeftCenter',
                  'LeftBottom', 'RightTop', 'RightCenter', 'RightBottom',
                  'BottomLeft', 'BottomCenter', 'BottomRight')

    aboutToShow = qt.Signal()
    accepted = qt.Signal()
    closed = qt.Signal()
    rejected = qt.Signal()
    resetRequested = qt.Signal()
    shown = qt.Signal()
    buttonClicked = qt.Signal(QAbstractButton)

    def __init__(self, parent=None, buttons=None):
        super(XPopupWidget, self).__init__(parent)

        # define custom properties
        self._anchor = XPopupWidget.Anchor.TopCenter
        self._autoCalculateAnchor = False
        self._autoCloseOnAccept = True
        self._autoCloseOnReject = True
        self._autoCloseOnFocusOut = False
        self._autoDefault = True
        self._first = True
        self._animated = False
        self._currentMode = None
        self._positionLinkedTo = []

        # define controls
        self._resizable = True
        self._popupPadding = 10
        self._titleBarVisible = True
        self._buttonBoxVisible = True
        self._dialogButton = QToolButton(self)
        self._closeButton = QToolButton(self)
        self._scrollArea = QScrollArea(self)
        self._sizeGrip = QSizeGrip(self)
        self._sizeGrip.setFixedWidth(12)
        self._sizeGrip.setFixedHeight(12)

        self._leftSizeGrip = QSizeGrip(self)
        self._leftSizeGrip.setFixedWidth(12)
        self._leftSizeGrip.setFixedHeight(12)

        if buttons is None:
            buttons = QDialogButtonBox.NoButton

        self._buttonBox = QDialogButtonBox(buttons, Qt.Horizontal, self)
        self._buttonBox.setContentsMargins(3, 0, 3, 9)

        self._scrollArea.setWidgetResizable(True)
        self._scrollArea.setFrameShape(QScrollArea.NoFrame)
        self._scrollArea.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Expanding)

        palette = self.palette()
        self._scrollArea.setPalette(palette)

        self._dialogButton.setToolTip('Popout to Dialog')
        self._closeButton.setToolTip('Close Popup')

        for btn in (self._dialogButton, self._closeButton):
            btn.setAutoRaise(True)
            btn.setIconSize(QSize(14, 14))
            btn.setMaximumSize(16, 16)

        # setup the icons
        icon = QIcon(projexui.resources.find('img/dialog.png'))
        self._dialogButton.setIcon(icon)

        icon = QIcon(projexui.resources.find('img/close.png'))
        self._closeButton.setIcon(icon)

        # define the ui
        hlayout = QHBoxLayout()
        hlayout.setSpacing(0)
        hlayout.addStretch(1)
        hlayout.addWidget(self._dialogButton)
        hlayout.addWidget(self._closeButton)
        hlayout.setContentsMargins(0, 0, 0, 0)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self._buttonBox)
        hlayout2.setContentsMargins(0, 0, 3, 0)

        vlayout = QVBoxLayout()
        vlayout.addLayout(hlayout)
        vlayout.addWidget(self._scrollArea)
        vlayout.addLayout(hlayout2)
        vlayout.setContentsMargins(3, 2, 3, 2)
        vlayout.setSpacing(0)

        self.setLayout(vlayout)
        self.setPositionLinkedTo(parent)

        # set default properties
        self.setAutoFillBackground(True)
        self.setBackgroundRole(QPalette.Button)
        self.setWindowTitle('Popup')
        self.setFocusPolicy(Qt.StrongFocus)
        self.setCurrentMode(XPopupWidget.Mode.Popup)

        # create connections
        self._dialogButton.clicked.connect(self.setDialogMode)
        self._closeButton.clicked.connect(self.reject)
        self._buttonBox.accepted.connect(self.accept)
        self._buttonBox.rejected.connect(self.reject)
        self._buttonBox.clicked.connect(self.handleButtonClick)

    def addButton(self, button, role=QDialogButtonBox.ActionRole):
        """
        Adds a custom button to the button box for this popup widget.
        
        :param      button | <QAbstractButton> || <str>
        
        :return     <button> || None (based on if a button or string was given)
        """
        return self._buttonBox.addButton(button, role)

    def adjustContentsMargins(self):
        """
        Adjusts the contents for this widget based on the anchor and \
        mode.
        """
        anchor = self.anchor()
        mode = self.currentMode()

        # margins for a dialog
        if (mode == XPopupWidget.Mode.Dialog):
            self.setContentsMargins(0, 0, 0, 0)

        # margins for a top anchor point
        elif (anchor &
              (XPopupWidget.Anchor.TopLeft | XPopupWidget.Anchor.TopCenter
               | XPopupWidget.Anchor.TopRight)):
            self.setContentsMargins(0, self.popupPadding() + 5, 0, 0)

        # margins for a bottom anchor point
        elif (
                anchor &
            (XPopupWidget.Anchor.BottomLeft | XPopupWidget.Anchor.BottomCenter
             | XPopupWidget.Anchor.BottomRight)):
            self.setContentsMargins(0, 0, 0, self.popupPadding())

        # margins for a left anchor point
        elif (anchor &
              (XPopupWidget.Anchor.LeftTop | XPopupWidget.Anchor.LeftCenter
               | XPopupWidget.Anchor.LeftBottom)):
            self.setContentsMargins(self.popupPadding(), 0, 0, 0)

        # margins for a right anchor point
        else:
            self.setContentsMargins(0, 0, self.popupPadding(), 0)

        self.adjustMask()

    def adjustMask(self):
        """
        Updates the alpha mask for this popup widget.
        """
        if self.currentMode() == XPopupWidget.Mode.Dialog:
            self.clearMask()
            return

        path = self.borderPath()
        bitmap = QBitmap(self.width(), self.height())
        bitmap.fill(QColor('white'))

        painter = QPainter()
        painter.begin(bitmap)
        painter.setRenderHint(QPainter.Antialiasing)
        pen = QPen(QColor('black'))
        pen.setWidthF(0.75)
        painter.setPen(pen)
        painter.setBrush(QColor('black'))
        painter.drawPath(path)
        painter.end()

        self.setMask(bitmap)

    def adjustSize(self):
        """
        Adjusts the size of this popup to best fit the new widget size.
        """
        widget = self.centralWidget()
        if widget is None:
            super(XPopupWidget, self).adjustSize()
            return

        widget.adjustSize()
        hint = widget.minimumSizeHint()
        size = widget.minimumSize()

        width = max(size.width(), hint.width())
        height = max(size.height(), hint.height())

        width += 20
        height += 20

        if self._buttonBoxVisible:
            height += self.buttonBox().height() + 10

        if self._titleBarVisible:
            height += max(self._dialogButton.height(),
                          self._closeButton.height()) + 10

        curr_w = self.width()
        curr_h = self.height()

        # determine if we need to move based on our anchor
        anchor = self.anchor()
        if anchor & (self.Anchor.LeftBottom | self.Anchor.RightBottom | \
                       self.Anchor.BottomLeft | self.Anchor.BottomCenter | \
                       self.Anchor.BottomRight):
            delta_y = height - curr_h

        elif anchor & (self.Anchor.LeftCenter | self.Anchor.RightCenter):
            delta_y = (height - curr_h) / 2

        else:
            delta_y = 0

        if anchor & (self.Anchor.RightTop | self.Anchor.RightCenter | \
                       self.Anchor.RightTop | self.Anchor.TopRight):
            delta_x = width - curr_w

        elif anchor & (self.Anchor.TopCenter | self.Anchor.BottomCenter):
            delta_x = (width - curr_w) / 2

        else:
            delta_x = 0

        self.setMinimumSize(width, height)
        self.resize(width, height)

        pos = self.pos()
        pos.setX(pos.x() - delta_x)
        pos.setY(pos.y() - delta_y)

        self.move(pos)

    @qt.Slot()
    def accept(self):
        """
        Emits the accepted signal and closes the popup.
        """
        if not self.signalsBlocked():
            self.accepted.emit()

        if self.autoCloseOnAccept():
            self.close()

    def anchor(self):
        """
        Returns the anchor point for this popup widget.
        
        :return     <XPopupWidget.Anchor>
        """
        return self._anchor

    def autoCalculateAnchor(self):
        """
        Returns whether or not this popup should calculate the anchor point
        on popup based on the parent widget and the popup point.
        
        :return     <bool>
        """
        return self._autoCalculateAnchor

    def autoCloseOnAccept(self):
        """
        Returns whether or not this popup widget manages its own close on accept
        behavior.
        
        :return     <bool>
        """
        return self._autoCloseOnAccept

    def autoCloseOnReject(self):
        """
        Returns whether or not this popup widget manages its own close on reject
        behavior.
        
        :return     <bool>
        """
        return self._autoCloseOnReject

    def autoCloseOnFocusOut(self):
        """
        Returns whether or not this popup widget should auto-close when the user
        clicks off the view.
        
        :return     <bool>
        """
        return self._autoCloseOnFocusOut

    def autoDefault(self):
        """
        Returns whether or not clicking enter should default to the accept key.
        
        :return     <bool>
        """
        return self._autoDefault

    def borderPath(self):
        """
        Returns the border path that will be drawn for this widget.
        
        :return     <QPainterPath>
        """

        path = QPainterPath()

        x = 1
        y = 1
        w = self.width() - 2
        h = self.height() - 2
        pad = self.popupPadding()
        anchor = self.anchor()

        # create a path for a top-center based popup
        if anchor == XPopupWidget.Anchor.TopCenter:
            path.moveTo(x, y + pad)
            path.lineTo(x + ((w / 2) - pad), y + pad)
            path.lineTo(x + (w / 2), y)
            path.lineTo(x + ((w / 2) + pad), y + pad)
            path.lineTo(x + w, y + pad)
            path.lineTo(x + w, y + h)
            path.lineTo(x, y + h)
            path.lineTo(x, y + pad)

        # create a path for a top-left based popup
        elif anchor == XPopupWidget.Anchor.TopLeft:
            path.moveTo(x, y + pad)
            path.lineTo(x + pad, y)
            path.lineTo(x + 2 * pad, y + pad)
            path.lineTo(x + w, y + pad)
            path.lineTo(x + w, y + h)
            path.lineTo(x, y + h)
            path.lineTo(x, y + pad)

        # create a path for a top-right based popup
        elif anchor == XPopupWidget.Anchor.TopRight:
            path.moveTo(x, y + pad)
            path.lineTo(x + w - 2 * pad, y + pad)
            path.lineTo(x + w - pad, y)
            path.lineTo(x + w, y + pad)
            path.lineTo(x + w, y + h)
            path.lineTo(x, y + h)
            path.lineTo(x, y + pad)

        # create a path for a bottom-left based popup
        elif anchor == XPopupWidget.Anchor.BottomLeft:
            path.moveTo(x, y)
            path.lineTo(x + w, y)
            path.lineTo(x + w, y + h - pad)
            path.lineTo(x + 2 * pad, y + h - pad)
            path.lineTo(x + pad, y + h)
            path.lineTo(x, y + h - pad)
            path.lineTo(x, y)

        # create a path for a south based popup
        elif anchor == XPopupWidget.Anchor.BottomCenter:
            path.moveTo(x, y)
            path.lineTo(x + w, y)
            path.lineTo(x + w, y + h - pad)
            path.lineTo(x + ((w / 2) + pad), y + h - pad)
            path.lineTo(x + (w / 2), y + h)
            path.lineTo(x + ((w / 2) - pad), y + h - pad)
            path.lineTo(x, y + h - pad)
            path.lineTo(x, y)

        # create a path for a bottom-right based popup
        elif anchor == XPopupWidget.Anchor.BottomRight:
            path.moveTo(x, y)
            path.lineTo(x + w, y)
            path.lineTo(x + w, y + h - pad)
            path.lineTo(x + w - pad, y + h)
            path.lineTo(x + w - 2 * pad, y + h - pad)
            path.lineTo(x, y + h - pad)
            path.lineTo(x, y)

        # create a path for a right-top based popup
        elif anchor == XPopupWidget.Anchor.RightTop:
            path.moveTo(x, y)
            path.lineTo(x + w - pad, y)
            path.lineTo(x + w, y + pad)
            path.lineTo(x + w - pad, y + 2 * pad)
            path.lineTo(x + w - pad, y + h)
            path.lineTo(x, y + h)
            path.lineTo(x, y)

        # create a path for a right-center based popup
        elif anchor == XPopupWidget.Anchor.RightCenter:
            path.moveTo(x, y)
            path.lineTo(x + w - pad, y)
            path.lineTo(x + w - pad, y + ((h / 2) - pad))
            path.lineTo(x + w, y + (h / 2))
            path.lineTo(x + w - pad, y + ((h / 2) + pad))
            path.lineTo(x + w - pad, y + h)
            path.lineTo(x, y + h)
            path.lineTo(x, y)

        # create a path for a right-bottom based popup
        elif anchor == XPopupWidget.Anchor.RightBottom:
            path.moveTo(x, y)
            path.lineTo(x + w - pad, y)
            path.lineTo(x + w - pad, y + h - 2 * pad)
            path.lineTo(x + w, y + h - pad)
            path.lineTo(x + w - pad, y + h)
            path.lineTo(x, y + h)
            path.lineTo(x, y)

        # create a path for a left-top based popup
        elif anchor == XPopupWidget.Anchor.LeftTop:
            path.moveTo(x + pad, y)
            path.lineTo(x + w, y)
            path.lineTo(x + w, y + h)
            path.lineTo(x + pad, y + h)
            path.lineTo(x + pad, y + 2 * pad)
            path.lineTo(x, y + pad)
            path.lineTo(x + pad, y)

        # create a path for an left-center based popup
        elif anchor == XPopupWidget.Anchor.LeftCenter:
            path.moveTo(x + pad, y)
            path.lineTo(x + w, y)
            path.lineTo(x + w, y + h)
            path.lineTo(x + pad, y + h)
            path.lineTo(x + pad, y + ((h / 2) + pad))
            path.lineTo(x, y + (h / 2))
            path.lineTo(x + pad, y + ((h / 2) - pad))
            path.lineTo(x + pad, y)

        # create a path for a left-bottom based popup
        elif anchor == XPopupWidget.Anchor.LeftBottom:
            path.moveTo(x + pad, y)
            path.lineTo(x + w, y)
            path.lineTo(x + w, y + h)
            path.lineTo(x + pad, y + h)
            path.lineTo(x, y + h - pad)
            path.lineTo(x + pad, y + h - 2 * pad)
            path.lineTo(x + pad, y)

        return path

    def buttonBox(self):
        """
        Returns the button box that is used to control this popup widget.
        
        :return     <QDialogButtonBox>
        """
        return self._buttonBox

    def centralWidget(self):
        """
        Returns the central widget that is being used by this popup.
        
        :return     <QWidget>
        """
        return self._scrollArea.widget()

    def close(self):
        """
        Closes the popup widget and central widget.
        """
        widget = self.centralWidget()
        if widget and not widget.close():
            return

        super(XPopupWidget, self).close()

    def closeEvent(self, event):
        widget = self.centralWidget()
        if widget and not widget.close() and \
           self.currentMode() != XPopupWidget.Mode.ToolTip:
            event.ignore()
        else:
            super(XPopupWidget, self).closeEvent(event)

        self.closed.emit()

    def currentMode(self):
        """
        Returns the current mode for this widget.
        
        :return     <XPopupWidget.Mode>
        """
        return self._currentMode

    @deprecatedmethod('XPopupWidget',
                      'Direction is no longer used, use anchor instead')
    def direction(self):
        """
        Returns the current direction parameter for this widget.
        
        :return     <XPopupWidget.Direction>
        """
        anchor = self.anchor()
        if (anchor &
            (XPopupWidget.Anchor.TopLeft | XPopupWidget.Anchor.TopCenter
             | XPopupWidget.Anchor.TopRight)):
            return XPopupWidget.Direction.North

        elif (
                anchor &
            (XPopupWidget.Anchor.BottomLeft | XPopupWidget.Anchor.BottomCenter
             | XPopupWidget.Anchor.BottomRight)):
            return XPopupWidget.Direction.South

        elif (anchor &
              (XPopupWidget.Anchor.LeftTop | XPopupWidget.Anchor.LeftCenter
               | XPopupWidget.Anchor.LeftBottom)):
            return XPopupWidget.Direction.East

        else:
            return XPopupWidget.Direction.West

    def eventFilter(self, object, event):
        """
        Processes when the window is moving to update the position for the
        popup if in popup mode.
        
        :param      object | <QObject>
                    event  | <QEvent>
        """
        links = self.positionLinkedTo()
        is_dialog = self.currentMode() == self.Mode.Dialog
        if object not in links:
            return False

        if event.type() == event.Close:
            self.close()
            return False

        if event.type() == event.Hide and not is_dialog:
            self.hide()
            return False

        if event.type() == event.Move and not is_dialog:
            deltaPos = event.pos() - event.oldPos()
            self.move(self.pos() + deltaPos)
            return False

        if self.currentMode() != self.Mode.ToolTip:
            return False

        if event.type() == event.Leave:
            pos = object.mapFromGlobal(QCursor.pos())
            if (not object.rect().contains(pos)):
                self.close()
                event.accept()
                return True

        if event.type() in (event.MouseButtonPress, event.MouseButtonDblClick):
            self.close()
            event.accept()
            return True

        return False

    @qt.Slot(QAbstractButton)
    def handleButtonClick(self, button):
        """
        Handles the button click for this widget.  If the Reset button was
        clicked, then the resetRequested signal will be emitted.  All buttons
        will emit the buttonClicked signal.
        
        :param      button | <QAbstractButton>
        """
        if (self.signalsBlocked()):
            return

        if (button == self._buttonBox.button(QDialogButtonBox.Reset)):
            self.resetRequested.emit()

        self.buttonClicked.emit(button)

    def isAnimated(self):
        """
        Returns whether or not the popup widget should animate its opacity
        when it is shown.
        
        :return     <bool>
        """
        return self._animated

    def isResizable(self):
        """
        Returns if this popup is resizable or not.
        
        :return     <bool>
        """
        return self._resizable

    def keyPressEvent(self, event):
        """
        Looks for the Esc key to close the popup.
        
        :param      event | <QKeyEvent>
        """
        if (event.key() == Qt.Key_Escape):
            self.reject()
            event.accept()
            return

        elif (event.key() in (Qt.Key_Return, Qt.Key_Enter)):
            if self._autoDefault:
                self.accept()
                event.accept()
            return

        super(XPopupWidget, self).keyPressEvent(event)

    def mapAnchorFrom(self, widget, globalPos):
        """
        Returns the anchor point that best fits within the given widget from
        the inputed global position.
        
        :param      widget      | <QWidget>
                    globalPos   | <QPoint>
        
        :return     <XPopupWidget.Anchor>
        """
        localPos = widget.mapFromGlobal(globalPos)

        x = localPos.x()
        y = localPos.y()
        w = widget.width()
        h = widget.height()

        cw = self.width() / 2
        ch = self.height() / 2

        # by default, try to do a center point, so make sure the center point
        # is at least 1/2 the width longer from each edge
        if x < cw and h - y < ch:
            return XPopupWidget.Anchor.BottomLeft
        elif x < cw:
            return XPopupWidget.Anchor.TopLeft
        elif w - x < cw and h - y < ch:
            return XPopupWidget.Anchor.BottomRight
        elif w - x < cw:
            return XPopupWidget.Anchor.TopRight
        elif h - y < ch:
            return XPopupWidget.Anchor.BottomCenter
        else:
            return XPopupWidget.Anchor.TopCenter

    def popup(self, pos=None):
        """
        Pops up this widget at the inputed position.  The inputed point should \
        be in global space.
        
        :param      pos | <QPoint>
        
        :return     <bool> success
        """
        if self._first and self.centralWidget() is not None:
            self.adjustSize()
            self._first = False

        if not self.signalsBlocked():
            self.aboutToShow.emit()

        if not pos:
            pos = QCursor.pos()

        if self.currentMode() == XPopupWidget.Mode.Dialog and \
             self.isVisible():
            return False

        elif self.currentMode() == XPopupWidget.Mode.Dialog:
            self.setPopupMode()

        # auto-calculate the point
        if self.autoCalculateAnchor():
            self.setAnchor(self.mapAnchorFrom(self.parent(), pos))

        pad = self.popupPadding()

        # determine where to move based on the anchor
        anchor = self.anchor()

        # MODIFY X POSITION
        # align x-left
        if (anchor &
            (XPopupWidget.Anchor.TopLeft | XPopupWidget.Anchor.BottomLeft)):
            pos.setX(pos.x() - pad)

        # align x-center
        elif (anchor & (XPopupWidget.Anchor.TopCenter
                        | XPopupWidget.Anchor.BottomCenter)):
            pos.setX(pos.x() - self.width() / 2)

        # align x-right
        elif (
                anchor &
            (XPopupWidget.Anchor.TopRight | XPopupWidget.Anchor.BottomRight)):
            pos.setX(pos.x() - self.width() + pad)

        # align x-padded
        elif (anchor &
              (XPopupWidget.Anchor.RightTop | XPopupWidget.Anchor.RightCenter
               | XPopupWidget.Anchor.RightBottom)):
            pos.setX(pos.x() - self.width())

        # MODIFY Y POSITION
        # align y-top
        if (anchor &
            (XPopupWidget.Anchor.LeftTop | XPopupWidget.Anchor.RightTop)):
            pos.setY(pos.y() - pad)

        # align y-center
        elif (anchor & (XPopupWidget.Anchor.LeftCenter
                        | XPopupWidget.Anchor.RightCenter)):
            pos.setY(pos.y() - self.height() / 2)

        # align y-bottom
        elif (anchor & (XPopupWidget.Anchor.LeftBottom
                        | XPopupWidget.Anchor.RightBottom)):
            pos.setY(pos.y() - self.height() + pad)

        # align y-padded
        elif (
                anchor &
            (XPopupWidget.Anchor.BottomLeft | XPopupWidget.Anchor.BottomCenter
             | XPopupWidget.Anchor.BottomRight)):
            pos.setY(pos.y() - self.height())

        self.adjustMask()
        self.move(pos)
        self.update()
        self.setUpdatesEnabled(True)

        if self.isAnimated():
            anim = QPropertyAnimation(self, 'windowOpacity')
            anim.setParent(self)
            anim.setStartValue(0.0)
            anim.setEndValue(self.windowOpacity())
            anim.setDuration(500)
            anim.finished.connect(anim.deleteLater)
            self.setWindowOpacity(0.0)
        else:
            anim = None

        self.show()

        if self.currentMode() != XPopupWidget.Mode.ToolTip:
            self.activateWindow()

            widget = self.centralWidget()
            if widget:
                self.centralWidget().setFocus()

        if anim:
            anim.start()

        if not self.signalsBlocked():
            self.shown.emit()

        return True

    def paintEvent(self, event):
        """
        Overloads the paint event to handle painting pointers for the popup \
        mode.
        
        :param      event | <QPaintEvent>
        """
        # use the base technique for the dialog mode
        if self.currentMode() == XPopupWidget.Mode.Dialog:
            super(XPopupWidget, self).paintEvent(event)
            return

        # setup the coloring options
        palette = self.palette()

        painter = QPainter()
        painter.begin(self)

        pen = QPen(palette.color(palette.Window).darker(130))
        pen.setWidthF(1.75)
        painter.setPen(pen)
        painter.setRenderHint(painter.Antialiasing)
        painter.setBrush(palette.color(palette.Window))
        painter.drawPath(self.borderPath())
        painter.end()

    def popupPadding(self):
        """
        Returns the amount of pixels to pad the popup arrow for this widget.
        
        :return     <int>
        """
        return self._popupPadding

    def positionLinkedTo(self):
        """
        Returns the widget that this popup is linked to for positional changes.
        
        :return     [<QWidget>, ..]
        """
        return self._positionLinkedTo

    @qt.Slot()
    def reject(self):
        """
        Emits the accepted signal and closes the popup.
        """
        if not self.signalsBlocked():
            self.rejected.emit()

        if self.autoCloseOnReject():
            self.close()

    def resizeEvent(self, event):
        """
        Resizes this widget and updates the mask.
        
        :param      event | <QResizeEvent>
        """
        self.setUpdatesEnabled(False)
        super(XPopupWidget, self).resizeEvent(event)

        self.adjustMask()
        self.setUpdatesEnabled(True)

        x = self.width() - self._sizeGrip.width()
        y = self.height() - self._sizeGrip.height()

        self._leftSizeGrip.move(0, y)
        self._sizeGrip.move(x, y)

    def scrollArea(self):
        """
        Returns the scroll area widget for this popup.
        
        :return     <QScrollArea>
        """
        return self._scrollArea

    def setAnimated(self, state):
        """
        Sets whether or not the popup widget should animate its opacity
        when it is shown.
        
        :param      state | <bool>
        """
        self._animated = state
        self.setAttribute(Qt.WA_TranslucentBackground, state)

    def setAutoCloseOnAccept(self, state):
        """
        Sets whether or not the popup handles closing for accepting states.
        
        :param      state | <bool>
        """
        self._autoCloseOnAccept = state

    def setAutoCloseOnReject(self, state):
        """
        Sets whether or not the popup handles closing for rejecting states.
        
        :param      state | <bool>
        """
        self._autoCloseOnReject = state

    def setAutoDefault(self, state):
        """
        Sets whether or not the buttons should respond to defaulting options
        when the user is interacting with it.
        
        :param      state | <bool>
        """
        self._autoDefault = state
        for button in self.buttonBox().buttons():
            button.setAutoDefault(state)
            button.setDefault(state)

    def setAnchor(self, anchor):
        """
        Sets the anchor position for this popup widget to the inputed point.
        
        :param      anchor | <XPopupWidget.Anchor>
        """
        self._anchor = anchor
        self.adjustContentsMargins()

    def setAutoCalculateAnchor(self, state):
        """
        Sets whether or not this widget should auto-calculate the anchor point
        based on the parent position when the popup is triggered.
        
        :param      state | <bool>
        """
        self._autoCalculateAnchor = state

    def setAutoCloseOnFocusOut(self, state):
        """
        Sets whether or not this popup widget should auto-close when the user
        clicks off the view.
        
        :param     state | <bool>
        """
        self._autoCloseOnFocusOut = state
        self.updateModeSettings()

    def setCentralWidget(self, widget):
        """
        Sets the central widget that will be used by this popup.
        
        :param      widget | <QWidget> || None
        """
        self._scrollArea.takeWidget()
        self._scrollArea.setWidget(widget)

        self.adjustSize()

    def setCurrentMode(self, mode):
        """
        Sets the current mode for this dialog to the inputed mode.
        
        :param      mode | <XPopupWidget.Mode>
        """
        if (self._currentMode == mode):
            return

        self._currentMode = mode
        self.updateModeSettings()

    @qt.Slot()
    def setDialogMode(self):
        """
        Sets the current mode value to Dialog.
        """
        self.setCurrentMode(XPopupWidget.Mode.Dialog)

    @deprecatedmethod('XPopupWidget',
                      'Direction is no longer used, use setAnchor instead')
    def setDirection(self, direction):
        """
        Sets the direction for this widget to the inputed direction.
        
        :param      direction | <XPopupWidget.Direction>
        """
        if (direction == XPopupWidget.Direction.North):
            self.setAnchor(XPopupWidget.Anchor.TopCenter)

        elif (direction == XPopupWidget.Direction.South):
            self.setAnchor(XPopupWidget.Anchor.BottomCenter)

        elif (direction == XPopupWidget.Direction.East):
            self.setAnchor(XPopupWidget.Anchor.LeftCenter)

        else:
            self.setAnchor(XPopupWidget.Anchor.RightCenter)

    def setPalette(self, palette):
        """
        Sets the palette for this widget and the scroll area.
        
        :param      palette | <QPalette>
        """
        super(XPopupWidget, self).setPalette(palette)
        self._scrollArea.setPalette(palette)

    def setPopupMode(self):
        """
        Sets the current mode value to Popup.
        """
        self.setCurrentMode(XPopupWidget.Mode.Popup)

    def setPopupPadding(self, padding):
        """
        Sets the amount to pad the popup area when displaying this widget.
        
        :param      padding | <int>
        """
        self._popupPadding = padding
        self.adjustContentsMargins()

    def setPositionLinkedTo(self, widgets):
        """
        Sets the widget that this popup will be linked to for positional
        changes.
        
        :param      widgets | <QWidget> || [<QWidget>, ..]
        """
        if type(widgets) in (list, set, tuple):
            new_widgets = list(widgets)
        else:
            new_widgets = []
            widget = widgets
            while widget:
                widget.installEventFilter(self)
                new_widgets.append(widget)
                widget = widget.parent()

        self._positionLinkedTo = new_widgets

    def setResizable(self, state):
        self._resizable = state
        self._sizeGrip.setVisible(state)
        self._leftSizeGrip.setVisible(state)

    def setShowButtonBox(self, state):
        self._buttonBoxVisible = state
        self.buttonBox().setVisible(state)

    def setShowTitleBar(self, state):
        self._titleBarVisible = state
        self._dialogButton.setVisible(state)
        self._closeButton.setVisible(state)

    def setToolTipMode(self):
        """
        Sets the mode for this popup widget to ToolTip
        """
        self.setCurrentMode(XPopupWidget.Mode.ToolTip)

    def setVisible(self, state):
        super(XPopupWidget, self).setVisible(state)
        widget = self.centralWidget()
        if widget:
            widget.setVisible(state)

    def timerEvent(self, event):
        """
        When the timer finishes, hide the tooltip popup widget.
        
        :param      event | <QEvent>
        """
        if self.currentMode() == XPopupWidget.Mode.ToolTip:
            self.killTimer(event.timerId())
            event.accept()
            self.close()
        else:
            super(XPopupWidget, self).timerEvent(event)

    def updateModeSettings(self):
        mode = self.currentMode()
        is_visible = self.isVisible()

        # display as a floating dialog
        if mode == XPopupWidget.Mode.Dialog:
            self.setWindowFlags(Qt.Dialog | Qt.Tool)
            self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
            self._closeButton.setVisible(False)
            self._dialogButton.setVisible(False)

        # display as a user tooltip
        elif mode == XPopupWidget.Mode.ToolTip:
            flags = Qt.Popup | Qt.FramelessWindowHint

            self.setWindowFlags(flags)
            self.setBackgroundRole(QPalette.Window)
            self.setAttribute(Qt.WA_TransparentForMouseEvents)
            self.setShowTitleBar(False)
            self.setShowButtonBox(False)
            self.setFocusPolicy(Qt.NoFocus)

            # hide the scrollbars
            policy = Qt.ScrollBarAlwaysOff
            self._scrollArea.setVerticalScrollBarPolicy(policy)
            self._scrollArea.setHorizontalScrollBarPolicy(policy)

        # display as a popup widget
        else:
            flags = Qt.Popup | Qt.FramelessWindowHint

            if not self.autoCloseOnFocusOut():
                flags |= Qt.Tool

            self.setWindowFlags(flags)
            self._closeButton.setVisible(self._titleBarVisible)
            self._dialogButton.setVisible(self._titleBarVisible)
            self.setBackgroundRole(QPalette.Button)

        self.adjustContentsMargins()

        if (is_visible):
            self.show()

    @staticmethod
    @deprecatedmethod('XPopupWidget',
                      'This method no longer has an effect as we are not '\
                      'storing references to the tooltip.')
    def hideToolTip(key=None):
        """
        Hides any existing tooltip popup widgets.
        
        :warning    This method is deprecated!
        """
        pass

    @staticmethod
    def showToolTip(text,
                    point=None,
                    anchor=None,
                    parent=None,
                    background=None,
                    foreground=None,
                    key=None,
                    seconds=5):
        """
        Displays a popup widget as a tooltip bubble.
        
        :param      text        | <str>
                    point       | <QPoint> || None
                    anchor      | <XPopupWidget.Mode.Anchor> || None
                    parent      | <QWidget> || None
                    background  | <QColor> || None
                    foreground  | <QColor> || None
                    key         | <str> || None
                    seconds     | <int>
        """
        if point is None:
            point = QCursor.pos()

        if parent is None:
            parent = QApplication.activeWindow()

        if anchor is None and parent is None:
            anchor = XPopupWidget.Anchor.TopCenter

        # create a new tooltip widget
        widget = XPopupWidget(parent)
        widget.setToolTipMode()
        widget.setResizable(False)

        # create the tooltip label
        label = QLabel(text, widget)
        label.setOpenExternalLinks(True)
        label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        label.setMargin(3)
        label.setIndent(3)
        label.adjustSize()

        widget.setCentralWidget(label)

        # update the tip
        label.adjustSize()
        widget.adjustSize()

        palette = widget.palette()
        if not background:
            background = palette.color(palette.ToolTipBase)
        if not foreground:
            foreground = palette.color(palette.ToolTipText)

        palette.setColor(palette.Window, QColor(background))
        palette.setColor(palette.WindowText, QColor(foreground))
        widget.setPalette(palette)
        widget.centralWidget().setPalette(palette)

        if anchor is None:
            widget.setAutoCalculateAnchor(True)
        else:
            widget.setAnchor(anchor)

        widget.setAutoCloseOnFocusOut(True)
        widget.setAttribute(Qt.WA_DeleteOnClose)
        widget.popup(point)
        widget.startTimer(1000 * seconds)

        return widget
Example #3
0
class XGanttWidget(QWidget):
    dateRangeChanged = qt.Signal()

    Timescale = enum('Week', 'Month', 'Year')

    def __init__(self, parent=None, _availabilityEnabled=0):
        super(XGanttWidget, self).__init__(parent)
        '''
	menubar = QMenuBar(self)
	#menubar.sizeHint(QSize.setHeight(10))
	
	fileMenu = menubar.addMenu('&File')
	
	fileMenu.addAction('Create Project')
	fileMenu.addSeparator()
	fileMenu.addAction('Save')
	fileMenu.addSeparator()
	fileMenu.addAction('Exit')
	fileMenu.triggered.connect( self.fileMenuActions )
	'''

        # load the user interface
        if getattr(sys, 'frozen', None):
            #print (sys._MEIPASS+"/ui/xganttwidget.ui");
            projexui.loadUi(sys._MEIPASS,
                            self,
                            uifile=(sys._MEIPASS + "/ui/xganttwidget.ui"))

        else:
            projexui.loadUi(__file__, self)

        # define custom properties
        self._backend = None
        self._dateStart = QDate.currentDate()
        self._dateEnd = QDate.currentDate().addMonths(12)
        self._alternatingRowColors = False
        self._cellWidth = 15
        self._cellHeight = 15
        self._first = True
        self._dateFormat = 'M/d/yy'
        self._timescale = XGanttWidget.Timescale.Year
        self._scrolling = False

        # setup the palette colors
        palette = self.palette()
        color = palette.color(palette.Base)

        self._gridPen = QPen(color.darker(135))
        self._brush = QBrush(color)
        self._alternateBrush = QBrush(color.darker(105))
        self._currentDayBrush = QBrush(QColor(146, 252, 186))
        self._holidayBrush = QBrush(QColor(166, 46, 46))
        self._bookedBrush = QBrush(QColor(20, 250, 0))
        self._unavailableBrush = QBrush(QColor(75, 75, 75))
        self._underbookedBrush = QBrush(QColor(255, 255, 20))
        self._overbookedBrush = QBrush(QColor(255, 25, 25))
        self._overbookedAmount = {}
        self._unassignedBrush = QBrush(QColor(25, 25, 255))

        weekendColor = color.darker(148)

        self._availabilityEnabled = _availabilityEnabled

        self._weekendBrush = QBrush(weekendColor)

        # setup the columns for the tree
        if _availabilityEnabled:
            self.setColumns(['Name'])
        else:
            self.setColumns(
                ['Name', 'Start', 'End', 'Calendar Days', 'Work Days'])
        header = self.uiGanttTREE.header()
        header.setFixedHeight(self._cellHeight * 2)
        header.setResizeMode(0, header.ResizeToContents)
        header.setDefaultSectionSize(60)
        headerItem = self.uiGanttTREE.headerItem()
        headerItem.setSizeHint(0, QSize(10, header.height()))

        self.uiGanttTREE.setContextMenuPolicy(Qt.CustomContextMenu)

        # connect signals
        self.uiGanttTREE.customContextMenuRequested.connect(
            self.showProjectMenu)

        # initialize the tree widget
        self.uiGanttTREE.setShowGrid(False)

        #enable drag and drop
        self.uiGanttTREE.setDragDropFilter(
            self.uiGanttTREE.setDragDropFilter(XGanttWidget.handleDragDrop))

        if (sharedDB.currentUser._idPrivileges == 3):
            self.uiGanttTREE.setEditable(False)
        for act in sharedDB.mainWindow._fileMenu.actions():
            if act.text() == "Save" or act.text() == "Create Project":
                act.setEnabled(False)

        else:
            self.uiGanttTREE.setEditable(True)

        self.uiGanttTREE.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.uiGanttTREE.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.uiGanttTREE.setVerticalScrollMode(self.uiGanttTREE.ScrollPerPixel)

        #left half size
        self.uiGanttTREE.resize(400, 20)

        # initialize the view widget
        #self.uiGanttVIEW.setDragMode( self.uiGanttVIEW.RubberBandDrag )
        self.uiGanttVIEW.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.uiGanttVIEW.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.uiGanttVIEW.setScene(XGanttScene(self))
        self.uiGanttVIEW.installEventFilter(self)
        #self.uiGanttVIEW.horizontalScrollBar().setValue(50)

        # create connections
        self.uiGanttTREE.itemExpanded.connect(self.syncView)
        self.uiGanttTREE.itemCollapsed.connect(self.syncView)

        # connect scrollbars
        tree_bar = self.uiGanttTREE.verticalScrollBar()
        view_bar = self.uiGanttVIEW.verticalScrollBar()

        tree_bar.rangeChanged.connect(self.__updateViewRect)
        tree_bar.valueChanged.connect(self.__scrollView)
        view_bar.valueChanged.connect(self.__scrollTree)

        # connect selection
        self.uiGanttTREE.itemSelectionChanged.connect(self.__selectView)
        self.uiGanttVIEW.scene().selectionChanged.connect(self.__selectTree)
        self.uiGanttTREE.itemChanged.connect(self.updateItemData)

        if self._availabilityEnabled:
            self._currentDayBrush = None
            self._holidayBrush = QBrush(QColor(75, 75, 75))
            weekendColor = QBrush(QColor(75, 75, 75))
            self.uiGanttTREE.setEditable(False)
            #self._cellHeight = 12

    def __del__(self):
        self.uiGanttVIEW.scene().selectionChanged.disconnect(self.__selectTree)

    def __scrollTree(self, value):
        """
	Updates the tree view scrolling to the inputed value.
	
	:param      value | <int>
	"""
        if (self._scrolling):
            return

        tree_bar = self.uiGanttTREE.verticalScrollBar()
        self._scrolling = True
        tree_bar.setValue(value)
        self._scrolling = False

    def __scrollView(self, value):
        """
	Updates the gantt view scrolling to the inputed value.
	
	:param      value | <int>
	"""
        if (self._scrolling):
            return

        view_bar = self.uiGanttVIEW.verticalScrollBar()
        self._scrolling = True
        view_bar.setValue(value)
        self._scrolling = False

    def __selectTree(self):
        """
	Matches the tree selection to the views selection.
	"""
        self.uiGanttTREE.blockSignals(True)
        self.uiGanttTREE.clearSelection()
        for item in self.uiGanttVIEW.scene().selectedItems():
            item.treeItem().setSelected(True)
            sharedDB.sel.select([item.treeItem()._dbEntry])
        self.uiGanttTREE.blockSignals(False)

    def __selectView(self):
        """
	Matches the view selection to the trees selection.
	"""
        self.uiGanttVIEW.scene().blockSignals(True)
        self.uiGanttVIEW.scene().clearSelection()
        for item in self.uiGanttTREE.selectedItems():
            item.viewItem().setSelected(True)
            sharedDB.sel.select([item._dbEntry])
        self.uiGanttVIEW.scene().blockSignals(False)

    def __updateViewRect(self):
        """
	Updates the view rect to match the current tree value.
	"""
        header_h = self._cellHeight * 2
        rect = self.uiGanttVIEW.scene().sceneRect()
        sbar_max = self.uiGanttTREE.verticalScrollBar().maximum()
        sbar_max += self.uiGanttTREE.viewport().height() + header_h
        widget_max = self.uiGanttVIEW.height()
        widget_max -= (self.uiGanttVIEW.horizontalScrollBar().height() + 10)

        rect.setHeight(max(widget_max, sbar_max))
        self.uiGanttVIEW.scene().setSceneRect(rect)

    def addTopLevelItem(self, item):
        """
	Adds the inputed item to the gantt widget.
	
	:param      item | <XGanttWidgetItem>
	"""
        vitem = item.viewItem()

        self.treeWidget().addTopLevelItem(item)
        if not self._availabilityEnabled:
            self.viewWidget().scene().addItem(vitem)

            item._viewItem = weakref.ref(vitem)

        #set scrollbar offset
        #item.treeWidget._scrollBar = self.uiGanttTREE.verticalScrollBar()

        item.sync(recursive=True)

    def alternateBrush(self):
        """
	Returns the alternate brush to be used for the grid view.
	
	:return     <QBrush>
	"""
        return self._alternateBrush

    def alternatingRowColors(self):
        """
	Returns whether or not this widget should show alternating row colors.
	
	:return     <bool>
	"""
        return self._alternatingRowColors

    def brush(self):
        """
	Returns the background brush to be used for the grid view.
	
	:return     <QBrush>
	"""
        return self._brush

    def cellHeight(self):
        """
	Returns the height for the cells in this gantt's views.
	
	:return     <int>
	"""
        return self._cellHeight

    def cellWidth(self):
        """
	Returns the width for the cells in this gantt's views.
	
	:return     <int>
	"""
        return self._cellWidth

    def clear(self):
        """
	Clears all the gantt widget items for this widget.
	"""
        self.uiGanttTREE.clear()
        self.uiGanttVIEW.scene().clear()

    def closeEvent(self, event):
        if sharedDB.changesToBeSaved and sharedDB.users.currentUser._idPrivileges != 3:
            quit_msg = "Save before exit?"
            reply = QtGui.QMessageBox.question(self, 'Message', quit_msg,
                                               QtGui.QMessageBox.Yes,
                                               QtGui.QMessageBox.No,
                                               QtGui.QMessageBox.Cancel)

            if reply == QtGui.QMessageBox.Yes:
                self.SaveToDatabase()
                event.accept()
            elif reply == QtGui.QMessageBox.No:
                event.accept()
            else:
                event.ignore()

    def columns(self):
        """
	Returns a list of the columns being used in the treewidget of this gantt
	chart.
	
	:return     [<str>, ..]
	"""
        return self.treeWidget().columns()

    '''def CreateProject(self):
	#self._myCreateProjectWidget = CreateProjectWidget()
	#self._myCreateProjectWidget.show()
	sharedDB.app.CreateProjectWidget() 
    '''

    def dateEnd(self):
        """
	Returns the date end for this date range of this gantt widget.
	
	:return     <QDate>
	"""
        return self._dateEnd

    def dateFormat(self):
        """
	Returns the date format that will be used when rendering items in the
	view.
	
	:return     <str>
	"""
        return self._dateFormat

    def dateStart(self):
        """
	Returns the date start for the date range of this gantt widget.
	
	:return     <QDate>
	"""
        return self._dateStart

    def emitDateRangeChanged(self):
        """
	Emits the date range changed signal provided signals aren't being
	blocked.
	"""
        if (not self.signalsBlocked()):
            self.dateRangeChanged.emit()

    def collapseAllTrees(self):
        self.uiGanttTREE.blockSignals(True)
        #for x in range(0,self.treeWidget().topLevelItemCount()):
        #self.treeWidget().topLevelItem(x).setExpanded(True)
        self.treeWidget().collapseAll()
        self.uiGanttTREE.blockSignals(False)
        self.syncView()

    def expandAllTrees(self):
        self.uiGanttTREE.blockSignals(True)
        #for x in range(0,self.treeWidget().topLevelItemCount()):
        #self.treeWidget().topLevelItem(x).setExpanded(True)
        self.treeWidget().expandAll()
        self.uiGanttTREE.blockSignals(False)
        self.syncView()

    def eventFilter(self, object, event):
        if (event.type() == event.Resize):
            self.__updateViewRect()
        return False

    def frameCurrentDate(self):
        # Subtract start date from current date
        prerollDays = self._dateStart.day() - QDate.currentDate().day()
        #set scroll to multiply difference against cel width

        view_bar = self.uiGanttVIEW.horizontalScrollBar()
        self._scrolling = True
        view_bar.setMaximum(1)
        view_bar.setMinimum(0)
        #print view_bar.maximum()
        view_bar.setValue(1)
        #view_bar.update()
        #self.update()
        #self.uiGanttVIEW.update()
        self._scrolling = False

        #self.__scrollView(self._cellWidth * prerollDays)

    def gridPen(self):
        """
	Returns the pen that this widget uses to draw in the view.
	
	:return     <QPen>
	"""
        return self._gridPen

    @staticmethod
    def handleDragDrop(object, event):
        if (event.type() == QEvent.DragEnter):
            event.acceptProposedActions()
        elif (event.type() == QEvent.Drop):
            print 'dropping'

    def indexOfTopLevelItem(self, item):
        """
	Returns the index for the inputed item from the tree.
	
	:return     <int>
	"""
        return self.treeWidget().indexOfTopLevelItem(item)

    def insertTopLevelItem(self, index, item):
        """
	Inserts the inputed item at the given index in the tree.
	
	:param      index   | <int>
		    item    | <XGanttWidgetItem>
	"""
        self.treeWidget().insertTopLevelItem(index, item)

        item.sync(recursive=True)

    def setAlternateBrush(self, brush):
        """
	Sets the alternating brush used for this widget to the inputed brush.
	
	:param      brush | <QBrush> || <QColor>
	"""
        self._alternateBrush = QBrush(brush)

    def setAlternatingRowColors(self, state):
        """
	Sets the alternating row colors state for this widget.
	
	:param      state | <bool>
	"""
        self._alternatingRowColors = state

        self.treeWidget().setAlternatingRowColors(state)

    def setBrush(self, brush):
        """
	Sets the main background brush used for this widget to the inputed
	brush.
	
	:param      brush | <QBrush> || <QColor>
	"""
        self._brush = QBrush(brush)

    def setCellHeight(self, cellHeight):
        """
	Sets the height for the cells in this gantt's views.
	
	:param      cellHeight | <int>
	"""
        self._cellHeight = cellHeight

    def setCellWidth(self, cellWidth):
        """
	Sets the width for the cells in this gantt's views.
	
	:param      cellWidth | <int>
	"""
        self._cellWidth = cellWidth

    def setColumns(self, columns):
        """
	Sets the columns for this gantt widget's tree to the inputed list of
	columns.
	
	:param      columns | {<str>, ..]
	"""
        self.treeWidget().setColumns(columns)
        item = self.treeWidget().headerItem()
        for i in range(item.columnCount()):
            item.setTextAlignment(i, Qt.AlignBottom | Qt.AlignHCenter)

    def setDateEnd(self, dateEnd):
        """
	Sets the end date for the range of this gantt widget.
	
	:param      dateEnd | <QDate>
	"""
        self._dateEnd = dateEnd
        self.emitDateRangeChanged()

    def setDateFormat(self, format):
        """
	Sets the date format that will be used when rendering in the views.
	
	:return     <str>
	"""
        return self._dateFormat

    def setDateStart(self, dateStart):
        """
	Sets the start date for the range of this gantt widget.
	
	:param      dateStart | <QDate>
	"""
        self._dateStart = dateStart
        self.emitDateRangeChanged()

    def setGridPen(self, pen):
        """
	Sets the pen used to draw the grid lines for the view.
	
	:param      pen | <QPen> || <QColor>
	"""
        self._gridPen = QPen(pen)

    def setTimescale(self, timescale):
        """
	Sets the timescale value for this widget to the inputed value.
	
	:param      timescale | <XGanttWidget.Timescale>
	"""
        self._timescale = timescale

    #def setupUserView(self, privileges, department):
        #sif department

    def setWeekendBrush(self, brush):
        """
	Sets the brush to be used when coloring weekend columns.
	
	:param      brush | <QBrush> || <QColor>
	"""
        self._weekendBrush = QBrush(brush)

    def syncView(self):
        """
	Syncs all the items to the view.
	"""
        if (not self.signalsBlocked()):
            for i in range(self.topLevelItemCount()):
                item = self.topLevelItem(i)
                item.syncView(recursive=True)

    def takeTopLevelItem(self, index):
        """
	Removes the top level item at the inputed index from the widget.
	
	:param      index | <int>
	
	:return     <XGanttWidgetItem> || None
	"""
        item = self.topLevelItem(index)
        if (item):
            self.viewWidget().scene().removeItem(item.viewItem())
            self.treeWidget().takeTopLevelItem(index)

            return item
        return None

    def timescale(self):
        """
	Returns the timescale that is being used for this widget.
	
	:return     <XGanttWidget.Timescale>
	"""
        return self._timescale

    def topLevelItem(self, index):
        """
	Returns the top level item at the inputed index.
	
	:return     <XGanttWidgetItem>
	"""
        return self.treeWidget().topLevelItem(index)

    def topLevelItemCount(self):
        """
	Returns the number of top level items for this widget.
	
	:return     <int>
	"""
        return self.treeWidget().topLevelItemCount()

    def treeWidget(self):
        """
	Returns the tree widget for this gantt widget.
	
	:return     <QTreeWidget>
	"""
        return self.uiGanttTREE

    def updateItemData(self, item, index):
        """
	Updates the item information from the tree.
	
	:param      item    | <XGanttWidgetItem>
		    index   | <int>
	"""
        value = qt.unwrapVariant(item.data(index, Qt.EditRole))

        if type(value) == QDateTime:
            value = value.date()
            item.setData(index, Qt.EditRole, qt.wrapVariant(value))

        if type(value) == QDate:
            value = value.toPython()

        columnName = self.treeWidget().columnOf(index)
        item.setProperty(columnName, value)
        item.sync()

    def updatePhaseVisibility(self, visibility, phaseName=''):

        if (phaseName != ''):
            #print ("Changing "+ phaseName + " to : "+ str(visibility))
            #iterate through all projects
            for x in range(0, self.treeWidget().topLevelItemCount()):
                projectWidgetItem = self.treeWidget().topLevelItem(x)
                keepVisible = False
                for c in range(projectWidgetItem.childCount()):
                    child = projectWidgetItem.child(c)
                    if (child._dbEntry._phase._name == phaseName):
                        #projectWidgetItem.setHidden(not visibility)
                        child.setHidden(not visibility)
                        if (visibility):
                            keepVisible = True

                    elif (visibility == False and not child.isHidden()
                          and keepVisible == False):
                        keepVisible = True

                        #print child._name
                #print ("item: " + str(x))
                #iterate through all phases

                if (keepVisible):
                    if (projectWidgetItem.isHidden()):
                        projectWidgetItem.setHidden(False)
                elif (not visibility):
                    if (not projectWidgetItem.isHidden()):
                        #self.syncView()
                        projectWidgetItem.setHidden(True)

            #if phase matches, change visibility

            for phase in sharedDB.myPhases.values():
                if (phase._name == phaseName):
                    phase._visible = visibility

        else:
            #iterate through all projects
            #iterate through all phases
            #change visibility
            if visibility == False:
                self.collapseAllTrees()

            for x in range(0, self.treeWidget().topLevelItemCount()):
                projectWidgetItem = self.treeWidget().topLevelItem(x)
                for c in range(projectWidgetItem.childCount()):
                    child = projectWidgetItem.child(c)
                    child.setHidden(not visibility)
                #self.syncView()
                projectWidgetItem.setHidden(not visibility)

            for phase in sharedDB.myPhases.values():
                phase._visible = visibility
            #print ("Changing all phases to: "+ str(visibility))

        self.syncView()

    def updateUserVisibility(self, visibility, username=''):
        if (username != ''):
            #print ("Changing "+ phaseName + " to : "+ str(visibility))
            #iterate through all projects
            for x in range(0, self.treeWidget().topLevelItemCount()):
                projectWidgetItem = self.treeWidget().topLevelItem(x)
                keepProjectVisible = False
                for c in range(projectWidgetItem.childCount()):
                    child = projectWidgetItem.child(c)

                    if (child._dbEntry.type() == "phaseassignment"):
                        print "keeping " + username + " visible."

                        if self.CheckUserAssignmentForUser(
                                child._dbEntry._userAssignments.values(),
                                username):
                            child.setHidden(not visibility)
                            if (visibility):
                                keepProjectVisible = True

                    if (visibility == False and not child.isHidden()
                            and keepProjectVisible == False):
                        keepProjectVisible = True

                if (keepProjectVisible):
                    if (projectWidgetItem.isHidden()):
                        projectWidgetItem.setHidden(False)
                elif (not visibility):
                    if (not projectWidgetItem.isHidden()):
                        #self.syncView()
                        for c in range(projectWidgetItem.childCount()):
                            projectWidgetItem.child(c).setHidden(True)

                        projectWidgetItem.setHidden(True)

            #if phase matches, change visibility

            for user in sharedDB.myUsers.values():
                if (user.name() == username):
                    user._calendarVisibility = visibility

        else:
            #iterate through all projects
            #iterate through all phases
            #change visibility
            if visibility == False:
                self.collapseAllTrees()

            for x in range(0, self.treeWidget().topLevelItemCount()):
                projectWidgetItem = self.treeWidget().topLevelItem(x)
                for c in range(projectWidgetItem.childCount()):
                    child = projectWidgetItem.child(c)
                    child.setHidden(not visibility)
                #self.syncView()
                projectWidgetItem.setHidden(not visibility)

            for phase in sharedDB.myPhases.values():
                phase._visible = visibility

            for user in sharedDB.myUsers.values():
                user._calendarVisibility = visibility

            #print ("Changing all phases to: "+ str(visibility))

        self.syncView()

    def CheckUserAssignmentForUser(self, userAssignments, username):
        for ua in userAssignments:
            if ua._hours > 1 and ua.idUsers() is not None:
                if (sharedDB.myUsers[str(ua.idUsers())].name() == username):
                    return True

        return False

    def viewWidget(self):
        """
	Returns the view widget for this gantt widget.
	
	:return     <QGraphicsView>
	"""
        return self.uiGanttVIEW

    def weekendBrush(self):
        """
	Returns the weekend brush to be used for coloring in weekends.
	
	:return     <QBrush>
	"""
        return self._weekendBrush

    def bookedBrush(self):
        """
	Returns the booked brush to be used for coloring in booked days.
	
	:return     <QBrush>
	"""
        return self._bookedBrush

    def unavailableBrush(self):
        """
	Returns the unavailable brush to be used for coloring in unavailable days.
	
	:return     <QBrush>
	"""
        return self._unavailableBrush

    def underbookedBrush(self):
        """
	Returns the underbookedBrush brush to be used for coloring in underbooked days.
	
	:return     <QBrush>
	"""
        return self._underbookedBrush

    def overbookedBrush(self):
        """
	Returns the overbookedBrush brush to be used for coloring in overbooked days.
	
	:return     <QBrush>
	"""
        return self._overbookedBrush

    def unassignedBrush(self):
        """
	Returns the unassignedBrush brush to be used for coloring in unassigned days.
	
	:return     <QBrush>
	"""
        return self._unassignedBrush

    def showProjectMenu(self, pos):
        """
        Displays the header menu for this tree widget.
        
        :param      pos | <QPoint> || None
        """
        '''
	
	header = self.header()
        index  = header.logicalIndexAt(pos)
        self._headerIndex = index
        
        # show a pre-set menu
        if self._headerMenu:
            menu = self._headerMenu
        else:
            menu = self.createHeaderMenu(index)
        '''
        # determine the point to show the menu from
        #if pos is not None:
        #    point = header.mapToGlobal(pos)
        #else:
        index = self.uiGanttTREE.indexAt(pos)

        typ = "None"
        if index.isValid():
            dbentry = self.uiGanttTREE.itemFromIndex(index)._dbEntry
            if hasattr(dbentry, '_type'):
                typ = dbentry._type

        point = QCursor.pos()

        #self.headerMenuAboutToShow.emit(menu, index)

        menu = QtGui.QMenu()

        if typ == "phaseassignment":
            statusMenu = menu.addMenu("TEST")
        elif typ == "project":
            statusAction = menu.addAction("Open in Project View")
            statusAction.setData(dbentry.id())
            menu.addSeparator()
            if sharedDB.currentUser._idPrivileges < 2:
                addPhaseMenu = menu.addMenu("Add Phase")

                phases = sharedDB.myPhases.values()
                phases.sort(key=operator.attrgetter('_name'))

                middleChar = phases[len(phases) / 2]._name[0]

                AMMenu = addPhaseMenu.addMenu('A - ' + middleChar)
                NZMenu = addPhaseMenu.addMenu(
                    chr(ord(middleChar) + 1) + ' - Z')

                for x in range(0, len(phases)):
                    phase = phases[x]

                    if phase._name == "DUE":
                        continue

                    #col    = self.column(column)
                    if x < len(phases) / 2 or phase._name[0] == middleChar:
                        action = AMMenu.addAction(phase._name)
                        action.setData("addphase_" + str(phase.id()) + "_" +
                                       str(dbentry.id()))
                    else:
                        action = NZMenu.addAction(phase._name)
                        action.setData("addphase_" + str(phase.id()) + "_" +
                                       str(dbentry.id()))
                '''
		for phase in sharedDB.myPhases.values():
		    if phase._name != "DUE":
			addPhaseAction = addPhaseMenu.addAction(phase._name)
			addPhaseAction.setData("addphase_"+str(phase.id())+"_"+str(dbentry.id()))
		'''
            if sharedDB.currentUser._idPrivileges == 1:
                archiveAction = menu.addAction("Archive Project")
                archiveAction.setData(dbentry.id())
        else:
            if sharedDB.currentUser._idPrivileges < 2:
                menu.addAction("Create Project")

        menu.triggered.connect(self.mActions)

        menu.exec_(point)

    def mActions(self, action):
        act = action.text()

        if act == "Open in Project View":
            self.loadinprojectview(sharedDB.myProjects[str(
                action.data().toPyObject())])
            #print sharedDB.myProjects[str(projectId)]._name
        elif act == "Archive Project":
            sharedDB.myProjects[str(action.data().toPyObject())].setArchived(1)
        elif act == "Create Project":
            if not hasattr(sharedDB, 'myCreateProjectWidget'):
                sharedDB.myCreateProjectWidget = createprojectwidget.CreateProjectWidget(
                    sharedDB.mainWindow)

            sharedDB.myCreateProjectWidget.setDefaults()
            sharedDB.myCreateProjectWidget.dockWidget.show()
        elif "addphase" in str(action.data().toPyObject()):
            phaseId = str(action.data().toPyObject()).split("_")[1]
            proj = sharedDB.myProjects[str(
                action.data().toPyObject()).split("_")[2]]
            phase = sharedDB.phaseAssignments.PhaseAssignments(
                _idphases=phaseId,
                _startdate=proj._startdate,
                _enddate=proj._startdate,
                _updated=0)
            proj.AddPhase(phase)

            #iterate through shots for
            for image in proj._images.values():
                currentTask = sharedDB.tasks.Tasks(
                    _idphaseassignments=phase._idphaseassignments,
                    _idprojects=proj._idprojects,
                    _idshots=image._idshots,
                    _idphases=phase._idphases,
                    _new=1)
                currentTask.Save()
                image._tasks[str(currentTask.id())] = (currentTask)

                currentTask.Save()
            for seq in proj._sequences.values():
                for shot in seq._shots.values():
                    currentTask = sharedDB.tasks.Tasks(
                        _idphaseassignments=phase._idphaseassignments,
                        _idprojects=proj._idprojects,
                        _idshots=shot._idshots,
                        _idphases=phase._idphases,
                        _new=1)
                    currentTask.Save()
                    shot._tasks[str(currentTask.id())] = (currentTask)

    def loadinprojectview(self, project):
        #print "Loading Project"+self.cellWidget(row,column)._phaseassignment._name
        sharedDB.mainWindow.centralTabbedWidget.setCurrentIndex(0)
        sharedDB.myProjectViewWidget._currentProject = project

        sharedDB.myProjectViewWidget.LoadProjectValues()

        sharedDB.myProjectViewWidget.projectPartWidget.setCurrentIndex(0)
Example #4
0
class XComboBox(QComboBox):
    """
    ~~>[img:widgets/xcombobox.png]
    The XComboBox class is a simple extension to the standard QComboBox
    that provides a couple enhancement features, namely the ability to 
    add a hint to the line edit and supporting multi-selection via checkable
    items.
    
    == Example ==
    
    |>>> from projexui.widgets.xcombobox import XComboBox
    |>>> import projexui
    |
    |>>> # create the combobox
    |>>> combo = projexui.testWidget(XComboBox)
    |
    |>>> # set the hint
    |>>> combo.setHint('select type')
    |
    |>>> # create items, make checkable
    |>>> combo.addItems(['A', 'B', 'C'])
    |>>> combo.setCheckable(True)
    |
    |>>> # set the checked items
    |>>> combo.setCheckedItems(['C'])
    |>>> combo.setCheckedIndexes([0, 2])
    |
    |>>> # retrieve checked items
    |>>> combo.checkedItems()
    |['A', 'C']
    |>>> combo.checkedIndexes()
    |[0, 2]
    |
    |>>> # connect to signals
    |>>> def printChecked(): print checked.checkedItems()
    |>>> combo.checkedIndexesChanged.connect(printChecked)
    |
    |>>> # modify selection and see the output
    """
    __designer_icon__ = projexui.resources.find('img/ui/combobox.png')

    checkedIndexesChanged = qt.Signal(list)
    checkedItemsChanged = qt.Signal(list)

    def __init__(self, parent=None):
        super(XComboBox, self).__init__(parent)

        # define custom properties
        self._checkable = False
        self._hint = ''
        self._separator = ','

        # setup the checkable popup widget
        self._checkablePopup = None

        # set default properties
        self.setLineEdit(XLineEdit(self))

    def adjustCheckState(self):
        """
        Updates when new items are added to the system.
        """
        if (self.isCheckable()):
            self.updateCheckState()

    def checkablePopup(self):
        """
        Returns the popup if this widget is checkable.
        
        :return     <QListView> || None
        """
        if (not self._checkablePopup and self.isCheckable()):
            popup = QListView(self)
            popup.setSelectionMode(QListView.NoSelection)
            popup.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            popup.setWindowFlags(Qt.Popup)
            popup.installEventFilter(self)
            popup.doubleClicked.connect(self.checkModelIndex)
            self._checkablePopup = popup

        return self._checkablePopup

    def checkModelIndex(self, modelIndex):
        """
        Sets the current index as the checked index.
        
        :param      modelIndex | <QModelIndex>
        """
        self.checkablePopup().hide()

        if (not self.isCheckable()):
            return

        self.setCheckedIndexes([modelIndex.row()])

    def currentText(self):
        """
        Returns the current text for this combobox, including the hint option \
        if no text is set.
        """
        lineEdit = self.lineEdit()
        if (lineEdit):
            return lineEdit.currentText()

        return super(XComboBox, self).currentText()

    def checkedIndexes(self):
        """
        Returns a list of checked indexes for this combobox.
        
        :return     [<int>, ..]
        """
        if (not self.isCheckable()):
            return []

        model = self.model()
        return [i for i in range(self.count()) if model.item(i).checkState()]

    def checkedItems(self):
        """
        Returns the checked items for this combobox.
        
        :return     [<str>, ..]
        """
        if (not self.isCheckable()):
            return []

        return [str(self.itemText(i)) for i in self.checkedIndexes()]

    def eventFilter(self, object, event):
        """
        Filters events for the popup widget.
        
        :param      object | <QObject>
                    event  | <QEvent>
        """
        # popup the editor when clicking in the line edit for a checkable state
        if object == self.lineEdit() and self.isEnabled():
            if not self.isCheckable():
                return super(XComboBox, self).eventFilter(object, event)

            # show the popup when the user clicks on it
            elif event.type() == event.MouseButtonPress:
                self.showPopup()

            # eat the wheel event when the user is scrolling
            elif event.type() == event.Wheel:
                return True

        # make sure we're looking for the checkable popup
        elif object == self._checkablePopup:
            if event.type() == event.KeyPress and \
                 event.key() in (Qt.Key_Escape, Qt.Key_Return, Qt.Key_Enter):
                object.close()

            elif event.type() == event.MouseButtonPress:
                if not object.geometry().contains(event.pos()):
                    object.close()

        return super(XComboBox, self).eventFilter(object, event)

    def hint(self):
        """
        Returns the hint for this combobox.
        
        :return     <str>
        """
        return self._hint

    def hintColor(self):
        """
        Returns the hint color for this combo box provided its line edit is
        an XLineEdit instance.
        
        :return     <QColor>
        """
        lineEdit = self.lineEdit()
        if isinstance(lineEdit, XLineEdit):
            return lineEdit.hintColor()
        return QColor()

    def isCheckable(self):
        """
        Returns whether or not this combobox has checkable options.
        
        :return     <bool>
        """
        return self._checkable

    def items(self):
        """
        Returns the labels for the different items in this combo box.
        
        :return     [<str>, ..]
        """
        return [self.itemText(i) for i in range(self.count())]

    def separator(self):
        """
        Returns the separator that will be used for joining together 
        the options when in checked mode.  By default, this will be a comma.
        
        :return     <str>
        """
        return self._separator

    def setCheckedIndexes(self, indexes):
        """
        Sets a list of checked indexes for this combobox.
        
        :param      indexes | [<int>, ..]
        """
        if (not self.isCheckable()):
            return

        model = self.model()
        for i in range(self.count()):
            if (not self.itemText(i)):
                continue

            item = model.item(i)

            if (i in indexes):
                state = Qt.Checked
            else:
                state = Qt.Unchecked

            item.setCheckState(state)

    def setCheckedItems(self, items):
        """
        Returns the checked items for this combobox.
        
        :return     items | [<str>, ..]
        """
        if (not self.isCheckable()):
            return

        model = self.model()
        for i in range(self.count()):
            item_text = self.itemText(i)
            if (not item_text):
                continue

            if (str(item_text) in items):
                state = Qt.Checked
            else:
                state = Qt.Unchecked

            model.item(i).setCheckState(state)

    def setCheckable(self, state):
        """
        Sets whether or not this combobox stores checkable items.
        
        :param      state | <bool>
        """
        self._checkable = state

        # need to be editable to be checkable
        edit = self.lineEdit()
        if state:
            self.setEditable(True)
            edit.setReadOnly(True)

            # create connections
            model = self.model()
            model.rowsInserted.connect(self.adjustCheckState)
            model.dataChanged.connect(self.updateCheckedText)

        elif edit:
            edit.setReadOnly(False)

        self.updateCheckState()
        self.updateCheckedText()

    def setEditable(self, state):
        """
        Sets whether or not this combobox will be editable, updating its \
        line edit to an XLineEdit if necessary.
        
        :param      state | <bool>
        """
        super(XComboBox, self).setEditable(state)

        if (state):
            edit = self.lineEdit()
            if (edit and isinstance(edit, XLineEdit)):
                return
            elif (edit):
                edit.setParent(None)
                edit.deleteLater()

            edit = XLineEdit(self)
            edit.setHint(self.hint())
            self.setLineEdit(edit)

    def setLineEdit(self, edit):
        """
        Sets the line edit for this widget.
        
        :warning    If the inputed edit is NOT an instance of XLineEdit, \
                    this method will destroy the edit and create a new \
                    XLineEdit instance.
        
        :param      edit | <XLineEdit>
        """
        if (edit and not isinstance(edit, XLineEdit)):
            edit.setParent(None)
            edit.deleteLater()

            edit = XLineEdit(self)

        edit.installEventFilter(self)
        super(XComboBox, self).setLineEdit(edit)

    def setHint(self, hint):
        """
        Sets the hint for this line edit that will be displayed when in \
        editable mode.
        
        :param      hint | <str>
        """
        self._hint = hint

        lineEdit = self.lineEdit()
        if isinstance(lineEdit, XLineEdit):
            lineEdit.setHint(hint)

    def setHintColor(self, color):
        """
        Sets the hint color for this combo box provided its line edit is
        an XLineEdit instance.
        
        :param      color | <QColor>
        """
        lineEdit = self.lineEdit()
        if isinstance(lineEdit, XLineEdit):
            lineEdit.setHintColor(color)

    @qt.Slot(str)
    def setSeparator(self, separator):
        """
        Sets the separator that will be used when joining the checked items
        for this combo in the display.
        
        :param      separator | <str>
        """
        self._separator = str(separator)
        self.updateCheckedText()

    def showPopup(self):
        """
        Displays a custom popup widget for this system if a checkable state \
        is setup.
        """
        if not self.isCheckable():
            return super(XComboBox, self).showPopup()

        if not self.isVisible():
            return

        # update the checkable widget popup
        point = self.mapToGlobal(QPoint(0, self.height() - 1))
        popup = self.checkablePopup()
        popup.setModel(self.model())
        popup.move(point)
        popup.setFixedWidth(self.width())

        height = (self.count() * 19) + 2
        if (height > 400):
            height = 400
            popup.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        else:
            popup.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        popup.setFixedHeight(height)
        popup.show()
        popup.raise_()

    def updateCheckState(self):
        """
        Updates the items to reflect the current check state system.
        """
        checkable = self.isCheckable()
        model = self.model()
        flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled

        for i in range(self.count()):
            item = model.item(i)

            if (not (checkable and item.text())):
                item.setCheckable(False)
                item.setFlags(flags)

            # only allow checking for items with text
            else:
                item.setCheckable(True)
                item.setFlags(flags | Qt.ItemIsUserCheckable)

    def updateCheckedText(self):
        """
        Updates the text in the editor to reflect the latest state.
        """
        if (not self.isCheckable()):
            return

        self.lineEdit().setText(self.separator().join(self.checkedItems()))

    def toggleModelIndex(self, modelIndex):
        """
        Toggles the index's check state.
        
        :param      modelIndex | <QModelIndex>
        """
        if (not self.isCheckable()):
            return

        item = self.model().item(modelIndex.row())
        if (item.checkState() == Qt.Checked):
            state = Qt.Unchecked
        else:
            state = Qt.Checked

        item.setCheckState(state)

    # define qt properties
    x_hint = qt.Property(str, hint, setHint)
    x_checkable = qt.Property(bool, isCheckable, setCheckable)
    x_separator = qt.Property(str, separator, setSeparator)
Example #5
0
class XNodeWidget(QGraphicsView):
    """ Defines the main widget for creating node graph views. """
    __designer_icon__ = projexui.resources.find('img/ui/node.png')

    zoomAmountChanged = qt.Signal(int)

    def __init__(self, parent, sceneClass=None):
        # initialize the super class
        super(XNodeWidget, self).__init__(parent)

        # set the scene
        if (not sceneClass):
            sceneClass = XNodeScene

        self._cleanupOnClose = True

        self.setScene(sceneClass(self))
        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.setBaseSize(QSize(300, 250))

    def __dir__(self):
        out = set(self.__dict__.keys())
        out.update(dir(self.scene()))
        return list(out)

    def __getattr__(self, key):
        return getattr(self.scene(), key)

    def _runLayoutTest(self, layoutName):
        """
        Runs a layout test for this widget for the inputed layout plugin
        name.
        
        :param      layoutName | <str>
        
        :return     <bool> | success
        """
        layout = XNodeLayout.plugin(layoutName)
        if not layout:
            return False

        layout.runTest(self.scene())
        return True

    @qt.Slot()
    def autoLayout(self):
        """
        Auto-lays out the whole scene.
        """
        self.scene().autoLayout()

    @qt.Slot()
    def autoLayoutSelected(self):
        """
        Auto-lays out the selected items.
        """
        self.scene().autoLayoutSelected()

    def centerOn(self, *args):
        """
        Updates the center on method to ensure the viewport is updated.
        
        :param      *args | <variant>
        """
        super(XNodeWidget, self).centerOn(*args)

        for con in self.connections():
            con.setPath(con.rebuild())
            con.update()

    def centerOnAnimated(self, centerOn, animate=0):
        """
        Animates the centering options over a given number of seconds.
        
        :param      centerOn | <QRectF> | <QPointF> | <XNode>
                    animate  | <float> | seconds
        """
        if isinstance(centerOn, XNode):
            center = centerOn.sceneRect().center()
        elif isinstance(centerOn, QRectF):
            center = centerOn.center()
        elif isinstance(centerOn, QPointF):
            center = centerOn
        else:
            return

        anim = XObjectAnimation(self, 'centerOn', self)
        anim.setStartValue(self.viewportRect().center())
        anim.setEndValue(center)
        anim.setDuration(1000 * animate)
        anim.start()
        anim.finished.connect(anim.deleteLater)

    def centerOnItems(self, items=None):
        """
        Centers on the given items, if no items are supplied, then all
        items will be centered on.
        
        :param      items | [<QGraphicsItem>, ..]
        """
        if not items:
            rect = self.scene().visibleItemsBoundingRect()
            if not rect.width():
                rect = self.scene().sceneRect()

            self.centerOn(rect.center())
        else:
            self.centerOn(self.scene().calculateBoundingRect(items).center())

    def centerOnSelection(self):
        """
        Centers on the selected items.
        
        :sa     centerOnItems
        """
        self.centerOnItems(self.scene().selectedItems())

    def cleanupOnClose(self):
        """
        Sets whether or not this widget should clean up its scene before
        closing.
        
        :return     <bool>
        """
        return self._cleanupOnClose

    def closeEvent(self, event):
        """
        Cleans up the scene before closing.
        
        :param      event | <QEvent>
        """
        if (self.cleanupOnClose()):
            scene = self.scene()
            scene.cleanup()
            self.setScene(None)

        super(XNodeWidget, self).closeEvent(event)

    @qt.Slot()
    def disableViewMode(self):
        """
        Sets the node widget into selection mode which allows the user to select
        vs. pan and zoom.
        """
        self.scene().setViewMode(False)

    @qt.Slot()
    def enableViewMode(self):
        """
        Sets the node widget into view mode which allows the user to pan
        and zoom vs. select.
        """
        self.scene().setViewMode(True)

    def findNodeByRegex(self, nodeRegex):
        """
        Returns the first node that matches the inputed regular expression.
        
        :param      nodeRegex | <str>
        
        :return     <XNode> || None
        """
        return self.scene().findNodeByRegex(nodeRegex)

    def findNode(self, nodeName):
        """
        Returns the node for the given node name.
        
        :param     nodeName | <str>
        
        :return     <XNode> || None
        """
        return self.scene().findNode(nodeName)

    def isolationMode(self):
        """
        Returns whether or not this widget is in isolation mode.
        
        :return     <bool>
        """
        return self.scene().isolationMode()

    def setCleanupOnClose(self, state):
        """
        Sets whether or not the scene should be cleaned up before closing.
        
        :param      state | <bool>
        """
        self._cleanupOnClose = state

    @qt.Slot(bool)
    def setIsolationMode(self, state):
        """
        Sets whether or not the widget is in isolation mode.
        
        :param      state | <bool>
        """
        self.scene().setIsolationMode(state)

    @qt.Slot(int)
    def setZoomAmount(self, amount):
        """
        Sets the zoom amount for this widget to the inputed amount.
        
        :param      amount | <int>
        """
        self.scene().setZoomAmount(amount)

    def viewportRect(self):
        """
        Returns the QRectF that represents the visible viewport rect for the
        current view.
        
        :return     <QRectF>
        """
        w = self.width()
        h = self.height()

        vbar = self.verticalScrollBar()
        hbar = self.horizontalScrollBar()

        if vbar.isVisible():
            w -= vbar.width()
        if hbar.isVisible():
            h -= hbar.height()

        top_l = self.mapToScene(QPoint(0, 0))
        bot_r = self.mapToScene(QPoint(w, h))

        return QRectF(top_l.x(), top_l.y(),
                      bot_r.x() - top_l.x(),
                      bot_r.y() - top_l.y())

    def zoomAmount(self):
        """
        Returns the zoom amount for this widget to the inputed amount.
        
        :param      amount | <int>
        """
        return self.scene().zoomAmount()

    @qt.Slot()
    def zoomExtents(self):
        """
        Fits all the nodes in the view.
        """
        rect = self.scene().visibleItemsBoundingRect()
        vrect = self.viewportRect()
        if rect.width():
            if rect.width() < vrect.width() and rect.height() < vrect.height():
                self.centerOn(rect.center())
            else:
                self.fitInView(rect, Qt.KeepAspectRatio)

        if not self.signalsBlocked():
            self.zoomAmountChanged.emit(self.zoomAmount())

    @qt.Slot()
    def zoomIn(self):
        """
        Zooms in for this widget by the scene's zoom step amount.
        """
        self.scene().zoomIn()

    @qt.Slot()
    def zoomOut(self):
        """
        Zooms out for this widget by the scene's zoom step amount.
        """
        self.scene().zoomOut()

    x_isolationMode = qt.Property(bool, isolationMode, setIsolationMode)
    x_cleanupOnClose = qt.Property(bool, cleanupOnClose, setCleanupOnClose)
Example #6
0
class XToolBar(QToolBar):
    collapseToggled = qt.Signal(bool)
    
    def __init__( self, *args ):
        super(XToolBar, self).__init__( *args )
        
        # set custom properties
        self._collapseButton    = None
        self._collapsed         = True
        self._collapsedSize     = 14
        self._precollapseSize   = None
        
        # set standard options
        self.layout().setSpacing(0)
        self.layout().setContentsMargins(1, 1, 1, 1)
        self.setMovable(False)
        self.clear()
        self.setOrientation(Qt.Horizontal)
        self.setCollapsed(False)
    
    def clear( self ):
        """
        Clears out this toolbar from the system.
        """
        # preserve the collapse button
        super(XToolBar, self).clear()
        
        self._collapseButton = QToolButton(self)
        self._collapseButton.setAutoRaise(True)
        self._collapseButton.setSizePolicy( QSizePolicy.Expanding,
                                            QSizePolicy.Expanding )
        
        self.addWidget(self._collapseButton)
        self.refreshButton()
        
        # create connection
        self._collapseButton.clicked.connect( self.toggleCollapsed )
    
    def count( self ):
        """
        Returns the number of actions linked with this toolbar.
        
        :return     <int>
        """
        return len(self.actions())
    
    def collapseButton( self ):
        """
        Returns the collapsing button for this toolbar.
        
        :return     <QToolButton>
        """
        return self._collapseButton
    
    def isCollapsed( self ):
        """
        Returns whether or not this toolbar is in a collapsed state.
        
        :return     <bool>
        """
        return self._collapsed
    
    def refreshButton( self ):
        """
        Refreshes the button for this toolbar.
        """
        collapsed   = self.isCollapsed()
        btn         = self._collapseButton
        if ( not btn ):
            return
        
        btn.setMaximumSize(MAX_SIZE, MAX_SIZE)
        
        # set up a vertical scrollbar
        if ( self.orientation() == Qt.Vertical ):
            btn.setMaximumHeight(12)
        else:
            btn.setMaximumWidth(12)
            
        icon = ''
        
        # collapse/expand a vertical toolbar
        if ( self.orientation() == Qt.Vertical ):
            if ( collapsed ):
                self.setFixedWidth(self._collapsedSize)
                btn.setMaximumHeight(MAX_SIZE)
                btn.setArrowType(Qt.RightArrow)
            else:
                self.setMaximumWidth(1000)
                self._precollapseSize = None
                btn.setMaximumHeight(12)
                btn.setArrowType(Qt.LeftArrow)
                
        else:
            if ( collapsed ):
                self.setFixedHeight(self._collapsedSize)
                btn.setMaximumWidth(MAX_SIZE)
                btn.setArrowType(Qt.DownArrow)
            else:
                self.setMaximumHeight(1000)
                self._precollapseSize = None
                btn.setMaximumWidth(12)
                btn.setArrowType(Qt.UpArrow)
        
        for index in range(1, self.layout().count()):
            item = self.layout().itemAt(index)
            if ( not item.widget() ):
                continue
                
            if ( collapsed ):
                item.widget().setMaximumSize(0, 0)
            else:
                item.widget().setMaximumSize(MAX_SIZE, MAX_SIZE)
    
    def setCollapsed( self, state ):
        """
        Sets whether or not this toolbar is in a collapsed state.
        
        :return     <bool> changed
        """
        if ( state == self._collapsed ):
            return False
        
        self._collapsed = state
        self.refreshButton()
        
        if ( not self.signalsBlocked() ):
            self.collapseToggled.emit(state)
        
        return True
    
    def setOrientation( self, orientation ):
        """
        Sets the orientation for this toolbar to the inputed value, and \
        updates the contents margins and collapse button based on the vaule.
        
        :param      orientation | <Qt.Orientation>
        """
        super(XToolBar, self).setOrientation(orientation)
        self.refreshButton()
    
    def toggleCollapsed( self ):
        """
        Toggles the collapsed state for this toolbar.
        
        :return     <bool> changed
        """
        return self.setCollapsed(not self.isCollapsed())
Example #7
0
class XFilepathEdit(QWidget):
    """
    The XFilepathEdit class provides a common interface to prompt the user to
    select a filepath from the filesystem.  It can be configured to load
    directories, point to a save file path location, or to an open file path
    location.  It can also be setup to color changed based on the validity
    of the existance of the filepath.
    
    == Example ==
    
    |>>> from projexui.widgets.xfilepathedit import XFilepathEdit
    |>>> import projexui
    |
    |>>> # create the edit
    |>>> edit = projexui.testWidget(XFilepathEdit)
    |
    |>>> # set the filepath
    |>>> edit.setFilepath('/path/to/file')
    |
    |>>> # prompt the user to select the filepath
    |>>> edit.pickFilepath()
    |
    |>>> # enable the coloring validation
    |>>> edit.setValidated(True)
    """

    __designer_icon__ = projexui.resources.find('img/file.png')

    Mode = enum('OpenFile', 'SaveFile', 'Path', 'OpenFiles')

    filepathChanged = qt.Signal(str)

    def __init__(self, parent=None):
        super(XFilepathEdit, self).__init__(parent)

        # define custom properties
        self._validated = False
        self._validForeground = QColor(0, 120, 0)
        self._validBackground = QColor(0, 120, 0, 100)
        self._invalidForeground = QColor(255, 0, 0)
        self._invalidBackground = QColor(255, 0, 0, 100)
        self._normalizePath = False

        self._filepathMode = XFilepathEdit.Mode.OpenFile
        self._filepathEdit = XLineEdit(self)
        self._filepathButton = QToolButton(self)
        self._filepathTypes = 'All Files (*.*)'

        # set default properties
        ico = projexui.resources.find('img/folder.png')
        self._filepathEdit.setReadOnly(False)
        self._filepathButton.setText('...')
        self._filepathButton.setFixedSize(25, 23)
        self._filepathButton.setAutoRaise(True)
        self._filepathButton.setIcon(QIcon(ico))
        self._filepathEdit.setContextMenuPolicy(Qt.CustomContextMenu)

        self.setWindowTitle('Load File')
        self.setAcceptDrops(True)

        # define the layout
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self._filepathEdit)
        layout.addWidget(self._filepathButton)
        self.setLayout(layout)

        # create connections
        self._filepathEdit.installEventFilter(self)

        self._filepathButton.clicked.connect(self.pickFilepath)
        self._filepathEdit.textChanged.connect(self.emitFilepathChanged)
        self._filepathEdit.textChanged.connect(self.validateFilepath)
        self._filepathEdit.customContextMenuRequested.connect(self.showMenu)

    def autoRaise(self):
        """
        Returns whether or not the tool button will auto raise.
        
        :return     <bool>
        """
        return self._filepathButton.autoRaise()

    @qt.Slot()
    def clearFilepath(self):
        """
        Clears the filepath contents for this path.
        """
        self.setFilepath('')

    @qt.Slot()
    def copyFilepath(self):
        """
        Copies the current filepath contents to the current clipboard.
        """
        clipboard = QApplication.instance().clipboard()
        clipboard.setText(self.filepath())
        clipboard.setText(self.filepath(), clipboard.Selection)

    def dragEnterEvent(self, event):
        """
        Processes drag enter events.
        
        :param      event | <QDragEvent>
        """
        if (event.mimeData().hasUrls()):
            event.acceptProposedAction()

    def dragMoveEvent(self, event):
        """
        Processes drag move events.
        
        :param      event | <QDragEvent>
        """
        if (event.mimeData().hasUrls()):
            event.acceptProposedAction()

    def dropEvent(self, event):
        """
        Processes drop event.
        
        :param      event | <QDropEvent>
        """
        if (event.mimeData().hasUrls()):
            url = event.mimeData().urls()[0]
            filepath = url.toLocalFile()
            if (filepath):
                self.setFilepath(filepath)

    def emitFilepathChanged(self):
        """
        Emits the filepathChanged signal for this widget if the signals are \
        not being blocked.
        """
        if (not self.signalsBlocked()):
            self.filepathChanged.emit(self.filepath())

    def eventFilter(self, object, event):
        """
        Overloads the eventFilter to look for click events on the line edit.
        
        :param      object | <QObject>
                    event  | <QEvent>
        """
        if ( object == self._filepathEdit and \
             self._filepathEdit.isReadOnly() and \
             event.type() == event.MouseButtonPress and \
             event.button() == Qt.LeftButton ):
            self.pickFilepath()

        return False

    def filepath(self, validated=False):
        """
        Returns the filepath for this widget.  If the validated flag is set \
        then this method will only return if the file or folder actually \
        exists for this path.  In the case of a SaveFile, only the base folder \
        needs to exist on the system, in other modes the actual filepath must \
        exist.  If not validated, the text will return whatever is currently \
        entered.
        
        :return     <str>
        """
        paths = self.filepaths()
        if not paths:
            return ''

        if not validated or self.isValid():
            return paths[0]
        return ''

    def filepaths(self):
        """
        Returns a list of the filepaths for this edit.
        
        :return     [<str>, ..]
        """
        return str(self._filepathEdit.text()).split(os.path.pathsep)

    def filepathMode(self):
        """
        Returns the filepath mode for this widget.
        
        :return     <XFilepathEdit.Mode>
        """
        return self._filepathMode

    def filepathModeText(self):
        """
        Returns the text representation for this filepath mode.
        
        :return     <str>
        """
        return XFilepathEdit.Mode[self._filepathMode]

    def filepathTypes(self):
        """
        Returns the filepath types that will be used for this widget.
        
        :return     <str>
        """
        return self._filepathTypes

    def hint(self):
        """
        Returns the hint for this filepath.
        
        :return     <str>
        """
        return self._filepathEdit.hint()

    def icon(self):
        """
        Returns the icon that is used for this filepath widget.
        
        :return     <QIcon>
        """
        return self._filepathButton.icon()

    def invalidBackground(self):
        """
        Returns the invalid background color for this widget.
        
        :return     <QColor>
        """
        return self._invalidBackground

    def invalidForeground(self):
        """
        Returns the invalid foreground color for this widget.
        
        :return     <QColor>
        """
        return self._invalidForeground

    def isValid(self):
        """
        Returns whether or not the filepath exists on the system. \
        In the case of a SaveFile, only the base folder \
        needs to exist on the system, in other modes the actual filepath must \
        exist.
        
        :return     <bool>
        """
        check = str(self._filepathEdit.text()).split(os.path.pathsep)[0]
        if (self.filepathMode() == XFilepathEdit.Mode.SaveFile):
            check = os.path.dirname(check)

        return os.path.exists(check)

    def isValidated(self):
        """
        Set whether or not to validate the filepath as the user is working \
        with it.
        
        :return     <bool>
        """
        return self._validated

    def isReadOnly(self):
        """
        Returns if the widget is read only for text editing or not.
        
        :return     <bool>
        """
        return self._filepathEdit.isReadOnly()

    def normalizePath(self):
        """
        Returns whether or not the path should be normalized for the current
        operating system.  When off, it will be defaulted to forward slashes
        (/).
        
        :return     <bool>
        """
        return self._normalizePath

    def pickFilepath(self):
        """
        Prompts the user to select a filepath from the system based on the \
        current filepath mode.
        """
        mode = self.filepathMode()

        filepath = ''
        filepaths = []
        curr_dir = str(self._filepathEdit.text())
        if (not curr_dir):
            curr_dir = QDir.currentPath()

        if mode == XFilepathEdit.Mode.SaveFile:
            filepath = QFileDialog.getSaveFileName(self, self.windowTitle(),
                                                   curr_dir,
                                                   self.filepathTypes())

        elif mode == XFilepathEdit.Mode.OpenFile:
            filepath = QFileDialog.getOpenFileName(self, self.windowTitle(),
                                                   curr_dir,
                                                   self.filepathTypes())

        elif mode == XFilepathEdit.Mode.OpenFiles:
            filepaths = QFileDialog.getOpenFileNames(self, self.windowTitle(),
                                                     curr_dir,
                                                     self.filepathTypes())

        else:
            filepath = QFileDialog.getExistingDirectory(
                self, self.windowTitle(), curr_dir)

        if filepath:
            if type(filepath) == tuple:
                filepath = filepath[0]
            self.setFilepath(str(filepath))
        elif filepaths:
            self.setFilepaths(map(str, filepaths))

    def setAutoRaise(self, state):
        """
        Sets whether or not the tool button will auto raise.
        
        :param      state | <bool>
        """
        self._filepathButton.setAutoRaise(state)

    @qt.Slot(int)
    def setFilepathMode(self, mode):
        """
        Sets the filepath mode for this widget to the inputed mode.
        
        :param      mode | <XFilepathEdit.Mode>
        """
        self._filepathMode = mode

    @qt.Slot(str)
    def setFilepathModeText(self, text):
        """
        Sets the filepath mode for this widget based on the inputed text.
        
        :param      text | <str>
        
        :return     <bool> | success
        """
        try:
            self.setFilepathMode(XFilepathEdit.Mode[str(text)])
            return True
        except KeyError:
            return False

    @qt.Slot(str)
    def setFilepathTypes(self, filepathTypes):
        """
        Sets the filepath type string that will be used when looking up \
        filepaths.
        
        :param      filepathTypes | <str>
        """
        self._filepathTypes = filepathTypes

    @qt.Slot(str)
    def setFilepath(self, filepath):
        """
        Sets the filepath text for this widget to the inputed path.
        
        :param      filepath | <str>
        """
        if self.normalizePath():
            filepath = os.path.normpath(str(filepath))
        else:
            filepath = os.path.normpath(str(filepath)).replace('\\', '/')

        self._filepathEdit.setText(filepath)

    def setFilepaths(self, filepaths):
        """
        Sets the list of the filepaths for this widget to the inputed paths.
        
        :param      filepaths | [<str>, ..]
        """
        self.setFilepath(os.path.pathsep.join(filepaths))

    def setHint(self, hint):
        """
        Sets the hint for this filepath.
        
        :param      hint | <str>
        """
        if self.normalizePath():
            filepath = os.path.normpath(str(hint))
        else:
            filepath = os.path.normpath(str(hint)).replace('\\', '/')

        self._filepathEdit.setHint(hint)

    def setIcon(self, icon):
        """
        Sets the icon that will be used for this widget's tool button.
        
        :param      icon | <QIcon> || <str>
        """
        self._filepathButton.setIcon(QIcon(icon))

    def setInvalidBackground(self, bg):
        """
        Sets the invalid background color for this widget to the inputed widget.
        
        :param      bg | <QColor>
        """
        self._invalidBackground = QColor(bg)

    def setInvalidForeground(self, fg):
        """
        Sets the invalid foreground color for this widget to the inputed widget.
        
        :param      fg | <QColor>
        """
        self._invalidForeground = QColor(fg)

    def setNormalizePath(self, state):
        """
        Sets whether or not the path should be normalized for the current
        operating system.  When off, it will be defaulted to forward slashes
        (/).
        
        :param      state | <bool>
        """
        self._normalizePath = state

    @qt.Slot(bool)
    def setReadOnly(self, state):
        """
        Sets whether or not this filepath widget is readonly in the text edit.
        
        :param      state | <bool>
        """
        self._filepathEdit.setReadOnly(state)

    @qt.Slot(bool)
    def setValidated(self, state):
        """
        Set whether or not to validate the path as the user edits it.
        
        :param      state | <bool>
        """
        self._validated = state
        palette = self.palette()

        # reset the palette to default, revalidate
        self._filepathEdit.setPalette(palette)
        self.validate()

    def setValidBackground(self, bg):
        """
        Sets the valid background color for this widget to the inputed color.
        
        :param      bg | <QColor>
        """
        self._validBackground = QColor(bg)

    def setValidForeground(self, fg):
        """
        Sets the valid foreground color for this widget to the inputed color.
        
        :param      fg | <QColor>
        """
        self._validForeground = QColor(fg)

    def showMenu(self, pos):
        """
        Popups a menu for this widget.
        """
        menu = QMenu(self)
        menu.setAttribute(Qt.WA_DeleteOnClose)
        menu.addAction('Clear').triggered.connect(self.clearFilepath)
        menu.addSeparator()
        menu.addAction('Copy Filepath').triggered.connect(self.copyFilepath)

        menu.exec_(self.mapToGlobal(pos))

    def validBackground(self):
        """
        Returns the valid background color for this widget.
        
        :return     <QColor>
        """
        return self._validBackground

    def validForeground(self):
        """
        Returns the valid foreground color for this widget.
        
        :return     <QColor>
        """
        return self._validForeground

    def validateFilepath(self):
        """
        Alters the color scheme based on the validation settings.
        """
        if (not self.isValidated()):
            return

        valid = self.isValid()
        if (not valid):
            fg = self.invalidForeground()
            bg = self.invalidBackground()
        else:
            fg = self.validForeground()
            bg = self.validBackground()

        palette = self.palette()
        palette.setColor(palette.Base, bg)
        palette.setColor(palette.Text, fg)
        self._filepathEdit.setPalette(palette)

    # map Qt properties
    x_autoRaise = qt.Property(bool, autoRaise, setAutoRaise)
    x_filepathTypes = qt.Property(str, filepathTypes, setFilepathTypes)
    x_filepath = qt.Property(str, filepath, setFilepath)
    x_readOnly = qt.Property(bool, isReadOnly, setReadOnly)
    x_validated = qt.Property(bool, isValidated, setValidated)
    x_hint = qt.Property(str, hint, setHint)
    x_icon = qt.Property('QIcon', icon, setIcon)
    x_normalizePath = qt.Property(bool, normalizePath, setNormalizePath)

    x_invalidForeground = qt.Property('QColor', invalidForeground,
                                      setInvalidForeground)

    x_invalidBackground = qt.Property('QColor', invalidBackground,
                                      setInvalidBackground)

    x_validForeground = qt.Property('QColor', validForeground,
                                    setValidForeground)

    x_validBackground = qt.Property('QColor', validBackground,
                                    setValidBackground)

    x_filepathModeText = qt.Property(str, filepathModeText,
                                     setFilepathModeText)
Example #8
0
class XView(QWidget):
    activated               = qt.Signal()
    currentStateChanged     = qt.Signal()
    windowTitleChanged      = qt.Signal(str)
    sizeConstraintChanged   = qt.Signal()
    initialized             = qt.Signal()
    visibleStateChanged     = qt.Signal(bool)
    
    _registry       = {}
    _globals        = {}
    
    _viewName           = ''
    _viewGroup          = 'Default'
    _viewIcon           = resources.find('img/view/view.png')
    _viewSingleton      = False
    _popupSingleton     = True
    _popupInstance      = None
    
    _currentViewRef     = None
    _instances          = None
    _instanceSingleton  = None
    _dispatcher         = None
    _dispatch           = {}
    
    __designer_icon__ = resources.find('img/ui/view.png')
    
    SignalPolicy        = enum('BlockIfNotCurrent',
                               'BlockIfNotInGroup',
                               'BlockIfNotVisible',
                               'BlockIfNotInitialized',
                               'NeverBlock')
    
    def __init__(self, parent, autoKillThreads=True):
        super(XView, self).__init__( parent )
        
        if not self._viewSingleton:
            self.setAttribute(Qt.WA_DeleteOnClose)
        
        # define custom properties
        self._initialized = False
        self._destroyThreadsOnClose = True
        self._viewingGroup = 0
        self._signalPolicy = XView.SignalPolicy.BlockIfNotInitialized | \
                             XView.SignalPolicy.BlockIfNotVisible | \
                             XView.SignalPolicy.BlockIfNotInGroup
        
        self._visibleState = False  # storing this state for knowing if a
                                    # widget WILL be visible once Qt finishes
                                    # processing for purpose of signal
                                    # validation.
        
        # setup default properties
        self.setFocusPolicy( Qt.StrongFocus )
        self.setWindowTitle( self.viewName() )
        self.registerInstance(self)
        
        if autoKillThreads:
            QApplication.instance().aboutToQuit.connect(self.killChildThreads)
    
    def canClose( self ):
        return True
    
    def closeEvent( self, event ):
        if self.isViewSingleton():
            self.setParent(None)
        
        # make sure to destroy any threads running on close
        if self.testAttribute(Qt.WA_DeleteOnClose) and \
           self.destroyThreadsOnClose():
            self.killChildThreads()
        
        # clear out any progress loaders
        XLoaderWidget.stopAll(self)
        
        # remove any registered instances
        if self.testAttribute(Qt.WA_DeleteOnClose):
            self.unregisterInstance(self)
        
        super(XView, self).closeEvent(event)
    
    def dispatchConnect( self, signal, slot ):
        """
        Connect the slot for this view to the given signal that gets
        emitted by the XView.dispatch() instance.
        
        :param      signal | <str>
                    slot   | <callable>
        """
        XView.dispatch().connect(signal, slot)
    
    def dispatchEmit( self, signal, *args ):
        """
        Emits the given signal via the XView dispatch instance with the
        given arguments.
        
        :param      signal | <str>
                    args   | <tuple>
        """
        XView.setGlobal('emitGroup', self.viewingGroup())
        XView.dispatch().emit(signal, *args)
    
    def destroyThreadsOnClose( self ):
        """
        Marks whether or not any child threads should be destroyed when \
        this view is closed.
        
        :return     <bool>
        """
        return self._destroyThreadsOnClose
    
    def duplicate( self, parent ):
        """
        Duplicates this current view for another.  Subclass this method to 
        provide any additional duplication options.
        
        :param      parent | <QWidget>
        
        :return     <XView> | instance of this class
        """
        # only return a single singleton instance
        if ( self.isViewSingleton() ):
            return self
            
        output = type(self).createInstance(parent)
        
        # save/restore the current settings
        xdata = ElementTree.Element('data')
        self.saveXml(xdata)
        new_name = output.objectName()
        output.setObjectName(self.objectName())
        output.restoreXml(xdata)
        output.setObjectName(new_name)
        
        return output
    
    def killChildThreads(self):
        """
        Kills all child threads for this view.
        """
        threads = self.findChildren(QThread)
        for thread in threads:
            thread.finished.connect(thread.deleteLater)
            thread.quit()
            thread.wait(100)
    
    def hideEvent(self, event):
        """
        Sets the visible state for this widget.  If it is the first time this
        widget will be visible, the initialized signal will be emitted.
        
        :param      state | <bool>
        """
        super(XView, self).hideEvent(event)
        
        # record the visible state for this widget to be separate of Qt's
        # system to know if this view WILL be visible or not once the 
        # system is done processing.  This will affect how signals are
        # validated as part of the visible slot delegation
        self._visibleState = False
        
        if not self.signalsBlocked():
            self.visibleStateChanged.emit(False)
    
    def initialize(self, force=False):
        """
        Initializes the view if it is visible or being loaded.
        """
        if force or (self.isVisible() and \
                     not self.isInitialized() and \
                     not self.signalsBlocked()):
            
            self._initialized = True
            self.initialized.emit()
    
    def isCurrent( self ):
        return self == self.currentView()
    
    def isInitialized(self):
        """
        Returns whether or not this view has been initialized.  A view will
        be initialized the first time it becomes visible to the user.  You
        can use this to delay loading of information until it is needed by
        listening for the initialized signal.
        
        :return     <bool>
        """
        return self._initialized
    
    @deprecatedmethod('XView', 'Use restoreXml instead.')
    def restoreSettings( self, settings ):
        """
        Restores the settings for this view from the inputed QSettings.
        
        :param      settings | <QSettings>
        """
        pass
    
    def restoreXml( self, xml ):
        """
        Restores the settings for this view from the inputed XML node.
        
        :param      xml | <xml.etree.ElementTree.Element>
        """
        pass
    
    @deprecatedmethod('XView', 'Use saveXml instead.')
    def saveSettings( self, settings ):
        """
        Saves the current settings for this view to the inputed QSettings.
        
        :param      settings | <QSettings>
        """
        pass
    
    def saveXml( self, xml ):
        """
        Saves the settings for this view to the inputed XML node.
        
        :param      xml | <xml.etree.ElementTree.Element>
        """
        pass
    
    def settingsName( self ):
        """
        Returns the default settings name for this view.
        
        :return     <str>
        """
        return 'Views/%s' % self.objectName()
    
    def setCurrent( self, state = True ):
        """
        Marks this view as the current source based on the inputed flag.  \
        This method will return True if the currency changes.
        
        :return     <bool>
        """
        if ( state ):
            changed = self.setCurrentView(self)
            
        elif ( self.currentView() == self ):
            changed = self.setCurrentView(None)
        
        if ( changed and not self.signalsBlocked() ):
            self.currentStateChanged.emit()
            if state:
                self.activated.emit()
        
        return changed
    
    def setDestroyThreadsOnClose( self, state ):
        """
        Marks whether or not any child threads should be destroyed when \
        this view is closed.
        
        :param     state | <bool>
        """
        self._destroyThreadsOnClose = state
    
    def setFixedHeight( self, height ):
        """
        Sets the maximum height value to the inputed height and emits the \
        sizeConstraintChanged signal.
        
        :param      height | <int>
        """
        super(XView, self).setFixedHeight(height)
        
        if ( not self.signalsBlocked() ):
            self.sizeConstraintChanged.emit()
    
    def setFixedWidth( self, width ):
        """
        Sets the maximum width value to the inputed width and emits the \
        sizeConstraintChanged signal.
        
        :param      width | <int>
        """
        super(XView, self).setFixedWidth(width)
        
        if ( not self.signalsBlocked() ):
            self.sizeConstraintChanged.emit()
    
    def setMaximumHeight( self, height ):
        """
        Sets the maximum height value to the inputed height and emits the \
        sizeConstraintChanged signal.
        
        :param      height | <int>
        """
        super(XView, self).setMaximumHeight(height)
        
        if ( not self.signalsBlocked() ):
            self.sizeConstraintChanged.emit()
    
    def setMaximumSize( self, *args ):
        """
        Sets the maximum size value to the inputed size and emits the \
        sizeConstraintChanged signal.
        
        :param      *args | <tuple>
        """
        super(XView, self).setMaximumSize(*args)
        
        if ( not self.signalsBlocked() ):
            self.sizeConstraintChanged.emit()
    
    def setMaximumWidth( self, width ):
        """
        Sets the maximum width value to the inputed width and emits the \
        sizeConstraintChanged signal.
        
        :param      width | <int>
        """
        super(XView, self).setMaximumWidth(width)
        
        if ( not self.signalsBlocked() ):
            self.sizeConstraintChanged.emit()
    
    def setMinimumHeight( self, height ):
        """
        Sets the minimum height value to the inputed height and emits the \
        sizeConstraintChanged signal.
        
        :param      height | <int>
        """
        super(XView, self).setMinimumHeight(height)
        
        if ( not self.signalsBlocked() ):
            self.sizeConstraintChanged.emit()
    
    def setMinimumSize( self, *args ):
        """
        Sets the minimum size value to the inputed size and emits the \
        sizeConstraintChanged signal.
        
        :param      *args | <tuple>
        """
        super(XView, self).setMinimumSize(*args)
        
        if ( not self.signalsBlocked() ):
            self.sizeConstraintChanged.emit()
    
    def setMinimumWidth( self, width ):
        """
        Sets the minimum width value to the inputed width and emits the \
        sizeConstraintChanged signal.
        
        :param      width | <int>
        """
        super(XView, self).setMinimumWidth(width)
        
        if ( not self.signalsBlocked() ):
            self.sizeConstraintChanged.emit()
    
    def setSignalPolicy( self, policy ):
        """
        Sets the signal delegation policy for this instance to the given 
        policy.  By default, signals will be delegates for groups or
        by currency if they are not in a group.  This will not directly
        affect signal propogation, only the result of the validateSignal
        method, so if you want to test against this, then you will need
        to check in your slot.
        
        :param      policy | <XView.SignalPolicy>
        """
        self._signalPolicy = policy
    
    def setViewingGroup( self, grp ):
        """
        Sets the viewing group that this view is associated with.
        
        :param      grp | <int>
        """
        self._viewingGroup = grp
    
    def setWindowTitle( self, title ):
        """
        Sets the window title for this view, and emits the windowTitleChanged \
        signal if the signals are not blocked.  Setting this title will update \
        the tab title for the view within the widget.
        
        :param      title | <str>
        """
        super(XView, self).setWindowTitle(title)
        if ( not self.signalsBlocked() ):
            self.windowTitleChanged.emit(title)
    
    def showActiveState( self, state ):
        """
        Shows this view in the active state based on the inputed state settings.
        
        :param      state | <bool>
        """
        self.setAutoFillBackground(True)
        
        palette = self.window().palette()
        clr = palette.color(palette.Window)
        avg = (clr.red() + clr.green() + clr.blue()) / 3
        
        if ( avg < 180 and state ):
            clr = clr.lighter(105)
        elif ( not state ):
            clr = clr.darker(105)
        
        palette.setColor(palette.Window, clr)
        self.setPalette(palette)
    
    def showEvent(self, event):
        """
        Sets the visible state for this widget.  If it is the first time this
        widget will be visible, the initialized signal will be emitted.
        
        :param      state | <bool>
        """
        super(XView, self).showEvent(event)
        
        # record the visible state for this widget to be separate of Qt's
        # system to know if this view WILL be visible or not once the 
        # system is done processing.  This will affect how signals are
        # validated as part of the visible slot delegation
        self._visibleState = True
        
        if not self.isInitialized():
            self.initialize()
        
        # after the initial time the view is loaded, the visibleStateChanged
        # signal will be emitted
        elif not self.signalsBlocked():
            self.visibleStateChanged.emit(True)
            
    def signalPolicy( self ):
        """
        Returns the signal policy for this instance.
        
        :return     <XView.SignalPolicy>
        """
        return self._signalPolicy
    
    def rootWidget( self ):
        widget = self
        while ( widget.parent() ):
            widget = widget.parent()
        return widget
    
    def validateSignal( self, policy = None ):
        """
        Validates that this view is part of the group that was emitting
        the signal.  Views that are not in any viewing group will accept
        all signals.
        
        :param      policy | <XView.SignalPolicy> || None
        
        :return     <bool>
        """
        # validate whether or not to process a signal
        if policy is None:
            policy = self.signalPolicy()
        
        group_check   = XView.getGlobal('emitGroup') == self.viewingGroup()
        current_check = self.isCurrent()
        
        # always delegate signals if they are not set to block,
        # or if the method is called directly (not from a signal)
        if not self.sender() or policy & XView.SignalPolicy.NeverBlock:
            return True
        
        # block delegation of the signal if the view is not initialized
        elif policy & XView.SignalPolicy.BlockIfNotInitialized and \
             not self.isInitialized():
            return False
        
        # block delegation if the view is not visible
        elif policy & XView.SignalPolicy.BlockIfNotVisible and \
            not self._visibleState:
            return False
        
        # block delegation if the view is not part of a group
        elif self.viewingGroup() and \
             policy & XView.SignalPolicy.BlockIfNotInGroup:
            return group_check
        
        # look for only currency releated connections
        elif policy & XView.SignalPolicy.BlockIfNotCurrent:
            return current_check
        
        else:
            return True
        
    def viewingGroup( self ):
        """
        Returns the viewing group that this view is assigned to.
        
        :return     <int>
        """
        return self._viewingGroup
    
    @classmethod
    def currentView( cls ):
        # look for last focused view
        if ( cls._currentViewRef ):
            inst = cls._currentViewRef()
            if ( inst ):
                return inst
        
        cls._currentViewRef = None
        return None
    
    @classmethod
    def createInstance( cls, parent ):
        # assign the singleton instance
        if ( cls._instanceSingleton ):
            instance = cls._instanceSingleton()
            
            # make sure the instance is still in use
            if ( not instance ):
                cls._instanceSingleton = None
            else:
                instance.setParent(parent)
                return instance
        
        # determine if we need to store a singleton
        inst = cls(parent)
        inst.setObjectName( cls.uniqueName() )
        if ( cls.isViewSingleton() ):
            cls._instanceSingleton = weakref.ref(inst)
        
        return inst
    
    @classmethod
    def destroyInstance( cls, inst ):
        if ( cls.isViewSingleton() ):
            inst.close()
            return
        
        inst.close()
        inst.setParent(None)
        inst.deleteLater()
    
    @classmethod
    @deprecatedmethod('XView', 'Use the XView.dispatch() syntax now.')
    def dispatcher( cls ):
        if ( not cls._dispatcher ):
            cls._dispatcher = cls.MetaDispatcher(cls)
            
        return cls._dispatcher
    
    @classmethod
    def instances( cls ):
        """
        Returns all generated instances of a particular view type.
        
        :return     [<XView>, ..]
        """
        if ( not cls._instances ):
            return []
        
        # purge the instance list
        insts = []
        refs  = []
        for ref in cls._instances:
            inst = ref()
            if ( not inst ):
                continue
            
            insts.append(inst)
            refs.append(ref)
        
        cls._instances = refs
        return insts
    
    @classmethod
    def isViewSingleton( cls ):
        return cls._viewSingleton
    
    @classmethod
    def isPopupSingleton( cls ):
        return cls._popupSingleton
    
    @classmethod
    def popup( cls, parent = None ):
        """
        Pops up this view as a new dialog.  If the forceDialog flag is set to \
        False, then it will try to activate the first instance of the view \
        within an existing viewwidget context before creating a new dialog.
        
        :param      parent      | <QWidget> || None
        """
        # popup the singleton view for this class
        if ( cls.isViewSingleton() ):
            inst = cls.currentView()
            if ( not inst ):
                inst = cls.createInstance(parent)
                
            inst.setWindowFlags(Qt.Dialog)
            inst.show()
        
        # popup the popupSingleton for this class
        inst = cls.popupInstance(parent)
        inst.show()
    
    @classmethod
    def popupInstance( cls, parent ):
        if ( cls._popupInstance ):
            return cls._popupInstance
        
        inst = cls.createInstance(parent)
        inst.setWindowFlags(Qt.Dialog)
        if cls.isPopupSingleton():
            inst.setAttribute(Qt.WA_DeleteOnClose, False)
            cls._popupInstance = inst
        
        return inst
    
    @classmethod
    def registerInstance( cls, instance ):
        if ( not cls._instances ):
            cls._instances = []
        
        cls._instances.append(weakref.ref(instance))
    
    @classmethod
    def registerToWindow( cls, window ):
        """
        Registers this view to the window to update additional menu items, \
        actions, and toolbars.
        
        :param      window | <QWidget>
        """
        pass
    
    @classmethod
    def restoreGlobalSettings( cls, settings ):
        """
        Restores the global settings for the inputed view class type.
        
        :param      cls      | <subclass of XView>
                    settings | <QSettings>
        """
        pass
    
    @classmethod
    def saveGlobalSettings( cls, settings ):
        """
        Saves the global settings for the inputed view class type.
        
        :param      cls      | <subclass of XView>
                    settings | <QSettings>
        """
        pass
    
    @classmethod
    def setViewGroup( cls, grp ):
        cls._viewGroup = grp
    
    @classmethod
    def setCurrentView( cls, view ):
        current = cls.currentView()
        if ( current == view ):
            return False
            
        elif ( current ):
            current.showActiveState(False)
        
        if ( view ):
            view.showActiveState(True)
        
        cls._currentViewRef = weakref.ref(view)
        return True
    
    @classmethod
    def setViewIcon( cls, icon ):
        cls._viewIcon = icon
    
    @classmethod
    def setViewName( cls, name ):
        cls._viewName = name
    
    @classmethod
    def setViewSingleton( cls, state ):
        cls._viewSingleton = state
    
    @classmethod
    def setPopupSingleton( cls, state ):
        cls._popupSingleton = state
    
    @classmethod
    def uniqueName( cls ):
        names = map(lambda x: str(x.objectName()), cls.instances())
        index = 1
        base  = cls.viewName()
        name  = '%s%02i' % (base, index)
        
        while ( name in names ):
            index += 1
            name = '%s%02i' % (base, index)
        
        return name
    
    @classmethod
    def unregisterInstance( cls, instance ):
        if ( not cls._instances ):
            return
        
        if ( cls._currentViewRef and instance == cls._currentViewRef() ):
            cls._currentViewRef = None
        
        refs = []
        for ref in cls._instances:
            inst = ref()
            if ( not inst or inst == instance ):
                continue
            
            refs.append(ref)
        
        cls._instances = refs
    
    @classmethod
    def unregisterToWindow( cls, window ):
        """
        Registers this view to the window to update additional menu items, \
        actions, and toolbars.
        
        :param      window | <QWidget>
        """
        pass
    
    @classmethod
    def viewGroup( cls ):
        return cls._viewGroup
    
    @classmethod
    def viewIcon( cls ):
        return cls._viewIcon
    
    @classmethod
    def viewName( cls ):
        return cls._viewName
    
    @classmethod
    def viewTypeName( cls ):
        """
        Returns the unique name for this view type by joining its group with \
        its name.
        
        :return     <str>
        """
        return '%s.%s' % (cls.viewGroup(), cls.viewName())
    
    #--------------------------------------------------------------------------
    
    @staticmethod
    def dispatch( location = 'Central' ):
        """
        Returns the instance of the global view dispatching system.  All views \
        will route their signals through the central hub so no single view \
        necessarily depends on another.
        
        :return     <XViewDispatch>
        """
        dispatch = XView._dispatch.get(str(location))
        if ( not dispatch ):
            dispatch = XViewDispatch(QApplication.instance())
            XView._dispatch[str(location)] = dispatch
        
        return dispatch
        
    @staticmethod
    def getGlobal( key, default = None ):
        """
        Returns the global value for the inputed key.
        
        :param      key     | <str>
                    default | <variant>
        
        :return     <variant>
        """
        return XView._globals.get(key, default)
    
    @staticmethod
    def registeredView( viewName, location = 'Central' ):
        """
        Returns the view that is registered to the inputed location for the \
        given name.
        
        :param      viewName | <str>
                    location | <str>
        
        :return     <subclass of XView> || None
        """
        for viewType in XView._registry.get(str(location), []):
            if ( viewType.viewName() == viewName ):
                return viewType
        return None
    
    @staticmethod
    def registeredViews( location = 'Central' ):
        """
        Returns all the views types that have bene registered to a particular \
        location.
        
        :param      location | <str>
        
        :return     [<subclass of XView>, ..]
        """
        return XView._registry.get(str(location), [])
        
    @staticmethod
    def registerView( viewType, location = 'Central' ):
        """
        Registers the inputed view type to the given location.  The location \
        is just a way to group and organize potential view plugins for a \
        particular widget, and is determined per application.  This eases \
        use when building a plugin based system.  It has no relevance to the \
        XView class itself where you register a view.
        
        :param      viewType | <subclass of XView>
        """
        # update the dispatch signals
        if ( '__xview_signals__' in viewType.__dict__ ):
            XView.dispatch(location).registerSignals(viewType.__xview_signals__)
            
        location = str(location)
        XView._registry.setdefault(location, [])
        XView._registry[str(location)].append(viewType)
    
    @staticmethod
    def setGlobal( key, value ):
        """
        Shares a global value across all views by setting the key in the \
        globals dictionary to the inputed value.
        
        :param      key     | <str> 
                    value   | <variant>
        """
        XView._globals[key] = value
    
    @staticmethod
    def updateCurrentView( oldWidget, newWidget ):
        """
        Updates the current view for each view class type based on the new \
        focused widget.
        
        :param      oldWidget | <QWidget>
                    newWidget | <QWidget>
        """
        widget = newWidget
        
        while widget:
            if isinstance(widget, XView):
                widget.setCurrent()
                break
                
            widget = widget.parent()
Example #9
0
class XIconButton(QPushButton):
    """ """
    __designer_icon__ = projexui.resources.find('img/ui/icon.png')

    filepathChanged = qt.Signal(str)

    def __init__(self, parent=None):
        super(XIconButton, self).__init__(parent)

        # define custom properties
        self._filepath = ''
        self._fileTypes = 'PNG Files (*.png);;All Files (*.*)'

        # set default properties
        self.setFixedWidth(64)
        self.setFixedHeight(64)
        self.setAcceptDrops(True)

        # create connections
        self.clicked.connect(self.pickFilepath)

    def filepath(self):
        """
        Returns the filepath for this button.
        
        :return     <str>
        """
        return self._filepath

    def fileTypes(self):
        """
        Returns the file types that will be used to filter this button.
        
        :return     <str>
        """
        return self._fileTypes

    def dragEnterEvent(self, event):
        """
        Handles a drag enter event.
        
        :param      event | <QEvent>
        """
        if (event.mimeData().hasUrls()):
            event.acceptProposedAction()

    def dragMoveEvent(self, event):
        """
        Handles a drag move event.
        
        :param      event | <QEvent>
        """
        if (event.mimeData().hasUrls()):
            event.acceptProposedAction()

    def dropEvent(self, event):
        """
        Handles a drop event.
        """
        url = event.mimeData().urls()[0]
        url_path = str(url.toString())

        # download an icon from the web
        if (not url_path.startswith('file:')):
            filename = os.path.basename(url_path)
            temp_path = os.path.join(str(QDir.tempPath()), filename)

            try:
                urllib.urlretrieve(url_path, temp_path)
            except IOError:
                return

            self.setFilepath(temp_path)
        else:
            self.setFilepath(url_path.replace('file://', ''))

    def pickFilepath(self):
        """
        Picks the image file to use for this icon path.
        """
        filepath = QFileDialog.getOpenFileName(self, 'Select Image File',
                                               QDir.currentPath(),
                                               self.fileTypes())

        if type(filepath) == tuple:
            filepath = str(filepath[0])

        if (filepath):
            self.setFilepath(filepath)

    def setFilepath(self, filepath):
        """
        Sets the filepath for this button to the inputed path.
        
        :param      filepath | <str>
        """
        self._filepath = str(filepath)
        self.setIcon(QIcon(filepath))
        if (not self.signalsBlocked()):
            self.filepathChanged.emit(filepath)

    def setFileTypes(self, fileTypes):
        """
        Sets the filetypes for this button to the inputed types.
        
        :param      fileTypes | <str>
        """
        self._fileTypes = fileTypes

    @staticmethod
    def buildIcon(icon):
        """
        Builds an icon from the inputed information.
        
        :param      icon | <variant>
        """
        if icon is None:
            return QIcon()

        if type(icon) == buffer:
            try:
                icon = QIcon(projexui.generatePixmap(icon))
            except:
                icon = QIcon()
        else:
            try:
                icon = QIcon(icon)
            except:
                icon = QIcon()

        return icon

    x_filepath = qt.Property(str, filepath, setFilepath)
    x_fileTypes = qt.Property(str, fileTypes, setFileTypes)
Example #10
0
class XLineEdit(QLineEdit):
    """
    Creates a new QLineEdit that allows the user to define a grayed out text
    hint that will be drawn when there is no text assigned to the widget.
    """

    __designer_icon__ = projexui.resources.find('img/ui/lineedit.png')

    textEntered = qt.Signal(str)

    InputFormat = enum('Normal', 'CamelHump', 'Underscore', 'Dash',
                       'ClassName', 'NoSpaces', 'Capitalize', 'Uppercase',
                       'Lowercase', 'Pretty')

    def __init__(self, *args):
        super(XLineEdit, self).__init__(*args)

        palette = self.palette()
        hint_clr = palette.color(palette.Disabled, palette.Text)

        # set the hint property
        self._hint = ''
        self._spacer = '_'
        self._hintColor = hint_clr
        self._cornerRadius = 0
        self._inputFormat = XLineEdit.InputFormat.Normal
        self._selectAllOnFocus = False
        self._focusedIn = False
        self._useHintValue = False

        self._icon = QIcon()
        self._iconSize = QSize(14, 14)
        self._buttons = {}

        self.textChanged.connect(self.adjustText)
        self.returnPressed.connect(self.emitTextEntered)

    def adjustText(self):
        """
        Updates the text based on the current format options.
        """
        pos = self.cursorPosition()
        self.blockSignals(True)
        super(XLineEdit, self).setText(self.formatText(self.text()))
        self.setCursorPosition(pos)
        self.blockSignals(False)

    def addButton(self, button, alignment=None):
        """
        Adds a button the edit.  All the buttons will be layed out at the \
        end of the widget.
        
        :param      button      | <QToolButton>
                    alignment   | <Qt.Alignment>
        
        :return     <bool> | success
        """
        if alignment == None:
            if button.pos().x() < self.pos().x():
                alignment = Qt.AlignLeft
            else:
                alignment = Qt.AlignRight

        all_buttons = self.buttons()
        if button in all_buttons:
            return False

        # move the button to this edit
        button.setAutoRaise(True)
        button.setParent(self)
        button.setIconSize(self.iconSize())
        button.setCursor(Qt.ArrowCursor)
        button.setFixedSize(QSize(self.height() - 2, self.height() - 2))

        self._buttons.setdefault(alignment, [])
        self._buttons[alignment].append(button)
        self.adjustButtons()
        return True

    def adjustButtons(self):
        """
        Adjusts the placement of the buttons for this line edit.
        """
        y = 1

        for btn in self.buttons():
            btn.setIconSize(self.iconSize())
            btn.setFixedSize(QSize(self.height() - 2, self.height() - 2))

        # adjust the location for the left buttons
        left_buttons = self._buttons.get(Qt.AlignLeft, [])
        x = (self.cornerRadius() / 2.0) + 2

        for btn in left_buttons:
            btn.move(x, y)
            x += btn.width()

        # adjust the location for the right buttons
        right_buttons = self._buttons.get(Qt.AlignRight, [])

        w = self.width()
        bwidth = sum([btn.width() for btn in right_buttons])
        bwidth += (self.cornerRadius() / 2.0) + 1

        for btn in right_buttons:
            btn.move(w - bwidth, y)
            bwidth -= btn.width()

        self.adjustTextMargins()

    def adjustTextMargins(self):
        """
        Adjusts the margins for the text based on the contents to be displayed.
        """
        left_buttons = self._buttons.get(Qt.AlignLeft, [])

        if left_buttons:
            bwidth = left_buttons[-1].pos().x() + left_buttons[-1].width() - 4
        else:
            bwidth = 0 + (max(8, self.cornerRadius()) - 8)

        self.setTextMargins(bwidth, 0, 0, 0)

    def adjustStyleSheet(self):
        """
        Adjusts the stylesheet for this widget based on whether it has a \
        corner radius and/or icon.
        """
        radius = self.cornerRadius()
        icon = self.icon()

        if not self.objectName():
            self.setStyleSheet('')
        elif not (radius or icon):
            self.setStyleSheet('')
        else:
            palette = self.palette()

            options = {}
            options['corner_radius'] = radius
            options['padding'] = 5
            options['objectName'] = self.objectName()

            if icon and not icon.isNull():
                options['padding'] += self.iconSize().width() + 2

            self.setStyleSheet(LINEEDIT_STYLE % options)

    def buttons(self):
        """
        Returns all the buttons linked to this edit.
        
        :return     [<QToolButton>, ..]
        """
        all_buttons = []
        for buttons in self._buttons.values():
            all_buttons += buttons
        return all_buttons

    def cornerRadius(self):
        """
        Returns the rounding radius for this widget's corner, allowing a \
        developer to round the edges for a line edit on the fly.
        
        :return     <int>
        """
        return self._cornerRadius

    def currentText(self):
        """
        Returns the text that is available currently, \
        if the user has set standard text, then that \
        is returned, otherwise the hint is returned.
        
        :return     <str>
        """
        text = str(self.text())
        if (text):
            return text
        return self._hint

    def emitTextEntered(self):
        """
        Emits the text entered signal for this line edit, provided the
        signals are not being blocked.
        """
        if not self.signalsBlocked():
            self.textEntered.emit(self.text())

    def focusInEvent(self, event):
        """
        Updates the focus in state for this edit.
        
        :param      event | <QFocusEvent>
        """
        super(XLineEdit, self).focusInEvent(event)

        self._focusedIn = True

    def focusOutEvent(self, event):
        """
        Updates the focus in state for this edit.
        
        :param      event | <QFocusEvent>
        """
        super(XLineEdit, self).focusOutEvent(event)

        self._focusedIn = False

    def formatText(self, text):
        """
        Formats the inputed text based on the input format assigned to this
        line edit.
        
        :param      text | <str>
        
        :return     <str> | frormatted text
        """
        format = self.inputFormat()
        if format == XLineEdit.InputFormat.Normal:
            return text

        text = projex.text.toUtf8(text)
        if format == XLineEdit.InputFormat.CamelHump:
            return projex.text.camelHump(text)

        elif format == XLineEdit.InputFormat.Pretty:
            return projex.text.pretty(text)

        elif format == XLineEdit.InputFormat.Underscore:
            return projex.text.underscore(text)

        elif format == XLineEdit.InputFormat.Dash:
            return projex.text.dashed(text)

        elif format == XLineEdit.InputFormat.ClassName:
            return projex.text.classname(text)

        elif format == XLineEdit.InputFormat.NoSpaces:
            return projex.text.joinWords(text, self.spacer())

        elif format == XLineEdit.InputFormat.Capitalize:
            return text.capitalize()

        elif format == XLineEdit.InputFormat.Uppercase:
            return text.upper()

        elif format == XLineEdit.InputFormat.Lowercase:
            return text.lower()

        return text

    def hint(self):
        """
        Returns the hint value for this line edit.
        
        :return     <str>
        """
        return self._hint

    def hintColor(self):
        """
        Returns the hint color for this text item.
        
        :return     <QColor>
        """
        return self._hintColor

    def icon(self):
        """
        Returns the icon instance that is being used for this widget.
        
        :return     <QIcon> || None
        """
        return self._icon

    def iconSize(self):
        """
        Returns the icon size that will be used for this widget.
        
        :return     <QSize>
        """
        return self._iconSize

    def inputFormat(self):
        """
        Returns the input format for this widget.
        
        :return     <int>
        """
        return self._inputFormat

    def inputFormatText(self):
        """
        Returns the input format as a text value for this widget.
        
        :return     <str>
        """
        return XLineEdit.InputFormat[self.inputFormat()]

    def mousePressEvent(self, event):
        """
        Selects all the text if the property is set after this widget
        first gains focus.
        
        :param      event | <QMouseEvent>
        """
        super(XLineEdit, self).mousePressEvent(event)

        if self._focusedIn and self.selectAllOnFocus():
            self.selectAll()
            self._focusedIn = False

    def paintEvent(self, event):
        """
        Overloads the paint event to paint additional \
        hint information if no text is set on the \
        editor.
        
        :param      event      | <QPaintEvent>
        """
        super(XLineEdit, self).paintEvent(event)

        # paint the hint text if not text is set
        if self.text() and not (self.icon() and not self.icon().isNull()):
            return

        # paint the hint text
        painter = QPainter(self)
        painter.setPen(self.hintColor())

        icon = self.icon()
        left, top, right, bottom = self.getTextMargins()

        w = self.width()
        h = self.height() - 2

        w -= (right + left)
        h -= (bottom + top)

        if icon and not icon.isNull():
            size = icon.actualSize(self.iconSize())
            x = 5 + left
            y = (self.height() - size.height()) / 2.0

            painter.drawPixmap(x, y, icon.pixmap(size.width(), size.height()))

            w -= size.width() - 2
        else:
            x = 6 + left

        w -= sum([btn.width() for btn in self.buttons()])
        y = 2 + top

        # create the elided hint
        if not self.text() and self.hint():
            rect = self.cursorRect()
            metrics = QFontMetrics(self.font())
            hint = metrics.elidedText(self.hint(), Qt.ElideRight, w)
            align = self.alignment()

            if align & Qt.AlignHCenter:
                x = 0
            else:
                x = rect.center().x()

            painter.drawText(x, y, w, h, align, hint)

    def resizeEvent(self, event):
        """
        Overloads the resize event to handle updating of buttons.
        
        :param      event | <QResizeEvent>
        """
        super(XLineEdit, self).resizeEvent(event)
        self.adjustButtons()

    def selectAllOnFocus(self):
        """
        Returns whether or not this edit will select all its contents on
        focus in.
        
        :return     <bool>
        """
        return self._selectAllOnFocus

    def setCornerRadius(self, radius):
        """
        Sets the corner radius for this widget tot he inputed radius.
        
        :param      radius | <int>
        """
        self._cornerRadius = radius

        self.adjustStyleSheet()

    @qt.Slot(str)
    def setHint(self, hint):
        """
        Sets the hint text to the inputed value.
        
        :param      hint       | <str>
        """
        self._hint = self.formatText(hint)
        self.repaint()

    def setHintColor(self, clr):
        """
        Sets the color for the hint for this edit.
        
        :param      clr     | <QColor>
        """
        self._hintColor = clr

    def setIcon(self, icon):
        """
        Sets the icon that will be used for this widget to the inputed icon.
        
        :param      icon | <QIcon> || None
        """
        self._icon = QIcon(icon)

        self.adjustStyleSheet()

    def setIconSize(self, size):
        """
        Sets the icon size that will be used for this edit.
        
        :param      size | <QSize>
        """
        self._iconSize = size
        self.adjustTextMargins()

    def setInputFormat(self, inputFormat):
        """
        Sets the input format for this text.
        
        :param      inputFormat | <int>
        """
        self._inputFormat = inputFormat

    def setInputFormatText(self, text):
        """
        Sets the input format text for this widget to the given value.
        
        :param      text | <str>
        """
        try:
            self._inputFormat = XLineEdit.InputFormat[str(text)]
        except KeyError:
            pass

    def setObjectName(self, objectName):
        """
        Updates the style sheet for this line edit when the name changes.
        
        :param      objectName | <str>
        """
        super(XLineEdit, self).setObjectName(objectName)
        self.adjustStyleSheet()

    def setSelectAllOnFocus(self, state):
        """
        Returns whether or not this edit will select all its contents on
        focus in.
        
        :param      state | <bool>
        """
        self._selectAllOnFocus = state

    def setSpacer(self, spacer):
        """
        Sets the spacer that will be used for this line edit when replacing
        NoSpaces input formats.
        
        :param      spacer | <str>
        """
        self._spacer = spacer

    def setUseHintValue(self, state):
        """
        This method sets whether or not the value for this line edit should
        use the hint value if no text is found (within the projexui.xwidgetvalue
        plugin system).  When set to True, the value returned will first look
        at the text of the widget, and if it is blank, will then return the
        hint value.  If it is False, only the text value will be returned.
        
        :param      state | <bool>
        """
        self._useHintValue = state

    def setText(self, text):
        """
        Sets the text for this widget to the inputed text, converting it based \
        on the current input format if necessary.
        
        :param      text | <str>
        """
        if text is None:
            text = ''

        super(XLineEdit, self).setText(self.formatText(text))

    def setVisible(self, state):
        """
        Sets the visible state for this line edit.
        
        :param      state | <bool>
        """
        super(XLineEdit, self).setVisible(state)

        self.adjustStyleSheet()
        self.adjustTextMargins()

    def spacer(self):
        """
        Returns the spacer that is used to replace spaces when the NoSpaces
        input format is used.
        
        :return     <str>
        """
        return self._spacer

    def useHintValue(self):
        """
        This method returns whether or not the value for this line edit should
        use the hint value if no text is found (within the projexui.xwidgetvalue
        plugin system).  When set to True, the value returned will first look
        at the text of the widget, and if it is blank, will then return the
        hint value.  If it is False, only the text value will be returned.
        
        :return     <bool>
        """
        return self._useHintValue

    # create Qt properties
    x_hint = qt.Property(str, hint, setHint)
    x_icon = qt.Property('QIcon', icon, setIcon)
    x_iconSize = qt.Property(QSize, iconSize, setIconSize)
    x_hintColor = qt.Property('QColor', hintColor, setHintColor)
    x_cornerRadius = qt.Property(int, cornerRadius, setCornerRadius)
    x_inputFormatText = qt.Property(str, inputFormatText, setInputFormatText)
    x_spacer = qt.Property(str, spacer, setSpacer)
    x_selectAllOnFocus = qt.Property(bool, selectAllOnFocus,
                                     setSelectAllOnFocus)
    x_useHintValue = qt.Property(bool, useHintValue, setUseHintValue)

    # hack for qt
    setX_icon = setIcon
Example #11
0
class XViewProfileManager(QWidget):
    currentProfileChanged = qt.Signal(qt.PyObject)
    optionsMenuRequested = qt.Signal(QPoint)

    def __init__(self, parent=None):
        super(XViewProfileManager, self).__init__(parent)

        # define custom properties
        self._profiles = []
        self._optionsMenuPolicy = Qt.DefaultContextMenu
        self._viewWidget = None

        # define the interface
        self._profileCombo = QComboBox(self)
        self._optionsButton = QToolButton(self)
        self._optionsButton.setAutoRaise(True)
        self._optionsButton.setToolTip('Advanced Options')
        self._optionsButton.setIcon(QIcon(resources.find('img/advanced.png')))

        layout = QHBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)

        layout.addWidget(self._profileCombo)
        layout.addWidget(self._optionsButton)

        self.setLayout(layout)

        # create connections
        self._profileCombo.currentIndexChanged.connect(
            self.handleProfileChange)
        self._optionsButton.clicked.connect(self.showOptionsMenu)

    def addProfile(self, profile):
        """
        Adds the inputed profile to the system.
        
        :param      profile | <XViewProfile>
        """
        if (profile in self._profiles):
            return

        self._profiles.append(profile)
        self._profileCombo.blockSignals(True)
        self._profileCombo.addItem(profile.name())
        self._profileCombo.setCurrentIndex(self._profileCombo.count() - 1)
        self._profileCombo.blockSignals(False)

    def currentProfile(self):
        """
        Returns the currently selected profile from the system.
        
        :return     <XViewProfile>
        """
        index = self._profileCombo.currentIndex()

        if (0 <= index and index < len(self._profiles)):
            return self._profiles[index]
        return None

    def handleProfileChange(self):
        """
        Emits that the current profile has changed.
        """
        # restore the profile settings
        prof = self.currentProfile()
        vwidget = self.viewWidget()
        if (vwidget):
            prof.restore(vwidget)

        if (not self.signalsBlocked()):
            self.currentProfileChanged.emit(self.currentProfile())

    def optionsMenuPolicy(self):
        """
        Returns the option menu policy for this widget.
        
        :return     <Qt.MenuPolicy>
        """
        return self._optionsMenuPolicy

    def profiles(self):
        """
        Returns a list of all the profiles for this system.
        
        :return     [<XViewProfile>, ..]
        """
        return self._profiles

    def removeProfile(self, profile):
        """
        Adds the inputed profile to the system.
        
        :param      profile | <XViewProfile>
        """
        if (not profile in self._profiles):
            return

        index = self._profiles.index(profile)
        self._profiles.remove(profile)
        self._profileCombo.blockSignals(True)
        self._profileCombo.takeItem(index)
        self._profileCombo.blockSignals(False)

    def restoreSettings(self, settings):
        """
        Restores settings from the application.
        
        :param      settings | <QSettings>
        """
        settings.beginGroup(self.objectName())

        curr_prof = None
        curr_name = qt.unwrapVariant(settings.value('current'))

        profiles = []
        for prof_name in settings.childGroups():
            settings.beginGroup(prof_name)

            prof_str = qt.unwrapVariant(settings.value('profile'))
            profile = XViewProfile.fromString(prof_str)
            profile.setName(prof_name)

            if (prof_name == curr_name):
                curr_prof = profile

            profiles.append(profile)

            settings.endGroup()

        self.blockSignals(True)
        self._profileCombo.blockSignals(True)
        self.setProfiles(profiles)

        if (curr_prof):
            self.setCurrentProfile(curr_prof)

        self._profileCombo.blockSignals(False)
        self.blockSignals(False)

        settings.endGroup()

    def saveSettings(self, settings):
        """
        Saves the settings for this widget to the application
        
        :param      settings | <QSettings>
        """
        settings.beginGroup(self.objectName())

        curr_prof = self.currentProfile()
        if (curr_prof):
            settings.setValue('current', curr_prof.name())

        for profile in self.profiles():
            settings.beginGroup(profile.name())
            settings.setValue('profile', qt.wrapVariant(profile.toString()))
            settings.endGroup()

        settings.endGroup()

    def setCurrentProfile(self, profile):
        """
        Sets the current profile to the inputed profile.
        
        :param      profile | <XViewProfile>
        """
        try:
            index = self._profiles.index(profile)
        except ValueError:
            index = -1

        self._profileCombo.setCurrentIndex(index)

    def setOptionsMenuPolicy(self, menuPolicy):
        """
        Sets the options menu policy for this item.
        
        :param      menuPolicy | <Qt.MenuPolicy>
        """
        self._optionsMenuPolicy = menuPolicy

    def setProfiles(self, profiles):
        """
        Sets a list of profiles to be the options for the manager.
        
        :param      profiles | [<XViewProfile>, ..]
        """
        self.blockSignals(True)
        self._profileCombo.blockSignals(True)

        self._profiles = profiles[:]
        self._profileCombo.clear()
        self._profileCombo.addItems(map(lambda x: x.name(), profiles))

        self._profileCombo.blockSignals(False)
        self.blockSignals(False)

    def setViewWidget(self, viewWidget):
        """
        Sets the view widget instance linked with this manager.
        
        :param      viewWidget | <XViewWidget>
        """
        self._viewWidget = viewWidget

    def showOptionsMenu(self):
        """
        Displays the options menu.  If the option menu policy is set to 
        CustomContextMenu, then the optionMenuRequested signal will be emitted,
        otherwise the default context menu will be displayed.
        """
        point = QPoint(0, self._optionsButton.height())
        global_point = self._optionsButton.mapToGlobal(point)

        # prompt the custom context menu
        if (self.optionsMenuPolicy() == Qt.CustomContextMenu):
            if (not self.signalsBlocked()):
                self.optionsMenuRequested.emit(global_point)
            return

        # use the default context menu
        menu = XViewProfileManagerMenu(self)
        menu.exec_(global_point)

    def viewWidget(self):
        """
        Returns the view widget that is associated with this manager.
        
        :return     <XViewWidget>
        """
        return self._viewWidget
Example #12
0
class XViewProfileToolBar(XToolBar):
    profileCreated = qt.Signal(qt.PyObject)
    profileChanged = qt.Signal(qt.PyObject)
    profileRemoved = qt.Signal(qt.PyObject)
    currentProfileChanged = qt.Signal(qt.PyObject)

    def __init__(self, parent):
        super(XViewProfileToolBar, self).__init__(parent)

        # create custom properties
        self._editingEnabled = True
        self._viewWidget = None
        self._profileGroup = QActionGroup(self)

        # set the default options
        self.setIconSize(QSize(48, 48))
        self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.setContextMenuPolicy(Qt.CustomContextMenu)

        # create connections
        self.actionTriggered.connect(self.handleActionTrigger)
        self.customContextMenuRequested.connect(self.showProfileMenu)

    def addProfile(self, profile):
        """
        Adds the inputed profile as an action to the toolbar.
        
        :param      profile | <projexui.widgets.xviewwidget.XViewProfile>
        """
        act = XViewProfileAction(profile, self)
        self._profileGroup.addAction(act)
        self.addAction(act)
        return act

    def currentProfile(self):
        """
        Returns the current profile for this toolbar.
        
        :return     <projexui.widgets.xviewwidget.XViewProfile> || None
        """
        act = self._profileGroup.checkedAction()
        if (act):
            return act.profile()
        return None

    def createProfile(self, profile=None, clearLayout=True):
        """
        Prompts the user to create a new profile.
        """
        if (profile):
            prof = profile
        elif (not self.viewWidget() or clearLayout):
            prof = XViewProfile()
        else:
            prof = self.viewWidget().saveProfile()

        blocked = self.signalsBlocked()
        self.blockSignals(False)
        changed = self.editProfile(prof)
        self.blockSignals(blocked)

        if (not changed):
            return

        act = self.addProfile(prof)
        act.setChecked(True)

        # update the interface
        if (self.viewWidget() and (profile or clearLayout)):
            self.viewWidget().restoreProfile(prof)

        if (not self.signalsBlocked()):
            self.profileCreated.emit(prof)

    @qt.Slot(qt.PyObject)
    def editProfile(self, profile):
        """
        Prompts the user to edit the given profile.
        
        :param      profile | <projexui.widgets.xviewwidget.XViewProfile>
        """
        mod = XViewProfileDialog.edit(self, profile)
        if (not mod):
            return False

        # update the action interface
        for act in self._profileGroup.actions():
            if (act.profile() == profile):
                act.setProfile(profile)
                break

        # signal the change
        if (not self.signalsBlocked()):
            self.profileChanged.emit(profile)

        return True

    def exportProfiles(self, filename=None):
        """
        Exports this toolbar to the given filename.
        
        :param      filename | <str> || None
        """
        if (not filename):
            filename = QFileDialog.getSaveFileName(self, 'Export Toolbar', '',
                                                   'Toolbar Files (*.xtool)')

        if (not filename):
            return False

        profile_xml = self.toXml()

        projex.text.xmlindent(profile_xml)
        profile_string = ElementTree.tostring(profile_xml)

        f = open(str(filename), 'w')
        f.write(profile_string)
        f.close()

        return True

    def handleActionTrigger(self, action):
        """
        Handles when an action has been triggered.  If the inputed action is a 
        XViewProfileAction, then the currentProfileChanged signal will emit.
        
        :param      action | <QAction>
        """
        # trigger a particular profile
        if (isinstance(action, XViewProfileAction)):
            if (not self.signalsBlocked()):
                self.currentProfileChanged.emit(action.profile())

            if (self._viewWidget):
                self._viewWidget.restoreProfile(action.profile())

    def importProfiles(self, filename=None):
        """
        Imports the profiles from the given filename.
        
        :param      filename | <str> || None
        """
        if (not filename):
            filename = QFileDialog.getOpenFileName(self, 'Import Toolbar', '',
                                                   'Toolbar Files (*.xtool)')

            if type(filename) == tuple:
                filename = str(filename[0])

        if (not (filename and os.path.exists(filename))):
            return False

        f = open(str(filename), 'r')
        profile_string = f.read()
        f.close()

        self.loadString(profile_string)

        # load the default toolbar
        action = self._profileGroup.checkedAction()
        if (action):
            self.handleActionTrigger(action)

    def isEditingEnabled(self):
        """
        Sets whether or not the create is enabled for this toolbar.
        
        :return     <bool>
        """
        return self._editingEnabled

    def isEmpty(self):
        """
        Returns whether or not this toolbar is empty.
        
        :return     <bool>
        """
        return len(self._profileGroup.actions()) == 0

    def loadString(self, profilestr):
        """
        Loads the information for this toolbar from the inputed string.
        
        :param      profilestr | <str>
        """
        try:
            xtoolbar = ElementTree.fromstring(str(profilestr))
        except ExpatError, e:
            return

        self.clear()
        curr = xtoolbar.get('current')

        for xprofile in xtoolbar:
            prof = XViewProfile.fromXml(xprofile)
            act = self.addProfile(prof)
            if (prof.name() == curr):
                act.setChecked(True)
Example #13
0
class XOrbQueryContainer(QWidget):
    """ """
    entriesUpdated = qt.Signal()
    enterCompoundRequested = qt.Signal(object, object)
    exitCompoundRequested = qt.Signal()

    def __init__(self, parent=None):
        super(XOrbQueryContainer, self).__init__(parent)

        # load the user interface
        projexui.loadUi(__file__, self)

        # define custom properties
        self._queryWidget = parent
        self._entryWidget = QWidget(self)
        self._currentJoiner = QueryCompound.Op.And

        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.setSpacing(3)
        self._entryWidget.setLayout(layout)
        self.uiQueryAREA.setWidget(self._entryWidget)

        # set default properties
        self.setContextMenuPolicy(Qt.CustomContextMenu)

        # create connections (use old-style syntax or PySide errors)
        self.connect(self.uiBackBTN, SIGNAL('clicked()'), self.exitCompound)
        self.entriesUpdated.connect(self.refreshEntries)

    def addEntry(self, query=None, entry=None):
        if query is None:
            query = Query()

        layout = self._entryWidget.layout()
        index = layout.count() - 1
        if entry:
            index = layout.indexOf(entry) + 1

        widget = XOrbQueryEntryWidget(self, self.tableType())
        layout.insertWidget(index, widget)

        widget.setQuery(query)

        if not self.signalsBlocked():
            self.entriesUpdated.emit()

        return widget

    def checkedEntries(self):
        """
        Returns the widgets that are checked for this widget.
        
        :return     [<XOrbQueryEntryWidget>, ..]
        """
        return [entry for entry in self.entries() if entry.isChecked()]

    def clear(self):
        """
        Clears out the widgets for this query builder.
        """
        layout = self._entryWidget.layout()
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            widget.close()

    def createCompoundFromChecked(self):
        """
        Creates a new compound query from the checked entry list.
        
        :return     <orb.QueryCompound>
        """
        checked_entries = self.checkedEntries()

        if len(checked_entries) <= 1:
            return QueryCompound()

        self.setUpdatesEnabled(False)
        joiner = self.currentJoiner()
        query = Query()
        for entry in checked_entries:
            if joiner == QueryCompound.Op.And:
                query &= entry.query()
            else:
                query |= entry.query()

        # clear out the existing containers
        first = checked_entries[0]
        first.setQuery(query)
        first.setChecked(False)

        layout = self._entryWidget.layout()
        for i in range(len(checked_entries) - 1, 0, -1):
            w = checked_entries[i]
            layout.takeAt(layout.indexOf(w))
            w.close()

        self.refreshEntries()
        self.setUpdatesEnabled(True)

        if not self.signalsBlocked():
            self.enterCompound(first, query)

    def currentJoiner(self):
        return self._currentJoiner

    def enterCompound(self, entry, query):
        # enter an existing compound
        if QueryCompound.typecheck(query):
            self.enterCompoundRequested.emit(entry, query)

        # create a new compound from the checked entries
        else:
            self.createCompoundFromChecked()

    def entries(self):
        """
        Returns the entry widgets for this widget.
        
        :return     [<XOrbQueryEntryWidget>, ..]
        """
        layout = self._entryWidget.layout()
        output = []
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            if not isinstance(widget, XOrbQueryEntryWidget):
                continue
            output.append(widget)
        return output

    def exitCompound(self):
        self.exitCompoundRequested.emit()

    def isNull(self):
        """
        Returns whether or not any widgets have been defined for this
        container yet.
        
        :return     <bool>
        """
        return self._entryWidget.layout().count() <= 1

    def moveDown(self, entry):
        """
        Moves the current query down one entry.
        """
        if not entry:
            return

        entries = self.entries()
        next = entries[entries.index(entry) + 1]

        entry_q = entry.query()
        next_q = next.query()

        next.setQuery(entry_q)
        entry.setQuery(next_q)

    def moveUp(self, entry):
        """
        Moves the current query down up one entry.
        """
        if not entry:
            return

        entries = self.entries()
        next = entries[entries.index(entry) - 1]

        entry_q = entry.query()
        next_q = next.query()

        next.setQuery(entry_q)
        entry.setQuery(next_q)

    def pluginFactory(self):
        """
        Returns the plugin factory for this widget.  You can define a custom
        factory for handling specific columns or column types based on your
        table type.
        
        :return     <XOrbQueryPluginFactory>
        """
        return self._queryWidget.pluginFactory()

    def query(self):
        """
        Returns the query that is defined by this current panel.
        
        :return     <orb.Query>
        """
        joiner = self.currentJoiner()
        query = Query()
        for entry in self.entries():
            if joiner == QueryCompound.Op.And:
                query &= entry.query()
            else:
                query |= entry.query()

        query.setName(self.uiNameTXT.text())

        return query

    def queryWidget(self):
        """
        Returns the query widget linked with this container.
        
        :return     <XOrbQueryWidget>
        """
        return self._queryWidget

    def removeEntry(self, entry):
        if not entry:
            return

        layout = self._entryWidget.layout()
        if layout.count() == 2:
            print 'clearing query'
            entry.setQuery(Query())
            return

        layout.takeAt(layout.indexOf(entry))
        entry.close()

        if not self.signalsBlocked():
            self.entriesUpdated.emit()

    def refreshEntries(self):
        layout = self._entryWidget.layout()
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            widget.setFirst(i == 0)
            widget.setLast(i == (layout.count() - 2))

    def setCurrentJoiner(self, joiner):
        self._currentJoiner = joiner
        layout = self._entryWidget.layout()
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            widget.setJoiner(joiner)

    def setQuery(self, query):
        """
        Sets the query for this wigdet to the inputed query instance.
        
        :param      query | <orb.Query> || <orb.QueryCompound>
        """
        if not self.isNull() and hash(query) == hash(self.query()):
            return

        # add entries
        table = self.tableType()

        self.setUpdatesEnabled(False)
        self.blockSignals(True)
        self.clear()

        if query is None or table is None:
            self.setEnabled(False)
            self.setUpdatesEnabled(True)
            self.blockSignals(False)
            return
        else:
            self.setEnabled(True)

        # load the queries for this item
        if QueryCompound.typecheck(query):
            queries = query.queries()
            self.setCurrentJoiner(query.operatorType())
        else:
            queries = [query]

        self.uiNameTXT.setText(query.name())

        layout = self._entryWidget.layout()
        for index, query in enumerate(queries):
            widget = self.addEntry(query)
            widget.setFirst(index == 0)
            widget.setLast(index == (len(queries) - 1))
            widget.setJoiner(self.currentJoiner())

        self.setUpdatesEnabled(True)
        self.blockSignals(False)

    def setShowBack(self, state):
        # check to see if we're working on the current query
        self.uiBackBTN.setVisible(state)
        self.uiNameTXT.setVisible(state)

    def tableType(self):
        """
        Returns the table type instance for this widget.
        
        :return     <subclass of orb.Table>
        """
        return self._queryWidget.tableType()
Example #14
0
class XTabWidget(QTabWidget):
    addRequested = qt.Signal(QPoint)
    optionsRequested = qt.Signal(QPoint)

    def __init__(self, *args):
        super(XTabWidget, self).__init__(*args)

        # create the tab bar
        self.setTabBar(XTabBar(self))

        # create custom properties
        self._showAddButton = True
        self._showOptionsButton = True

        # create the add button
        self._addButton = QPushButton(self)
        self._addButton.setIcon(QIcon(resources.find('img/tab/add.png')))
        self._addButton.setFixedSize(18, 18)
        self._addButton.setIconSize(QSize(10, 10))

        # create the option button
        self._optionsButton = QPushButton(self)
        self._optionsButton.setFixedSize(22, 18)
        self._optionsButton.setIcon(QIcon(resources.find('img/tab/gear.png')))
        self._optionsButton.setIconSize(QSize(10, 10))

        # create connection
        self.connect(self.tabBar(), SIGNAL('currentChanged(int)'),
                     self.adjustButtons)

        self.connect(self.tabBar(), SIGNAL('resized()'), self.adjustButtons)

        self.connect(self._optionsButton, SIGNAL('clicked()'),
                     self.emitOptionsRequested)

        self.connect(self._addButton, SIGNAL('clicked()'),
                     self.emitAddRequested)

    def __nonzero__(self):
        """
        At somepoint, QTabWidget's nonzero became linked to whether it had
        children vs. whether it was none.  This returns the original
        functionality.
        """
        return self is not None

    def adjustButtons(self):
        """
        Updates the position of the buttons based on the current geometry.
        """
        tabbar = self.tabBar()
        tabbar.adjustSize()

        w = self.width() - self._optionsButton.width() - 2
        self._optionsButton.move(w, 0)

        if self.count():
            if self.currentIndex() == self.count() - 1:
                self._addButton.move(tabbar.width() - 2, -1)
                self._addButton.setFixedHeight(tabbar.height() + 2)
            else:
                self._addButton.move(tabbar.width() - 4, 1)
                self._addButton.setFixedHeight(tabbar.height())
        else:
            self._addButton.move(tabbar.width() + 2, 1)

        self._addButton.stackUnder(self.currentWidget())

    def addButton(self):
        """
        Returns the add button linked with this tab widget.
        
        :return     <QPushButton>
        """
        return self._addButton

    def emitAddRequested(self, point=None):
        """
        Emitted when the option menu button is clicked provided the signals \
        are not being blocked for this widget.
        
        :param      point | <QPoint>
        """
        if self.signalsBlocked():
            return

        if not point:
            point = QCursor.pos()

        self.addRequested.emit(point)

    def emitOptionsRequested(self, point=None):
        """
        Emitted when the option menu button is clicked provided the signals \
        are not being blocked for this widget.
        
        :param      point | <QPoint>
        """
        if self.signalsBlocked():
            return

        if not point:
            point = QCursor.pos()

        self.optionsRequested.emit(point)

    def optionsButton(self):
        """
        Returns the options button linked with this tab widget.
        
        :return     <QPushButton>
        """
        return self._optionsButton

    def paintEvent(self, event):
        if not self.count():
            return

        super(XTabWidget, self).paintEvent(event)

    def resizeEvent(self, event):
        """
        Updates the position of the additional buttons when this widget \
        resizes.
        
        :param      event | <QResizeEvet>
        """
        super(XTabWidget, self).resizeEvent(event)
        self.adjustButtons()

    def setShowAddButton(self, state):
        """
        Sets whether or not the add button is visible.
        
        :param      state | <bool>
        """
        self._showAddButton = state
        self._addButton.setVisible(state)

    def setShowOptionsButton(self, state):
        """
        Sets whether or not the option button is visible.
        
        :param          state   | <bool>
        """
        self._showOptionsButton = state
        self._optionsButton.setVisible(state)

    def showAddButton(self):
        """
        Returns whether or not the add button is visible.
        
        :return     <bool>
        """
        return self._showAddButton

    def showOptionsButton(self):
        """
        Returns whether or not the option button should be visible.
        
        :return     <bool>
        """
        return self._showOptionsButton

    x_showAddButton = qt.Property(bool, showAddButton, setShowAddButton)
    x_showOptionsButton = qt.Property(bool, showOptionsButton,
                                      setShowOptionsButton)
Example #15
0
class XLoggerWidget(QTextEdit):
    __designer_icon__ = resources.find('img/log/info.png')

    LoggingMap = {
        logging.DEBUG: ('debug', resources.find('img/log/bug.png')),
        logging.INFO: ('info', resources.find('img/log/info.png')),
        logging.WARN: ('warning', resources.find('img/log/warning.png')),
        logging.ERROR: ('error', resources.find('img/log/error.png')),
        logging.CRITICAL: ('critical', resources.find('img/log/critical.png')),
    }

    messageLogged = qt.Signal(int, unicode)
    pythonMessageLogged = qt.Signal(int, unicode)
    """ Defines the main logger widget class. """
    def __init__(self, parent):
        super(XLoggerWidget, self).__init__(parent)

        # set standard properties
        self.setReadOnly(True)
        self.setLineWrapMode(XLoggerWidget.NoWrap)

        # define custom properties
        self._logger = None
        self._clearOnClose = True
        self._handler = XLoggerHandler(self, True)
        self._currentMode = 'standard'
        self._blankCache = ''
        self._mutex = QMutex()

        # determine whether or not to use the light or dark configuration
        palette = self.palette()
        base = palette.color(palette.Base)
        avg = (base.red() + base.green() + base.blue()) / 3.0

        if (avg < 160):
            colorSet = XLoggerColorSet.darkScheme()
        else:
            colorSet = XLoggerColorSet.lightScheme()

        self._colorSet = colorSet
        palette.setColor(palette.Text, colorSet.color('Standard'))
        palette.setColor(palette.Base, colorSet.color('Background'))
        self.setPalette(palette)

        # setup the levels
        self._loggingEnabled = {
            'debug': True,
            'info': True,
            'warning': True,
            'error': True,
            'critical': True,
            'fatal': True,
        }

        # create connections
        self.pythonMessageLogged.connect(self.log)

    def clearOnClose(self):
        """
        Returns whether or not this widget should clear the link to its \
        logger when it closes.
        
        :return     <bool>
        """
        return self._clearOnClose

    def closeEvent(self, event):
        """
        Clear the handler from the logger when this widget closes.
        
        :param      event | <QCloseEvent>
        """
        if (self.clearOnClose()):
            self.setLogger(None)

        super(XLoggerWidget, self).closeEvent(event)

    def color(self, key):
        """
        Returns the color value for the given key for this console.
        
        :param      key | <unicode>
        
        :return     <QColor>
        """
        return self._colorSet.color(str(key).capitalize())

    def colorSet(self):
        """
        Returns the colors used for this console.
        
        :return     <XLoggerColorSet>
        """
        return self._colorSet

    def critical(self, msg):
        """
        Logs a critical message to the console.
        
        :param      msg | <unicode>
        """
        self.log('critical', msg)

    def currentMode(self):
        """
        Returns the current mode that the console is in for coloring.
        
        :return     <unicode>
        """
        return self._currentMode

    def debug(self, msg):
        """
        Inserts a debug message to the current system.
        
        :param      msg | <unicode>
        """
        self.log('debug', msg)

    def error(self, msg):
        """
        Inserts an error message to the current system.
        
        :param      msg | <unicode>
        """
        self.log('error', msg)

    def fatal(self, msg):
        """
        Logs a fatal message to the system.
        
        :param      msg | <unicode>
        """
        self.log('fatal', msg)

    def handler(self):
        """
        Returns the logging handler that is linked to this widget.
        
        :return     <XLoggerHandler>
        """
        return self._handler

    def information(self, msg):
        """
        Inserts an information message to the current system.
        
        :param      msg | <unicode>
        """
        self.log('info', msg)

    def isLoggingEnabled(self, level):
        """
        Returns whether or not logging is enabled for the given level.
        
        :param      level | <int>
        """
        if (type(level) == int):
            level = self.LoggingMap.get(level, ('info', ''))[0]

        return self._loggingEnabled.get(level, True)

    def log(self, level, msg):
        """
        Logs the inputed message with the given level.
        
        :param      level | <int> | logging level value
                    msg   | <unicode>
        
        :return     <bool> success
        """
        locker = QMutexLocker(self._mutex)

        if (not self.isLoggingEnabled(level)):
            return False

        msg = self._blankCache + projex.text.encoded(msg)
        if msg.endswith('\n'):
            self._blankCache = '\n'
            msg = msg[:-1]
        else:
            self._blankCache = ''

        self.setCurrentMode(level)
        self.insertPlainText(msg)

        if not self.signalsBlocked():
            self.messageLogged.emit(level, msg)

        self.scrollToEnd()

        return True

    def logger(self):
        """
        Returns the logger instance that this widget will monitor.
        
        :return     <logging.Logger>
        """
        return self._logger

    def scrollToEnd(self):
        """
        Scrolls to the end for this console edit.
        """
        vsbar = self.verticalScrollBar()
        vsbar.setValue(vsbar.maximum())

        hbar = self.horizontalScrollBar()
        hbar.setValue(0)

    def setClearOnClose(self, state):
        """
        Sets whether or not this widget should clear the logger link on close.
        
        :param      state | <bool>
        """
        self._clearOnClose = state

    def setColor(self, key, value):
        """
        Sets the color value for the inputed color.
        
        :param      key     | <unicode>
                    value   | <QColor>
        """
        key = str(key).capitalize()
        self._colorSet.setColor(key, value)

        # update the palette information
        if (key == 'Background'):
            palette = self.palette()
            palette.setColor(palette.Base, value)
            self.setPalette(palette)

    def setColorSet(self, colorSet):
        """
        Sets the colors for this console to the inputed collection.
        
        :param      colors | <XLoggerColorSet>
        """
        self._colorSet = colorSet

        # update the palette information
        palette = self.palette()
        palette.setColor(palette.Text, colorSet.color('Standard'))
        palette.setColor(palette.Base, colorSet.color('Background'))
        self.setPalette(palette)

    def setCurrentMode(self, mode):
        """
        Sets the current color mode for this console to the inputed value.
        
        :param      mode | <unicode>
        """
        if (type(mode) == int):
            mode = self.LoggingMap.get(mode, ('standard', ''))[0]

        if (mode == self._currentMode):
            return

        self._currentMode = mode

        color = self.color(mode)
        if (not color.isValid()):
            return

        format = QTextCharFormat()
        format.setForeground(color)
        self.setCurrentCharFormat(format)

    def setLoggingEnabled(self, level, state):
        """
        Sets whether or not this widget should log the inputed level amount.
        
        :param      level | <int>
                    state | <bool>
        """
        if (type(level) == int):
            level = self.LoggingMap.get(level, ('standard', ''))[0]

        self._loggingEnabled[level] = state

    def setLogger(self, logger):
        """
        Sets the logger instance that this widget will monitor.
        
        :param      logger  | <logging.Logger>
        """
        if (self._logger == logger):
            return

        if (self._logger):
            self._logger.removeHandler(self._handler)

        self._logger = logger

        if (logger):
            logger.addHandler(self._handler)

    def setShowDetails(self, state):
        """
        Sets whether or not the level should be logged with the message in the \
        output.
        
        :param      state | <bool>
        """
        self._handler.setShowDetails(state)

    def setShowLevel(self, state):
        """
        Sets whether or not the level should be logged with the message in the \
        output.
        
        :param      state | <bool>
        """
        self._handler.setShowLevel(state)

    def showDetails(self):
        """
        Returns whether or not to show details for this logger.
        
        :return     <bool>
        """
        return self._handler.showDetails()

    def showLevel(self):
        """
        Returns whether or not this logger should output the level when a \
        message is printed.
        
        :return     <bool>
        """
        return self._handler.showLevel()

    def warning(self, msg):
        """
        Logs a warning message to the system.
        
        :param      msg | <unicode>
        """
        self.log('warning', msg)
Example #16
0
class XColorButton(QPushButton):
    """
    The XColorButton class is a simple extension to the standard QPushButton
    that will control color settings.  When teh user clicks on the button, the
    QColorDialog will be displayed, prompting the user to select a new color.
    Colors are stored internally can can be accessed by etter and setter 
    methods, as well as the colorChanged signal.
    
    As the color is modified, either through code or by a user, the background
    color for the button will automatically update to match.
    
    == Example ==
    
    |>>> from projexui.widgets.xcolorbutton import XColorButton
    |>>> import projexui
    |
    |>>> # create the widget
    |>>> btn = projexui.testWidget(XColorButton)
    |
    |>>> # click around, change the color
    |>>> from PyQt4.QtGui import QColor
    |>>> print btn.color().red(), btn.color().green(), btn.color().blue()
    |255 170 0
    |>>> btn.setColor(QColor('red'))
    |
    |>>> # create connections
    |>>> def printColor(clr): print clr.red(), clr.green(), clr.blue()
    |>>> btn.colorChanged.connect(printColor)
    |
    |>>> # prompt the user to select a color for that button
    |>>> btn.pickColor()
    """
    colorChanged = qt.Signal(QColor)

    def __init__(self, parent):
        super(XColorButton, self).__init__(parent)

        # initialize the color
        color = QColor('black')
        self._color = color
        palette = self.palette()
        palette.setColor(palette.Button, color)
        self.setPalette(palette)

        # create connections
        self.clicked.connect(self.pickColor)

    def color(self):
        """
        Returns the color value for this button.
        
        :return     <QColor>
        """
        return self._color

    def pickColor(self):
        """
        Prompts the user to select a color for this button.
        """
        color = QColorDialog.getColor(self.color(), self)

        if (color.isValid()):
            self.setColor(color)

    def setColor(self, color):
        """
        Sets the color value for this button to the given color.
        
        :param      color | <QColor>
        """
        self._color = color
        palette = self.palette()
        palette.setColor(palette.Button, color)
        self.setPalette(palette)

        if (not self.signalsBlocked()):
            self.colorChanged.emit(color)

    x_color = qt.Property(QColor, color, setColor)
Example #17
0
class XLocationWidget(QWidget):
    locationChanged = qt.Signal(str)
    locationEdited = qt.Signal()

    def __init__(self, parent):
        super(XLocationWidget, self).__init__(parent)

        # define the interface
        self._locationEdit = XLineEdit(self)
        self._locationButton = QToolButton(self)
        self._urlTemplate = 'http://maps.google.com/maps?%(params)s'
        self._urlQueryKey = 'q'

        self._locationButton.setAutoRaise(True)
        self._locationButton.setIcon(QIcon(resources.find('img/map.png')))

        self._locationEdit.setHint('no location set')

        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self._locationEdit)
        layout.addWidget(self._locationButton)

        self.setLayout(layout)

        # create connections
        self._locationEdit.textChanged.connect(self.locationChanged)
        self._locationEdit.textEdited.connect(self.locationEdited)
        self._locationButton.clicked.connect(self.browseMaps)

    def blockSignals(self, state):
        """
        Blocks the signals for this widget and its sub-parts.
        
        :param      state | <bool>
        """
        super(XLocationWidget, self).blockSignals(state)
        self._locationEdit.blockSignals(state)
        self._locationButton.blockSignals(state)

    def browseMaps(self):
        """
        Brings up a web browser with the address in a Google map.
        """
        url = self.urlTemplate()
        params = urllib.urlencode({self.urlQueryKey(): self.location()})

        url = url % {'params': params}
        webbrowser.open(url)

    def hint(self):
        """
        Returns the hint associated with this widget.
        
        :return     <str>
        """
        return self._locationEdit.hint()

    def lineEdit(self):
        """
        Returns the line edit linked with this widget.
        
        :return     <XLineEdit>
        """
        return self._locationEdit

    def location(self):
        """
        Returns the current location from the edit.
        
        :return     <str>
        """
        return str(self._locationEdit.text())

    @qt.Slot(str)
    def setHint(self, hint):
        """
        Sets the hint associated with this widget.
        
        :param      hint | <str>
        """
        self._locationEdit.setHint(hint)

    @qt.Slot(str)
    def setLocation(self, location):
        """
        Sets the location for this widget to the inputed location.
        
        :param      location | <str>
        """
        self._locationEdit.setText(str(location))

    def setUrlQueryKey(self, key):
        """
        Sets the key for the URL to the inputed key.
        
        :param      key | <str>
        """
        self._urlQueryKey = str(key)

    def setUrlTemplate(self, templ):
        """
        Sets the URL path template that will be used when looking up locations
        on the web.
        
        :param      templ | <str>
        """
        self._urlQueryTemplate = str(templ)

    def urlQueryKey(self):
        """
        Returns the query key that will be used for this location.
        
        :return     <str>
        """
        return self._urlQueryKey

    def urlTemplate(self):
        """
        Returns the url template that will be used when mapping this location.
        
        :return     <str>
        """
        return self._urlTemplate

    x_hint = qt.Property(str, hint, setHint)
    x_location = qt.Property(str, location, setLocation)
    x_urlQueryKey = qt.Property(str, urlQueryKey, setUrlQueryKey)
    x_urlTemplate = qt.Property(str, urlTemplate, setUrlTemplate)
Example #18
0
class XUrlWidget(QWidget):
    urlChanged = qt.Signal(str)
    urlEdited = qt.Signal()

    def __init__(self, parent):
        super(XUrlWidget, self).__init__(parent)

        # define the interface
        self._urlEdit = XLineEdit(self)
        self._urlButton = QToolButton(self)

        self._urlButton.setAutoRaise(True)
        self._urlButton.setIcon(QIcon(resources.find('img/web.png')))
        self._urlButton.setToolTip('Browse Link')
        self._urlButton.setFocusPolicy(Qt.NoFocus)

        self._urlEdit.setHint('http://')

        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self._urlEdit)
        layout.addWidget(self._urlButton)

        self.setLayout(layout)
        self.setFocusPolicy(Qt.StrongFocus)

        # create connections
        self._urlEdit.textChanged.connect(self.urlChanged)
        self._urlEdit.textEdited.connect(self.urlEdited)
        self._urlButton.clicked.connect(self.browse)

    def blockSignals(self, state):
        """
        Blocks the signals for this widget and its sub-parts.
        
        :param      state | <bool>
        """
        super(XUrlWidget, self).blockSignals(state)
        self._urlEdit.blockSignals(state)
        self._urlButton.blockSignals(state)

    def browse(self):
        """
        Brings up a web browser with the address in a Google map.
        """
        webbrowser.open(self.url())

    def hint(self):
        """
        Returns the hint associated with this widget.
        
        :return     <str>
        """
        return self._urlEdit.hint()

    def lineEdit(self):
        """
        Returns the line edit linked with this widget.
        
        :return     <XLineEdit>
        """
        return self._urlEdit

    def setFocus(self):
        """
        Sets the focus for this widget on its line edit.
        """
        self._urlEdit.setFocus()

    @qt.Slot(str)
    def setHint(self, hint):
        """
        Sets the hint associated with this widget.
        
        :param      hint | <str>
        """
        self._urlEdit.setHint(hint)

    @qt.Slot(str)
    def setUrl(self, url):
        """
        Sets the url for this widget to the inputed url.
        
        :param      url | <str>
        """
        self._urlEdit.setText(str(url))

    def url(self):
        """
        Returns the current url from the edit.
        
        :return     <str>
        """
        return str(self._urlEdit.text())

    x_hint = qt.Property(str, hint, setHint)
    x_url = qt.Property(str, url, setUrl)
Example #19
0
class XChart(QFrame):
    """ """
    middleClicked = qt.Signal(QPoint)

    def __init__(self, parent=None):
        super(XChart, self).__init__(parent)

        # load the interface
        projexui.loadUi(__file__, self)

        # define custom properties
        self._renderer = None
        self._chartTitle = ''
        self._axes = []
        self._datasets = []
        self._horizontalAxis = None
        self._verticalAxis = None
        self._showDatasetToolbar = True
        self._showTypeButton = True
        self._dataChanged = False
        self._showGrid = True
        self._showRows = True
        self._showColumns = True
        self._showXAxis = True
        self._showYAxis = True

        # set default properties
        self.uiChartVIEW.setScene(XChartScene(self))
        self.uiXAxisVIEW.setScene(XChartScene(self))
        self.uiYAxisVIEW.setScene(XChartScene(self))
        self.uiChartVIEW.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.uiXAxisVIEW.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.uiYAxisVIEW.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        self.uiDatasetTBAR.setFixedHeight(32)

        self.uiXAxisVIEW.hide()
        self.uiYAxisVIEW.hide()

        self.setAutoFillBackground(True)
        self.setBackgroundRole(QPalette.Base)
        self.setStyleSheet(STYLE)
        self.uiChartVIEW.setMouseTracking(True)
        self.uiDatasetTBAR.setStyleSheet(TOOLBAR_STYLE)

        # load renderers
        for renderer in XChartRenderer.plugins():
            act = QAction('%s Chart' % renderer, self)
            ico = 'img/chart/%s.png' % renderer.lower()
            act.setIcon(QIcon(resources.find(ico)))
            self.uiTypeBTN.addAction(act)

            # assign the default renderer
            if not self.uiTypeBTN.defaultAction():
                self._renderer = XChartRenderer.plugin(renderer)
                self.uiTypeBTN.setDefaultAction(act)

        # create connections
        chart_hbar = self.uiChartVIEW.horizontalScrollBar()
        chart_vbar = self.uiChartVIEW.verticalScrollBar()

        x_bar = self.uiXAxisVIEW.horizontalScrollBar()
        y_bar = self.uiYAxisVIEW.verticalScrollBar()

        chart_hbar.valueChanged.connect(self.syncScrollbars)
        chart_hbar.rangeChanged.connect(self.syncScrollbars)
        y_bar.valueChanged.connect(self.syncScrollbars)
        y_bar.rangeChanged.connect(self.syncScrollbars)
        self.uiTypeBTN.triggered.connect(self.assignRenderer)

    def _addDatasetAction(self, dataset):
        """
        Adds an action for the inputed dataset to the toolbar
        
        :param      dataset | <XChartDataset>
        """
        # create the toolbar action
        action = QAction(dataset.name(), self)
        action.setIcon(XColorIcon(dataset.color()))
        action.setCheckable(True)
        action.setChecked(True)
        action.setData(qt.wrapVariant(dataset))
        action.toggled.connect(self.toggleDataset)

        self.uiDatasetTBAR.addAction(action)

    def _drawBackground(self, scene, painter, rect):
        """
        Draws the backgroud for a particular scene within the charts.
        
        :param      scene   | <XChartScene>
                    painter | <QPainter>
                    rect    | <QRectF>
        """
        rect = scene.sceneRect()
        if scene == self.uiChartVIEW.scene():
            self.renderer().drawGrid(painter, rect, self.showGrid(),
                                     self.showColumns(), self.showRows())
        elif scene == self.uiXAxisVIEW.scene():
            self.renderer().drawAxis(painter, rect, self.horizontalAxis())
        elif scene == self.uiYAxisVIEW.scene():
            self.renderer().drawAxis(painter, rect, self.verticalAxis())

    def addAxis(self, axis):
        """
        Adds a new axis for this chart.  Axis can define X & Y data
        including notch and value processing, as well as define
        individual lines within the chart that any chart items can
        reference or use.
        
        :param      axis | <projexui.widgets.xchart.XChartAxis>
        """
        self._axes.append(axis)

    def addDataset(self, dataset):
        """
        Adds the given data set to this chart widget.
        
        :param      dataSet | <XChartDataset>
        """
        self._datasets.append(dataset)
        self._dataChanged = True
        self._addDatasetAction(dataset)

    def addToolbarWidget(self, widget):
        """
        Adds a new widget to the toolbar layout for the chart.
        
        :param      widget | <QWidget>
        """
        self.uiToolbarHBOX.addWidget(widget)

    def assignRenderer(self, action):
        """
        Assigns the renderer for this chart to the current selected
        renderer.
        """
        name = str(action.text()).split(' ')[0]
        self._renderer = XChartRenderer.plugin(name)
        self.uiTypeBTN.setDefaultAction(action)
        self.recalculate()

    def axes(self):
        """
        Returns all the axes that have been defined for this chart.
        
        :return     [<projexui.widgets.xchart.XChartAxis>, ..]
        """
        out = self._axes[:]
        if self._horizontalAxis:
            out.append(self._horizontalAxis)
        if self._verticalAxis:
            out.append(self._verticalAxis)
        return out

    def axis(self, name):
        """
        Looks up an axis for this chart by the given name.
        
        :return     <projexui.widgets.xchart.XChartAxis> || None
        """
        for axis in self.axes():
            if axis.name() == name:
                return axis
        return None

    def clear(self):
        """
        Clears out all the dataset information from the chart.
        """
        self.clearAxes()
        self.clearDatasets()

    def clearAxes(self):
        self._axes = []
        self._verticalAxis = None
        self._horizontalAxis = None

    def clearDatasets(self):
        self._datasets = []
        for act in self.uiDatasetTBAR.actions():
            act.deleteLater()

        self.uiChartVIEW.scene().clear()

    def chartTitle(self):
        """
        Returns the title for this plot.
        
        :return     <str>
        """
        return self._title

    def compareValues(self, a, b):
        """
        Compares two values based on their values for this axis.
        
        :param      a | <variant>
                    b | <variant>
        """
        values = self.values()
        try:
            return cmp(values.index(a), values.index(b))
        except ValueError:
            return cmp(a, b)

    def datasets(self, visible=True):
        """
        Returns a list of the data sets that are assigned with this
        chart widget.
        
        :param      visible | <bool>
        
        :return     [<XChartDataSet>, ..]
        """
        if visible is not None:
            return filter(lambda x: x.isVisible(), self._datasets)
        return self._datasets[:]

    def showEvent(self, event):
        super(XChart, self).showEvent(event)
        self.recalculate()

    def findRenderer(self, name):
        """
        Returns the renderer based on the inputed name.
        
        :return     <str>
        """
        return XChartRenderer.plugin(name)

    def horizontalAxis(self):
        """
        Returns the axis that is assigned to the horizontal direction for this
        chart.
        
        :return     <XChartAxis>
        """
        return self._horizontalAxis

    def insertToolbarWidget(self, index, widget):
        """
        Inserts a new widget to the toolbar layout for the chart.
        
        :param      index  | <int>
                    widget | <QWidget>
        """
        self.uiToolbarHBOX.insertWidget(index, widget)

    def pointAt(self, **axis_values):
        """
        Returns the point on the chart where the inputed values are located.
        
        :return     <QPointF>
        """
        scene_point = self.renderer().pointAt(self.axes(), axis_values)
        chart_point = self.uiChartVIEW.mapFromScene(scene_point)
        return self.uiChartVIEW.mapToParent(chart_point)

    def mousePressEvent(self, event):
        if event.button() == Qt.MidButton:
            self.middleClicked.emit(event.pos())

        super(XChart, self).mousePressEvent(event)

    def recalculate(self):
        """
        Recalculates the information for this chart.
        """
        if not (self.isVisible() and self.renderer()):
            return

        # update dynamic range
        if self._dataChanged:
            for axis in self.axes():
                if axis.useDynamicRange():
                    axis.calculateRange(self.values(axis.name()))

            self._dataChanged = False

        # recalculate the main grid
        xaxis = self.horizontalAxis()
        yaxis = self.verticalAxis()
        renderer = self.renderer()

        xvisible = xaxis is not None and self.showXAxis(
        ) and renderer.showXAxis()
        yvisible = yaxis is not None and self.showYAxis(
        ) and renderer.showYAxis()

        self.uiXAxisVIEW.setVisible(xvisible)
        self.uiYAxisVIEW.setVisible(yvisible)

        # calculate the main view
        view = self.uiChartVIEW
        chart_scene = view.scene()
        chart_scene.setSceneRect(0, 0, view.width() - 2, view.height() - 2)
        rect = renderer.calculate(chart_scene, xaxis, yaxis)

        # recalculate the xaxis
        if xaxis and self.showXAxis() and renderer.showXAxis():
            view = self.uiXAxisVIEW
            scene = view.scene()
            scene.setSceneRect(0, 0, rect.width(), view.height())
            scene.invalidate()

        # render the yaxis
        if yaxis and self.showYAxis() and renderer.showYAxis():
            view = self.uiYAxisVIEW
            scene = view.scene()
            scene.setSceneRect(0, 0, view.width(), rect.height())
            scene.invalidate()

        # recalculate the items
        renderer.calculateDatasets(chart_scene, self.axes(), self.datasets())

        chart_scene.invalidate()

    def removeAxis(self, axis):
        """
        Removes an axis from this chart either by direct reference or by
        name.
        
        :param      axis | <projexui.widgets.XChartAxis> || <str>
        """
        if not isinstance(axis, XChartAxis):
            axis = self.axis(str(axis))

        try:
            self._axes.remove(axis)
        except ValueError:
            pass

    def renderer(self):
        """
        Returns the current renderer associated with this plot.
        
        :return     <projexui.widgets.xchart.XChartRenderer>
        """
        return self._renderer

    def renderers(self):
        """
        Returns the renderer instances associated with this chart.
        
        :return     [<XChartRenderer>, ..]
        """
        return map(XChartRenderer.plugin, XChartRenderer.plugins())

    def resizeEvent(self, event):
        """
        Recalculates the chart information when the widget resizes.
        
        :param      event | <QResizeEvent>
        """
        super(XChart, self).resizeEvent(event)

        if self.isVisible():
            self.recalculate()

    def restoreXml(self, xchart):
        """
        Restores the xml information for this chart.
        
        :param      xparent | <xml.etree.ElementTree.Element>
        """
        if xchart is None:
            return

        self.setRenderer(xchart.get('renderer', 'Bar'))

    def saveXml(self, xchart):
        """
        Saves the xml information for this chart to the inputed xml.
        
        :param      xchart | <xml.etree.ElementTree.Element>
        """
        if xchart is None:
            return

        xchart.set('renderer', self.renderer().name())

    def setRenderer(self, renderer):
        """
        Sets the current renderer associated with this plot.
        
        :param      renderer | <XChartRenderer> || <str>
        
        :return     <bool> | success
        """
        if not isinstance(renderer, XChartRenderer):
            renderer = XChartRenderer.plugin(str(renderer))

        if renderer is None:
            return False

        self._renderer = renderer

        for act in self.uiTypeBTN.actions():
            if act.text() == '%s Chart' % renderer.name():
                self.uiTypeBTN.setDefaultAction(act)
                break

        return True

    def setAxes(self, axes):
        """
        Sets the axes for this chart to the inputed list of axes.
        
        :param      axes | [<projexui.widgets.xchart.XChartAxis>, ..]
        """
        self._axes = axes

    def setDatasets(self, datasets):
        """
        Sets the dataset list for this chart to the inputed data.
        
        :param      datasets | [<XChartDataset>, ..]
        """
        self.clearDatasets()
        self._datasets = datasets

        for dataset in datasets:
            self._addDatasetAction(dataset)

        self._dataChanged = True
        self.recalculate()

    def setChartTitle(self, title):
        """
        Sets the title for the plot to the inputed title.
        
        :param      title | <str>
        """
        self._chartTitle = title

    def setHorizontalAxis(self, axis):
        """
        Sets the horizontal axis for this chart.
        
        :param      axis | <XChartAxis>
        """
        self._horizontalAxis = axis
        if axis:
            axis.setOrientation(Qt.Horizontal)
            self.uiXAxisVIEW.setFixedHeight(axis.minimumLabelHeight() + 5)

        self.uiXAxisVIEW.setVisible(axis is not None)

    def setVerticalAxis(self, axis):
        """
        Sets the vertical axis for this chart.
        
        :param      axis | <XChartAxis>
        """
        self._verticalAxis = axis
        if axis:
            axis.setOrientation(Qt.Vertical)
            self.uiYAxisVIEW.setFixedWidth(axis.minimumLabelWidth() + 15)

        self.uiYAxisVIEW.setVisible(axis is not None)

    def setShowColumns(self, state):
        """
        Sets the whether or not this renderer should draw the grid.
        
        :param      state | <bool>
        """
        self._showColumns = state

    def setShowDatasetToolbar(self, state):
        """
        Sets whether or not the dataset toolbar is visible.
        
        :param      state | <bool>
        """
        self._showDatasetToolbar = state
        if not state:
            self.uiDatasetTBAR.hide()
        else:
            self.uiDatasetTBAR.show()

    def setShowGrid(self, state):
        """
        Sets the whether or not this renderer should draw the grid.
        
        :param      state | <bool>
        """
        self._showGrid = state

    def setShowRows(self, state):
        """
        Sets the whether or not this renderer should draw the grid.
        
        :param      state | <bool>
        """
        self._showRows = state

    def setShowTypeButton(self, state):
        """
        Sets whether or not the type button is visible.
        
        :param      state | <bool>
        """
        self._showTypeButton = state
        if not state:
            self.uiTypeBTN.hide()
        else:
            self.uiTypeBTN.show()

    def setShowXAxis(self, state):
        """
        Sets the whether or not this renderer should draw the x-axis.
        
        :param      state | <bool>
        """
        self._showXAxis = state

    def setShowYAxis(self, state):
        """
        Sets the whether or not this renderer should draw the y-axis.
        
        :param      state | <bool>
        """
        self._showYAxis = state

    def showColumns(self):
        """
        Returns whether or not this renderer should draw the grid.
        
        :return     <bool>
        """
        return self._showColumns and self.showXAxis()

    def showDatasetToolbar(self):
        """
        Returns whether or not the dataset toolbar is visible.
        
        :return     <bool>
        """
        return self._showDatasetToolbar

    def showGrid(self):
        """
        Returns whether or not this renderer should draw the grid.
        
        :return     <bool>
        """
        return self._showGrid

    def showRows(self):
        """
        Returns whether or not this renderer should draw the grid.
        
        :return     <bool>
        """
        return self._showRows and self.showYAxis()

    def showTypeButton(self):
        """
        Returns whether or not the type button is visible.
        
        :return     <bool>
        """
        return self._showTypeButton

    def showXAxis(self):
        """
        Returns whether or not this renderer should draw the x-axis.
        
        :return     <bool>
        """
        return self._showXAxis

    def showYAxis(self):
        """
        Returns whether or not this renderer should draw the y-axis.
        
        :return     <bool>
        """
        return self._showYAxis

    def sizeHint(self):
        """
        Returns the size hint for this chart.
        """
        return QSize(300, 300)

    def syncScrollbars(self):
        """
        Synchronizes the various scrollbars within this chart.
        """
        chart_hbar = self.uiChartVIEW.horizontalScrollBar()
        chart_vbar = self.uiChartVIEW.verticalScrollBar()

        x_hbar = self.uiXAxisVIEW.horizontalScrollBar()
        x_vbar = self.uiXAxisVIEW.verticalScrollBar()

        y_hbar = self.uiYAxisVIEW.horizontalScrollBar()
        y_vbar = self.uiYAxisVIEW.verticalScrollBar()

        x_hbar.setRange(chart_hbar.minimum(), chart_hbar.maximum())
        x_hbar.setValue(chart_hbar.value())
        x_vbar.setValue(0)

        chart_vbar.setRange(y_vbar.minimum(), y_vbar.maximum())
        chart_vbar.setValue(y_vbar.value())

        y_hbar.setValue(4)

    def toggleDataset(self, state, dataset=None):
        """
        Toggles the dataset based on the given action or dataset.
        
        :param      state | <bool>
                    dataset | <XChartDataset>
        """
        if dataset is None and self.sender():
            dataset = qt.unwrapVariant(self.sender().data())

        dataset.setVisible(state)
        self._dataChanged = True
        self.recalculate()

    def valueAt(self, point):
        """
        Returns the value within the chart for the given point.
        
        :param      point | <QPoint>
        
        :return     {<str> axis name: <variant> value, ..}
        """
        chart_point = self.uiChartVIEW.mapFromParent(point)
        scene_point = self.uiChartVIEW.mapToScene(chart_point)
        return self.renderer().valueAt(self.axes(), scene_point)

    def values(self, axis):
        """
        Returns the values of the given axis from all the datasets within
        this chart.
        
        :param      axis | <str>
        
        :return     [<variant>, ..]
        """
        output = []
        for dataset in self.datasets():
            output += dataset.values(axis)

        return output

    def verticalAxis(self):
        """
        Returns the axis that is used for the vertical view for
        this graph.
        
        :return     <XChartAxis>
        """
        return self._verticalAxis

    x_showColumns = qt.Property(bool, showColumns, setShowColumns)
    x_showRows = qt.Property(bool, showRows, setShowRows)
    x_showGrid = qt.Property(bool, showGrid, setShowGrid)
    x_showYAxis = qt.Property(bool, showYAxis, setShowYAxis)
    x_showXAxis = qt.Property(bool, showXAxis, setShowXAxis)

    x_showDatasetToolbar = qt.Property(bool, showDatasetToolbar,
                                       setShowDatasetToolbar)

    x_showTypeButton = qt.Property(bool, showTypeButton, setShowTypeButton)