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)
Example #2
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 AttributeError:
            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)
Example #3
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)
Example #4
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)
Example #5
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)
Example #6
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]
Example #7
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)