Esempio n. 1
0
class XOverlayWizardPage(QtGui.QFrame):
    completeChanged = QtCore.Signal()

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

        self.setFrameShape(QtGui.QFrame.Box)

        # define custom properties
        self._commitPage = False
        self._finalPage = False
        self._retryEnabled = False
        self._nextId = None

        # create the title information
        font = self.font()
        font.setBold(True)
        base_size = font.pointSize()
        font.setPointSize(base_size + 4)

        # set the palette coloring
        pal = self.palette()
        pal.setColor(pal.WindowText, QtGui.QColor('white'))
        self.setPalette(pal)

        # create the title labels
        self._titleLabel = QtGui.QLabel('', self)
        self._titleLabel.setFont(font)
        self._titleLabel.setPalette(pal)
        font.setPointSize(base_size + 2)
        self._subTitleLabel = QtGui.QLabel('', self)
        self._subTitleLabel.setPalette(pal)

        self.adjustMargins()

    def adjustMargins(self):
        """
        Adjusts the margins to incorporate enough room for the widget's title and sub-title.
        """
        y = 0
        height = 0
        if self._titleLabel.text():
            height += self._titleLabel.height() + 3
            y += height

        if self._subTitleLabel.text():
            self._subTitleLabel.move(0, y)
            height += self._subTitleLabel.height() + 3

        self.setContentsMargins(0, height, 0, 0)

    def cleanupPage(self):
        """
        Performs any cleanup operations that are necessary for this page.
        """
        pass

    def commit(self):
        """
        Commits any data for this wizard page.
        """
        pass

    def field(self, name):
        """
        Returns the field value for this page from its wizard.

        :param      name | <str>

        :return     <variant>
        """
        return self.wizard().field(name)

    def isCommitPage(self):
        """
        Returns whether or not this is a page that requires the commit button to be displayed.

        :return     <bool>
        """
        return self._commitPage

    def isComplete(self):
        """
        Returns whether or not this page has fully completed its operations.

        :return     <bool>
        """
        return False

    def isFinalPage(self):
        """
        Returns whether or not this is the final page within the wizard.

        :return     <bool>
        """
        return self._finalPage or self.nextId() == -1

    def isRetryEnabled(self):
        """
        Returns whether or not this page can retry its methods.

        :return     <bool>
        """
        return self._retryEnabled

    def initializePage(self):
        """
        Performs any initialization operations that are necessary for this page.
        """
        pass

    def nextId(self):
        """
        Returns the next id for this page.  By default, it will provide the next id from the wizard, but
        this method can be overloaded to create a custom path.  If -1 is returned, then it will be considered
        the final page.

        :return     <int>
        """
        if self._nextId is not None:
            return self._nextId

        wizard = self.wizard()
        curr_id = wizard.currentId()
        all_ids = wizard.pageIds()
        try:
            return all_ids[all_ids.index(curr_id) + 1]
        except IndexError:
            return -1

    def setCommitPage(self, state):
        """
        Sets whether or not this is a commit page for the wizard.

        :param      state | <bool>
        """
        self._commitPage = state

    def setField(self, name, value):
        """
        Sets the field value for this page from its wizard.

        :param      name  | <str>
                    value | <variant>
        """
        return self.wizard().setField(name, value)

    def setFinalPage(self, state):
        """
        Sets whether or not this is a final page for the wizard.

        :param      state | <bool>
        """
        self._finalPage = state

    def setNextId(self, pageId):
        """
        Sets the next page id that this page will point to.  If the id is None, then it will lookup the
        id from the wizard.

        :param      pageID | <int> || None
        """
        self._nextId = pageId

    def setSubTitle(self, title):
        """
        Sets the sub-title for this page to the inputed title.

        :param      title | <str>
        """
        self._subTitleLabel.setText(title)
        self._subTitleLabel.adjustSize()
        self.adjustMargins()

    def setRetryEnabled(self, state=True):
        """
        Sets whether or not this page has retrying allowed.

        :param      state | <bool>
        """
        self._retryEnabled = state

    def setTitle(self, title):
        """
        Sets the title for this page to the inputed title.

        :param      title | <str>
        """
        self._titleLabel.setText(title)
        self._titleLabel.adjustSize()
        self.adjustMargins()

    def subTitle(self):
        """
        Returns the sub-title for this page.

        :return     <str>
        """
        return self._subTitleLabel.text()

    def title(self):
        """
        Returns the title for this page.

        :return     <str>
        """
        return self._titleLabel.text()

    def validatePage(self):
        """
        Performs a validation check before trying to continue from this page.

        :return     <bool>
        """
        return True

    def wizard(self):
        """
        Returns the wizard associated with this page.

        :return     <projexui.widgets.xoverlaywizard.XOverlayWizard>
        """
        return projexui.ancestor(self, XOverlayWizard)
Esempio n. 2
0
 def sizeHint(self):
     return QtCore.QSize(self.codeEditor().numberAreaWidth(), 0)
