Example #1
0
 def __init__( self, parent = None ):
     super(XKeyValueDialog, self).__init__(parent)
     
     # create the interface
     self._keyEdit   = XLineEdit(self)
     self._keyEdit.setMaximumWidth(80)
     self._keyEdit.setHint( 'set key' )
     
     self._valueEdit = XLineEdit(self)
     self._valueEdit.setHint( 'set value' )
     
     hbox = QHBoxLayout()
     hbox.addWidget(self._keyEdit)
     hbox.addWidget(self._valueEdit)
     
     opts    = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
     buttons = QDialogButtonBox( opts, Qt.Horizontal, self )
     
     vbox = QVBoxLayout()
     vbox.addLayout(hbox)
     vbox.addWidget(buttons)
     
     # update the look and size
     self.setLayout(vbox)
     self.setWindowTitle('Edit Pair')
     self.setMinimumWidth(350)
     self.adjustSize()
     self.setFixedHeight(self.height())
     
     # create connections
     buttons.accepted.connect( self.accept )
     buttons.rejected.connect( self.reject )
Example #2
0
    def getText(cls,
                parent=None,
                windowTitle='Get Text',
                label='',
                text='',
                plain=True,
                wrapped=True):
        """
        Prompts the user for a text entry using the text edit class.
        
        :param      parent | <QWidget>
                    windowTitle | <str>
                    label       | <str>
                    text        | <str>
                    plain       | <bool> | return plain text or not
        
        :return     (<str> text, <bool> accepted)
        """
        # create the dialog
        dlg = QDialog(parent)
        dlg.setWindowTitle(windowTitle)

        # create the layout
        layout = QVBoxLayout()

        # create the label
        if label:
            lbl = QLabel(dlg)
            lbl.setText(label)
            layout.addWidget(lbl)

        # create the widget
        widget = cls(dlg)
        widget.setText(text)

        if not wrapped:
            widget.setLineWrapMode(XTextEdit.NoWrap)

        layout.addWidget(widget)

        # create the buttons
        btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
                                Qt.Horizontal, dlg)
        layout.addWidget(btns)

        dlg.setLayout(layout)
        dlg.adjustSize()

        # create connections
        btns.accepted.connect(dlg.accept)
        btns.rejected.connect(dlg.reject)

        if dlg.exec_():
            if plain:
                return (widget.toPlainText(), True)
            else:
                return (widget.toHtml(), True)
        else:
            return ('', False)
Example #3
0
 def getDialog(cls, name, parent=None):
     """
     Generates a dialog for this class widget and returns it.
     
     :param      parent | <QtGui.QWidget> || None
     
     :return     <QtGui.QDialog>
     """
     key = '_{0}__{1}_dialog'.format(cls.__name__, name)
     dlgref = getattr(cls, key, None)
     
     if dlgref is not None:
         dlg = dlgref()
         if dlg:
             return dlg
         
     if parent is None:
         parent = QApplication.activeWindow()
     
     dlg = QDialog(parent)
     
     # create widget
     widget = cls(dlg)
     dlg.__dict__['_mainwidget'] = widget
     widget.layout().setContentsMargins(0, 0, 0, 0)
     
     # create buttons
     opts    = QDialogButtonBox.Save | QDialogButtonBox.Cancel
     buttons = QDialogButtonBox(opts, Qt.Horizontal, dlg)
     
     # create layout
     layout = QVBoxLayout()
     layout.addWidget(widget)
     layout.addWidget(buttons)
     dlg.setLayout(layout)
     dlg.resize(widget.minimumSize() + QSize(15, 15))
     widget.resizeRequested.connect(dlg.adjustSize)
     
     # create connections
     buttons.accepted.connect(widget.save)
     buttons.rejected.connect(dlg.reject)
     widget.saved.connect(dlg.accept)
     widget.setFocus()
     
     dlg.adjustSize()
     if parent and parent.window():
         center = parent.window().geometry().center()
         dlg.move(center.x() - dlg.width() / 2.0,
                  center.y() - dlg.height() / 2.0)
     
     setattr(cls, key, weakref.ref(dlg))
     return dlg
