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)
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)
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)
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)
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)
: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]
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)