Esempio n. 3
0
class XOverlayWizard(XOverlayWidget):
    WizardButton = enum(BackButton=0,
                        NextButton=1,
                        CommitButton=2,
                        FinishButton=3,
                        CancelButton=4,
                        HelpButton=5,
                        RetryButton=6)

    WizardButtonColors = {
        WizardButton.BackButton: '#58A6D6',
        WizardButton.NextButton: '#58A6D6',
        WizardButton.CommitButton: '#49B84D',
        WizardButton.FinishButton: '#369939',
        WizardButton.CancelButton: '#B0B0B0',
        WizardButton.HelpButton: '#8CA4E6',
        WizardButton.RetryButton: '#D93D3D'
    }

    currentIdChanged = QtCore.Signal(int)
    helpRequested = QtCore.Signal()
    pageAdded = QtCore.Signal(int)
    pageRemoved = QtCore.Signal(int)

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

        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        # define custom properties
        self._fixedPageSize = QtCore.QSize()
        self._minimumPageSize = QtCore.QSize(0, 0)
        self._maximumPageSize = QtCore.QSize(10000, 10000)
        self._currentId = -1
        self._startId = -1
        self._pages = OrderedDict()
        self._buttons = {}
        self._fields = {}
        self._navigation = []
        self._animationSpeed = 350

        # create the buttons
        self._buttons[self.WizardButton.HelpButton] = QtGui.QPushButton(
            self.tr('Help'), self)
        self._buttons[self.WizardButton.BackButton] = QtGui.QPushButton(
            self.tr('Back'), self)
        self._buttons[self.WizardButton.NextButton] = QtGui.QPushButton(
            self.tr('Next'), self)
        self._buttons[self.WizardButton.CommitButton] = QtGui.QPushButton(
            self.tr('Commit'), self)
        self._buttons[self.WizardButton.FinishButton] = QtGui.QPushButton(
            self.tr('Finish'), self)
        self._buttons[self.WizardButton.CancelButton] = QtGui.QPushButton(
            self.tr('Cancel'), self)
        self._buttons[self.WizardButton.RetryButton] = QtGui.QPushButton(
            self.tr('Retry'), self)

        # don't show any buttons by default
        pal = self.palette()
        for which, btn in self._buttons.items():
            pal.setColor(pal.Active, pal.Button,
                         QtGui.QColor(self.WizardButtonColors[which]))
            pal.setColor(pal.Active, pal.ButtonText, QtGui.QColor('white'))
            pal.setColor(pal.Inactive, pal.Button,
                         QtGui.QColor(self.WizardButtonColors[which]))
            pal.setColor(pal.Inactive, pal.ButtonText, QtGui.QColor('white'))
            btn.setPalette(pal)
            btn.setFixedSize(QtCore.QSize(120, 30))
            btn.hide()

        # create connections
        self._buttons[self.WizardButton.HelpButton].clicked.connect(
            self.helpRequested)
        self._buttons[self.WizardButton.BackButton].clicked.connect(self.back)
        self._buttons[self.WizardButton.NextButton].clicked.connect(self.next)
        self._buttons[self.WizardButton.CommitButton].clicked.connect(
            self.commit)
        self._buttons[self.WizardButton.FinishButton].clicked.connect(
            self.accept)
        self._buttons[self.WizardButton.CancelButton].clicked.connect(
            self.reject)
        self._buttons[self.WizardButton.RetryButton].clicked.connect(
            self.retry)

    def accept(self):
        if not self.currentPage() or self.currentPage().validatePage():
            return super(XOverlayWizard, self).accept()
        return False

    def addPage(self, page):
        """
        Adds a new overlay wizard page to this wizard.

        :param      page | <projexui.widgets.xoverlaywizard.XOverlayWizardPage>
        """
        self.setPage(len(self._pages), page)

    def adjustSize(self):
        """
        Adjusts the size of this wizard and its page contents to the inputed size.

        :param      size | <QtCore.QSize>
        """
        super(XOverlayWizard, self).adjustSize()

        # adjust the page size
        page_size = self.pageSize()

        # resize the current page to the inputed size
        x = (self.width() - page_size.width()) / 2
        y = (self.height() - page_size.height()) / 2
        btn_height = max([btn.height() for btn in self._buttons.values()])

        curr_page = self.currentPage()
        for page in self._pages.values():
            page.resize(page_size.width(), page_size.height() - btn_height - 6)
            if page == curr_page:
                page.move(x, y)
            else:
                page.move(self.width(), y)

        # move the left most buttons
        y += page_size.height() - btn_height
        left_btns = (self._buttons[self.WizardButton.HelpButton],
                     self._buttons[self.WizardButton.BackButton])
        for btn in left_btns:
            if btn.isVisible():
                btn.move(x, y)
                btn.raise_()
                x += btn.width() + 6

        # move the right buttons
        x = self.width() - (self.width() - page_size.width()) / 2
        for key in reversed(sorted(self._buttons.keys())):
            btn = self._buttons[key]
            if not btn.isVisible() or btn in left_btns:
                continue

            btn.move(x - btn.width(), y)
            btn.raise_()
            x -= btn.width() + 6

    def animationSpeed(self):
        """
        Returns the speed that the pages should animate in/out in milliseconds.

        :return     <int>
        """
        return self._animationSpeed

    def back(self):
        """
        Goes to the previous page for this wizard.
        """
        try:
            pageId = self._navigation[-2]
            last_page = self.page(pageId)
        except IndexError:
            return

        curr_page = self.page(self._navigation.pop())
        if not (last_page and curr_page):
            return

        self._currentId = pageId

        y = curr_page.y()
        last_page.move(-last_page.width(), y)
        last_page.show()

        # animate the last page in
        anim_in = QtCore.QPropertyAnimation(self)
        anim_in.setTargetObject(last_page)
        anim_in.setPropertyName('pos')
        anim_in.setStartValue(last_page.pos())
        anim_in.setEndValue(curr_page.pos())
        anim_in.setDuration(self.animationSpeed())
        anim_in.setEasingCurve(QtCore.QEasingCurve.Linear)

        # animate the current page out
        anim_out = QtCore.QPropertyAnimation(self)
        anim_out.setTargetObject(curr_page)
        anim_out.setPropertyName('pos')
        anim_out.setStartValue(curr_page.pos())
        anim_out.setEndValue(QtCore.QPoint(self.width() + curr_page.width(),
                                           y))
        anim_out.setDuration(self.animationSpeed())
        anim_out.setEasingCurve(QtCore.QEasingCurve.Linear)

        # create the anim group
        anim_grp = QtCore.QParallelAnimationGroup(self)
        anim_grp.addAnimation(anim_in)
        anim_grp.addAnimation(anim_out)
        anim_grp.finished.connect(curr_page.hide)
        anim_grp.finished.connect(anim_grp.deleteLater)

        # update the button states
        self._buttons[self.WizardButton.BackButton].setVisible(
            self.canGoBack())
        self._buttons[self.WizardButton.NextButton].setVisible(True)
        self._buttons[self.WizardButton.RetryButton].setVisible(
            self.canRetry())
        self._buttons[self.WizardButton.CommitButton].setVisible(
            last_page.isCommitPage())
        self._buttons[self.WizardButton.FinishButton].setVisible(
            last_page.isFinalPage())

        self.adjustSize()
        self.currentIdChanged.emit(pageId)
        anim_grp.start()

    def button(self, which):
        """
        Returns the button associated with the inputed wizard button.

        :param      which | <XOverlayWizard.WizardButton>

        :return     <bool> || None
        """
        return self._buttons.get(which)

    def buttonText(self, which):
        """
        Returns the button text for a given wizard button.

        :param      which | <XOverlayWizard.WizardButton>
        """
        try:
            return self._buttons[which].text()
        except KeyError:
            return ''

    def canGoBack(self):
        """
        Returns whether or not this wizard can move forward.

        :return     <bool>
        """
        try:
            backId = self._navigation.index(self.currentId()) - 1
            if backId >= 0:
                self._navigation[backId]
            else:
                return False
        except StandardError:
            return False
        else:
            return True

    def canGoForward(self):
        """
        Returns whether or not this wizard can move forward.

        :return     <bool>
        """
        try:
            return not self.currentPage().isFinalPage()
        except AttributeError:
            return False

    def canRetry(self):
        """
        Returns whether or not this wizard can retry the current page.

        :return     <bool>
        """
        try:
            return self.currentPage().retryEnabled()
        except AttributeError:
            return False

    def commit(self):
        """
        Commits the current page information.
        """
        try:
            self.currentPage().commit()
        except AttributeError:
            pass

    def currentId(self):
        """
        Returns the page ID of the current page that this wizard is on.

        :return     <int>
        """
        return self._currentId

    def currentPage(self):
        """
        Returns the current page for this wizard.

        :return     <projexui.widgets.xoverlaywizard.XOverlayWizardPage> || None
        """
        try:
            return self._pages[self.currentId()]
        except KeyError:
            return None

    def field(self, name, default=None):
        """
        Returns the value for the inputed field property for this wizard.

        :param      name | <str>
                    default | <variant>

        :return     <variant>
        """
        return self._fields.get(name, default)

    def fixedPageSize(self):
        """
        Returns the fixed page size for this wizard's contents.

        :return     <QtCore.QSize>
        """
        return self._fixedPageSize

    def hasVisitedPage(self, pageId):
        """
        Returns whether or not the user has seen the inputed page id.

        :return     <bool>
        """
        return False

    def minimumPageSize(self):
        """
        Returns the minimum page size for this wizard's contents.

        :return     <QtCore.QSize>
        """
        return self._minimumPageSize

    def maximumPageSize(self):
        """
        Returns the maximum page size for this wizard's contents.

        :return     <QtCore.QSize>
        """
        return self._maximumPageSize

    def next(self):
        """
        Goes to the previous page for this wizard.
        """
        curr_page = self.currentPage()
        if not curr_page:
            return
        elif not curr_page.validatePage():
            return

        pageId = curr_page.nextId()
        try:
            next_page = self._pages[pageId]
        except KeyError:
            return

        self._currentId = pageId
        self._navigation.append(pageId)

        y = curr_page.y()
        next_page.move(self.width(), y)

        # animate the last page in
        anim_in = QtCore.QPropertyAnimation(self)
        anim_in.setTargetObject(curr_page)
        anim_in.setPropertyName('pos')
        anim_in.setStartValue(curr_page.pos())
        anim_in.setEndValue(QtCore.QPoint(-curr_page.width(), y))
        anim_in.setDuration(self.animationSpeed())
        anim_in.setEasingCurve(QtCore.QEasingCurve.Linear)

        # animate the current page out
        anim_out = QtCore.QPropertyAnimation(self)
        anim_out.setTargetObject(next_page)
        anim_out.setPropertyName('pos')
        anim_out.setStartValue(next_page.pos())
        anim_out.setEndValue(curr_page.pos())
        anim_out.setDuration(self.animationSpeed())
        anim_out.setEasingCurve(QtCore.QEasingCurve.Linear)

        # create the anim group
        anim_grp = QtCore.QParallelAnimationGroup(self)
        anim_grp.addAnimation(anim_in)
        anim_grp.addAnimation(anim_out)
        anim_grp.finished.connect(curr_page.hide)
        anim_grp.finished.connect(anim_grp.deleteLater)

        next_page.show()

        # update the button states
        self._buttons[self.WizardButton.BackButton].setVisible(True)
        self._buttons[self.WizardButton.NextButton].setVisible(
            self.canGoForward())
        self._buttons[self.WizardButton.RetryButton].setVisible(
            self.canRetry())
        self._buttons[self.WizardButton.CommitButton].setVisible(
            next_page.isCommitPage())
        self._buttons[self.WizardButton.FinishButton].setVisible(
            next_page.isFinalPage())
        self.adjustSize()

        # initialize the new page
        self.currentIdChanged.emit(pageId)
        next_page.initializePage()
        anim_grp.start()

    def page(self, pageId):
        """
        Returns the page at the inputed id.

        :return     <projexui.widgets.xoverlaywizard.XOverlayWizardPage> || None
        """
        return self._pages.get(pageId)

    def pageCount(self):
        """
        Returns the number of pages associated with this wizard.

        :return     <int>
        """
        return len(self._pages)

    def pageIds(self):
        """
        Returns a list of all the page ids for this wizard.

        :return     [<int>, ..]
        """
        return self._pages.keys()

    def pageSize(self):
        """
        Returns the current page size for this wizard.

        :return     <QtCore.QSize>
        """

        # update the size based on the new size
        page_size = self.fixedPageSize()
        if page_size.isEmpty():
            w = self.width() - 80
            h = self.height() - 80

            min_w = self.minimumPageSize().width()
            min_h = self.minimumPageSize().height()
            max_w = self.maximumPageSize().width()
            max_h = self.maximumPageSize().height()

            page_size = QtCore.QSize(min(max(min_w, w), max_w),
                                     min(max(min_h, h), max_h))
        return page_size

    def exec_(self):
        """
        Executes this wizard within the system.
        """
        self.show()
        self.adjustSize()
        self.restart()

    def restart(self):
        """
        Restarts the whole wizard from the beginning.
        """
        # hide all of the pages
        for page in self._pages.values():
            page.hide()

        pageId = self.startId()
        try:
            first_page = self._pages[pageId]
        except KeyError:
            return

        self._currentId = pageId
        self._navigation = [pageId]

        page_size = self.pageSize()
        x = (self.width() - page_size.width()) / 2
        y = (self.height() - page_size.height()) / 2

        first_page.move(self.width() + first_page.width(), y)
        first_page.show()

        # animate the current page out
        anim_out = QtCore.QPropertyAnimation(self)
        anim_out.setTargetObject(first_page)
        anim_out.setPropertyName('pos')
        anim_out.setStartValue(first_page.pos())
        anim_out.setEndValue(QtCore.QPoint(x, y))
        anim_out.setDuration(self.animationSpeed())
        anim_out.setEasingCurve(QtCore.QEasingCurve.Linear)
        anim_out.finished.connect(anim_out.deleteLater)

        # update the button states
        self._buttons[self.WizardButton.BackButton].setVisible(False)
        self._buttons[self.WizardButton.NextButton].setVisible(
            self.canGoForward())
        self._buttons[self.WizardButton.CommitButton].setVisible(
            first_page.isCommitPage())
        self._buttons[self.WizardButton.FinishButton].setVisible(
            first_page.isFinalPage())
        self.adjustSize()

        first_page.initializePage()
        self.currentIdChanged.emit(pageId)
        anim_out.start()

    def removePage(self, pageId):
        """
        Removes the inputed page from this wizard.

        :param      pageId | <int>
        """
        try:
            self._pages[pageId].deleteLater()
            del self._pages[pageId]
        except KeyError:
            pass

    def retry(self):
        """
        Reruns the current page operation.
        """
        try:
            self.currentPage().initializePage()
        except AttributeError:
            pass

    def setAnimationSpeed(self, speed):
        """
        Sets the speed that the pages should animate in/out in milliseconds.

        :param      speed | <int> | milliseconds
        """
        self._animationSpeed = speed

    def setButton(self, which, button):
        """
        Sets the button for this wizard for the inputed type.

        :param      which  | <XOverlayWizard.WizardButton>
                    button | <QtGui.QPushButton>
        """
        try:
            self._buttons[which].deleteLater()
        except KeyError:
            pass

        button.setParent(self)
        self._buttons[which] = button

    def setButtonText(self, which, text):
        """
        Sets the display text for the inputed button to the given text.

        :param      which | <XOverlayWizard.WizardButton>
                    text  | <str>
        """
        try:
            self._buttons[which].setText(text)
        except KeyError:
            pass

    def setField(self, name, value):
        """
        Sets the field value for this wizard to the given value.

        :param      name | <str>
                    value | <variant>
        """
        self._fields[name] = value

    def setFixedPageSize(self, size):
        """
        Sets the page size for the wizard.  This will define the width/height for the contents
        for this wizards page widgets as well as the bounding information for the buttons.  If
        an empty size is given, then the size will automatically be recalculated as the widget sizes.

        :param      size | <QtCore.QSize>
        """
        self._fixedPageSize = size
        self.adjustSize()

    def setMinimumPageSize(self, size):
        """
        Sets the page size for the wizard.  This will define the width/height for the contents
        for this wizards page widgets as well as the bounding information for the buttons.  If
        an empty size is given, then the size will automatically be recalculated as the widget sizes.

        :param      size | <QtCore.QSize>
        """
        self._minimumPageSize = size
        self.adjustSize()

    def setMaximumPageSize(self, size):
        """
        Sets the page size for the wizard.  This will define the width/height for the contents
        for this wizards page widgets as well as the bounding information for the buttons.  If
        an empty size is given, then the size will automatically be recalculated as the widget sizes.

        :param      size | <QtCore.QSize>
        """
        self._maximumPageSize = size
        self.adjustSize()

    def setPage(self, pageId, page):
        """
        Sets the page and id for the given page vs. auto-constructing it.  This will allow the
        developer to create a custom order for IDs.

        :param      pageId | <int>
                    page   | <projexui.widgets.xoverlaywizard.XOverlayWizardPage>
        """
        page.setParent(self)

        if self.property("useShadow") is not False:
            # create the drop shadow effect
            effect = QtGui.QGraphicsDropShadowEffect(page)
            effect.setColor(QtGui.QColor('black'))
            effect.setBlurRadius(50)
            effect.setOffset(0, 0)

            page.setGraphicsEffect(effect)

        self._pages[pageId] = page
        if self._startId == -1:
            self._startId = pageId

    def setStartId(self, pageId):
        """
        Sets the starting page id for this wizard to the inputed page id.

        :param      pageId | <int>
        """
        self._startId = pageId

    def startId(self):
        """
        Returns the starting id for this wizard.

        :return     <int>
        """
        return self._startId