Example #4
0
 def edit( cls, record, parent = None, uifile = '', commit = True ):
     """
     Prompts the user to edit the inputed record.
     
     :param      record | <orb.Table>
                 parent | <QWidget>
     
     :return     <bool> | accepted
     """
     # create the dialog
     dlg = QDialog(parent)
     dlg.setWindowTitle('Edit %s' % record.schema().name())
     
     # create the widget
     cls    = record.schema().property('widgetClass', cls)
     
     widget = cls(dlg)
     
     if ( uifile ):
         widget.setUiFile(uifile)
     
     widget.setRecord(record)
     widget.layout().setContentsMargins(0, 0, 0, 0)
     
     # create buttons
     opts = QDialogButtonBox.Save | QDialogButtonBox.Cancel
     btns = QDialogButtonBox(opts, Qt.Horizontal, dlg)
     
     # create layout
     layout = QVBoxLayout()
     layout.addWidget(widget)
     layout.addWidget(btns)
     
     dlg.setLayout(layout)
     dlg.adjustSize()
     
     # create connections
     #btns.accepted.connect(widget.save)
     btns.rejected.connect(dlg.reject)
     widget.saved.connect(dlg.accept)
     
     if ( dlg.exec_() ):
         if commit:
             result = widget.record().commit()
             if 'errored' in result:
                 QMessageBox.information(self.window(),
                                         'Error Committing to Database',
                                         result['errored'])
                 return False
         return True
     return False
Example #5
0
    def edit(parent, template, actions=None):
        """
        Prompts the user to edit the menu template with the given actions. \
        If no actions are supplied, then the actions from the parent will \
        be used.
        
        :param      parent   | <QWidget>
                    template | <str>
                    actions  | {<str> name: <QAction>, .. } || None
        
        :return     (<str> template, <bool> accepted)
        """
        # collect the potential actions from the widget
        if (actions is None):
            actions = {}
            for action in parent.actions():
                key = nativestring(action.objectName())
                if (not key):
                    key = nativestring(action.text()).replace('&', '')

                if (key):
                    actions[key] = action

        if (not actions):
            return ('', False)

        dlg = QDialog(parent)
        dlg.setWindowTitle('Edit Menu')

        widget = XMenuTemplateWidget(dlg)
        widget.setActions(actions)
        widget.setMenuTemplate(template)
        widget.layout().setContentsMargins(0, 0, 0, 0)

        opts = QDialogButtonBox.Save | QDialogButtonBox.Cancel
        btns = QDialogButtonBox(opts, Qt.Horizontal, dlg)

        btns.accepted.connect(dlg.accept)
        btns.rejected.connect(dlg.reject)

        layout = QVBoxLayout()
        layout.addWidget(widget)
        layout.addWidget(btns)

        dlg.setLayout(layout)
        dlg.adjustSize()
        dlg.resize(650, 400)

        if (dlg.exec_()):
            return (widget.menuTemplate(), True)
        return ('', False)
Example #6
0
 def create( cls, model, parent = None, uifile = '', commit = True ):
     """
     Prompts the user to create a new record for the inputed table.
     
     :param      model   | <subclass of orb.Table>
                 parent  | <QWidget>
     
     :return     <orb.Table> || None/ | instance of the inputed table class
     """
     # create the dialog
     dlg = QDialog(parent)
     dlg.setWindowTitle('Create %s' % model.schema().name())
     
     # create the widget
     cls    = model.schema().property('widgetClass', cls)
     widget = cls(dlg)
     
     if ( uifile ):
         widget.setUiFile(uifile)
     
     widget.setModel(model)
     widget.layout().setContentsMargins(0, 0, 0, 0)
     
     # create buttons
     opts = QDialogButtonBox.Save | QDialogButtonBox.Cancel
     btns = QDialogButtonBox(opts, Qt.Horizontal, dlg)
     
     # create layout
     layout = QVBoxLayout()
     layout.addWidget(widget)
     layout.addWidget(btns)
     
     dlg.setLayout(layout)
     dlg.adjustSize()
     
     # create connections
     btns.accepted.connect(widget.save)
     btns.rejected.connect(dlg.reject)
     widget.saved.connect(dlg.accept)
     
     if ( dlg.exec_() ):
         record = widget.record()
         if ( commit ):
             record.commit()
         return record
     return None
Example #7
0
 def getPlainText( parent, title, caption, text = '' ):
     """
     Prompts the user for more advanced text input.
     
     :param      parent  | <QWidget> || None
                 title   | <str>
                 caption | <str>
                 text    | <str>
     
     :return     (<str>, <bool> accepted)
     """
     dlg = QDialog(parent)
     dlg.setWindowTitle(title)
     
     label = QLabel(dlg)
     label.setText(caption)
     
     edit = QTextEdit(dlg)
     edit.setText(text)
     edit.selectAll()
     
     opts = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
     btns = QDialogButtonBox(opts, Qt.Horizontal, dlg)
     
     btns.accepted.connect(dlg.accept)
     btns.rejected.connect(dlg.reject)
     
     layout = QVBoxLayout()
     layout.addWidget(label)
     layout.addWidget(edit)
     layout.addWidget(btns)
     
     dlg.setLayout(layout)
     dlg.adjustSize()
     
     if ( dlg.exec_() ):
         return (edit.toPlainText(), True)
     return ('', False)
