class XOverlayWizardPage(QtGui.QFrame): completeChanged = QtCore.Signal() def __init__(self, parent=None): super(XOverlayWizardPage, self).__init__(parent) self.setFrameShape(QtGui.QFrame.Box) # define custom properties self._commitPage = False self._finalPage = False self._retryEnabled = False self._nextId = None # create the title information font = self.font() font.setBold(True) base_size = font.pointSize() font.setPointSize(base_size + 4) # set the palette coloring pal = self.palette() pal.setColor(pal.WindowText, QtGui.QColor('white')) self.setPalette(pal) # create the title labels self._titleLabel = QtGui.QLabel('', self) self._titleLabel.setFont(font) self._titleLabel.setPalette(pal) font.setPointSize(base_size + 2) self._subTitleLabel = QtGui.QLabel('', self) self._subTitleLabel.setPalette(pal) self.adjustMargins() def adjustMargins(self): """ Adjusts the margins to incorporate enough room for the widget's title and sub-title. """ y = 0 height = 0 if self._titleLabel.text(): height += self._titleLabel.height() + 3 y += height if self._subTitleLabel.text(): self._subTitleLabel.move(0, y) height += self._subTitleLabel.height() + 3 self.setContentsMargins(0, height, 0, 0) def cleanupPage(self): """ Performs any cleanup operations that are necessary for this page. """ pass def commit(self): """ Commits any data for this wizard page. """ pass def field(self, name): """ Returns the field value for this page from its wizard. :param name | <str> :return <variant> """ return self.wizard().field(name) def isCommitPage(self): """ Returns whether or not this is a page that requires the commit button to be displayed. :return <bool> """ return self._commitPage def isComplete(self): """ Returns whether or not this page has fully completed its operations. :return <bool> """ return False def isFinalPage(self): """ Returns whether or not this is the final page within the wizard. :return <bool> """ return self._finalPage or self.nextId() == -1 def isRetryEnabled(self): """ Returns whether or not this page can retry its methods. :return <bool> """ return self._retryEnabled def initializePage(self): """ Performs any initialization operations that are necessary for this page. """ pass def nextId(self): """ Returns the next id for this page. By default, it will provide the next id from the wizard, but this method can be overloaded to create a custom path. If -1 is returned, then it will be considered the final page. :return <int> """ if self._nextId is not None: return self._nextId wizard = self.wizard() curr_id = wizard.currentId() all_ids = wizard.pageIds() try: return all_ids[all_ids.index(curr_id) + 1] except IndexError: return -1 def setCommitPage(self, state): """ Sets whether or not this is a commit page for the wizard. :param state | <bool> """ self._commitPage = state def setField(self, name, value): """ Sets the field value for this page from its wizard. :param name | <str> value | <variant> """ return self.wizard().setField(name, value) def setFinalPage(self, state): """ Sets whether or not this is a final page for the wizard. :param state | <bool> """ self._finalPage = state def setNextId(self, pageId): """ Sets the next page id that this page will point to. If the id is None, then it will lookup the id from the wizard. :param pageID | <int> || None """ self._nextId = pageId def setSubTitle(self, title): """ Sets the sub-title for this page to the inputed title. :param title | <str> """ self._subTitleLabel.setText(title) self._subTitleLabel.adjustSize() self.adjustMargins() def setRetryEnabled(self, state=True): """ Sets whether or not this page has retrying allowed. :param state | <bool> """ self._retryEnabled = state def setTitle(self, title): """ Sets the title for this page to the inputed title. :param title | <str> """ self._titleLabel.setText(title) self._titleLabel.adjustSize() self.adjustMargins() def subTitle(self): """ Returns the sub-title for this page. :return <str> """ return self._subTitleLabel.text() def title(self): """ Returns the title for this page. :return <str> """ return self._titleLabel.text() def validatePage(self): """ Performs a validation check before trying to continue from this page. :return <bool> """ return True def wizard(self): """ Returns the wizard associated with this page. :return <projexui.widgets.xoverlaywizard.XOverlayWizard> """ return projexui.ancestor(self, XOverlayWizard)
def sizeHint(self): return QtCore.QSize(self.codeEditor().numberAreaWidth(), 0)
class XOverlayWizard(XOverlayWidget): WizardButton = enum(BackButton=0, NextButton=1, CommitButton=2, FinishButton=3, CancelButton=4, HelpButton=5, RetryButton=6) WizardButtonColors = { WizardButton.BackButton: '#58A6D6', WizardButton.NextButton: '#58A6D6', WizardButton.CommitButton: '#49B84D', WizardButton.FinishButton: '#369939', WizardButton.CancelButton: '#B0B0B0', WizardButton.HelpButton: '#8CA4E6', WizardButton.RetryButton: '#D93D3D' } currentIdChanged = QtCore.Signal(int) helpRequested = QtCore.Signal() pageAdded = QtCore.Signal(int) pageRemoved = QtCore.Signal(int) def __init__(self, parent=None): super(XOverlayWizard, self).__init__(parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) # define custom properties self._fixedPageSize = QtCore.QSize() self._minimumPageSize = QtCore.QSize(0, 0) self._maximumPageSize = QtCore.QSize(10000, 10000) self._currentId = -1 self._startId = -1 self._pages = OrderedDict() self._buttons = {} self._fields = {} self._navigation = [] self._animationSpeed = 350 # create the buttons self._buttons[self.WizardButton.HelpButton] = QtGui.QPushButton( self.tr('Help'), self) self._buttons[self.WizardButton.BackButton] = QtGui.QPushButton( self.tr('Back'), self) self._buttons[self.WizardButton.NextButton] = QtGui.QPushButton( self.tr('Next'), self) self._buttons[self.WizardButton.CommitButton] = QtGui.QPushButton( self.tr('Commit'), self) self._buttons[self.WizardButton.FinishButton] = QtGui.QPushButton( self.tr('Finish'), self) self._buttons[self.WizardButton.CancelButton] = QtGui.QPushButton( self.tr('Cancel'), self) self._buttons[self.WizardButton.RetryButton] = QtGui.QPushButton( self.tr('Retry'), self) # don't show any buttons by default pal = self.palette() for which, btn in self._buttons.items(): pal.setColor(pal.Active, pal.Button, QtGui.QColor(self.WizardButtonColors[which])) pal.setColor(pal.Active, pal.ButtonText, QtGui.QColor('white')) pal.setColor(pal.Inactive, pal.Button, QtGui.QColor(self.WizardButtonColors[which])) pal.setColor(pal.Inactive, pal.ButtonText, QtGui.QColor('white')) btn.setPalette(pal) btn.setFixedSize(QtCore.QSize(120, 30)) btn.hide() # create connections self._buttons[self.WizardButton.HelpButton].clicked.connect( self.helpRequested) self._buttons[self.WizardButton.BackButton].clicked.connect(self.back) self._buttons[self.WizardButton.NextButton].clicked.connect(self.next) self._buttons[self.WizardButton.CommitButton].clicked.connect( self.commit) self._buttons[self.WizardButton.FinishButton].clicked.connect( self.accept) self._buttons[self.WizardButton.CancelButton].clicked.connect( self.reject) self._buttons[self.WizardButton.RetryButton].clicked.connect( self.retry) def accept(self): if not self.currentPage() or self.currentPage().validatePage(): return super(XOverlayWizard, self).accept() return False def addPage(self, page): """ Adds a new overlay wizard page to this wizard. :param page | <projexui.widgets.xoverlaywizard.XOverlayWizardPage> """ self.setPage(len(self._pages), page) def adjustSize(self): """ Adjusts the size of this wizard and its page contents to the inputed size. :param size | <QtCore.QSize> """ super(XOverlayWizard, self).adjustSize() # adjust the page size page_size = self.pageSize() # resize the current page to the inputed size x = (self.width() - page_size.width()) / 2 y = (self.height() - page_size.height()) / 2 btn_height = max([btn.height() for btn in self._buttons.values()]) curr_page = self.currentPage() for page in self._pages.values(): page.resize(page_size.width(), page_size.height() - btn_height - 6) if page == curr_page: page.move(x, y) else: page.move(self.width(), y) # move the left most buttons y += page_size.height() - btn_height left_btns = (self._buttons[self.WizardButton.HelpButton], self._buttons[self.WizardButton.BackButton]) for btn in left_btns: if btn.isVisible(): btn.move(x, y) btn.raise_() x += btn.width() + 6 # move the right buttons x = self.width() - (self.width() - page_size.width()) / 2 for key in reversed(sorted(self._buttons.keys())): btn = self._buttons[key] if not btn.isVisible() or btn in left_btns: continue btn.move(x - btn.width(), y) btn.raise_() x -= btn.width() + 6 def animationSpeed(self): """ Returns the speed that the pages should animate in/out in milliseconds. :return <int> """ return self._animationSpeed def back(self): """ Goes to the previous page for this wizard. """ try: pageId = self._navigation[-2] last_page = self.page(pageId) except IndexError: return curr_page = self.page(self._navigation.pop()) if not (last_page and curr_page): return self._currentId = pageId y = curr_page.y() last_page.move(-last_page.width(), y) last_page.show() # animate the last page in anim_in = QtCore.QPropertyAnimation(self) anim_in.setTargetObject(last_page) anim_in.setPropertyName('pos') anim_in.setStartValue(last_page.pos()) anim_in.setEndValue(curr_page.pos()) anim_in.setDuration(self.animationSpeed()) anim_in.setEasingCurve(QtCore.QEasingCurve.Linear) # animate the current page out anim_out = QtCore.QPropertyAnimation(self) anim_out.setTargetObject(curr_page) anim_out.setPropertyName('pos') anim_out.setStartValue(curr_page.pos()) anim_out.setEndValue(QtCore.QPoint(self.width() + curr_page.width(), y)) anim_out.setDuration(self.animationSpeed()) anim_out.setEasingCurve(QtCore.QEasingCurve.Linear) # create the anim group anim_grp = QtCore.QParallelAnimationGroup(self) anim_grp.addAnimation(anim_in) anim_grp.addAnimation(anim_out) anim_grp.finished.connect(curr_page.hide) anim_grp.finished.connect(anim_grp.deleteLater) # update the button states self._buttons[self.WizardButton.BackButton].setVisible( self.canGoBack()) self._buttons[self.WizardButton.NextButton].setVisible(True) self._buttons[self.WizardButton.RetryButton].setVisible( self.canRetry()) self._buttons[self.WizardButton.CommitButton].setVisible( last_page.isCommitPage()) self._buttons[self.WizardButton.FinishButton].setVisible( last_page.isFinalPage()) self.adjustSize() self.currentIdChanged.emit(pageId) anim_grp.start() def button(self, which): """ Returns the button associated with the inputed wizard button. :param which | <XOverlayWizard.WizardButton> :return <bool> || None """ return self._buttons.get(which) def buttonText(self, which): """ Returns the button text for a given wizard button. :param which | <XOverlayWizard.WizardButton> """ try: return self._buttons[which].text() except KeyError: return '' def canGoBack(self): """ Returns whether or not this wizard can move forward. :return <bool> """ try: backId = self._navigation.index(self.currentId()) - 1 if backId >= 0: self._navigation[backId] else: return False except StandardError: return False else: return True def canGoForward(self): """ Returns whether or not this wizard can move forward. :return <bool> """ try: return not self.currentPage().isFinalPage() except AttributeError: return False def canRetry(self): """ Returns whether or not this wizard can retry the current page. :return <bool> """ try: return self.currentPage().retryEnabled() except AttributeError: return False def commit(self): """ Commits the current page information. """ try: self.currentPage().commit() except AttributeError: pass def currentId(self): """ Returns the page ID of the current page that this wizard is on. :return <int> """ return self._currentId def currentPage(self): """ Returns the current page for this wizard. :return <projexui.widgets.xoverlaywizard.XOverlayWizardPage> || None """ try: return self._pages[self.currentId()] except KeyError: return None def field(self, name, default=None): """ Returns the value for the inputed field property for this wizard. :param name | <str> default | <variant> :return <variant> """ return self._fields.get(name, default) def fixedPageSize(self): """ Returns the fixed page size for this wizard's contents. :return <QtCore.QSize> """ return self._fixedPageSize def hasVisitedPage(self, pageId): """ Returns whether or not the user has seen the inputed page id. :return <bool> """ return False def minimumPageSize(self): """ Returns the minimum page size for this wizard's contents. :return <QtCore.QSize> """ return self._minimumPageSize def maximumPageSize(self): """ Returns the maximum page size for this wizard's contents. :return <QtCore.QSize> """ return self._maximumPageSize def next(self): """ Goes to the previous page for this wizard. """ curr_page = self.currentPage() if not curr_page: return elif not curr_page.validatePage(): return pageId = curr_page.nextId() try: next_page = self._pages[pageId] except KeyError: return self._currentId = pageId self._navigation.append(pageId) y = curr_page.y() next_page.move(self.width(), y) # animate the last page in anim_in = QtCore.QPropertyAnimation(self) anim_in.setTargetObject(curr_page) anim_in.setPropertyName('pos') anim_in.setStartValue(curr_page.pos()) anim_in.setEndValue(QtCore.QPoint(-curr_page.width(), y)) anim_in.setDuration(self.animationSpeed()) anim_in.setEasingCurve(QtCore.QEasingCurve.Linear) # animate the current page out anim_out = QtCore.QPropertyAnimation(self) anim_out.setTargetObject(next_page) anim_out.setPropertyName('pos') anim_out.setStartValue(next_page.pos()) anim_out.setEndValue(curr_page.pos()) anim_out.setDuration(self.animationSpeed()) anim_out.setEasingCurve(QtCore.QEasingCurve.Linear) # create the anim group anim_grp = QtCore.QParallelAnimationGroup(self) anim_grp.addAnimation(anim_in) anim_grp.addAnimation(anim_out) anim_grp.finished.connect(curr_page.hide) anim_grp.finished.connect(anim_grp.deleteLater) next_page.show() # update the button states self._buttons[self.WizardButton.BackButton].setVisible(True) self._buttons[self.WizardButton.NextButton].setVisible( self.canGoForward()) self._buttons[self.WizardButton.RetryButton].setVisible( self.canRetry()) self._buttons[self.WizardButton.CommitButton].setVisible( next_page.isCommitPage()) self._buttons[self.WizardButton.FinishButton].setVisible( next_page.isFinalPage()) self.adjustSize() # initialize the new page self.currentIdChanged.emit(pageId) next_page.initializePage() anim_grp.start() def page(self, pageId): """ Returns the page at the inputed id. :return <projexui.widgets.xoverlaywizard.XOverlayWizardPage> || None """ return self._pages.get(pageId) def pageCount(self): """ Returns the number of pages associated with this wizard. :return <int> """ return len(self._pages) def pageIds(self): """ Returns a list of all the page ids for this wizard. :return [<int>, ..] """ return self._pages.keys() def pageSize(self): """ Returns the current page size for this wizard. :return <QtCore.QSize> """ # update the size based on the new size page_size = self.fixedPageSize() if page_size.isEmpty(): w = self.width() - 80 h = self.height() - 80 min_w = self.minimumPageSize().width() min_h = self.minimumPageSize().height() max_w = self.maximumPageSize().width() max_h = self.maximumPageSize().height() page_size = QtCore.QSize(min(max(min_w, w), max_w), min(max(min_h, h), max_h)) return page_size def exec_(self): """ Executes this wizard within the system. """ self.show() self.adjustSize() self.restart() def restart(self): """ Restarts the whole wizard from the beginning. """ # hide all of the pages for page in self._pages.values(): page.hide() pageId = self.startId() try: first_page = self._pages[pageId] except KeyError: return self._currentId = pageId self._navigation = [pageId] page_size = self.pageSize() x = (self.width() - page_size.width()) / 2 y = (self.height() - page_size.height()) / 2 first_page.move(self.width() + first_page.width(), y) first_page.show() # animate the current page out anim_out = QtCore.QPropertyAnimation(self) anim_out.setTargetObject(first_page) anim_out.setPropertyName('pos') anim_out.setStartValue(first_page.pos()) anim_out.setEndValue(QtCore.QPoint(x, y)) anim_out.setDuration(self.animationSpeed()) anim_out.setEasingCurve(QtCore.QEasingCurve.Linear) anim_out.finished.connect(anim_out.deleteLater) # update the button states self._buttons[self.WizardButton.BackButton].setVisible(False) self._buttons[self.WizardButton.NextButton].setVisible( self.canGoForward()) self._buttons[self.WizardButton.CommitButton].setVisible( first_page.isCommitPage()) self._buttons[self.WizardButton.FinishButton].setVisible( first_page.isFinalPage()) self.adjustSize() first_page.initializePage() self.currentIdChanged.emit(pageId) anim_out.start() def removePage(self, pageId): """ Removes the inputed page from this wizard. :param pageId | <int> """ try: self._pages[pageId].deleteLater() del self._pages[pageId] except KeyError: pass def retry(self): """ Reruns the current page operation. """ try: self.currentPage().initializePage() except AttributeError: pass def setAnimationSpeed(self, speed): """ Sets the speed that the pages should animate in/out in milliseconds. :param speed | <int> | milliseconds """ self._animationSpeed = speed def setButton(self, which, button): """ Sets the button for this wizard for the inputed type. :param which | <XOverlayWizard.WizardButton> button | <QtGui.QPushButton> """ try: self._buttons[which].deleteLater() except KeyError: pass button.setParent(self) self._buttons[which] = button def setButtonText(self, which, text): """ Sets the display text for the inputed button to the given text. :param which | <XOverlayWizard.WizardButton> text | <str> """ try: self._buttons[which].setText(text) except KeyError: pass def setField(self, name, value): """ Sets the field value for this wizard to the given value. :param name | <str> value | <variant> """ self._fields[name] = value def setFixedPageSize(self, size): """ Sets the page size for the wizard. This will define the width/height for the contents for this wizards page widgets as well as the bounding information for the buttons. If an empty size is given, then the size will automatically be recalculated as the widget sizes. :param size | <QtCore.QSize> """ self._fixedPageSize = size self.adjustSize() def setMinimumPageSize(self, size): """ Sets the page size for the wizard. This will define the width/height for the contents for this wizards page widgets as well as the bounding information for the buttons. If an empty size is given, then the size will automatically be recalculated as the widget sizes. :param size | <QtCore.QSize> """ self._minimumPageSize = size self.adjustSize() def setMaximumPageSize(self, size): """ Sets the page size for the wizard. This will define the width/height for the contents for this wizards page widgets as well as the bounding information for the buttons. If an empty size is given, then the size will automatically be recalculated as the widget sizes. :param size | <QtCore.QSize> """ self._maximumPageSize = size self.adjustSize() def setPage(self, pageId, page): """ Sets the page and id for the given page vs. auto-constructing it. This will allow the developer to create a custom order for IDs. :param pageId | <int> page | <projexui.widgets.xoverlaywizard.XOverlayWizardPage> """ page.setParent(self) if self.property("useShadow") is not False: # create the drop shadow effect effect = QtGui.QGraphicsDropShadowEffect(page) effect.setColor(QtGui.QColor('black')) effect.setBlurRadius(50) effect.setOffset(0, 0) page.setGraphicsEffect(effect) self._pages[pageId] = page if self._startId == -1: self._startId = pageId def setStartId(self, pageId): """ Sets the starting page id for this wizard to the inputed page id. :param pageId | <int> """ self._startId = pageId def startId(self): """ Returns the starting id for this wizard. :return <int> """ return self._startId
def next(self): """ Goes to the previous page for this wizard. """ curr_page = self.currentPage() if not curr_page: return elif not curr_page.validatePage(): return pageId = curr_page.nextId() try: next_page = self._pages[pageId] except KeyError: return self._currentId = pageId self._navigation.append(pageId) y = curr_page.y() next_page.move(self.width(), y) # animate the last page in anim_in = QtCore.QPropertyAnimation(self) anim_in.setTargetObject(curr_page) anim_in.setPropertyName('pos') anim_in.setStartValue(curr_page.pos()) anim_in.setEndValue(QtCore.QPoint(-curr_page.width(), y)) anim_in.setDuration(self.animationSpeed()) anim_in.setEasingCurve(QtCore.QEasingCurve.Linear) # animate the current page out anim_out = QtCore.QPropertyAnimation(self) anim_out.setTargetObject(next_page) anim_out.setPropertyName('pos') anim_out.setStartValue(next_page.pos()) anim_out.setEndValue(curr_page.pos()) anim_out.setDuration(self.animationSpeed()) anim_out.setEasingCurve(QtCore.QEasingCurve.Linear) # create the anim group anim_grp = QtCore.QParallelAnimationGroup(self) anim_grp.addAnimation(anim_in) anim_grp.addAnimation(anim_out) anim_grp.finished.connect(curr_page.hide) anim_grp.finished.connect(anim_grp.deleteLater) next_page.show() # update the button states self._buttons[self.WizardButton.BackButton].setVisible(True) self._buttons[self.WizardButton.NextButton].setVisible( self.canGoForward()) self._buttons[self.WizardButton.RetryButton].setVisible( self.canRetry()) self._buttons[self.WizardButton.CommitButton].setVisible( next_page.isCommitPage()) self._buttons[self.WizardButton.FinishButton].setVisible( next_page.isFinalPage()) self.adjustSize() # initialize the new page self.currentIdChanged.emit(pageId) next_page.initializePage() anim_grp.start()
class XLoggerControls(QtGui.QWidget): activeLevelsChanged = QtCore.Signal(list) def __init__(self, loggerWidget): super(XLoggerControls, self).__init__(loggerWidget) # load the user interface projexui.loadUi(__file__, self) self._url = 'https://docs.python.org/2/library/logging.html#logrecord-attributes' self._loggerWidget = loggerWidget self.uiLoggerTREE.setLoggerWidget(loggerWidget) self.uiFormatTXT.setText(loggerWidget.formatText()) # load the levels if 'designer' not in sys.executable: tree = self.uiLevelTREE from projexui.widgets.xloggerwidget import XLoggerWidget items = sorted(XLoggerWidget.LoggingMap.items()) for i, (level, data) in enumerate(items): item = XTreeWidgetItem(tree, [projex.text.pretty(data[0])]) item.setFixedHeight(22) item.setData(0, QtCore.Qt.UserRole, wrapVariant(level)) item.setCheckState(0, QtCore.Qt.Unchecked) # create connections self.uiFormatTXT.textChanged.connect(loggerWidget.setFormatText) self.uiLevelTREE.itemChanged.connect(self.updateLevels) self.uiHelpBTN.clicked.connect(self.showHelp) def showEvent(self, event): super(XLoggerControls, self).showEvent(event) # update the format text widget = self._loggerWidget self.uiFormatTXT.setText(widget.formatText()) # update the active levels lvls = widget.activeLevels() tree = self.uiLevelTREE tree.blockSignals(True) for item in tree.topLevelItems(): if unwrapVariant(item.data(0, QtCore.Qt.UserRole)) in lvls: item.setCheckState(0, QtCore.Qt.Checked) else: item.setCheckState(0, QtCore.Qt.Unchecked) tree.blockSignals(False) def showHelp(self): webbrowser.open(self._url) def updateLevels(self): levels = [] tree = self.uiLevelTREE tree.blockSignals(True) for item in tree.checkedItems(): level = unwrapVariant(item.data(0, QtCore.Qt.UserRole)) if level is not None: levels.append(level) tree.blockSignals(False) self._loggerWidget.setActiveLevels(levels)
class XCollapsibleLoggerWidget(QtGui.QWidget): """ """ def __init__(self, parent=None): super(XCollapsibleLoggerWidget, self).__init__(parent) # load the user interface projexui.loadUi(__file__, self) # define custom properties self._collapsed = False self._animated = True self._expandedHeight = 250 # animation properties self._startHeight = 0 self._targetHeight = 0 self._targetPercent = 0 self.uiFeedbackLBL.setFont(self.uiLoggerWGT.font()) # set default properties self.setCollapsed(True) # create connections self.uiShowBTN.clicked.connect(self.expand) self.uiHideBTN.clicked.connect(self.collapse) self.uiLoggerWGT.messageLogged.connect(self.updateFeedback) @QtCore.Slot() def clear(self): self.uiLoggerWGT.clear() self.uiFeedbackLBL.setText('') @QtCore.Slot() def collapse(self): self.setCollapsed(True) def critical(self, msg): """ Logs a critical message to the console. :param msg | <unicode> """ self.uiLoggerWGT.critical(msg) def debug(self, msg): """ Inserts a debug message to the current system. :param msg | <unicode> """ self.uiLoggerWGT.debug(msg) def error(self, msg): """ Inserts an error message to the current system. :param msg | <unicode> """ self.uiLoggerWGT.error(msg) @QtCore.Slot() def expand(self): self.setCollapsed(False) def expandedHeight(self): return self._expandedHeight def fatal(self, msg): """ Logs a fatal message to the system. :param msg | <unicode> """ self.uiLoggerWGT.fatal(msg) def formatText(self): return self.uiLoggerWGT.formatText() def information(self, msg): """ Inserts an information message to the current system. :param msg | <unicode> """ self.uiLoggerWGT.information(msg) def isAnimated(self): return self._animated def isCollapsed(self): """ Returns whether or not this logger widget is in the collapsed state. :return <bool> """ return self._collapsed def isConfigurable(self): """ Returns whether or not this widget can be configured by the user. :return <bool> """ return self.uiLoggerWGT.isConfigurable() def log(self, level, msg): """ Logs the inputed message with the given level. :param level | <int> | logging level value msg | <unicode> :return <bool> success """ return self.uiLoggerWGT.log(level, msg) def logger(self): """ Returns the logger associated with this widget. :return <logging.Logger> """ return self.uiLoggerWGT.logger() def loggerLevel(self, logger): return self.uiLoggerWGT.loggerLevel(logger) def loggerWidget(self): return self.uiLoggerWGT def setAnimated(self, state): self._animated = state @QtCore.Slot(bool) def setExpanded(self, state): self.setCollapsed(not state) def setExpandedHeight(self, height): self._expandedHeight = height @QtCore.Slot(bool) def setCollapsed(self, state): if self._collapsed == state: return self._collapsed = state # update the sizing constraints palette = self.palette() if state: height = 24 else: height = self.expandedHeight() if self.isVisible() and self.parent() and self.isAnimated(): # show the uncollapsed items collapsing if not state: # update the visible children based on the state for widget in self.children(): prop = unwrapVariant(widget.property('showCollapsed')) if prop is not None: widget.setVisible(bool(prop) == state) self._startHeight = self.height() self._targetHeight = height self._targetPercent = 0.0 self.startTimer(10) else: # update the visible children based on the state for widget in self.children(): prop = unwrapVariant(widget.property('showCollapsed')) if prop is not None: widget.setVisible(bool(prop) == state) self.setFixedHeight(height) def setConfigurable(self, state): """ Sets whether or not this logger widget can be configured by the user. :param state | <bool> """ self.uiLoggerWGT.setConfigurable(state) def setLoggerLevel(self, logger, level): self.uiLoggerWGT.setLoggerLevel(logger, level) def setFormatText(self, text): self.uiLoggerWGT.setFormatText(text) def setLogger(self, logger): """ Sets the logger associated with this widget. :param logger | <logging.Logger> """ self.uiLoggerWGT.setLogger(logger) def timerEvent(self, event): self._targetPercent += 0.05 if self._targetPercent >= 1: self.killTimer(event.timerId()) self.setFixedHeight(self._targetHeight) # always show the logger widget if self.isCollapsed(): # update the visible children based on the state for widget in self.children(): prop = unwrapVariant(widget.property('showCollapsed')) if prop is not None: widget.setVisible(bool(prop) == self.isCollapsed()) else: delta = (self._startHeight - self._targetHeight) * self._targetPercent self.setFixedHeight(self._startHeight - delta) def updateFeedback(self, level, message): clr = self.uiLoggerWGT.color(level) palette = self.uiFeedbackLBL.palette() palette.setColor(palette.WindowText, clr) self.uiFeedbackLBL.setPalette(palette) self.uiFeedbackLBL.setText(message) def warning(self, msg): """ Logs a warning message to the system. :param msg | <unicode> """ self.uiLoggerWGT.warning(msg) x_animated = QtCore.Property(bool, isAnimated, setAnimated) x_collapsed = QtCore.Property(bool, isCollapsed, setCollapsed) x_configurable = QtCore.Property(bool, isConfigurable, setConfigurable) x_expandedHeight = QtCore.Property(int, expandedHeight, setExpandedHeight) x_formatText = QtCore.Property(str, formatText, setFormatText)
class XLoggerWidget(QtGui.QTextEdit): __designer_icon__ = resources.find('img/log/info.png') LoggingMap = { logging.DEBUG: ('debug', resources.find('img/log/bug.png')), logging.INFO: ('info', resources.find('img/log/info.png')), logging.SUCCESS: ('success', resources.find('img/log/success.png')), logging.WARN: ('warning', resources.find('img/log/warning.png')), logging.ERROR: ('error', resources.find('img/log/error.png')), logging.CRITICAL: ('critical', resources.find('img/log/critical.png')), } messageLogged = QtCore.Signal(int, unicode) """ Defines the main logger widget class. """ def __init__(self, parent): super(XLoggerWidget, self).__init__(parent) # set standard properties self.setReadOnly(True) self.setLineWrapMode(XLoggerWidget.NoWrap) # define custom properties self._clearOnClose = True self._handler = XLoggerWidgetHandler(self) self._currentMode = 'standard' self._blankCache = '' self._mutex = QtCore.QMutex() self._loggers = set() self._configurable = True self._destroyed = False # define the popup button for congfiguration self._configButton = XPopupButton(self) self._configButton.setIcon( QtGui.QIcon(resources.find('img/config.png'))) self._configButton.setShadowed(True) popup = self._configButton.popupWidget() popup.setShowTitleBar(False) popup.setResizable(False) bbox = popup.buttonBox() bbox.clear() bbox.addButton(QtGui.QDialogButtonBox.Ok) # set to a monospace font font = QtGui.QFont('Courier New') font.setPointSize(9) self.setFont(font) metrics = QtGui.QFontMetrics(font) self.setTabStopWidth(4 * metrics.width(' ')) self.setAcceptRichText(False) # determine whether or not to use the light or dark configuration palette = self.palette() base = palette.color(palette.Base) avg = (base.red() + base.green() + base.blue()) / 3.0 if avg < 160: colorSet = XLoggerColorSet.darkScheme() else: colorSet = XLoggerColorSet.lightScheme() self._colorSet = colorSet palette.setColor(palette.Text, colorSet.color('Standard')) palette.setColor(palette.Base, colorSet.color('Background')) self.setPalette(palette) # create the logger tree widget controls = XLoggerControls(self) self._configButton.setCentralWidget(controls) self._configButton.setDefaultAnchor(popup.Anchor.TopRight) # create connections self._handler.dispatch().messageLogged.connect(self.log) self.destroyed.connect(self.markDestroyed) def activeLevels(self): """ Returns the active levels that will be displayed for this widget. :return [<int>, ..] """ return self._handler.activeLevels() def addLogger(self, logger, level=logging.INFO): """ Adds the inputed logger to the list of loggers that are being tracked with this widget. :param logger | <logging.Logger> || <str> level | <logging.LEVEL> """ if isinstance(logger, logging.Logger): logger = logger.name if logger in self._loggers: return # allow the handler to determine the level for this logger if logger == 'root': _log = logging.getLogger() else: _log = logging.getLogger(logger) _log.addHandler(self.handler()) self._loggers.add(logger) self.handler().setLoggerLevel(logger, level) def cleanup(self): self._destroyed = True try: self._handler.dispatch().messageLogged.disconnect(self.log) self.destroyed.disconnect(self.markDestroyed) except StandardError: pass self.markDestroyed() def clear(self): super(XLoggerWidget, self).clear() self._currentMode = 'standard' def clearLoggers(self, logger): """ Removes the inputed logger from the set for this widget. :param logger | <str> || <logging.Logger> """ for logger in self._loggers: if logger == 'root': logger = logging.getLogger() else: logger = logging.getLogger(logger) logger.removeHandler(self.handler()) self._loggers = set() def clearOnClose(self): """ Returns whether or not this widget should clear the link to its \ logger when it closes. :return <bool> """ return self._clearOnClose def color(self, key): """ Returns the color value for the given key for this console. :param key | <unicode> :return <QtGui.QColor> """ if type(key) == int: key = self.LoggingMap.get(key, ('NotSet', ''))[0] name = nativestring(key).capitalize() return self._colorSet.color(name) def colorSet(self): """ Returns the colors used for this console. :return <XLoggerColorSet> """ return self._colorSet def critical(self, msg): """ Logs a critical message to the console. :param msg | <unicode> """ self.log(logging.CRITICAL, msg) def currentMode(self): """ Returns the current mode that the console is in for coloring. :return <unicode> """ return self._currentMode def debug(self, msg): """ Inserts a debug message to the current system. :param msg | <unicode> """ self.log(logging.DEBUG, msg) def error(self, msg): """ Inserts an error message to the current system. :param msg | <unicode> """ self.log(logging.ERROR, msg) @deprecatedmethod('2.1', 'Use critical instead (same level as FATAL)') def fatal(self, msg): """ Logs a fatal message to the system. :param msg | <unicode> """ self.log(logging.FATAL, msg) def formatText(self): """ Returns the text that is used to format entries that are logged to this handler. :return <str> """ return self.handler().formatText() def handler(self): """ Returns the logging handler that is linked to this widget. :return <XLoggerWidgetHandler> """ return self._handler def hasLogger(self, logger): """ Returns whether or not the inputed logger is tracked by this widget. :param logger | <str> || <logging.Logger> """ if isinstance(logger, logging.Logger): logger = logging.name return logger in self._loggers def information(self, msg): """ Inserts an information message to the current system. :param msg | <unicode> """ self.log(logging.INFO, msg) def isConfigurable(self): """ Returns whether or not the user can configure the loggers associated with this widget. :return <bool> """ return self._configurable def isDestroyed(self): return self._destroyed @deprecatedmethod('2.1', 'Use the loggerLevel now.') def isLoggingEnabled(self, level): """ Returns whether or not logging is enabled for the given level. :param level | <int> """ return False def log(self, level, msg): """ Logs the inputed message with the given level. :param level | <int> | logging level value msg | <unicode> :return <bool> success """ if self.isDestroyed(): return locker = QtCore.QMutexLocker(self._mutex) try: msg = projex.text.nativestring(msg) self.moveCursor(QtGui.QTextCursor.End) self.setCurrentMode(level) if self.textCursor().block().text(): self.insertPlainText('\n') self.insertPlainText(msg.lstrip('\n\r')) self.scrollToEnd() except RuntimeError: return if not self.signalsBlocked(): self.messageLogged.emit(level, msg) return True @deprecatedmethod( '2.1', 'Loggers will not be explicitly set anymore. Use loggerLevel instead.' ) def logger(self): """ Returns the logger instance that this widget will monitor. :return <logging.Logger> || None """ return None def loggerLevel(self, logger='root'): """ Returns the logging level for the inputed logger. :param logger | <str> || <logging.Logger> """ if isinstance(logger, logging.Logger): logger = logger.name return self.handler().loggerLevel(logger) def loggerLevels(self): """ Returns a dictionary of the set logger levels for this widget. :return {<str> logger: <int> level, ..} """ return self._handler.loggerLevels() def markDestroyed(self): self._destroyed = True def resizeEvent(self, event): super(XLoggerWidget, self).resizeEvent(event) size = event.size() self._configButton.move(size.width() - 22, 3) def removeLogger(self, logger): """ Removes the inputed logger from the set for this widget. :param logger | <str> || <logging.Logger> """ if isinstance(logger, logging.Logger): logger = logger.name if logger in self._loggers: self._loggers.remove(logger) if logger == 'root': logger = logging.getLogger() else: logger = logging.getLogger(logger) logger.removeHandler(self.handler()) def restoreSettings(self, settings): """ Restores the settings for this logger from the inputed settings. :param <QtCore.QSettings> """ val = unwrapVariant(settings.value('format')) if val: self.setFormatText(val) levels = unwrapVariant(settings.value('levels')) if levels: self.setActiveLevels(map(int, levels.split(','))) logger_levels = unwrapVariant(settings.value('loggerLevels')) if logger_levels: for key in logger_levels.split(','): logger, lvl = key.split(':') lvl = int(lvl) self.setLoggerLevel(logger, lvl) def saveSettings(self, settings): """ Saves the logging settings for this widget to the inputed settings. :param <QtCore.QSettings> """ lvls = [] for logger, level in self.loggerLevels().items(): lvls.append('{0}:{1}'.format(logger, level)) settings.setValue('format', wrapVariant(self.formatText())) settings.setValue('levels', ','.join(map(str, self.activeLevels()))) settings.setValue('loggerLevels', ','.join(lvls)) def scrollToEnd(self): """ Scrolls to the end for this console edit. """ vsbar = self.verticalScrollBar() vsbar.setValue(vsbar.maximum()) hbar = self.horizontalScrollBar() hbar.setValue(0) def setActiveLevels(self, levels): """ Defines the levels for this widgets visible/processed levels. :param levels | [<int>, ..] """ self._handler.setActiveLevels(levels) def setClearOnClose(self, state): """ Sets whether or not this widget should clear the logger link on close. :param state | <bool> """ self._clearOnClose = state def setColor(self, key, value): """ Sets the color value for the inputed color. :param key | <unicode> value | <QtGui.QColor> """ key = nativestring(key).capitalize() self._colorSet.setColor(key, value) # update the palette information if (key == 'Background'): palette = self.palette() palette.setColor(palette.Base, value) self.setPalette(palette) def setColorSet(self, colorSet): """ Sets the colors for this console to the inputed collection. :param colors | <XLoggerColorSet> """ self._colorSet = colorSet # update the palette information palette = self.palette() palette.setColor(palette.Text, colorSet.color('Standard')) palette.setColor(palette.Base, colorSet.color('Background')) self.setPalette(palette) def setConfigurable(self, state): """ Sets whether or not this logger widget is configurable. :param state | <bool> """ self._configurable = state self._configButton.setVisible(state) def setCurrentMode(self, mode): """ Sets the current color mode for this console to the inputed value. :param mode | <unicode> """ if type(mode) == int: mode = self.LoggingMap.get(mode, ('standard', ''))[0] if mode == self._currentMode: return self._currentMode = mode color = self.color(mode) if not color.isValid(): return format = QtGui.QTextCharFormat() format.setForeground(color) self.setCurrentCharFormat(format) def setFormatText(self, text): """ Sets the format text for this logger to the inputed text. :param text | <str> """ self.handler().setFormatText(text) @deprecatedmethod('2.1', 'Use the setLoggerLevel method now') def setLoggingEnabled(self, level, state): """ Sets whether or not this widget should log the inputed level amount. :param level | <int> state | <bool> """ pass def setLoggerLevel(self, logger, level): """ Returns the logging level for the inputed logger. :param logger | <logging.Logger> || <str> level | <logging.LEVEL> """ if level == logging.NOTSET: self.removeLogger(logger) return if isinstance(logger, logging.Logger): logger = logger.name if not logger in self._loggers: self.addLogger(logger, level) else: self.handler().setLoggerLevel(logger, level) @deprecatedmethod('2.1', 'You should now use the addLogger method.') def setLogger(self, logger): """ Sets the logger instance that this widget will monitor. :param logger | <logging.Logger> """ pass @deprecatedmethod('2.1', 'You should now use the setFormatText method.') def setShowDetails(self, state): pass @deprecatedmethod('2.1', 'You should now use the setFormatText method.') def setShowLevel(self, state): pass @deprecatedmethod('2.1', 'You should now use the formatText method.') def showDetails(self): pass @deprecatedmethod('2.1', 'You should now use the formatText method.') def showLevel(self): pass def success(self, msg): """ Logs the message for this widget. :param msg | <str> """ self.log(logging.SUCCESS, msg) def warning(self, msg): """ Logs a warning message to the system. :param msg | <unicode> """ self.log(logging.WARNING, msg) x_configurable = QtCore.Property(bool, isConfigurable, setConfigurable) x_formatText = QtCore.Property(str, formatText, setFormatText)
def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
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)
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 XOrbWorker(QtCore.QObject): loadingStarted = QtCore.Signal() loadingFinished = QtCore.Signal() connectionLost = QtCore.Signal() WorkerCount = 0 def __init__(self, threaded, *args): super(XOrbWorker, self).__init__(*args) # define custom properties self._database = None self._loading = False self._databaseThreadId = 0 XOrbWorker.WorkerCount += 1 if threaded: self.moveToThread(XOrbWorkerThreadManager.thread()) def __del__(self): XOrbWorker.WorkerCount -= 1 if XOrbWorker.WorkerCount == 0: XOrbWorkerThreadManager.destroy() def database(self): """ Returns the database associated with this worker. :return <orb.Database> """ return self._database def databaseThreadId(self): """ Returns the thread id associated with this worker. :return <int> """ return self._threadId def deleteLater(self): """ Prepares to delete this worker. If a database and a particular thread id are associated with this worker, then it will be cleared when it is deleted. """ self.interrupt() super(XOrbWorker, self).deleteLater() def finishLoading(self): """ Marks the worker as having completed loading. """ self._loading = False self.loadingFinished.emit() def isLoading(self): """ Returns whether or not this worker is currently loading. :return <bool> """ return self._loading def interrupt(self): """ Interrupts the current database from processing. """ if self._database and self._databaseThreadId: # support Orb 2 interruption capabilities try: self._database.interrupt(self._databaseThreadId) except AttributeError: pass self._database = None self._databaseThreadId = 0 def setDatabase(self, database): """ Sets the database associated with this thread to the inputed database. :param database | <orb.Database> """ self._database = database if self.thread(): tid = threading.current_thread().ident self._databaseThreadId = tid def startLoading(self): """ Marks the workar as having started loading. """ self._loading = True self.loadingStarted.emit() def waitUntilFinished(self): """ Processes the main thread until the loading process has finished. This is a way to force the main thread to be synchronous in its execution. """ QtCore.QCoreApplication.processEvents() while self.isLoading(): QtCore.QCoreApplication.processEvents() @staticmethod def interruptionProtected(func): def wraps(*args, **kwds): try: func(*args, **kwds) except Interruption: pass return wraps
class XLocaleBox(XComboBox): __designer_icon__ = resources.find('img/flags/us.png') currentLocaleChanged = QtCore.Signal('QVariant') def __init__(self, parent=None): super(XLocaleBox, self).__init__(parent) # define custom properties self._allLocales = [] self._dirty = True self._baseLocale = 'en_US' self._showLanguage = True self._showTerritory = True self._showScriptName = True self._translated = True self._availableLocales = [] # all locales def allLocales(self): """ Returns all the locales that are defined within the babel architecture. :return [<str>, ..] """ if self._allLocales: return self._allLocales expr = re.compile('[a-z]+_[A-Z]+') locales = babel.core.localedata.locale_identifiers() babel_locales = {} for locale in locales: if expr.match(locale): babel_locale = babel.Locale.parse(locale) if babel_locale.territory and babel_locale.language: babel_locales[babel_locale.territory] = babel_locale babel_locales = babel_locales.values() babel_locales.sort(key=str) self._allLocales = babel_locales return self._allLocales def availableLocales(self): """ Returns a list of the available locales to use for displaying the locale information for. This provides a way to filter the interface for only locales that you care about. :return [<str>, ..] """ return [str(locale) for locale in self._availableLocales] def baseLocale(self): """ Returns the name to be used as the base name for the translation within the widget. This will alter the language that is displaying the contents. :return <str> """ return self._baseLocale def currentLocale(self): """ Returns the current locale for this box. :return <babel.Locale> || None """ try: return babel.Locale.parse(self.itemData(self.currentIndex())) except ImportError, err: log.error('babel is not installed.') return None
: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 XTimer(QtCore.QObject): """ The default <QtCore.QTimer> is not threadable -- you cannot call start or stop from a different thread. It must always reside on the thread that created it. The XTimer is a wrapper over the default <QtCore.QTimer> class instance that allows the ability to call the start or stop slots from any thread. :usage |>>> from xqt import QtCore |>>> thread = QtCore.QThread() |>>> thread.start() |>>> |>>> # non-thread safe calls |>>> qtimer = QtCore.QTimer() |>>> qtimer.moveToThread(thread) |>>> qtimer.start() |QObject::startTimer: timers cannot be started from another thread |>>> qtimer.stop() |QObject::killTimer: timers cannot be stopped from another thread |>>> |>>> # thread safe calls |>>> from projexui.xtimer import XTimer |>>> ttimer = XTimer() |>>> ttimer.moveToThread(thread) |>>> ttimer.start() |>>> ttimer.stop() |>>> """ _singleShotChanged = QtCore.Signal(bool) _intervalChanged = QtCore.Signal(int) _startRequested = QtCore.Signal(int) _stopRequested = QtCore.Signal() timeout = QtCore.Signal() def __init__(self, parent=None): super(XTimer, self).__init__(parent) # define custom properties self.__timer = None self.__active = False self.__singleShot = False self.__interval = 0 self.__lock = QtCore.QReadWriteLock() # create connections self._singleShotChanged.connect(self._setTimerSingleShot, QtCore.Qt.QueuedConnection) self._intervalChanged.connect(self._setTimerInterval, QtCore.Qt.QueuedConnection) self._startRequested.connect(self._startTimer, QtCore.Qt.QueuedConnection) self._stopRequested.connect(self._stopTimer, QtCore.Qt.QueuedConnection) def _setTimerInterval(self, interval): """ Sets the internal timer's interval. :param interval | <int> """ try: self.__timer.setInterval(interval) except AttributeError: pass def _setTimerSingleShot(self, state): """ Sets the internal timer's single shot state. :param state | <bool> """ try: self.__timer.setSingleShot(state) except AttributeError: pass def _startTimer(self, interval): """ Starts the internal timer. :param interval | <int> """ if not self.__timer: self.__timer = QtCore.QTimer(self) self.__timer.setSingleShot(self.__singleShot) self.__timer.setInterval(interval) self.__timer.timeout.connect(self.timeout) # ensure to stop this timer when the app quits QtCore.QCoreApplication.instance().aboutToQuit.connect( self.__timer.stop, QtCore.Qt.QueuedConnection) self.__timer.start(interval) def _stopTimer(self): """ Stops the internal timer, if one exists. :param stop | <int> """ try: self.__timer.stop() except AttributeError: pass def interval(self): """ Returns the interval in milliseconds for this timer. :return <int> | msecs """ with QtCore.QReadLocker(self.__lock): return self.__interval def isActive(self): """ Returns whether or not this timer is currently active. :return <bool> """ try: return self.__timer.isActive() except AttributeError: return False def isSingleShot(self): """ Returns whether or not this timer should operate only a single time. :return <bool> """ with QtCore.QReadLocker(self.__lock): return self.__singleShot def setInterval(self, msecs): """ Sets the interval in milliseconds for this timer. :param msecs | <int> """ with QtCore.QWriteLocker(self.__lock): self.__interval = msecs self._intervalChanged.emit(msecs) def setSingleShot(self, state): """ Sets whether or not this timer is setup for a single entry or not. :param state | <bool> """ with QtCore.QWriteLocker(self.__lock): self.__singleShot = state self._singleShotChanged.emit(state) def start(self, interval=None): """ Emits the start requested signal for this timer, effectively starting its internal timer. :param interval | <int> """ # update the interval value with QtCore.QReadLocker(self.__lock): if interval is None: interval = self.__interval else: self.__interval = interval # request the timer to start self._startRequested.emit(interval) def stop(self): """ Emits the stop requested signal for this timer, effectivly stopping its internal timer. """ self._stopRequested.emit() singleShot = QtCore.QTimer.singleShot
def __init__(self, parent): super(XLoggerWidget, self).__init__(parent) # set standard properties self.setReadOnly(True) self.setLineWrapMode(XLoggerWidget.NoWrap) # define custom properties self._clearOnClose = True self._handler = XLoggerWidgetHandler(self) self._currentMode = 'standard' self._blankCache = '' self._mutex = QtCore.QMutex() self._loggers = set() self._configurable = True self._destroyed = False # define the popup button for congfiguration self._configButton = XPopupButton(self) self._configButton.setIcon( QtGui.QIcon(resources.find('img/config.png'))) self._configButton.setShadowed(True) popup = self._configButton.popupWidget() popup.setShowTitleBar(False) popup.setResizable(False) bbox = popup.buttonBox() bbox.clear() bbox.addButton(QtGui.QDialogButtonBox.Ok) # set to a monospace font font = QtGui.QFont('Courier New') font.setPointSize(9) self.setFont(font) metrics = QtGui.QFontMetrics(font) self.setTabStopWidth(4 * metrics.width(' ')) self.setAcceptRichText(False) # determine whether or not to use the light or dark configuration palette = self.palette() base = palette.color(palette.Base) avg = (base.red() + base.green() + base.blue()) / 3.0 if avg < 160: colorSet = XLoggerColorSet.darkScheme() else: colorSet = XLoggerColorSet.lightScheme() self._colorSet = colorSet palette.setColor(palette.Text, colorSet.color('Standard')) palette.setColor(palette.Base, colorSet.color('Background')) self.setPalette(palette) # create the logger tree widget controls = XLoggerControls(self) self._configButton.setCentralWidget(controls) self._configButton.setDefaultAnchor(popup.Anchor.TopRight) # create connections self._handler.dispatch().messageLogged.connect(self.log) self.destroyed.connect(self.markDestroyed)
def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
class XViewWidget(QtGui.QScrollArea): __designer_icon__ = projexui.resources.find('img/ui/scrollarea.png') __designer_propspecs__ = {'x_hint': ('string', 'richtext')} lockToggled = QtCore.Signal(bool) resetFinished = QtCore.Signal() def __init__(self, parent): super(XViewWidget, self).__init__(parent) # define custom properties self._customData = {} self._viewTypes = [] self._locked = False self._tabMenu = None self._pluginMenu = None self._panelMenu = None self._defaultProfile = None self._scope = None # defines code execution scope for this widget self._hint = '' # intiailize the scroll area self.setBackgroundRole(QtGui.QPalette.Window) self.setFrameShape(QtGui.QScrollArea.NoFrame) self.setWidgetResizable(True) self.setWidget(XViewPanel(self, self.isLocked())) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) # update the current view app = QtGui.QApplication.instance() app.focusChanged.connect(self.updateCurrentView) self.customContextMenuRequested.connect(self.showMenu) def canClose(self): """ Checks to see if the view widget can close by checking all of its \ sub-views to make sure they're ok to close. :return <bool> """ for view in self.findChildren(XView): if not view.canClose(): return False return True def closeEvent(self, event): views = self.findChildren(XView) for view in views: if not view.canClose(): event.ignore() return for view in views: view.close() super(XViewWidget, self).closeEvent(event) def codeScope(self): """ Returns the code execution scope for this widget. :return <dict> """ if self._scope is None: import __main__ return __main__.__dict__ else: return self._scope def createMenu(self, parent): """ Creates a new menu for the inputed parent item. :param parent | <QMenu> """ menu = QtGui.QMenu(parent) menu.setTitle('&View') act = menu.addAction('&Lock/Unlock Layout') act.setIcon(QtGui.QIcon(projexui.resources.find('img/view/lock.png'))) act.triggered.connect(self.toggleLocked) menu.addSeparator() act = menu.addAction('&Export Layout as...') act.setIcon(QtGui.QIcon( projexui.resources.find('img/view/export.png'))) act.triggered.connect(self.exportProfile) act = menu.addAction('&Import Layout from...') act.setIcon(QtGui.QIcon( projexui.resources.find('img/view/import.png'))) act.triggered.connect(self.importProfile) menu.addSeparator() act = menu.addAction('&Clear Layout') act.setIcon(QtGui.QIcon( projexui.resources.find('img/view/remove.png'))) act.triggered.connect(self.resetForced) return menu def currentPanel(self): """ Returns the currently active panel based on whether or not it has \ focus. :return <XViewPanel> || None """ focus_widget = QtGui.QApplication.instance().focusWidget() focus_panel = projexui.ancestor(focus_widget, XViewPanel) panels = self.panels() if focus_panel in panels: return focus_panel try: return panels[0] except IndexError: return None def currentView(self): """ Returns the current view for this widget. :return <projexui.widgets.xviewwidget.XView> || NOne """ panel = self.currentPanel() if panel: return panel.currentWidget() return None def customData(self, key, default=None): """ Returns the custom data for the given key. :param key | <str> default | <variant> :return <variant> """ return self._customData.get(nativestring(key), default) def defaultProfile(self): """ Returns the default profile for this view widget. :return <XViewProfile> """ return self._defaultProfile def exportProfile(self, filename=''): """ Exports the current profile to a file. :param filename | <str> """ if not (filename and isinstance(filename, basestring)): filename = QtGui.QFileDialog.getSaveFileName( self, 'Export Layout as...', QtCore.QDir.currentPath(), 'XView (*.xview)') if type(filename) == tuple: filename = filename[0] filename = nativestring(filename) if not filename: return if not filename.endswith('.xview'): filename += '.xview' profile = self.saveProfile() profile.save(filename) def findViewType(self, viewTypeName): """ Looks up the view type based on the inputed view type name. :param viewTypeName | <str> """ for viewType in self._viewTypes: if (viewType.viewTypeName() == viewTypeName): return viewType return None def importProfile(self, filename=''): """ Exports the current profile to a file. :param filename | <str> """ if not (filename and isinstance(filename, basestring)): filename = QtGui.QFileDialog.getOpenFileName( self, 'Import Layout from...', QtCore.QDir.currentPath(), 'XView (*.xview)') if type(filename) == tuple: filename = nativestring(filename[0]) filename = nativestring(filename) if not filename: return if not filename.endswith('.xview'): filename += '.xview' profile = XViewProfile.load(filename) if not profile: return profile.restore(self) def hint(self): return self._hint def isEmpty(self): """ Returns whether or not there are any XView widgets loaded for this widget. :return <bool> """ return len(self.findChildren(XView)) == 0 def isLocked(self): """ Returns whether or not this widget is in locked mode. :return <bool> """ return self._locked def panels(self): """ Returns a lis of the panels that are assigned to this view widget. :return [<XViewPanel>, ..] """ return [ panel for panel in self.findChildren(XViewPanel) if panel.viewWidget() == self ] def registerViewType(self, cls, window=None): """ Registers the inputed widget class as a potential view class. If the \ optional window argument is supplied, then the registerToWindow method \ will be called for the class. :param cls | <subclass of XView> window | <QMainWindow> || <QDialog> || None """ if (not cls in self._viewTypes): self._viewTypes.append(cls) if (window): cls.registerToWindow(window) @QtCore.Slot(PyObject) def restoreProfile(self, profile): """ Restores the profile settings based on the inputed profile. :param profile | <XViewProfile> """ return profile.restore(self) def restoreSettings(self, settings): """ Restores the current structure of the view widget from the inputed \ settings instance. :param settings | <QSettings> """ key = self.objectName() value = unwrapVariant(settings.value('%s/profile' % key)) if not value: self.reset(force=True) return False profile = value # restore the view type settings for viewType in self.viewTypes(): viewType.restoreGlobalSettings(settings) # restore the profile self.restoreProfile(XViewProfile.fromString(profile)) if not self.views(): self.reset(force=True) return True def reset(self, force=False): """ Clears out all the views and panels and resets the widget to a blank \ parent. :return <bool> """ answer = QtGui.QMessageBox.Yes opts = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No if not force: answer = QtGui.QMessageBox.question( self, 'Reset Layout', 'Are you sure you want to reset?', opts) if answer == QtGui.QMessageBox.No: return widget = self.widget() # we should always have a widget, but double check if not widget: return False # make sure we can close the current view if not widget.close(): return False # reset the system self.takeWidget() # restore a default profile prof = self.defaultProfile() if prof: return prof.restore(self) # otherwise create a new panel else: self.setLocked(False) self.setWidget(XViewPanel(self, False)) self.resetFinished.emit() return True def resetForced(self): return self.reset(force=True) def saveProfile(self): """ Saves the profile for the current state and returns it. :return <XViewProfile> """ return XViewProfile.record(self) def saveSettings(self, settings): """ Records the current structure of the view widget to the inputed \ settings instance. :param settings | <QSettings> """ # record the profile profile = self.saveProfile() key = self.objectName() settings.setValue('%s/profile' % key, wrapVariant(profile.toString())) # record the view type settings for viewType in self.viewTypes(): viewType.saveGlobalSettings(settings) def setCodeScope(self, scope): """ Sets the code execution scope for this widget. If the scope is set to None, then the global execution scope will be used. :param scope | <dict> || None """ self._scope = scope def setCurrent(self): """ Sets this view widget as the current widget in case there are multiple ones. """ for view in self.findChildren(XView): view.setCurrent() def setCustomData(self, key, value): """ Sets the custom data for this instance to the inputed value. :param key | <str> value | <variant> """ self._customData[nativestring(key)] = value def setDefaultProfile(self, profile): """ Sets the default profile for this view to the inputed profile. :param profile | <XViewProfile> """ self._defaultProfile = profile def setHint(self, hint): self._hint = hint def setLocked(self, state): """ Sets the locked state for this view widget. When locked, a user no \ longer has control over editing views and layouts. A view panel with \ a single entry will hide its tab bar, and if it has multiple views, it \ will simply hide the editing buttons. :param state | <bool> """ changed = state != self._locked self._locked = state for panel in self.panels(): panel.setLocked(state) if changed and not self.signalsBlocked(): self.lockToggled.emit(state) def setViewTypes(self, viewTypes, window=None): """ Sets the view types that can be used for this widget. If the optional \ window member is supplied, then the registerToWindow method will be \ called for each view. :param viewTypes | [<sublcass of XView>, ..] window | <QMainWindow> || <QDialog> || None """ if window: for viewType in self._viewTypes: viewType.unregisterFromWindow(window) self._viewTypes = viewTypes[:] self._panelMenu = None self._pluginMenu = None if window: for viewType in viewTypes: viewType.registerToWindow(window) def showMenu(self, point=None): """ Displays the menu for this view widget. :param point | <QPoint> """ menu = self.createMenu(self) menu.exec_(QtGui.QCursor.pos()) menu.deleteLater() def showPanelMenu(self, panel, point=None): """ Creates the panel menu for this view widget. If no point is supplied,\ then the current cursor position will be used. :param panel | <XViewPanel> point | <QPoint> || None """ if not self._panelMenu: self._panelMenu = XViewPanelMenu(self) if point is None: point = QtGui.QCursor.pos() self._panelMenu.setCurrentPanel(panel) self._panelMenu.exec_(point) def showPluginMenu(self, panel, point=None): """ Creates the interface menu for this view widget. If no point is \ supplied, then the current cursor position will be used. :param panel | <XViewPanel> point | <QPoint> || None """ if not self._pluginMenu: self._pluginMenu = XViewPluginMenu(self) if point is None: point = QtGui.QCursor.pos() self._pluginMenu.setCurrentPanel(panel) self._pluginMenu.exec_(point) def showTabMenu(self, panel, point=None): """ Creates the panel menu for this view widget. If no point is supplied,\ then the current cursor position will be used. :param panel | <XViewPanel> point | <QPoint> || None """ if not self._tabMenu: self._tabMenu = XViewTabMenu(self) if point is None: point = QtGui.QCursor.pos() self._tabMenu.setCurrentPanel(panel) self._tabMenu.exec_(point) def toggleLocked(self): """ Toggles whether or not this view is locked """ self.setLocked(not self.isLocked()) def updateCurrentView(self, oldWidget, newWidget): """ Updates the current view widget. :param oldWidget | <QtGui.QWidget> newWidget | <QtGui.QWidget> """ view = projexui.ancestor(newWidget, XView) if view is not None: view.setCurrent() def unregisterViewType(self, cls, window=None): """ Unregisters the view at the given name. If the window option is \ supplied then the unregisterFromWindow method will be called for the \ inputed class. :param cls | <subclass of XView> window | <QMainWindow> || <QDialog> || None :return <bool> changed """ if (cls in self._viewTypes): self._viewTypes.remove(cls) if (window): cls.unregisterFromWindow(window) return True return False def views(self): """ Returns a list of the current views associated with this view widget. :return [<XView>, ..] """ return self.findChildren(XView) def viewAt(self, point): """ Looks up the view at the inputed point. :param point | <QtCore.QPoint> :return <projexui.widgets.xviewwidget.XView> || None """ widget = self.childAt(point) if widget: return projexui.ancestor(widget, XView) else: return None def viewType(self, name): """ Looks up the view class based on the inputd name. :param name | <str> :return <subclass of XView> || None """ for view in self._viewTypes: if view.viewName() == name: return view return None def viewTypes(self): """ Returns a list of all the view types registered for this widget. :return <str> """ return sorted(self._viewTypes, key=lambda x: x.viewName()) x_hint = QtCore.Property(unicode, hint, setHint)
class XOverlayWidget(QtGui.QWidget): finished = QtCore.Signal(int) def __init__(self, parent=None): super(XOverlayWidget, self).__init__(parent) # define custom properties self._centralWidget = None self._result = None self._closable = True self._closeAlignment = QtCore.Qt.AlignTop | QtCore.Qt.AlignRight self._closeButton = XToolButton(self) self._closeButton.setShadowed(True) self._closeButton.setIcon( QtGui.QIcon(resources.find('img/overlay/close.png'))) self._closeButton.setIconSize(QtCore.QSize(24, 24)) self._closeButton.setToolTip('Close') # create the coloring for the overlay palette = self.palette() clr = QtGui.QColor('#222222') clr.setAlpha(210) palette.setColor(palette.Window, clr) self.setPalette(palette) self.setAutoFillBackground(True) # listen to the parents event filter parent.installEventFilter(self) # initialize the widget self.hide() self.move(0, 0) self.resize(parent.size()) self._closeButton.clicked.connect(self.reject) def accept(self): """ Accepts this overlay and exits the modal window. """ self.close() self.setResult(1) self.finished.emit(1) def adjustSize(self): """ Adjusts the size of this widget as the parent resizes. """ # adjust the close button align = self.closeAlignment() if align & QtCore.Qt.AlignTop: y = 6 else: y = self.height() - 38 if align & QtCore.Qt.AlignLeft: x = 6 else: x = self.width() - 38 self._closeButton.move(x, y) # adjust the central widget widget = self.centralWidget() if widget is not None: center = self.rect().center() widget.move(center.x() - widget.width() / 2, center.y() - widget.height() / 2) def closeAlignment(self): """ Returns the alignment for the close button for this overlay widget. :return <QtCore.Qt.Alignment> """ return self._closeAlignment def centralWidget(self): """ Returns the central widget for this overlay. If there is one, then it will be automatically moved with this object. :return <QtGui.QWidget> """ return self._centralWidget def isClosable(self): """ Returns whether or not the user should be able to close this overlay widget. :return <bool> """ return self._closable def keyPressEvent(self, event): """ Exits the modal window on an escape press. :param event | <QtCore.QKeyPressEvent> """ if event.key() == QtCore.Qt.Key_Escape: self.reject() super(XOverlayWidget, self).keyPressEvent(event) def eventFilter(self, object, event): """ Resizes this overlay as the widget resizes. :param object | <QtCore.QObject> event | <QtCore.QEvent> :return <bool> """ if object == self.parent() and event.type() == QtCore.QEvent.Resize: self.resize(event.size()) elif event.type() == QtCore.QEvent.Close: self.setResult(0) return False def exec_(self, autodelete=True): self.setAttribute(QtCore.Qt.WA_DeleteOnClose, False) if self.centralWidget(): QtCore.QTimer.singleShot(0, self.centralWidget().setFocus) loop = QtCore.QEventLoop() while self.isVisible() and not QtCore.QCoreApplication.closingDown(): loop.processEvents() if autodelete: self.deleteLater() return self.result() def reject(self): """ Rejects this overlay and exits the modal window. """ self.close() self.setResult(1) self.finished.emit(1) def result(self): """ Returns the result from this overlay widget. :return <int> """ return int(self._result) def resizeEvent(self, event): """ Handles a resize event for this overlay, centering the central widget if one is found. :param event | <QtCore.QEvent> """ super(XOverlayWidget, self).resizeEvent(event) self.adjustSize() def setCentralWidget(self, widget): """ Sets the central widget for this overlay to the inputed widget. :param widget | <QtGui.QWidget> """ self._centralWidget = widget if widget is not None: widget.setParent(self) widget.installEventFilter(self) # create the drop shadow effect effect = QtGui.QGraphicsDropShadowEffect(self) effect.setColor(QtGui.QColor('black')) effect.setBlurRadius(80) effect.setOffset(0, 0) widget.setGraphicsEffect(effect) def setClosable(self, state): """ Sets whether or not the user should be able to close this overlay widget. :param state | <bool> """ self._closable = state if state: self._closeButton.show() else: self._closeButton.hide() def setCloseAlignment(self, align): """ Sets the alignment for the close button for this overlay widget. :param align | <QtCore.Qt.Alignment> """ self._closeAlignment = align def setResult(self, result): """ Sets the result for this overlay to the inputed value. :param result | <int> """ self._result = result def setVisible(self, state): """ Closes this widget and kills the result. """ super(XOverlayWidget, self).setVisible(state) if not state: self.setResult(0) def showEvent(self, event): """ Ensures this widget is the top-most widget for its parent. :param event | <QtCore.QEvent> """ super(XOverlayWidget, self).showEvent(event) # raise to the top self.raise_() self._closeButton.setVisible(self.isClosable()) widget = self.centralWidget() if widget: center = self.rect().center() start_x = end_x = center.x() - widget.width() / 2 start_y = -widget.height() end_y = center.y() - widget.height() / 2 start = QtCore.QPoint(start_x, start_y) end = QtCore.QPoint(end_x, end_y) # create the movement animation anim = QtCore.QPropertyAnimation(self) anim.setPropertyName('pos') anim.setTargetObject(widget) anim.setStartValue(start) anim.setEndValue(end) anim.setDuration(500) anim.setEasingCurve(QtCore.QEasingCurve.InOutQuad) anim.finished.connect(anim.deleteLater) anim.start() @staticmethod def modal(widget, parent=None, align=QtCore.Qt.AlignTop | QtCore.Qt.AlignRight, blurred=True): """ Creates a modal dialog for this overlay with the inputed widget. If the user accepts the widget, then 1 will be returned, otherwise, 0 will be returned. :param widget | <QtCore.QWidget> """ if parent is None: parent = QtGui.QApplication.instance().activeWindow() overlay = XOverlayWidget(parent) overlay.setAttribute(QtCore.Qt.WA_DeleteOnClose) overlay.setCentralWidget(widget) overlay.setCloseAlignment(align) overlay.show() return overlay
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)