Esempio n. 4
0
    def next(self):
        """
        Goes to the previous page for this wizard.
        """
        curr_page = self.currentPage()
        if not curr_page:
            return
        elif not curr_page.validatePage():
            return

        pageId = curr_page.nextId()
        try:
            next_page = self._pages[pageId]
        except KeyError:
            return

        self._currentId = pageId
        self._navigation.append(pageId)

        y = curr_page.y()
        next_page.move(self.width(), y)

        # animate the last page in
        anim_in = QtCore.QPropertyAnimation(self)
        anim_in.setTargetObject(curr_page)
        anim_in.setPropertyName('pos')
        anim_in.setStartValue(curr_page.pos())
        anim_in.setEndValue(QtCore.QPoint(-curr_page.width(), y))
        anim_in.setDuration(self.animationSpeed())
        anim_in.setEasingCurve(QtCore.QEasingCurve.Linear)

        # animate the current page out
        anim_out = QtCore.QPropertyAnimation(self)
        anim_out.setTargetObject(next_page)
        anim_out.setPropertyName('pos')
        anim_out.setStartValue(next_page.pos())
        anim_out.setEndValue(curr_page.pos())
        anim_out.setDuration(self.animationSpeed())
        anim_out.setEasingCurve(QtCore.QEasingCurve.Linear)

        # create the anim group
        anim_grp = QtCore.QParallelAnimationGroup(self)
        anim_grp.addAnimation(anim_in)
        anim_grp.addAnimation(anim_out)
        anim_grp.finished.connect(curr_page.hide)
        anim_grp.finished.connect(anim_grp.deleteLater)

        next_page.show()

        # update the button states
        self._buttons[self.WizardButton.BackButton].setVisible(True)
        self._buttons[self.WizardButton.NextButton].setVisible(
            self.canGoForward())
        self._buttons[self.WizardButton.RetryButton].setVisible(
            self.canRetry())
        self._buttons[self.WizardButton.CommitButton].setVisible(
            next_page.isCommitPage())
        self._buttons[self.WizardButton.FinishButton].setVisible(
            next_page.isFinalPage())
        self.adjustSize()

        # initialize the new page
        self.currentIdChanged.emit(pageId)
        next_page.initializePage()
        anim_grp.start()
Esempio n. 5
0
class XLoggerControls(QtGui.QWidget):
    activeLevelsChanged = QtCore.Signal(list)
    
    def __init__(self, loggerWidget):
        super(XLoggerControls, self).__init__(loggerWidget)
        
        # load the user interface
        projexui.loadUi(__file__, self)
        
        self._url = 'https://docs.python.org/2/library/logging.html#logrecord-attributes'
        self._loggerWidget = loggerWidget
        self.uiLoggerTREE.setLoggerWidget(loggerWidget)
        self.uiFormatTXT.setText(loggerWidget.formatText())
        
        # load the levels
        if 'designer' not in sys.executable:
            tree = self.uiLevelTREE
            from projexui.widgets.xloggerwidget import XLoggerWidget
            items = sorted(XLoggerWidget.LoggingMap.items())
            for i, (level, data) in enumerate(items):
                item = XTreeWidgetItem(tree, [projex.text.pretty(data[0])])
                item.setFixedHeight(22)
                item.setData(0, QtCore.Qt.UserRole, wrapVariant(level))
                item.setCheckState(0, QtCore.Qt.Unchecked)
        
        # create connections
        self.uiFormatTXT.textChanged.connect(loggerWidget.setFormatText)
        self.uiLevelTREE.itemChanged.connect(self.updateLevels)
        self.uiHelpBTN.clicked.connect(self.showHelp)

    def showEvent(self, event):
        super(XLoggerControls, self).showEvent(event)
        
        # update the format text
        widget = self._loggerWidget
        self.uiFormatTXT.setText(widget.formatText())
        
        # update the active levels
        lvls = widget.activeLevels()
        tree = self.uiLevelTREE
        tree.blockSignals(True)
        for item in tree.topLevelItems():
            if unwrapVariant(item.data(0, QtCore.Qt.UserRole)) in lvls:
                item.setCheckState(0, QtCore.Qt.Checked)
            else:
                item.setCheckState(0, QtCore.Qt.Unchecked)
        tree.blockSignals(False)

    def showHelp(self):
        webbrowser.open(self._url)

    def updateLevels(self):
        levels = []
        tree = self.uiLevelTREE
        tree.blockSignals(True)
        for item in tree.checkedItems():
            level = unwrapVariant(item.data(0, QtCore.Qt.UserRole))
            if level is not None:
                levels.append(level)
        tree.blockSignals(False)
        self._loggerWidget.setActiveLevels(levels)
class XCollapsibleLoggerWidget(QtGui.QWidget):
    """ """
    def __init__(self, parent=None):
        super(XCollapsibleLoggerWidget, self).__init__(parent)

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

        # define custom properties
        self._collapsed = False
        self._animated = True
        self._expandedHeight = 250

        # animation properties
        self._startHeight = 0
        self._targetHeight = 0
        self._targetPercent = 0

        self.uiFeedbackLBL.setFont(self.uiLoggerWGT.font())

        # set default properties
        self.setCollapsed(True)

        # create connections
        self.uiShowBTN.clicked.connect(self.expand)
        self.uiHideBTN.clicked.connect(self.collapse)
        self.uiLoggerWGT.messageLogged.connect(self.updateFeedback)

    @QtCore.Slot()
    def clear(self):
        self.uiLoggerWGT.clear()
        self.uiFeedbackLBL.setText('')

    @QtCore.Slot()
    def collapse(self):
        self.setCollapsed(True)

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

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

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

    @QtCore.Slot()
    def expand(self):
        self.setCollapsed(False)

    def expandedHeight(self):
        return self._expandedHeight

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

    def formatText(self):
        return self.uiLoggerWGT.formatText()

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

    def isAnimated(self):
        return self._animated

    def isCollapsed(self):
        """
        Returns whether or not this logger widget is in the collapsed
        state.
        
        :return     <bool>
        """
        return self._collapsed

    def isConfigurable(self):
        """
        Returns whether or not this widget can be configured by the user.
        
        :return     <bool>
        """
        return self.uiLoggerWGT.isConfigurable()

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

    def logger(self):
        """
        Returns the logger associated with this widget.
        
        :return     <logging.Logger>
        """
        return self.uiLoggerWGT.logger()

    def loggerLevel(self, logger):
        return self.uiLoggerWGT.loggerLevel(logger)

    def loggerWidget(self):
        return self.uiLoggerWGT

    def setAnimated(self, state):
        self._animated = state

    @QtCore.Slot(bool)
    def setExpanded(self, state):
        self.setCollapsed(not state)

    def setExpandedHeight(self, height):
        self._expandedHeight = height

    @QtCore.Slot(bool)
    def setCollapsed(self, state):
        if self._collapsed == state:
            return

        self._collapsed = state

        # update the sizing constraints
        palette = self.palette()
        if state:
            height = 24
        else:
            height = self.expandedHeight()

        if self.isVisible() and self.parent() and self.isAnimated():
            # show the uncollapsed items collapsing
            if not state:
                # update the visible children based on the state
                for widget in self.children():
                    prop = unwrapVariant(widget.property('showCollapsed'))
                    if prop is not None:
                        widget.setVisible(bool(prop) == state)

            self._startHeight = self.height()
            self._targetHeight = height
            self._targetPercent = 0.0
            self.startTimer(10)
        else:
            # update the visible children based on the state
            for widget in self.children():
                prop = unwrapVariant(widget.property('showCollapsed'))
                if prop is not None:
                    widget.setVisible(bool(prop) == state)

            self.setFixedHeight(height)

    def setConfigurable(self, state):
        """
        Sets whether or not this logger widget can be configured by the user.
        
        :param      state | <bool>
        """
        self.uiLoggerWGT.setConfigurable(state)

    def setLoggerLevel(self, logger, level):
        self.uiLoggerWGT.setLoggerLevel(logger, level)

    def setFormatText(self, text):
        self.uiLoggerWGT.setFormatText(text)

    def setLogger(self, logger):
        """
        Sets the logger associated with this widget.
        
        :param     logger | <logging.Logger>
        """
        self.uiLoggerWGT.setLogger(logger)

    def timerEvent(self, event):
        self._targetPercent += 0.05
        if self._targetPercent >= 1:
            self.killTimer(event.timerId())
            self.setFixedHeight(self._targetHeight)

            # always show the logger widget
            if self.isCollapsed():
                # update the visible children based on the state
                for widget in self.children():
                    prop = unwrapVariant(widget.property('showCollapsed'))
                    if prop is not None:
                        widget.setVisible(bool(prop) == self.isCollapsed())

        else:
            delta = (self._startHeight -
                     self._targetHeight) * self._targetPercent
            self.setFixedHeight(self._startHeight - delta)

    def updateFeedback(self, level, message):
        clr = self.uiLoggerWGT.color(level)
        palette = self.uiFeedbackLBL.palette()
        palette.setColor(palette.WindowText, clr)
        self.uiFeedbackLBL.setPalette(palette)
        self.uiFeedbackLBL.setText(message)

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

    x_animated = QtCore.Property(bool, isAnimated, setAnimated)
    x_collapsed = QtCore.Property(bool, isCollapsed, setCollapsed)
    x_configurable = QtCore.Property(bool, isConfigurable, setConfigurable)
    x_expandedHeight = QtCore.Property(int, expandedHeight, setExpandedHeight)
    x_formatText = QtCore.Property(str, formatText, setFormatText)