Example #8
0
    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 = []
        self._possibleAnchors = XPopupWidget.Anchor.all()

        # define controls
        self._result = 0
        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.Window)
        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)
Example #9
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 = Signal()
    accepted = Signal()
    closed = Signal()
    rejected = Signal()
    resetRequested = Signal()
    shown = Signal()
    buttonClicked = 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 = []
        self._possibleAnchors = XPopupWidget.Anchor.all()

        # define controls
        self._result = 0
        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.Window)
        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'))

        with XPainter(bitmap) as painter:
            painter.setRenderHint(XPainter.Antialiasing)
            pen = QPen(QColor('black'))
            pen.setWidthF(0.75)
            painter.setPen(pen)
            painter.setBrush(QColor('black'))
            painter.drawPath(path)

        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)

    @Slot()
    def accept(self):
        """
        Emits the accepted signal and closes the popup.
        """
        self._result = 1

        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 exec_(self, pos=None):
        self._result = 0
        self.setWindowModality(Qt.ApplicationModal)
        self.popup(pos)
        while self.isVisible():
            QApplication.processEvents()

        return self.result()

    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>
        """
        if not self.isVisible():
            return False

        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

    @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 isPossibleAnchor(self, anchor):
        return bool(anchor & self._possibleAnchors)

    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, point):
        """
        Returns the anchor point that best fits within the given widget from
        the inputed global position.
        
        :param      widget      | <QWidget>
                    point       | <QPoint>
        
        :return     <XPopupWidget.Anchor>
        """
        screen_geom = QtGui.QDesktopWidget(self).screenGeometry()

        # calculate the end rect for each position
        Anchor = self.Anchor
        w = self.width()
        h = self.height()

        possible_rects = {
            # top anchors
            Anchor.TopLeft:
            QtCore.QRect(point.x(), point.y(), w, h),
            Anchor.TopCenter:
            QtCore.QRect(point.x() - w / 2, point.y(), w, h),
            Anchor.TopRight:
            QtCore.QRect(point.x() - w, point.y(), w, h),

            # left anchors
            Anchor.LeftTop:
            QtCore.QRect(point.x(), point.y(), w, h),
            Anchor.LeftCenter:
            QtCore.QRect(point.x(),
                         point.y() - h / 2, w, h),
            Anchor.LeftBottom:
            QtCore.QRect(point.x(),
                         point.y() - h, w, h),

            # bottom anchors
            Anchor.BottomLeft:
            QtCore.QRect(point.x(),
                         point.y() - h, w, h),
            Anchor.BottomCenter:
            QtCore.QRect(point.x() - w / 2,
                         point.y() - h, w, h),
            Anchor.BottomRight:
            QtCore.QRect(point.x() - w,
                         point.y() - h, w, h),

            # right anchors
            Anchor.RightTop:
            QtCore.QRect(point.x() - self.width(), point.y(), w, h),
            Anchor.RightCenter:
            QtCore.QRect(point.x() - self.width(),
                         point.y() - h / 2, w, h),
            Anchor.RightBottom:
            QtCore.QRect(point.x() - self.width(),
                         point.y() - h, w, h)
        }

        for anchor in (Anchor.TopCenter, Anchor.BottomCenter,
                       Anchor.LeftCenter, Anchor.RightCenter, Anchor.TopLeft,
                       Anchor.LeftTop, Anchor.BottomLeft, Anchor.LeftBottom,
                       Anchor.TopRight, Anchor.RightTop, Anchor.BottomRight,
                       Anchor.RightBottom):

            if not self.isPossibleAnchor(anchor):
                continue

            rect = possible_rects[anchor]
            if screen_geom.contains(rect):
                return anchor

        return self.anchor()

    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()

        with XPainter(self) as painter:
            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())

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

    def possibleAnchors(self):
        return self._possibleAnchors

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

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

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

    def result(self):
        return self._result

    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=True):
        """
        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()

    @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 setPossibleAnchors(self, anchors):
        self._possibleAnchors = anchors

    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.Window)

        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 #10
0
 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      = []
     self._possibleAnchors       = XPopupWidget.Anchor.all()
     
     # define controls
     self._result        = 0
     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.Window)
     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)