Esempio n. 7
0
class XLoggerWidget(QtGui.QTextEdit):
    __designer_icon__ = resources.find('img/log/info.png')

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

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

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

        # define custom properties
        self._clearOnClose = True
        self._handler = XLoggerWidgetHandler(self)
        self._currentMode = 'standard'
        self._blankCache = ''
        self._mutex = QtCore.QMutex()
        self._loggers = set()
        self._configurable = True
        self._destroyed = False

        # define the popup button for congfiguration
        self._configButton = XPopupButton(self)
        self._configButton.setIcon(
            QtGui.QIcon(resources.find('img/config.png')))
        self._configButton.setShadowed(True)

        popup = self._configButton.popupWidget()
        popup.setShowTitleBar(False)
        popup.setResizable(False)

        bbox = popup.buttonBox()
        bbox.clear()
        bbox.addButton(QtGui.QDialogButtonBox.Ok)

        # set to a monospace font
        font = QtGui.QFont('Courier New')
        font.setPointSize(9)
        self.setFont(font)
        metrics = QtGui.QFontMetrics(font)
        self.setTabStopWidth(4 * metrics.width(' '))
        self.setAcceptRichText(False)

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

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

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

        # create the logger tree widget
        controls = XLoggerControls(self)
        self._configButton.setCentralWidget(controls)
        self._configButton.setDefaultAnchor(popup.Anchor.TopRight)

        # create connections
        self._handler.dispatch().messageLogged.connect(self.log)
        self.destroyed.connect(self.markDestroyed)

    def activeLevels(self):
        """
        Returns the active levels that will be displayed for this widget.
        
        :return     [<int>, ..]
        """
        return self._handler.activeLevels()

    def addLogger(self, logger, level=logging.INFO):
        """
        Adds the inputed logger to the list of loggers that are being tracked
        with this widget.
        
        :param      logger | <logging.Logger> || <str>
                    level  | <logging.LEVEL>
        """
        if isinstance(logger, logging.Logger):
            logger = logger.name

        if logger in self._loggers:
            return

        # allow the handler to determine the level for this logger
        if logger == 'root':
            _log = logging.getLogger()
        else:
            _log = logging.getLogger(logger)

        _log.addHandler(self.handler())

        self._loggers.add(logger)
        self.handler().setLoggerLevel(logger, level)

    def cleanup(self):
        self._destroyed = True

        try:
            self._handler.dispatch().messageLogged.disconnect(self.log)
            self.destroyed.disconnect(self.markDestroyed)
        except StandardError:
            pass

        self.markDestroyed()

    def clear(self):
        super(XLoggerWidget, self).clear()

        self._currentMode = 'standard'

    def clearLoggers(self, logger):
        """
        Removes the inputed logger from the set for this widget.
        
        :param      logger | <str> || <logging.Logger>
        """
        for logger in self._loggers:
            if logger == 'root':
                logger = logging.getLogger()
            else:
                logger = logging.getLogger(logger)
            logger.removeHandler(self.handler())

        self._loggers = set()

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

    def color(self, key):
        """
        Returns the color value for the given key for this console.
        
        :param      key | <unicode>
        
        :return     <QtGui.QColor>
        """
        if type(key) == int:
            key = self.LoggingMap.get(key, ('NotSet', ''))[0]
        name = nativestring(key).capitalize()
        return self._colorSet.color(name)

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

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

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

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

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

    @deprecatedmethod('2.1', 'Use critical instead (same level as FATAL)')
    def fatal(self, msg):
        """
        Logs a fatal message to the system.
        
        :param      msg | <unicode>
        """
        self.log(logging.FATAL, msg)

    def formatText(self):
        """
        Returns the text that is used to format entries that are logged
        to this handler.
        
        :return     <str>
        """
        return self.handler().formatText()

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

    def hasLogger(self, logger):
        """
        Returns whether or not the inputed logger is tracked by this widget.
        
        :param      logger | <str> || <logging.Logger>
        """
        if isinstance(logger, logging.Logger):
            logger = logging.name

        return logger in self._loggers

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

    def isConfigurable(self):
        """
        Returns whether or not the user can configure the loggers associated
        with this widget.
        
        :return     <bool>
        """
        return self._configurable

    def isDestroyed(self):
        return self._destroyed

    @deprecatedmethod('2.1', 'Use the loggerLevel now.')
    def isLoggingEnabled(self, level):
        """
        Returns whether or not logging is enabled for the given level.
        
        :param      level | <int>
        """
        return False

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

        locker = QtCore.QMutexLocker(self._mutex)
        try:
            msg = projex.text.nativestring(msg)
            self.moveCursor(QtGui.QTextCursor.End)
            self.setCurrentMode(level)
            if self.textCursor().block().text():
                self.insertPlainText('\n')
            self.insertPlainText(msg.lstrip('\n\r'))
            self.scrollToEnd()
        except RuntimeError:
            return

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

        return True

    @deprecatedmethod(
        '2.1',
        'Loggers will not be explicitly set anymore.  Use loggerLevel instead.'
    )
    def logger(self):
        """
        Returns the logger instance that this widget will monitor.
        
        :return     <logging.Logger> || None
        """
        return None

    def loggerLevel(self, logger='root'):
        """
        Returns the logging level for the inputed logger.
        
        :param      logger | <str> || <logging.Logger>
        """
        if isinstance(logger, logging.Logger):
            logger = logger.name

        return self.handler().loggerLevel(logger)

    def loggerLevels(self):
        """
        Returns a dictionary of the set logger levels for this widget.
        
        :return     {<str> logger: <int> level, ..}
        """
        return self._handler.loggerLevels()

    def markDestroyed(self):
        self._destroyed = True

    def resizeEvent(self, event):
        super(XLoggerWidget, self).resizeEvent(event)

        size = event.size()
        self._configButton.move(size.width() - 22, 3)

    def removeLogger(self, logger):
        """
        Removes the inputed logger from the set for this widget.
        
        :param      logger | <str> || <logging.Logger>
        """
        if isinstance(logger, logging.Logger):
            logger = logger.name

        if logger in self._loggers:
            self._loggers.remove(logger)
            if logger == 'root':
                logger = logging.getLogger()
            else:
                logger = logging.getLogger(logger)

            logger.removeHandler(self.handler())

    def restoreSettings(self, settings):
        """
        Restores the settings for this logger from the inputed settings.
        
        :param      <QtCore.QSettings>
        """
        val = unwrapVariant(settings.value('format'))
        if val:
            self.setFormatText(val)

        levels = unwrapVariant(settings.value('levels'))
        if levels:
            self.setActiveLevels(map(int, levels.split(',')))

        logger_levels = unwrapVariant(settings.value('loggerLevels'))
        if logger_levels:
            for key in logger_levels.split(','):
                logger, lvl = key.split(':')
                lvl = int(lvl)
                self.setLoggerLevel(logger, lvl)

    def saveSettings(self, settings):
        """
        Saves the logging settings for this widget to the inputed settings.
        
        :param      <QtCore.QSettings>
        """
        lvls = []
        for logger, level in self.loggerLevels().items():
            lvls.append('{0}:{1}'.format(logger, level))

        settings.setValue('format', wrapVariant(self.formatText()))
        settings.setValue('levels', ','.join(map(str, self.activeLevels())))
        settings.setValue('loggerLevels', ','.join(lvls))

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

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

    def setActiveLevels(self, levels):
        """
        Defines the levels for this widgets visible/processed levels.
        
        :param      levels | [<int>, ..]
        """
        self._handler.setActiveLevels(levels)

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

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

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

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

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

    def setConfigurable(self, state):
        """
        Sets whether or not this logger widget is configurable.
        
        :param      state | <bool>
        """
        self._configurable = state
        self._configButton.setVisible(state)

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

        if mode == self._currentMode:
            return

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

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

    def setFormatText(self, text):
        """
        Sets the format text for this logger to the inputed text.
        
        :param      text | <str>
        """
        self.handler().setFormatText(text)

    @deprecatedmethod('2.1', 'Use the setLoggerLevel method now')
    def setLoggingEnabled(self, level, state):
        """
        Sets whether or not this widget should log the inputed level amount.
        
        :param      level | <int>
                    state | <bool>
        """
        pass

    def setLoggerLevel(self, logger, level):
        """
        Returns the logging level for the inputed logger.
        
        :param      logger | <logging.Logger> || <str>
                    level  | <logging.LEVEL>
        """
        if level == logging.NOTSET:
            self.removeLogger(logger)
            return

        if isinstance(logger, logging.Logger):
            logger = logger.name

        if not logger in self._loggers:
            self.addLogger(logger, level)
        else:
            self.handler().setLoggerLevel(logger, level)

    @deprecatedmethod('2.1', 'You should now use the addLogger method.')
    def setLogger(self, logger):
        """
        Sets the logger instance that this widget will monitor.
        
        :param      logger  | <logging.Logger>
        """
        pass

    @deprecatedmethod('2.1', 'You should now use the setFormatText method.')
    def setShowDetails(self, state):
        pass

    @deprecatedmethod('2.1', 'You should now use the setFormatText method.')
    def setShowLevel(self, state):
        pass

    @deprecatedmethod('2.1', 'You should now use the formatText method.')
    def showDetails(self):
        pass

    @deprecatedmethod('2.1', 'You should now use the formatText method.')
    def showLevel(self):
        pass

    def success(self, msg):
        """
        Logs the message for this widget.
        
        :param      msg | <str>
        """
        self.log(logging.SUCCESS, msg)

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

    x_configurable = QtCore.Property(bool, isConfigurable, setConfigurable)
    x_formatText = QtCore.Property(str, formatText, setFormatText)
def qCleanupResources():
    QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
Esempio n. 9
0
class XListWidget(QtGui.QListWidget):
    """ Advanced QTreeWidget class. """

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

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

        # define custom properties
        self._filteredDataTypes = ['text']
        self._autoResizeToContents = False
        self._hint = ''
        self._hintAlignment = QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft

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

        # create connections
        model = self.model()
        model.dataChanged.connect(self._resizeToContentsIfAuto)
        model.rowsInserted.connect(self._resizeToContentsIfAuto)
        model.rowsRemoved.connect(self._resizeToContentsIfAuto)
        model.layoutChanged.connect(self._resizeToContentsIfAuto)

    def __filterItems(self, terms, caseSensitive=False):
        """
        Filters the items in this tree based on the inputed keywords.
        
        :param      terms           | {<str> dataType: [<str> term, ..], ..}
                    caseSensitive   | <bool>
        
        :return     <bool> | found
        """
        found = False
        items = []

        # collect the items to process
        for i in range(self.count()):
            items.append(self.item(i))

        for item in items:
            if not isinstance(item, XListWidgetItem):
                continue

            # if there is no filter keywords, then all items will be visible
            if not any(terms.values()):
                found = True
                item.setHidden(False)

            else:
                # match all generic keywords
                generic = terms.get('*', [])
                generic_found = dict((key, False) for key in generic)

                # match all specific keywords
                dtype_found = dict((col, False) for col in terms if col != '*')

                # look for any matches for any data type
                mfound = False

                for dataType in self._filteredDataTypes:
                    # determine the check text based on case sensitivity
                    if caseSensitive:
                        check = nativestring(item.filterData(dataType))
                    else:
                        check = nativestring(item.filterData(dataType)).lower()

                    specific = terms.get(dataType, [])

                    # make sure all the keywords match
                    for key in generic + specific:
                        if not key:
                            continue

                        # look for exact keywords
                        elif key.startswith('"') and key.endswith('"'):
                            if key.strip('"') == check:
                                if key in generic:
                                    generic_found[key] = True

                                if key in specific:
                                    dtype_found[dataType] = True

                        # look for ending keywords
                        elif key.startswith('*') and not key.endswith('*'):
                            if check.endswith(key.strip('*')):
                                if key in generic:
                                    generic_found[key] = True
                                if key in specific:
                                    dtype_found[dataType] = True

                        # look for starting keywords
                        elif key.endswith('*') and not key.startswith('*'):
                            if check.startswith(key.strip('*')):
                                if key in generic:
                                    generic_found[key] = True
                                if key in specific:
                                    dtype_found[dataType] = True

                        # look for generic keywords
                        elif key.strip('*') in check:
                            if key in generic:
                                generic_found[key] = True
                            if key in specific:
                                dtype_found[dataType] = True

                    mfound = all(dtype_found.values()) and \
                             all(generic_found.values())
                    if mfound:
                        break

                item.setHidden(not mfound)

                if mfound:
                    found = True

        return found

    def _resizeToContentsIfAuto(self):
        """
        Resizes this widget to fit its contents if auto resizing is enabled.
        """
        if self.autoResizeToContents():
            self.resizeToContents()

    def autoResizeToContents(self):
        """
        Sets whether or not this widget should automatically resize to its
        contents.
        
        :return     <bool>
        """
        return self._autoResizeToContents

    def filteredDataTypes(self):
        """
        Returns the data types that are used for filtering for this tree.
        
        :return     [<str>, ..]
        """
        return self._filteredDataTypes

    @QtCore.Slot(str)
    def filterItems(self, terms, caseSensitive=False):
        """
        Filters the items in this tree based on the inputed text.
        
        :param      terms           | <str> || {<str> datatype: [<str> opt, ..]}
                    caseSensitive   | <bool>
        """
        # create a dictionary of options
        if type(terms) != dict:
            terms = {'*': nativestring(terms)}

        # validate the "all search"
        if '*' in terms and type(terms['*']) != list:
            sterms = nativestring(terms['*'])

            if not sterms.strip():
                terms.pop('*')
            else:
                dtype_matches = DATATYPE_FILTER_EXPR.findall(sterms)

                # generate the filter for each data type
                for match, dtype, values in dtype_matches:
                    sterms = sterms.replace(match, '')
                    terms.setdefault(dtype, [])
                    terms[dtype] += values.split(',')

                keywords = sterms.replace(',', '').split()
                while '' in keywords:
                    keywords.remove('')

                terms['*'] = keywords

        # filter out any data types that are not being searched
        filtered_dtypes = self.filteredDataTypes()
        filter_terms = {}
        for dtype, keywords in terms.items():
            if dtype != '*' and not dtype in filtered_dtypes:
                continue

            if not caseSensitive:
                keywords = [
                    nativestring(keyword).lower() for keyword in keywords
                ]
            else:
                keywords = map(nativestring, keywords)

            filter_terms[dtype] = keywords

        self.__filterItems(filter_terms, caseSensitive)

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

    def hintAlignment(self):
        """
        Returns the alignment used for the hint rendering.
        
        :return     <QtCore.Qt.Alignment>
        """
        return self._hintAlignment

    def hintColor(self):
        """
        Returns the color used for the hint rendering.
        
        :return     <QtGui.QColor>
        """
        return self._hintColor

    def paintEvent(self, event):
        """
        Overloads the paint event to support rendering of hints if there are
        no items in the tree.
        
        :param      event | <QPaintEvent>
        """
        super(XListWidget, self).paintEvent(event)

        if not self.visibleCount() and self.hint():
            text = self.hint()
            rect = self.rect()

            # modify the padding on the rect
            w = min(250, rect.width() - 30)
            x = (rect.width() - w) / 2

            rect.setX(x)
            rect.setY(rect.y() + 15)
            rect.setWidth(w)
            rect.setHeight(rect.height() - 30)

            align = int(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop)

            # setup the coloring options
            clr = self.hintColor()

            # paint the hint
            with XPainter(self.viewport()) as painter:
                painter.setPen(clr)
                painter.drawText(rect, align | QtCore.Qt.TextWordWrap, text)

    def resizeEvent(self, event):
        super(XListWidget, self).resizeEvent(event)

        # resize the group items
        width = event.size().width()
        for i in range(self.count()):
            item = self.item(i)
            if isinstance(item, XListGroupItem):
                item.setSizeHint(QtCore.QSize(width, 24))

        # auto-resize based on the contents
        if self.autoResizeToContents():
            self.resizeToContents()

    @QtCore.Slot()
    def resizeToContents(self):
        """
        Resizes the list widget to fit its contents vertically.
        """
        if self.count():
            item = self.item(self.count() - 1)
            rect = self.visualItemRect(item)
            height = rect.bottom() + 8
            height = max(28, height)
            self.setFixedHeight(height)
        else:
            self.setFixedHeight(self.minimumHeight())

    def setAutoResizeToContents(self, state):
        """
        Sets whether or not this widget should automatically resize its
        height based on its contents.
        
        :param      state | <bool>
        """
        self._autoResizeToContents = state

        if state:
            self.resizeToContents()
            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        else:
            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)

    def setHint(self, hint):
        """
        Sets the hint for this list widget.
        
        :param     hint | <str>
        """
        self._hint = hint

    def setHintAlignment(self, align):
        """
        Sets the alignment used for the hint rendering.
        
        :param      align | <QtCore.Qt.Alignment>
        """
        self._hintAlignment = align

    def setHintColor(self, color):
        """
        Sets the color used for the hint rendering.
        
        :param      color | <QtGui.QColor>
        """
        self._hintColor = color

    def setFilteredDataTypes(self, dataTypes):
        """
        Sets the data types that will be used for filtering of this 
        tree's items.
        
        :param      data types | [<str>, ..]
        """
        self._filteredDataTypes = dataTypes

    def visibleCount(self):
        """
        Returns the number of visible items in this list.
        
        :return     <int>
        """
        return sum(
            int(not self.item(i).isHidden()) for i in range(self.count()))

    x_autoResizeToContents = QtCore.Property(bool, autoResizeToContents,
                                             setAutoResizeToContents)

    x_hint = QtCore.Property(str, hint, setHint)