Example #11
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     = Signal()
    accepted        = Signal()
    closed          = Signal()
    rejected        = Signal()
    resetRequested  = Signal()
    shown           = Signal()
    buttonClicked   = 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      = []
        self._possibleAnchors       = XPopupWidget.Anchor.all()
        
        # define controls
        self._result        = 0
        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.Window)
        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'))
        
        with XPainter(bitmap) as painter:
            painter.setRenderHint(XPainter.Antialiasing)
            pen = QPen(QColor('black'))
            pen.setWidthF(0.75)
            painter.setPen(pen)
            painter.setBrush(QColor('black'))
            painter.drawPath(path)
        
        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)
    
    @Slot()
    def accept(self):
        """
        Emits the accepted signal and closes the popup.
        """
        self._result = 1
        
        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 exec_(self, pos=None):
        self._result = 0
        self.setWindowModality(Qt.ApplicationModal)
        self.popup(pos)
        while self.isVisible():
            QApplication.processEvents()
        
        return self.result()
    
    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>
        """
        if not self.isVisible():
            return False
        
        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
    
    @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 isPossibleAnchor(self, anchor):
        return bool(anchor & self._possibleAnchors)

    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, point):
        """
        Returns the anchor point that best fits within the given widget from
        the inputed global position.
        
        :param      widget      | <QWidget>
                    point       | <QPoint>
        
        :return     <XPopupWidget.Anchor>
        """
        screen_geom = QtGui.QDesktopWidget(self).screenGeometry()

        # calculate the end rect for each position
        Anchor = self.Anchor
        w = self.width()
        h = self.height()

        possible_rects = {
            # top anchors
            Anchor.TopLeft: QtCore.QRect(point.x(), point.y(), w, h),
            Anchor.TopCenter: QtCore.QRect(point.x() - w / 2, point.y(), w, h),
            Anchor.TopRight: QtCore.QRect(point.x() - w, point.y(), w, h),

            # left anchors
            Anchor.LeftTop: QtCore.QRect(point.x(), point.y(), w, h),
            Anchor.LeftCenter: QtCore.QRect(point.x(), point.y() - h / 2, w, h),
            Anchor.LeftBottom: QtCore.QRect(point.x(), point.y() - h, w, h),

            # bottom anchors
            Anchor.BottomLeft: QtCore.QRect(point.x(), point.y() - h, w, h),
            Anchor.BottomCenter: QtCore.QRect(point.x() - w / 2, point.y() - h, w, h),
            Anchor.BottomRight: QtCore.QRect(point.x() - w, point.y() - h, w, h),

            # right anchors
            Anchor.RightTop: QtCore.QRect(point.x() - self.width(), point.y(), w, h),
            Anchor.RightCenter: QtCore.QRect(point.x() - self.width(), point.y() - h / 2, w, h),
            Anchor.RightBottom: QtCore.QRect(point.x() - self.width(), point.y() - h, w ,h)
        }

        for anchor in (Anchor.TopCenter,
                       Anchor.BottomCenter,
                       Anchor.LeftCenter,
                       Anchor.RightCenter,
                       Anchor.TopLeft,
                       Anchor.LeftTop,
                       Anchor.BottomLeft,
                       Anchor.LeftBottom,
                       Anchor.TopRight,
                       Anchor.RightTop,
                       Anchor.BottomRight,
                       Anchor.RightBottom):

            if not self.isPossibleAnchor(anchor):
                continue

            rect = possible_rects[anchor]
            if screen_geom.contains(rect):
                return anchor

        return self.anchor()

    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()
        
        with XPainter(self) as painter:
            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())
    
    def popupPadding(self):
        """
        Returns the amount of pixels to pad the popup arrow for this widget.
        
        :return     <int>
        """
        return self._popupPadding

    def possibleAnchors(self):
        return self._possibleAnchors

    def positionLinkedTo(self):
        """
        Returns the widget that this popup is linked to for positional changes.
        
        :return     [<QWidget>, ..]
        """
        return self._positionLinkedTo
    
    @Slot()
    def reject(self):
        """
        Emits the accepted signal and closes the popup.
        """
        self._result = 0
        if not self.signalsBlocked():
            self.rejected.emit()
        
        if self.autoCloseOnReject():
            self.close()
    
    def result(self):
        return self._result
    
    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=True):
        """
        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()
    
    @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 setPossibleAnchors(self, anchors):
        self._possibleAnchors = anchors

    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.Window)
        
        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