Esempio n. 10
0
class XTimerLabel(QtGui.QLabel):
    __designer_group__ = 'ProjexUI'
    __designer_icon__ = resources.find('img/clock.png')

    ticked = QtCore.Signal()
    timeout = QtCore.Signal()

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

        # create custom properties
        self._countdown = False
        self._starttime = None
        self._limit = 0
        self._elapsed = datetime.timedelta()
        self._delta = datetime.timedelta()
        self._format = '%(hours)02i:%(minutes)02i:%(seconds)02i'
        self._timer = QtCore.QTimer(self)
        self._timer.setInterval(500)

        # set the font for this instance to the inputed font
        font = self.font()
        font.setPointSize(12)
        self.setFont(font)

        # set default properties
        self.reset()
        self._timer.timeout.connect(self.increment)

    def countdown(self):
        """
        Returns whether or not this widget should display seconds as a
        countdown.
        
        :return     <bool>
        """
        return self._countdown

    def elapsed(self):
        """
        Returns the time delta from the beginning of the timer to now.

        :return     <datetime.timedelta>
        """
        return self._elapsed + self._delta

    def format(self):
        """
        Returns the format for the label that will be used for the timer
        display.

        :return     <str>
        """
        return self._format

    def hours(self):
        """
        Returns the elapsed number of hours for this timer.

        :return     <int>
        """
        seconds = self.elapsed().seconds
        if self.limit() and self.countdown():
            seconds = (self.limit() - self.elapsed().seconds)
        return seconds / 3600

    def increment(self):
        """
        Increments the delta information and refreshes the interface.
        """
        if self._starttime is not None:
            self._delta = datetime.datetime.now() - self._starttime
        else:
            self._delta = datetime.timedelta()

        self.refresh()
        self.ticked.emit()

    def interval(self):
        """
        Returns the number of milliseconds that this timer will run for.

        :return     <int>
        """
        return self._timer.interval()

    def isRunning(self):
        return self._timer.isActive()

    def limit(self):
        """
        Returns the limit for the amount of time to pass in seconds for this
        timer.  A limit of 0 would never timeout.  Otherwise, once the timer
        passes a certain number of seconds, then the timer will stop and the
        timeout signal will be emitted.
        
        :return     <int>
        """
        return self._limit

    def minutes(self):
        """
        Returns the elapsed number of minutes for this timer.

        :return     <int>
        """
        seconds = self.elapsed().seconds
        if self.limit() and self.countdown():
            seconds = (self.limit() - self.elapsed().seconds)
        return (seconds % 3600) / 60

    def refresh(self):
        """
        Updates the label display with the current timer information.
        """
        delta = self.elapsed()
        seconds = delta.seconds
        limit = self.limit()

        options = {}
        options['hours'] = self.hours()
        options['minutes'] = self.minutes()
        options['seconds'] = self.seconds()

        try:
            text = self.format() % options
        except ValueError:
            text = '#ERROR'

        self.setText(text)

        if limit and limit <= seconds:
            self.stop()
            self.timeout.emit()

    @QtCore.Slot()
    def reset(self):
        """
        Stops the timer and resets its values to 0.
        """
        self._elapsed = datetime.timedelta()
        self._delta = datetime.timedelta()
        self._starttime = datetime.datetime.now()

        self.refresh()

    @QtCore.Slot()
    def restart(self):
        """
        Resets the timer and then starts it again.
        """
        self.reset()
        self.start()

    def seconds(self):
        """
        Returns the elapsed number of seconds that this timer has been running.

        :return     <int>
        """
        seconds = self.elapsed().seconds
        if self.limit() and self.countdown():
            seconds = (self.limit() - self.elapsed().seconds)
        return (seconds % 3600) % 60

    def setCountdown(self, state):
        """
        Sets whether or not this widget should display the seconds as
        a countdown state.
        
        :param      state | <bool>
        """
        self._countdown = state
        self.refresh()

    def setLimit(self, limit):
        """
        Sets the number of seconds that this timer will process for.
        
        :param      limit | <int>
        """
        self._limit = limit
        self.refresh()

    def setFormat(self, format):
        """
        Sets the format for the label that will be used for the timer display.

        :param      format | <str>
        """
        self._format = str(format)
        self.refresh()

    def setInterval(self, interval):
        """
        Sets the number of milliseconds that this timer will run for.

        :param      interval | <int>
        """
        self._timer.setInterval(interval)

    @QtCore.Slot()
    def start(self):
        """
        Starts running the timer.  If the timer is currently running, then
        this method will do nothing.

        :sa     stop, reset
        """
        if self._timer.isActive():
            return

        self._starttime = datetime.datetime.now()
        self._timer.start()

    @QtCore.Slot()
    def stop(self):
        """
        Stops the timer.  If the timer is not currently running, then
        this method will do nothing.
        """
        if not self._timer.isActive():
            return

        self._elapsed += self._delta
        self._timer.stop()

    x_countdown = QtCore.Property(bool, countdown, setCountdown)
    x_limit = QtCore.Property(int, limit, setLimit)
    x_format = QtCore.Property(str, format, setFormat)
    x_interval = QtCore.Property(int, interval, setInterval)
Esempio n. 11
0
class XOrbWorker(QtCore.QObject):
    loadingStarted = QtCore.Signal()
    loadingFinished = QtCore.Signal()
    connectionLost = QtCore.Signal()
    
    WorkerCount = 0
    
    def __init__(self, threaded, *args):
        super(XOrbWorker, self).__init__(*args)
        
        # define custom properties
        self._database = None
        self._loading = False
        self._databaseThreadId = 0
        
        XOrbWorker.WorkerCount += 1
        
        if threaded:
            self.moveToThread(XOrbWorkerThreadManager.thread())
    
    def __del__(self):
        XOrbWorker.WorkerCount -= 1
        if XOrbWorker.WorkerCount == 0:
            XOrbWorkerThreadManager.destroy()
    
    def database(self):
        """
        Returns the database associated with this worker.
        
        :return     <orb.Database>
        """
        return self._database
    
    def databaseThreadId(self):
        """
        Returns the thread id associated with this worker.
        
        :return     <int>
        """
        return self._threadId
    
    def deleteLater(self):
        """
        Prepares to delete this worker.  If a database and a particular
        thread id are associated with this worker, then it will be cleared
        when it is deleted.
        """
        self.interrupt()
        super(XOrbWorker, self).deleteLater()
    
    def finishLoading(self):
        """
        Marks the worker as having completed loading.
        """
        self._loading = False
        self.loadingFinished.emit()
    
    def isLoading(self):
        """
        Returns whether or not this worker is currently loading.
        
        :return     <bool>
        """
        return self._loading
    
    def interrupt(self):
        """
        Interrupts the current database from processing.
        """
        if self._database and self._databaseThreadId:
            # support Orb 2 interruption capabilities
            try:
                self._database.interrupt(self._databaseThreadId)
            except AttributeError:
                pass
        
        self._database = None
        self._databaseThreadId = 0
    
    def setDatabase(self, database):
        """
        Sets the database associated with this thread to the inputed database.
        
        :param      database | <orb.Database>
        """
        self._database = database
        if self.thread():
            tid = threading.current_thread().ident
            self._databaseThreadId = tid
    
    def startLoading(self):
        """
        Marks the workar as having started loading.
        """
        self._loading = True
        self.loadingStarted.emit()
    
    def waitUntilFinished(self):
        """
        Processes the main thread until the loading process has finished.  This
        is a way to force the main thread to be synchronous in its execution.
        """
        QtCore.QCoreApplication.processEvents()
        while self.isLoading():
            QtCore.QCoreApplication.processEvents()
    
    @staticmethod
    def interruptionProtected(func):
        def wraps(*args, **kwds):
            try:
                func(*args, **kwds)
            except Interruption:
                pass
        return wraps
Esempio n. 12
0
class XLocaleBox(XComboBox):
    __designer_icon__ = resources.find('img/flags/us.png')

    currentLocaleChanged = QtCore.Signal('QVariant')

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

        # define custom properties
        self._allLocales = []
        self._dirty = True
        self._baseLocale = 'en_US'
        self._showLanguage = True
        self._showTerritory = True
        self._showScriptName = True
        self._translated = True
        self._availableLocales = []  # all locales

    def allLocales(self):
        """
        Returns all the locales that are defined within the babel
        architecture.
        
        :return     [<str>, ..]
        """
        if self._allLocales:
            return self._allLocales

        expr = re.compile('[a-z]+_[A-Z]+')
        locales = babel.core.localedata.locale_identifiers()
        babel_locales = {}
        for locale in locales:
            if expr.match(locale):
                babel_locale = babel.Locale.parse(locale)
                if babel_locale.territory and babel_locale.language:
                    babel_locales[babel_locale.territory] = babel_locale

        babel_locales = babel_locales.values()
        babel_locales.sort(key=str)
        self._allLocales = babel_locales
        return self._allLocales

    def availableLocales(self):
        """
        Returns a list of the available locales to use for displaying
        the locale information for.  This provides a way to filter the
        interface for only locales that you care about.
        
        :return     [<str>, ..]
        """
        return [str(locale) for locale in self._availableLocales]

    def baseLocale(self):
        """
        Returns the name to be used as the base name for the translation
        within the widget.  This will alter the language that is displaying
        the contents.
        
        :return     <str>
        """
        return self._baseLocale

    def currentLocale(self):
        """
        Returns the current locale for this box.
        
        :return     <babel.Locale> || None
        """
        try:
            return babel.Locale.parse(self.itemData(self.currentIndex()))
        except ImportError, err:
            log.error('babel is not installed.')
            return None
Esempio n. 13
0
        :return     <bool>
        """
        return self._showLanguage

    def showScriptName(self):
        """
        Returns the display mode for this widget to the inputed mode.
        
        :return     <bool>
        """
        return self._showScriptName

    def showTerritory(self):
        """
        Returns the display mode for this widget to the inputed mode.
        
        :return     <bool>
        """
        return self._showTerritory

    x_availableLocales = QtCore.Property('QStringList', availableLocales,
                                         setAvailableLocales)
    x_showLanguage = QtCore.Property(bool, showLanguage, setShowLanguage)
    x_showScriptName = QtCore.Property(bool, showScriptName, setShowScriptName)
    x_showTerritory = QtCore.Property(bool, showTerritory, setShowTerritory)
    x_baseLocale = QtCore.Property(str, baseLocale, setBaseLocale)
    x_translated = QtCore.Property(bool, isTranslated, setTranslated)


__designer_plugins__ = [XLocaleBox]
Esempio n. 14
0
class XTimer(QtCore.QObject):
    """
    The default <QtCore.QTimer> is not threadable -- you cannot call
    start or stop from a different thread.  It must always reside on the
    thread that created it.  The XTimer is a wrapper over the
    default <QtCore.QTimer> class instance that allows the ability to
    call the start or stop slots from any thread.
    
    :usage  |>>> from xqt import QtCore
            |>>> thread = QtCore.QThread()
            |>>> thread.start()
            |>>> 
            |>>> # non-thread safe calls
            |>>> qtimer = QtCore.QTimer()
            |>>> qtimer.moveToThread(thread)
            |>>> qtimer.start()
            |QObject::startTimer: timers cannot be started from another thread
            |>>> qtimer.stop()
            |QObject::killTimer: timers cannot be stopped from another thread
            |>>> 
            |>>> # thread safe calls
            |>>> from projexui.xtimer import XTimer
            |>>> ttimer = XTimer()
            |>>> ttimer.moveToThread(thread)
            |>>> ttimer.start()
            |>>> ttimer.stop()
            |>>> 
    """
    _singleShotChanged = QtCore.Signal(bool)
    _intervalChanged = QtCore.Signal(int)
    _startRequested = QtCore.Signal(int)
    _stopRequested = QtCore.Signal()

    timeout = QtCore.Signal()

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

        # define custom properties
        self.__timer = None
        self.__active = False
        self.__singleShot = False
        self.__interval = 0
        self.__lock = QtCore.QReadWriteLock()

        # create connections
        self._singleShotChanged.connect(self._setTimerSingleShot,
                                        QtCore.Qt.QueuedConnection)
        self._intervalChanged.connect(self._setTimerInterval,
                                      QtCore.Qt.QueuedConnection)
        self._startRequested.connect(self._startTimer,
                                     QtCore.Qt.QueuedConnection)
        self._stopRequested.connect(self._stopTimer,
                                    QtCore.Qt.QueuedConnection)

    def _setTimerInterval(self, interval):
        """
        Sets the internal timer's interval.
        
        :param      interval | <int>
        """
        try:
            self.__timer.setInterval(interval)
        except AttributeError:
            pass

    def _setTimerSingleShot(self, state):
        """
        Sets the internal timer's single shot state.
        
        :param      state | <bool>
        """
        try:
            self.__timer.setSingleShot(state)
        except AttributeError:
            pass

    def _startTimer(self, interval):
        """
        Starts the internal timer.
        
        :param      interval | <int>
        """
        if not self.__timer:
            self.__timer = QtCore.QTimer(self)
            self.__timer.setSingleShot(self.__singleShot)
            self.__timer.setInterval(interval)
            self.__timer.timeout.connect(self.timeout)

            # ensure to stop this timer when the app quits
            QtCore.QCoreApplication.instance().aboutToQuit.connect(
                self.__timer.stop, QtCore.Qt.QueuedConnection)

        self.__timer.start(interval)

    def _stopTimer(self):
        """
        Stops the internal timer, if one exists.
        
        :param      stop | <int>
        """
        try:
            self.__timer.stop()
        except AttributeError:
            pass

    def interval(self):
        """
        Returns the interval in milliseconds for this timer.
        
        :return     <int> | msecs
        """
        with QtCore.QReadLocker(self.__lock):
            return self.__interval

    def isActive(self):
        """
        Returns whether or not this timer is currently active.
        
        :return     <bool>
        """
        try:
            return self.__timer.isActive()
        except AttributeError:
            return False

    def isSingleShot(self):
        """
        Returns whether or not this timer should operate only a single time.
        
        :return     <bool>
        """
        with QtCore.QReadLocker(self.__lock):
            return self.__singleShot

    def setInterval(self, msecs):
        """
        Sets the interval in milliseconds for this timer.
        
        :param      msecs | <int>
        """
        with QtCore.QWriteLocker(self.__lock):
            self.__interval = msecs
            self._intervalChanged.emit(msecs)

    def setSingleShot(self, state):
        """
        Sets whether or not this timer is setup for a single entry or not.
        
        :param      state | <bool>
        """
        with QtCore.QWriteLocker(self.__lock):
            self.__singleShot = state
            self._singleShotChanged.emit(state)

    def start(self, interval=None):
        """
        Emits the start requested signal for this timer, effectively starting
        its internal timer.
        
        :param      interval | <int>
        """
        # update the interval value
        with QtCore.QReadLocker(self.__lock):
            if interval is None:
                interval = self.__interval
            else:
                self.__interval = interval

        # request the timer to start
        self._startRequested.emit(interval)

    def stop(self):
        """
        Emits the stop requested signal for this timer, effectivly stopping its
        internal timer.
        """
        self._stopRequested.emit()

    singleShot = QtCore.QTimer.singleShot
Esempio n. 15
0
    def __init__(self, parent):
        super(XLoggerWidget, self).__init__(parent)

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

        # define custom properties
        self._clearOnClose = True
        self._handler = XLoggerWidgetHandler(self)
        self._currentMode = 'standard'
        self._blankCache = ''
        self._mutex = QtCore.QMutex()
        self._loggers = set()
        self._configurable = True
        self._destroyed = False

        # define the popup button for congfiguration
        self._configButton = XPopupButton(self)
        self._configButton.setIcon(
            QtGui.QIcon(resources.find('img/config.png')))
        self._configButton.setShadowed(True)

        popup = self._configButton.popupWidget()
        popup.setShowTitleBar(False)
        popup.setResizable(False)

        bbox = popup.buttonBox()
        bbox.clear()
        bbox.addButton(QtGui.QDialogButtonBox.Ok)

        # set to a monospace font
        font = QtGui.QFont('Courier New')
        font.setPointSize(9)
        self.setFont(font)
        metrics = QtGui.QFontMetrics(font)
        self.setTabStopWidth(4 * metrics.width(' '))
        self.setAcceptRichText(False)

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

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

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

        # create the logger tree widget
        controls = XLoggerControls(self)
        self._configButton.setCentralWidget(controls)
        self._configButton.setDefaultAnchor(popup.Anchor.TopRight)

        # create connections
        self._handler.dispatch().messageLogged.connect(self.log)
        self.destroyed.connect(self.markDestroyed)
def qInitResources():
    QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
Esempio n. 17
0
class XViewWidget(QtGui.QScrollArea):
    __designer_icon__ = projexui.resources.find('img/ui/scrollarea.png')
    __designer_propspecs__ = {'x_hint': ('string', 'richtext')}

    lockToggled = QtCore.Signal(bool)
    resetFinished = QtCore.Signal()

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

        # define custom properties
        self._customData = {}
        self._viewTypes = []
        self._locked = False
        self._tabMenu = None
        self._pluginMenu = None
        self._panelMenu = None
        self._defaultProfile = None
        self._scope = None  # defines code execution scope for this widget
        self._hint = ''

        # intiailize the scroll area
        self.setBackgroundRole(QtGui.QPalette.Window)
        self.setFrameShape(QtGui.QScrollArea.NoFrame)
        self.setWidgetResizable(True)
        self.setWidget(XViewPanel(self, self.isLocked()))

        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)

        # update the current view
        app = QtGui.QApplication.instance()
        app.focusChanged.connect(self.updateCurrentView)
        self.customContextMenuRequested.connect(self.showMenu)

    def canClose(self):
        """
        Checks to see if the view widget can close by checking all of its \
        sub-views to make sure they're ok to close.
        
        :return     <bool>
        """
        for view in self.findChildren(XView):
            if not view.canClose():
                return False
        return True

    def closeEvent(self, event):
        views = self.findChildren(XView)
        for view in views:
            if not view.canClose():
                event.ignore()
                return

        for view in views:
            view.close()

        super(XViewWidget, self).closeEvent(event)

    def codeScope(self):
        """
        Returns the code execution scope for this widget.
        
        :return     <dict>
        """
        if self._scope is None:
            import __main__
            return __main__.__dict__
        else:
            return self._scope

    def createMenu(self, parent):
        """
        Creates a new menu for the inputed parent item.
        
        :param      parent | <QMenu>
        """
        menu = QtGui.QMenu(parent)
        menu.setTitle('&View')

        act = menu.addAction('&Lock/Unlock Layout')
        act.setIcon(QtGui.QIcon(projexui.resources.find('img/view/lock.png')))
        act.triggered.connect(self.toggleLocked)

        menu.addSeparator()
        act = menu.addAction('&Export Layout as...')
        act.setIcon(QtGui.QIcon(
            projexui.resources.find('img/view/export.png')))
        act.triggered.connect(self.exportProfile)

        act = menu.addAction('&Import Layout from...')
        act.setIcon(QtGui.QIcon(
            projexui.resources.find('img/view/import.png')))
        act.triggered.connect(self.importProfile)

        menu.addSeparator()

        act = menu.addAction('&Clear Layout')
        act.setIcon(QtGui.QIcon(
            projexui.resources.find('img/view/remove.png')))
        act.triggered.connect(self.resetForced)

        return menu

    def currentPanel(self):
        """
        Returns the currently active panel based on whether or not it has \
        focus.
        
        :return     <XViewPanel>  || None
        """
        focus_widget = QtGui.QApplication.instance().focusWidget()
        focus_panel = projexui.ancestor(focus_widget, XViewPanel)

        panels = self.panels()
        if focus_panel in panels:
            return focus_panel
        try:
            return panels[0]
        except IndexError:
            return None

    def currentView(self):
        """
        Returns the current view for this widget.
        
        :return     <projexui.widgets.xviewwidget.XView> || NOne
        """
        panel = self.currentPanel()
        if panel:
            return panel.currentWidget()
        return None

    def customData(self, key, default=None):
        """
        Returns the custom data for the given key.
        
        :param      key     | <str>
                    default | <variant>
        
        :return     <variant>
        """
        return self._customData.get(nativestring(key), default)

    def defaultProfile(self):
        """
        Returns the default profile for this view widget.
        
        :return     <XViewProfile>
        """
        return self._defaultProfile

    def exportProfile(self, filename=''):
        """
        Exports the current profile to a file.
        
        :param      filename | <str>
        """
        if not (filename and isinstance(filename, basestring)):
            filename = QtGui.QFileDialog.getSaveFileName(
                self, 'Export Layout as...', QtCore.QDir.currentPath(),
                'XView (*.xview)')

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

        filename = nativestring(filename)
        if not filename:
            return

        if not filename.endswith('.xview'):
            filename += '.xview'

        profile = self.saveProfile()
        profile.save(filename)

    def findViewType(self, viewTypeName):
        """
        Looks up the view type based on the inputed view type name.
        
        :param      viewTypeName | <str>
        """
        for viewType in self._viewTypes:
            if (viewType.viewTypeName() == viewTypeName):
                return viewType
        return None

    def importProfile(self, filename=''):
        """
        Exports the current profile to a file.
        
        :param      filename | <str>
        """
        if not (filename and isinstance(filename, basestring)):
            filename = QtGui.QFileDialog.getOpenFileName(
                self, 'Import Layout from...', QtCore.QDir.currentPath(),
                'XView (*.xview)')

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

        filename = nativestring(filename)
        if not filename:
            return

        if not filename.endswith('.xview'):
            filename += '.xview'

        profile = XViewProfile.load(filename)
        if not profile:
            return

        profile.restore(self)

    def hint(self):
        return self._hint

    def isEmpty(self):
        """
        Returns whether or not there are any XView widgets loaded for this
        widget.
        
        :return     <bool>
        """
        return len(self.findChildren(XView)) == 0

    def isLocked(self):
        """
        Returns whether or not this widget is in locked mode.
        
        :return     <bool>
        """
        return self._locked

    def panels(self):
        """
        Returns a lis of the panels that are assigned to this view widget.
        
        :return     [<XViewPanel>, ..]
        """
        return [
            panel for panel in self.findChildren(XViewPanel)
            if panel.viewWidget() == self
        ]

    def registerViewType(self, cls, window=None):
        """
        Registers the inputed widget class as a potential view class.  If the \
        optional window argument is supplied, then the registerToWindow method \
        will be called for the class.
        
        :param          cls     | <subclass of XView>
                        window  | <QMainWindow> || <QDialog> || None
        """
        if (not cls in self._viewTypes):
            self._viewTypes.append(cls)

            if (window):
                cls.registerToWindow(window)

    @QtCore.Slot(PyObject)
    def restoreProfile(self, profile):
        """
        Restores the profile settings based on the inputed profile.
        
        :param      profile | <XViewProfile>
        """
        return profile.restore(self)

    def restoreSettings(self, settings):
        """
        Restores the current structure of the view widget from the inputed \
        settings instance.
        
        :param      settings | <QSettings>
        """
        key = self.objectName()
        value = unwrapVariant(settings.value('%s/profile' % key))

        if not value:
            self.reset(force=True)
            return False

        profile = value

        # restore the view type settings
        for viewType in self.viewTypes():
            viewType.restoreGlobalSettings(settings)

        # restore the profile
        self.restoreProfile(XViewProfile.fromString(profile))

        if not self.views():
            self.reset(force=True)

        return True

    def reset(self, force=False):
        """
        Clears out all the views and panels and resets the widget to a blank \
        parent.
        
        :return     <bool>
        """
        answer = QtGui.QMessageBox.Yes
        opts = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No

        if not force:
            answer = QtGui.QMessageBox.question(
                self, 'Reset Layout', 'Are you sure you want to reset?', opts)

        if answer == QtGui.QMessageBox.No:
            return

        widget = self.widget()

        # we should always have a widget, but double check
        if not widget:
            return False

        # make sure we can close the current view
        if not widget.close():
            return False

        # reset the system
        self.takeWidget()

        # restore a default profile
        prof = self.defaultProfile()
        if prof:
            return prof.restore(self)

        # otherwise create a new panel
        else:
            self.setLocked(False)
            self.setWidget(XViewPanel(self, False))

        self.resetFinished.emit()
        return True

    def resetForced(self):
        return self.reset(force=True)

    def saveProfile(self):
        """
        Saves the profile for the current state and returns it.
        
        :return     <XViewProfile>
        """
        return XViewProfile.record(self)

    def saveSettings(self, settings):
        """
        Records the current structure of the view widget to the inputed \
        settings instance.
        
        :param      settings | <QSettings>
        """
        # record the profile
        profile = self.saveProfile()
        key = self.objectName()

        settings.setValue('%s/profile' % key, wrapVariant(profile.toString()))

        # record the view type settings
        for viewType in self.viewTypes():
            viewType.saveGlobalSettings(settings)

    def setCodeScope(self, scope):
        """
        Sets the code execution scope for this widget.  If the scope is
        set to None, then the global execution scope will be used.
        
        :param      scope | <dict> || None
        """
        self._scope = scope

    def setCurrent(self):
        """
        Sets this view widget as the current widget in case there are multiple
        ones.
        """
        for view in self.findChildren(XView):
            view.setCurrent()

    def setCustomData(self, key, value):
        """
        Sets the custom data for this instance to the inputed value.
        
        :param      key     | <str>
                    value   | <variant>
        """
        self._customData[nativestring(key)] = value

    def setDefaultProfile(self, profile):
        """
        Sets the default profile for this view to the inputed profile.
        
        :param      profile | <XViewProfile>
        """
        self._defaultProfile = profile

    def setHint(self, hint):
        self._hint = hint

    def setLocked(self, state):
        """
        Sets the locked state for this view widget.  When locked, a user no \
        longer has control over editing views and layouts.  A view panel with \
        a single entry will hide its tab bar, and if it has multiple views, it \
        will simply hide the editing buttons.
        
        :param      state | <bool>
        """
        changed = state != self._locked
        self._locked = state

        for panel in self.panels():
            panel.setLocked(state)

        if changed and not self.signalsBlocked():
            self.lockToggled.emit(state)

    def setViewTypes(self, viewTypes, window=None):
        """
        Sets the view types that can be used for this widget.  If the optional \
        window member is supplied, then the registerToWindow method will be \
        called for each view.
        
        :param      viewTypes | [<sublcass of XView>, ..]
                    window    | <QMainWindow> || <QDialog> || None
        """
        if window:
            for viewType in self._viewTypes:
                viewType.unregisterFromWindow(window)

        self._viewTypes = viewTypes[:]
        self._panelMenu = None
        self._pluginMenu = None

        if window:
            for viewType in viewTypes:
                viewType.registerToWindow(window)

    def showMenu(self, point=None):
        """
        Displays the menu for this view widget.
        
        :param      point | <QPoint>
        """
        menu = self.createMenu(self)
        menu.exec_(QtGui.QCursor.pos())
        menu.deleteLater()

    def showPanelMenu(self, panel, point=None):
        """
        Creates the panel menu for this view widget.  If no point is supplied,\
        then the current cursor position will be used.
        
        :param      panel   | <XViewPanel>
                    point   | <QPoint> || None
        """
        if not self._panelMenu:
            self._panelMenu = XViewPanelMenu(self)

        if point is None:
            point = QtGui.QCursor.pos()

        self._panelMenu.setCurrentPanel(panel)
        self._panelMenu.exec_(point)

    def showPluginMenu(self, panel, point=None):
        """
        Creates the interface menu for this view widget.  If no point is \
        supplied, then the current cursor position will be used.
        
        :param      panel | <XViewPanel>
                    point | <QPoint> || None
        """
        if not self._pluginMenu:
            self._pluginMenu = XViewPluginMenu(self)

        if point is None:
            point = QtGui.QCursor.pos()

        self._pluginMenu.setCurrentPanel(panel)
        self._pluginMenu.exec_(point)

    def showTabMenu(self, panel, point=None):
        """
        Creates the panel menu for this view widget.  If no point is supplied,\
        then the current cursor position will be used.
        
        :param      panel   | <XViewPanel>
                    point   | <QPoint> || None
        """
        if not self._tabMenu:
            self._tabMenu = XViewTabMenu(self)

        if point is None:
            point = QtGui.QCursor.pos()

        self._tabMenu.setCurrentPanel(panel)
        self._tabMenu.exec_(point)

    def toggleLocked(self):
        """
        Toggles whether or not this view is locked 
        """
        self.setLocked(not self.isLocked())

    def updateCurrentView(self, oldWidget, newWidget):
        """
        Updates the current view widget.
        
        :param      oldWidget | <QtGui.QWidget>
                    newWidget | <QtGui.QWidget>
        """
        view = projexui.ancestor(newWidget, XView)
        if view is not None:
            view.setCurrent()

    def unregisterViewType(self, cls, window=None):
        """
        Unregisters the view at the given name.  If the window option is \
        supplied then the unregisterFromWindow method will be called for the \
        inputed class.
        
        :param          cls    | <subclass of XView>    
                        window | <QMainWindow> || <QDialog> || None
        
        :return     <bool> changed
        """
        if (cls in self._viewTypes):
            self._viewTypes.remove(cls)

            if (window):
                cls.unregisterFromWindow(window)

            return True
        return False

    def views(self):
        """
        Returns a list of the current views associated with this view widget.
        
        :return     [<XView>, ..]
        """
        return self.findChildren(XView)

    def viewAt(self, point):
        """
        Looks up the view at the inputed point.

        :param      point | <QtCore.QPoint>

        :return     <projexui.widgets.xviewwidget.XView> || None
        """
        widget = self.childAt(point)
        if widget:
            return projexui.ancestor(widget, XView)
        else:
            return None

    def viewType(self, name):
        """
        Looks up the view class based on the inputd name.
        
        :param      name | <str>
        
        :return     <subclass of XView> || None
        """
        for view in self._viewTypes:
            if view.viewName() == name:
                return view
        return None

    def viewTypes(self):
        """
        Returns a list of all the view types registered for this widget.
        
        :return     <str>
        """
        return sorted(self._viewTypes, key=lambda x: x.viewName())

    x_hint = QtCore.Property(unicode, hint, setHint)
Esempio n. 18
0
class XOverlayWidget(QtGui.QWidget):
    finished = QtCore.Signal(int)

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

        # define custom properties
        self._centralWidget = None
        self._result = None
        self._closable = True
        self._closeAlignment = QtCore.Qt.AlignTop | QtCore.Qt.AlignRight
        self._closeButton = XToolButton(self)
        self._closeButton.setShadowed(True)
        self._closeButton.setIcon(
            QtGui.QIcon(resources.find('img/overlay/close.png')))
        self._closeButton.setIconSize(QtCore.QSize(24, 24))
        self._closeButton.setToolTip('Close')

        # create the coloring for the overlay
        palette = self.palette()
        clr = QtGui.QColor('#222222')
        clr.setAlpha(210)
        palette.setColor(palette.Window, clr)
        self.setPalette(palette)
        self.setAutoFillBackground(True)

        # listen to the parents event filter
        parent.installEventFilter(self)

        # initialize the widget
        self.hide()
        self.move(0, 0)
        self.resize(parent.size())
        self._closeButton.clicked.connect(self.reject)

    def accept(self):
        """
        Accepts this overlay and exits the modal window.
        """
        self.close()
        self.setResult(1)
        self.finished.emit(1)

    def adjustSize(self):
        """
        Adjusts the size of this widget as the parent resizes.
        """
        # adjust the close button
        align = self.closeAlignment()
        if align & QtCore.Qt.AlignTop:
            y = 6
        else:
            y = self.height() - 38

        if align & QtCore.Qt.AlignLeft:
            x = 6
        else:
            x = self.width() - 38

        self._closeButton.move(x, y)

        # adjust the central widget
        widget = self.centralWidget()
        if widget is not None:
            center = self.rect().center()
            widget.move(center.x() - widget.width() / 2,
                        center.y() - widget.height() / 2)

    def closeAlignment(self):
        """
        Returns the alignment for the close button for this overlay widget.

        :return <QtCore.Qt.Alignment>
        """
        return self._closeAlignment

    def centralWidget(self):
        """
        Returns the central widget for this overlay.  If there is one, then it will
        be automatically moved with this object.

        :return     <QtGui.QWidget>
        """
        return self._centralWidget

    def isClosable(self):
        """
        Returns whether or not the user should be able to close this overlay widget.

        :return     <bool>
        """
        return self._closable

    def keyPressEvent(self, event):
        """
        Exits the modal window on an escape press.

        :param      event | <QtCore.QKeyPressEvent>
        """
        if event.key() == QtCore.Qt.Key_Escape:
            self.reject()

        super(XOverlayWidget, self).keyPressEvent(event)

    def eventFilter(self, object, event):
        """
        Resizes this overlay as the widget resizes.

        :param      object | <QtCore.QObject>
                    event  | <QtCore.QEvent>

        :return     <bool>
        """
        if object == self.parent() and event.type() == QtCore.QEvent.Resize:
            self.resize(event.size())
        elif event.type() == QtCore.QEvent.Close:
            self.setResult(0)
        return False

    def exec_(self, autodelete=True):
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose, False)
        if self.centralWidget():
            QtCore.QTimer.singleShot(0, self.centralWidget().setFocus)

        loop = QtCore.QEventLoop()
        while self.isVisible() and not QtCore.QCoreApplication.closingDown():
            loop.processEvents()

        if autodelete:
            self.deleteLater()

        return self.result()

    def reject(self):
        """
        Rejects this overlay and exits the modal window.
        """
        self.close()
        self.setResult(1)
        self.finished.emit(1)

    def result(self):
        """
        Returns the result from this overlay widget.

        :return     <int>
        """
        return int(self._result)

    def resizeEvent(self, event):
        """
        Handles a resize event for this overlay, centering the central widget if
        one is found.

        :param      event | <QtCore.QEvent>
        """
        super(XOverlayWidget, self).resizeEvent(event)
        self.adjustSize()

    def setCentralWidget(self, widget):
        """
        Sets the central widget for this overlay to the inputed widget.

        :param      widget | <QtGui.QWidget>
        """
        self._centralWidget = widget

        if widget is not None:
            widget.setParent(self)

            widget.installEventFilter(self)

            # create the drop shadow effect
            effect = QtGui.QGraphicsDropShadowEffect(self)
            effect.setColor(QtGui.QColor('black'))
            effect.setBlurRadius(80)
            effect.setOffset(0, 0)
            widget.setGraphicsEffect(effect)

    def setClosable(self, state):
        """
        Sets whether or not the user should be able to close this overlay widget.

        :param      state | <bool>
        """
        self._closable = state
        if state:
            self._closeButton.show()
        else:
            self._closeButton.hide()

    def setCloseAlignment(self, align):
        """
        Sets the alignment for the close button for this overlay widget.

        :param      align | <QtCore.Qt.Alignment>
        """
        self._closeAlignment = align

    def setResult(self, result):
        """
        Sets the result for this overlay to the inputed value.

        :param      result | <int>
        """
        self._result = result

    def setVisible(self, state):
        """
        Closes this widget and kills the result.
        """
        super(XOverlayWidget, self).setVisible(state)

        if not state:
            self.setResult(0)

    def showEvent(self, event):
        """
        Ensures this widget is the top-most widget for its parent.

        :param      event | <QtCore.QEvent>
        """
        super(XOverlayWidget, self).showEvent(event)

        # raise to the top
        self.raise_()
        self._closeButton.setVisible(self.isClosable())

        widget = self.centralWidget()
        if widget:
            center = self.rect().center()
            start_x = end_x = center.x() - widget.width() / 2
            start_y = -widget.height()
            end_y = center.y() - widget.height() / 2

            start = QtCore.QPoint(start_x, start_y)
            end = QtCore.QPoint(end_x, end_y)

            # create the movement animation
            anim = QtCore.QPropertyAnimation(self)
            anim.setPropertyName('pos')
            anim.setTargetObject(widget)
            anim.setStartValue(start)
            anim.setEndValue(end)
            anim.setDuration(500)
            anim.setEasingCurve(QtCore.QEasingCurve.InOutQuad)
            anim.finished.connect(anim.deleteLater)
            anim.start()

    @staticmethod
    def modal(widget,
              parent=None,
              align=QtCore.Qt.AlignTop | QtCore.Qt.AlignRight,
              blurred=True):
        """
        Creates a modal dialog for this overlay with the inputed widget.  If the user
        accepts the widget, then 1 will be returned, otherwise, 0 will be returned.

        :param      widget | <QtCore.QWidget>
        """
        if parent is None:
            parent = QtGui.QApplication.instance().activeWindow()

        overlay = XOverlayWidget(parent)
        overlay.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        overlay.setCentralWidget(widget)
        overlay.setCloseAlignment(align)
        overlay.show()
        return overlay
Esempio n. 19
0
class XTimeEdit(QtGui.QWidget):
    def __init__(self, parent=None):
        super(XTimeEdit, self).__init__(parent)

        # define custom properties
        self._editable = False
        self._showSeconds = False
        self._showMinutes = True
        self._showHours = True
        self._militaryTime = False

        # define the ui
        self._hourCombo = QtGui.QComboBox(self)
        self._hourSeparator = QtGui.QLabel(':', self)
        self._minuteCombo = QtGui.QComboBox(self)
        self._minuteSeparator = QtGui.QLabel(':', self)
        self._secondCombo = QtGui.QComboBox(self)
        self._timeOfDayCombo = QtGui.QComboBox(self)

        self._secondCombo.hide()
        self._minuteSeparator.hide()

        # define default UI settings
        self._hourCombo.addItems(['{0}'.format(i + 1) for i in xrange(12)])
        self._minuteCombo.addItems(
            ['{0:02d}'.format(i) for i in xrange(0, 60, 5)])
        self._secondCombo.addItems(
            ['{0:02d}'.format(i) for i in xrange(0, 60, 5)])
        self._timeOfDayCombo.addItems(['am', 'pm'])

        # setup combo properties
        for combo in (self._hourCombo, self._minuteCombo, self._secondCombo,
                      self._timeOfDayCombo):
            combo.setInsertPolicy(combo.NoInsert)
            combo.setSizePolicy(QtGui.QSizePolicy.Expanding,
                                QtGui.QSizePolicy.Expanding)

        # layout the widgets
        h_layout = QtGui.QHBoxLayout()
        h_layout.setContentsMargins(0, 0, 0, 0)
        h_layout.setSpacing(0)
        h_layout.addWidget(self._hourCombo)
        h_layout.addWidget(self._hourSeparator)
        h_layout.addWidget(self._minuteCombo)
        h_layout.addWidget(self._minuteSeparator)
        h_layout.addWidget(self._secondCombo)
        h_layout.addWidget(self._timeOfDayCombo)

        self.setLayout(h_layout)

        # assign the default time
        self.setTime(QtCore.QDateTime.currentDateTime().time())

    def isEditable(self):
        """
        Returns whether or not the combo boxes within the edit are editable.

        :return     <bool>
        """
        return self._editable

    def isMilitaryTime(self):
        """
        Returns whether or not the clock is in military (24 hour) mode.

        :return     <bool>
        """
        return self._militaryTime

    def setEditable(self, state):
        """
        Sets whether or not this combo box is editable.

        :param      state | <bool>
        """
        self._editable = state
        self._hourCombo.setEditable(state)
        self._minuteCombo.setEditable(state)
        self._secondCombo.setEditable(state)
        self._timeOfDayCombo.setEditable(state)

    def setFont(self, font):
        """
        Assigns the font to this widget and all of its children.

        :param      font | <QtGui.QFont>
        """
        super(XTimeEdit, self).setFont(font)

        # update the fonts for the time combos
        self._hourCombo.setFont(font)
        self._minuteCombo.setFont(font)
        self._secondCombo.setFont(font)
        self._timeOfDayCombo.setFont(font)

    def setMilitaryTime(self, state=True):
        """
        Sets whether or not this widget will be displayed in military time.  When in military time, the hour options
        will go from 01-24, when in normal mode, the hours will go 1-12.

        :param      state   | <bool>
        """
        time = self.time()

        self._militaryTime = state
        self._hourCombo.clear()

        if state:
            self._timeOfDayCombo.hide()
            self._hourCombo.addItems(
                ['{0:02d}'.format(i + 1) for i in xrange(24)])
        else:
            self._timeOfDayCombo.show()
            self._hourCombo.addItems(['{0}'.format(i + 1) for i in xrange(12)])

        self.setTime(time)

    def setShowHours(self, state=True):
        """
        Sets whether or not to display the hours combo box for this widget.

        :param      state | <bool>
        """
        self._showHours = state
        if state:
            self._hourSeparator.show()
            self._hourCombo.show()
        else:
            self._hourSeparator.hide()
            self._hourCombo.hide()

    def setShowMinutes(self, state=True):
        """
        Sets whether or not to display the minutes combo box for this widget.

        :param      state | <bool>
        """
        self._showMinutes = state
        if state:
            self._minuteCombo.show()
        else:
            self._minuteCombo.hide()

    def setShowSeconds(self, state=True):
        """
        Sets whether or not to display the seconds combo box for this widget.

        :param      state | <bool>
        """
        self._showSeconds = state
        if state:
            self._minuteSeparator.show()
            self._secondCombo.show()
        else:
            self._minuteSeparator.hide()
            self._secondCombo.hide()

    def setTime(self, time):
        """
        Sets the current time for this edit.

        :param     time | <QtCore.QTime>
        """
        hour = time.hour()
        minute = time.minute()
        second = time.second()

        if not self.isMilitaryTime():
            if hour > 12:
                hour -= 12
                self._timeOfDayCombo.setCurrentIndex(1)
            else:
                self._timeOfDayCombo.setCurrentIndex(0)
            hour = str(hour)
        else:
            hour = '{0:02d}'.format(hour)

        nearest = lambda x: int(round(x / 5.0) * 5.0)

        self._hourCombo.setCurrentIndex(self._hourCombo.findText(hour))
        self._minuteCombo.setCurrentIndex(
            self._minuteCombo.findText('{0:02d}'.format(nearest(minute))))
        self._secondCombo.setCurrentIndex(
            self._secondCombo.findText('{0:02d}'.format(nearest(second))))

    def showHours(self):
        """
        Returns whether or not the hours combo should be visible.

        :return     <bool>
        """
        return self._showHours

    def showMinutes(self):
        """
        Returns whether or not the minutes combo should be visible.

        :return     <bool>
        """
        return self._showMinutes

    def showSeconds(self):
        """
        Returns whether or not the seconds combo should be visible.

        :return     <bool>
        """
        return self._showSeconds

    def time(self):
        """
        Returns the current time for this edit.

        :return     <QtCore.QTime>
        """
        if self.isMilitaryTime():
            format = 'hh:mm:ss'
            time_of_day = ''
        else:
            format = 'hh:mm:ssap'
            time_of_day = self._timeOfDayCombo.currentText().lower()

        try:
            hour = int(
                self._hourCombo.currentText()) if self.showHours() else 1
        except ValueError:
            hour = 1

        try:
            minute = int(
                self._minuteCombo.currentText()) if self.showMinutes() else 0
        except ValueError:
            minute = 0

        try:
            second = int(
                self._secondCombo.currentText()) if self.showSeconds() else 0
        except ValueError:
            second = 0

        combined = '{0:02}:{1:02}:{2:02}{3}'.format(hour, minute, second,
                                                    time_of_day)
        return QtCore.QTime.fromString(combined, format)

    x_time = QtCore.Property(QtCore.QTime, time, setTime)
    x_militaryTime = QtCore.Property(bool, isMilitaryTime, setMilitaryTime)
    x_showHours = QtCore.Property(bool, showHours, setShowHours)
    x_showMinutes = QtCore.Property(bool, showMinutes, setShowMinutes)
    x_showSeconds = QtCore.Property(bool, showSeconds, setShowSeconds)