class WelcomeDialog(QDialog): """ A welcome widget shown at startup presenting a series of buttons (actions) for a beginner to choose from. """ triggered = Signal(QAction) def __init__(self, *args, **kwargs): showAtStartup = kwargs.pop("showAtStartup", True) feedbackUrl = kwargs.pop("feedbackUrl", "") super().__init__(*args, **kwargs) self.__triggeredAction = None # type: Optional[QAction] self.__showAtStartupCheck = None self.__mainLayout = None self.__feedbackUrl = None self.__feedbackLabel = None self.setupUi() self.setFeedbackUrl(feedbackUrl) self.setShowAtStartup(showAtStartup) def setupUi(self): self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(0) self.__mainLayout = QVBoxLayout() self.__mainLayout.setContentsMargins(0, 40, 0, 40) self.__mainLayout.setSpacing(65) self.layout().addLayout(self.__mainLayout) self.setStyleSheet(WELCOME_WIDGET_BUTTON_STYLE) dir_path = Path(__file__).resolve() parent_path = dir_path.parent.parent icon_path = f'{str(parent_path)}/icons' guanzhu_box = QVBoxLayout() guanzhu_label = QLabel() guanzhu_pic = QPixmap(icon_path + '/weixin.png') guanzhu_label.setPixmap(guanzhu_pic) guanzhu_text = QLabel('关注我们') guanzhu_box.addWidget(guanzhu_label, alignment=Qt.AlignVCenter | Qt.AlignHCenter) guanzhu_box.addWidget(guanzhu_text, alignment=Qt.AlignVCenter | Qt.AlignHCenter) zan_box = QVBoxLayout() zan_label = QLabel() pixmap = QPixmap(icon_path + '/zan.png') zan_label.setPixmap(pixmap) zan_text = QLabel('赞赏开发者') zan_box.addWidget(zan_label, alignment=Qt.AlignVCenter | Qt.AlignHCenter) zan_box.addWidget(zan_text, alignment=Qt.AlignVCenter | Qt.AlignHCenter) weixinBox = QHBoxLayout() weixinBox.addLayout(guanzhu_box) weixinBox.addLayout(zan_box) self.layout().addLayout(weixinBox) bottom_bar = QWidget(objectName="bottom-bar") bottom_bar_layout = QHBoxLayout() bottom_bar_layout.setContentsMargins(20, 10, 20, 10) bottom_bar.setLayout(bottom_bar_layout) bottom_bar.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum) # self.__showAtStartupCheck = QCheckBox( # self.tr("启动时显示"), bottom_bar, checked=True # ) # self.__showAtStartupCheck = QCheckBox( # self.tr("启动时显示"), bottom_bar, checked=True # ) self.__feedbackLabel = QLabel( textInteractionFlags=Qt.TextBrowserInteraction, openExternalLinks=True, visible=False, ) # bottom_bar_layout.addWidget( # self.__showAtStartupCheck, alignment=Qt.AlignVCenter | Qt.AlignLeft # ) bottom_bar_layout.addWidget(self.__feedbackLabel, alignment=Qt.AlignVCenter | Qt.AlignRight) self.layout().addWidget(bottom_bar, alignment=Qt.AlignBottom, stretch=1) self.setSizeGripEnabled(False) self.setFixedSize(620, 590) def setShowAtStartup(self, show): # type: (bool) -> None """ Set the 'Show at startup' check box state. """ pass # if self.__showAtStartupCheck.isChecked() != show: # # self.__showAtStartupCheck.setChecked(show) def showAtStartup(self): # type: () -> bool """ Return the 'Show at startup' check box state. """ # return self.__showAtStartupCheck.isChecked() return True def setFeedbackUrl(self, url): # type: (str) -> None """ Set an 'feedback' url. When set a link is displayed in the bottom row. """ self.__feedbackUrl = url if url: text = self.tr("帮助我们改进!") self.__feedbackLabel.setText('<a href="{url}">{text}</a>'.format( url=url, text=escape(text))) else: self.__feedbackLabel.setText("") self.__feedbackLabel.setVisible(bool(url)) def addRow(self, actions, background="light-orange"): """Add a row with `actions`. """ count = self.__mainLayout.count() self.insertRow(count, actions, background) def insertRow(self, index, actions, background="light-orange"): # type: (int, Iterable[QAction], Union[QColor, str]) -> None """Insert a row with `actions` at `index`. """ widget = QWidget(objectName="icon-row") layout = QHBoxLayout() layout.setContentsMargins(40, 0, 40, 0) layout.setSpacing(65) widget.setLayout(layout) self.__mainLayout.insertWidget(index, widget, stretch=10, alignment=Qt.AlignCenter) for i, action in enumerate(actions): self.insertAction(index, i, action, background) def insertAction(self, row, index, action, background="light-orange"): """Insert `action` in `row` in position `index`. """ button = self.createButton(action, background) self.insertButton(row, index, button) def insertButton(self, row, index, button): # type: (int, int, QToolButton) -> None """Insert `button` in `row` in position `index`. """ item = self.__mainLayout.itemAt(row) layout = item.widget().layout() layout.insertWidget(index, button) button.triggered.connect(self.__on_actionTriggered) def createButton(self, action, background="light-orange"): # type: (QAction, Union[QColor, str]) -> QToolButton """Create a tool button for action. """ button = WelcomeActionButton(self) button.setDefaultAction(action) button.setText(action.iconText()) button.setIcon(decorate_welcome_icon(action.icon(), background)) button.setToolTip(action.toolTip()) button.setFixedSize(100, 100) button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) button.setVisible(action.isVisible()) font = QFont(button.font()) font.setPointSize(13) button.setFont(font) return button def buttonAt(self, i, j): # type: (int, int) -> QToolButton """Return the button at i-t row and j-th column. """ item = self.__mainLayout.itemAt(i) row = item.widget() item = row.layout().itemAt(j) return item.widget() def triggeredAction(self): # type: () -> Optional[QAction] """Return the action that was triggered by the user. """ return self.__triggeredAction def showEvent(self, event): # Clear the triggered action before show. self.__triggeredAction = None super().showEvent(event) def __on_actionTriggered(self, action): # type: (QAction) -> None """Called when the button action is triggered. """ self.triggered.emit(action) self.__triggeredAction = action
class SelectEncodingsWidget(QWidget): """ Popup widget for selecting a set of text encodings for use in other parts of the GUI. """ def __init__(self, *args, headingText="", **kwargs): super().__init__(*args, **kwargs) self.setLayout(QVBoxLayout()) self.__top_label = QLabel(headingText, visible=bool(headingText), objectName="-top-heading-text") self.__top_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.__model = model = encodings_model() self.__model.setParent(self) self.__view = view = EncodingsView( self, uniformItemSizes=True, editTriggers=QListView.NoEditTriggers) self.__view.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) view.setModel(model) self.layout().addWidget(self.__top_label) self.layout().addWidget(view) buttons = QDialogButtonBox( standardButtons=QDialogButtonBox.RestoreDefaults) b = buttons.addButton("Select all", QDialogButtonBox.ActionRole) b.clicked.connect(self.selectAll) b = buttons.button(QDialogButtonBox.RestoreDefaults) b.clicked.connect(self.reset) self.layout().addWidget(buttons) self.setAttribute(Qt.WA_MacSmallSize) def headingText(self): # type: () -> str return self.__top_label.text() def setHeadingText(self, text): # type: (str) -> None self.__top_label.setText(text) self.__top_label.setVisible(bool(text)) def setModel(self, model): if self.__model is not None: if self.__model.parent() is self: self.__model.deleteLater() self.__model = None self.__view.setModel(model) self.__model = model def model(self): return self.__model def selectedEncodings(self): # type: () -> List[str] """ Return a list of currently selected (checked) encodings. """ model = self.__model res = [] for i in range(model.rowCount()): data = model.itemData(model.index(i, 0)) if data.get(Qt.CheckStateRole) == Qt.Checked and \ EncodingNameRole in data: res.append(data[EncodingNameRole]) return res @Slot() def selectAll(self): """ Select all encodings. """ model = self.__model for i in range(model.rowCount()): item = model.item(i) item.setCheckState(Qt.Checked) @Slot() def clearAll(self): """ Clear (uncheck) all encodings. """ model = self.__model for i in range(model.rowCount()): item = model.item(i) item.setCheckState(Qt.Checked) @Slot() def reset(self): """ Reset the encodings model to the default selected set. """ model = self.__model for i in range(model.rowCount()): item = model.item(i) co = item.data(CodecInfoRole) if isinstance(co, codecs.CodecInfo): state = co.name in DEFAULT_ENCODINGS item.setCheckState(Qt.Checked if state else Qt.Unchecked)
class MessagesWidget(QWidget): """ An iconified multiple message display area. `MessagesWidget` displays a short message along with an icon. If there are multiple messages they are summarized. The user can click on the widget to display the full message text in a popup view. """ #: Signal emitted when an embedded html link is clicked #: (if `openExternalLinks` is `False`). linkActivated = Signal(str) #: Signal emitted when an embedded html link is hovered. linkHovered = Signal(str) Severity = Severity #: General informative message. Information = Severity.Information #: A warning message severity. Warning = Severity.Warning #: An error message severity. Error = Severity.Error Message = Message def __init__(self, parent=None, openExternalLinks=False, **kwargs): kwargs.setdefault( "sizePolicy", QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) super().__init__(parent, **kwargs) self.__openExternalLinks = openExternalLinks # type: bool self.__messages = OrderedDict() # type: Dict[Hashable, Message] #: The full (joined all messages text - rendered as html), displayed #: in a tooltip. self.__fulltext = "" #: The full text displayed in a popup. Is empty if the message is #: short self.__popuptext = "" #: Leading icon self.__iconwidget = IconWidget( sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) #: Inline message text self.__textlabel = QLabel( wordWrap=False, textInteractionFlags=Qt.LinksAccessibleByMouse, openExternalLinks=self.__openExternalLinks, sizePolicy=QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum), ) #: Indicator that extended contents are accessible with a click on the #: widget. self.__popupicon = QLabel( sizePolicy=QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum), text="\N{VERTICAL ELLIPSIS}", visible=False, ) self.__textlabel.linkActivated.connect(self.linkActivated) self.__textlabel.linkHovered.connect(self.linkHovered) self.setLayout(QHBoxLayout()) self.layout().setContentsMargins(2, 1, 2, 1) self.layout().setSpacing(0) self.layout().addWidget(self.__iconwidget) self.layout().addSpacing(4) self.layout().addWidget(self.__textlabel) self.layout().addWidget(self.__popupicon) self.__textlabel.setAttribute(Qt.WA_MacSmallSize) def sizeHint(self): sh = super().sizeHint() h = self.style().pixelMetric(QStyle.PM_SmallIconSize) return sh.expandedTo(QSize(0, h + 2)) def openExternalLinks(self): # type: () -> bool """ If True then linkActivated signal will be emitted when the user clicks on an html link in a message, otherwise links are opened using `QDesktopServices.openUrl` """ return self.__openExternalLinks def setOpenExternalLinks(self, state): # type: (bool) -> None """ """ # TODO: update popup if open self.__openExternalLinks = state self.__textlabel.setOpenExternalLinks(state) def setMessage(self, message_id, message): # type: (Hashable, Message) -> None """ Add a `message` for `message_id` to the current display. Note ---- Set an empty `Message` instance to clear the message display but retain the relative ordering in the display should a message for `message_id` reactivate. """ self.__messages[message_id] = message self.__update() def removeMessage(self, message_id): # type: (Hashable) -> None """ Remove message for `message_id` from the display. Note ---- Setting an empty `Message` instance will also clear the display, however the relative ordering of the messages will be retained, should the `message_id` 'reactivate'. """ del self.__messages[message_id] self.__update() def setMessages(self, messages): # type: (Union[Iterable[Tuple[Hashable, Message]], Dict[Hashable, Message]]) -> None """ Set multiple messages in a single call. """ messages = OrderedDict(messages) self.__messages.update(messages) self.__update() def clear(self): # type: () -> None """ Clear all messages. """ self.__messages.clear() self.__update() def messages(self): # type: () -> List[Message] return list(self.__messages.values()) def summarize(self): # type: () -> Message """ Summarize all the messages into a single message. """ messages = [m for m in self.__messages.values() if not m.isEmpty()] if messages: return summarize(messages) else: return Message() def __update(self): """ Update the current display state. """ self.ensurePolished() summary = self.summarize() icon = message_icon(summary) self.__iconwidget.setIcon(icon) self.__iconwidget.setVisible(not (summary.isEmpty() or icon.isNull())) self.__textlabel.setTextFormat(summary.textFormat) self.__textlabel.setText(summary.text) messages = [m for m in self.__messages.values() if not m.isEmpty()] if messages: messages = sorted(messages, key=attrgetter("severity"), reverse=True) fulltext = "<hr/>".join(m.asHtml() for m in messages) else: fulltext = "" self.__fulltext = fulltext self.setToolTip(fulltext) def is_short(m): return not (m.informativeText or m.detailedText) if not messages or len(messages) == 1 and is_short(messages[0]): self.__popuptext = "" else: self.__popuptext = fulltext self.__popupicon.setVisible(bool(self.__popuptext)) self.layout().activate() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: if self.__popuptext: popup = QMenu(self) label = QLabel( self, textInteractionFlags=Qt.TextBrowserInteraction, openExternalLinks=self.__openExternalLinks, text=self.__popuptext, ) label.linkActivated.connect(self.linkActivated) label.linkHovered.connect(self.linkHovered) action = QWidgetAction(popup) action.setDefaultWidget(label) popup.addAction(action) popup.popup(event.globalPos(), action) event.accept() return else: super().mousePressEvent(event) def enterEvent(self, event): super().enterEvent(event) self.update() def leaveEvent(self, event): super().leaveEvent(event) self.update() def changeEvent(self, event): super().changeEvent(event) self.update() def paintEvent(self, event): opt = QStyleOption() opt.initFrom(self) if not self.__popupicon.isVisible(): return if not (opt.state & QStyle.State_MouseOver or opt.state & QStyle.State_HasFocus): return palette = opt.palette # type: QPalette if opt.state & QStyle.State_HasFocus: pen = QPen(palette.color(QPalette.Highlight)) else: pen = QPen(palette.color(QPalette.Dark)) if (self.__fulltext and opt.state & QStyle.State_MouseOver and opt.state & QStyle.State_Active): g = QLinearGradient() g.setCoordinateMode(QLinearGradient.ObjectBoundingMode) base = palette.color(QPalette.Window) base.setAlpha(90) g.setColorAt(0, base.lighter(200)) g.setColorAt(0.6, base) g.setColorAt(1.0, base.lighter(200)) brush = QBrush(g) else: brush = QBrush(Qt.NoBrush) p = QPainter(self) p.setBrush(brush) p.setPen(pen) p.drawRect(opt.rect.adjusted(0, 0, -1, -1))
class MessagesWidget(QWidget): """ An iconified multiple message display area. `MessagesWidget` displays a short message along with an icon. If there are multiple messages they are summarized. The user can click on the widget to display the full message text in a popup view. """ #: Signal emitted when an embedded html link is clicked #: (if `openExternalLinks` is `False`). linkActivated = Signal(str) #: Signal emitted when an embedded html link is hovered. linkHovered = Signal(str) Severity = Severity #: General informative message. Information = Severity.Information #: A warning message severity. Warning = Severity.Warning #: An error message severity. Error = Severity.Error Message = Message def __init__(self, parent=None, openExternalLinks=False, defaultStyleSheet="", **kwargs): kwargs.setdefault( "sizePolicy", QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) ) super().__init__(parent, **kwargs) self.__openExternalLinks = openExternalLinks # type: bool self.__messages = OrderedDict() # type: Dict[Hashable, Message] #: The full (joined all messages text - rendered as html), displayed #: in a tooltip. self.__fulltext = "" #: The full text displayed in a popup. Is empty if the message is #: short self.__popuptext = "" #: Leading icon self.__iconwidget = IconWidget( sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) ) #: Inline message text self.__textlabel = QLabel( wordWrap=False, textInteractionFlags=Qt.LinksAccessibleByMouse, openExternalLinks=self.__openExternalLinks, sizePolicy=QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) ) #: Indicator that extended contents are accessible with a click on the #: widget. self.__popupicon = QLabel( sizePolicy=QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum), text="\N{VERTICAL ELLIPSIS}", visible=False, ) self.__textlabel.linkActivated.connect(self.linkActivated) self.__textlabel.linkHovered.connect(self.linkHovered) self.setLayout(QHBoxLayout()) self.layout().setContentsMargins(2, 1, 2, 1) self.layout().setSpacing(0) self.layout().addWidget(self.__iconwidget) self.layout().addSpacing(4) self.layout().addWidget(self.__textlabel) self.layout().addWidget(self.__popupicon) self.__textlabel.setAttribute(Qt.WA_MacSmallSize) self.__defaultStyleSheet = defaultStyleSheet def sizeHint(self): sh = super().sizeHint() h = self.style().pixelMetric(QStyle.PM_SmallIconSize) if all(m.isEmpty() for m in self.messages()): sh.setWidth(0) return sh.expandedTo(QSize(0, h + 2)) def minimumSizeHint(self): msh = super().minimumSizeHint() h = self.style().pixelMetric(QStyle.PM_SmallIconSize) if all(m.isEmpty() for m in self.messages()): msh.setWidth(0) else: msh.setWidth(h + 2) return msh.expandedTo(QSize(0, h + 2)) def setOpenExternalLinks(self, state): # type: (bool) -> None """ If `True` then `linkActivated` signal will be emitted when the user clicks on an html link in a message, otherwise links are opened using `QDesktopServices.openUrl` """ # TODO: update popup if open self.__openExternalLinks = state self.__textlabel.setOpenExternalLinks(state) def openExternalLinks(self): # type: () -> bool """ """ return self.__openExternalLinks def setDefaultStyleSheet(self, css): # type: (str) -> None """ Set a default css to apply to the rendered text. Parameters ---------- css : str A css style sheet as supported by Qt's Rich Text support. Note ---- Not to be confused with `QWidget.styleSheet` See Also -------- `Supported HTML Subset`_ .. _`Supported HTML Subset`: http://doc.qt.io/qt-5/richtext-html-subset.html """ if self.__defaultStyleSheet != css: self.__defaultStyleSheet = css self.__update() def defaultStyleSheet(self): """ Returns ------- css : str The current style sheet """ return self.__defaultStyleSheet def setMessage(self, message_id, message): # type: (Hashable, Message) -> None """ Add a `message` for `message_id` to the current display. Note ---- Set an empty `Message` instance to clear the message display but retain the relative ordering in the display should a message for `message_id` reactivate. """ self.__messages[message_id] = message self.__update() def removeMessage(self, message_id): # type: (Hashable) -> None """ Remove message for `message_id` from the display. Note ---- Setting an empty `Message` instance will also clear the display, however the relative ordering of the messages will be retained, should the `message_id` 'reactivate'. """ del self.__messages[message_id] self.__update() def setMessages(self, messages): # type: (Union[Iterable[Tuple[Hashable, Message]], Dict[Hashable, Message]]) -> None """ Set multiple messages in a single call. """ messages = OrderedDict(messages) self.__messages.update(messages) self.__update() def clear(self): # type: () -> None """ Clear all messages. """ self.__messages.clear() self.__update() def messages(self): # type: () -> List[Message] """ Return all set messages. Returns ------- messages: `List[Message]` """ return list(self.__messages.values()) def summarize(self): # type: () -> Message """ Summarize all the messages into a single message. """ messages = [m for m in self.__messages.values() if not m.isEmpty()] if messages: return summarize(messages) else: return Message() @staticmethod def __styled(css, html): # Prepend css style sheet before a html fragment. if css.strip(): return "<style>\n" + escape(css) + "\n</style>\n" + html else: return html def __update(self): """ Update the current display state. """ self.ensurePolished() summary = self.summarize() icon = message_icon(summary) self.__iconwidget.setIcon(icon) self.__iconwidget.setVisible(not (summary.isEmpty() or icon.isNull())) self.__textlabel.setTextFormat(summary.textFormat) self.__textlabel.setText(summary.text) self.__textlabel.setVisible(bool(summary.text)) messages = [m for m in self.__messages.values() if not m.isEmpty()] if messages: messages = sorted(messages, key=attrgetter("severity"), reverse=True) fulltext = "<hr/>".join(m.asHtml() for m in messages) else: fulltext = "" self.__fulltext = fulltext self.setToolTip(self.__styled(self.__defaultStyleSheet, fulltext)) def is_short(m): return not (m.informativeText or m.detailedText) if not messages or len(messages) == 1 and is_short(messages[0]): self.__popuptext = "" else: self.__popuptext = fulltext self.__popupicon.setVisible(bool(self.__popuptext)) self.layout().activate() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: if self.__popuptext: popup = QMenu(self) label = QLabel( self, textInteractionFlags=Qt.TextBrowserInteraction, openExternalLinks=self.__openExternalLinks, ) label.setText(self.__styled(self.__defaultStyleSheet, self.__popuptext)) label.linkActivated.connect(self.linkActivated) label.linkHovered.connect(self.linkHovered) action = QWidgetAction(popup) action.setDefaultWidget(label) popup.addAction(action) popup.popup(event.globalPos(), action) event.accept() return else: super().mousePressEvent(event) def enterEvent(self, event): super().enterEvent(event) self.update() def leaveEvent(self, event): super().leaveEvent(event) self.update() def changeEvent(self, event): super().changeEvent(event) self.update() def paintEvent(self, event): opt = QStyleOption() opt.initFrom(self) if not self.__popupicon.isVisible(): return if not (opt.state & QStyle.State_MouseOver or opt.state & QStyle.State_HasFocus): return palette = opt.palette # type: QPalette if opt.state & QStyle.State_HasFocus: pen = QPen(palette.color(QPalette.Highlight)) else: pen = QPen(palette.color(QPalette.Dark)) if self.__fulltext and \ opt.state & QStyle.State_MouseOver and \ opt.state & QStyle.State_Active: g = QLinearGradient() g.setCoordinateMode(QLinearGradient.ObjectBoundingMode) base = palette.color(QPalette.Window) base.setAlpha(90) g.setColorAt(0, base.lighter(200)) g.setColorAt(0.6, base) g.setColorAt(1.0, base.lighter(200)) brush = QBrush(g) else: brush = QBrush(Qt.NoBrush) p = QPainter(self) p.setBrush(brush) p.setPen(pen) p.drawRect(opt.rect.adjusted(0, 0, -1, -1))
class MessageWidget(QWidget): """ A widget displaying a simple message to the user. This is an alternative to a full QMessageBox intended for inline modeless messages. [[icon] {Message text} (Ok) (Cancel)] """ #: Emitted when a button with the AcceptRole is clicked accepted = Signal() #: Emitted when a button with the RejectRole is clicked rejected = Signal() #: Emitted when a button with the HelpRole is clicked helpRequested = Signal() #: Emitted when a button is clicked clicked = Signal(QAbstractButton) class StandardButton(enum.IntEnum): NoButton, Ok, Close, Help = 0x0, 0x1, 0x2, 0x4 NoButton, Ok, Close, Help = list(StandardButton) class ButtonRole(enum.IntEnum): InvalidRole, AcceptRole, RejectRole, HelpRole = 0, 1, 2, 3 InvalidRole, AcceptRole, RejectRole, HelpRole = list(ButtonRole) _Button = namedtuple("_Button", ["button", "role", "stdbutton"]) def __init__(self, parent=None, icon=QIcon(), text="", wordWrap=False, textFormat=Qt.AutoText, standardButtons=NoButton, **kwargs): super().__init__(parent, **kwargs) self.__text = text self.__icon = QIcon() self.__wordWrap = wordWrap self.__standardButtons = MessageWidget.NoButton self._buttons = [] layout = QHBoxLayout() layout.setContentsMargins(8, 0, 8, 0) self.__iconlabel = QLabel(objectName="icon-label") self.__iconlabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.__textlabel = QLabel(objectName="text-label", text=text, wordWrap=wordWrap, textFormat=textFormat) if sys.platform == "darwin": self.__textlabel.setAttribute(Qt.WA_MacSmallSize) layout.addWidget(self.__iconlabel) layout.addWidget(self.__textlabel) self.setLayout(layout) self.setIcon(icon) self.setStandardButtons(standardButtons) def setText(self, text): """ Set the current message text. :type message: str """ if self.__text != text: self.__text = text self.__textlabel.setText(text) def text(self): """ Return the current message text. :rtype: str """ return self.__text def setIcon(self, icon): """ Set the message icon. :type icon: QIcon | QPixmap | QString | QStyle.StandardPixmap """ if isinstance(icon, QStyle.StandardPixmap): icon = self.style().standardIcon(icon) else: icon = QIcon(icon) if self.__icon != icon: self.__icon = QIcon(icon) if not self.__icon.isNull(): size = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self) pm = self.__icon.pixmap(QSize(size, size)) else: pm = QPixmap() self.__iconlabel.setPixmap(pm) self.__iconlabel.setVisible(not pm.isNull()) def icon(self): """ Return the current icon. :rtype: QIcon """ return QIcon(self.__icon) def setWordWrap(self, wordWrap): """ Set the message text wrap property :type wordWrap: bool """ if self.__wordWrap != wordWrap: self.__wordWrap = wordWrap self.__textlabel.setWordWrap(wordWrap) def wordWrap(self): """ Return the message text wrap property. :rtype: bool """ return self.__wordWrap def setTextFormat(self, textFormat): """ Set message text format :type textFormat: Qt.TextFormat """ self.__textlabel.setTextFormat(textFormat) def textFormat(self): """ Return the message text format. :rtype: Qt.TextFormat """ return self.__textlabel.textFormat() def changeEvent(self, event): # reimplemented if event.type() == 177: # QEvent.MacSizeChange: ... super().changeEvent(event) def setStandardButtons(self, buttons): for button in MessageWidget.StandardButton: existing = self.button(button) if button & buttons and existing is None: self.addButton(button) elif existing is not None: self.removeButton(existing) def standardButtons(self): return functools.reduce( operator.ior, (slot.stdbutton for slot in self._buttons if slot.stdbutton is not None), MessageWidget.NoButton) def addButton(self, button, *rolearg): """ addButton(QAbstractButton, ButtonRole) addButton(str, ButtonRole) addButton(StandardButton) Add and return a button """ stdbutton = None if isinstance(button, QAbstractButton): if len(rolearg) != 1: raise TypeError("Wrong number of arguments for " "addButton(QAbstractButton, role)") role = rolearg[0] elif isinstance(button, MessageWidget.StandardButton): if len(rolearg) != 0: raise TypeError("Wrong number of arguments for " "addButton(StandardButton)") stdbutton = button if button == MessageWidget.Ok: role = MessageWidget.AcceptRole button = QPushButton("Ok", default=False, autoDefault=False) elif button == MessageWidget.Close: role = MessageWidget.RejectRole # button = QPushButton( # default=False, autoDefault=False, flat=True, # icon=QIcon(self.style().standardIcon( # QStyle.SP_TitleBarCloseButton))) button = SimpleButton(icon=QIcon(self.style().standardIcon( QStyle.SP_TitleBarCloseButton))) elif button == MessageWidget.Help: role = MessageWidget.HelpRole button = QPushButton("Help", default=False, autoDefault=False) elif isinstance(button, str): if len(rolearg) != 1: raise TypeError("Wrong number of arguments for " "addButton(str, ButtonRole)") role = rolearg[0] button = QPushButton(button, default=False, autoDefault=False) if sys.platform == "darwin": button.setAttribute(Qt.WA_MacSmallSize) self._buttons.append(MessageWidget._Button(button, role, stdbutton)) button.clicked.connect(self._button_clicked) self._relayout() return button def removeButton(self, button): """ Remove a `button`. :type button: QAbstractButton """ slot = [s for s in self._buttons if s.button is button] if slot: slot = slot[0] self._buttons.remove(slot) self.layout().removeWidget(slot.button) slot.button.setParent(None) def buttonRole(self, button): """ Return the ButtonRole for button :type button: QAbstractButton """ for slot in self._buttons: if slot.button is button: return slot.role else: return MessageWidget.InvalidRole def button(self, standardButton): """ Return the button for the StandardButton. :type standardButton: StandardButton """ for slot in self._buttons: if slot.stdbutton == standardButton: return slot.button else: return None def _button_clicked(self): button = self.sender() role = self.buttonRole(button) self.clicked.emit(button) if role == MessageWidget.AcceptRole: self.accepted.emit() self.close() elif role == MessageWidget.RejectRole: self.rejected.emit() self.close() elif role == MessageWidget.HelpRole: self.helpRequested.emit() def _relayout(self): for slot in self._buttons: self.layout().removeWidget(slot.button) order = { MessageOverlayWidget.HelpRole: 0, MessageOverlayWidget.AcceptRole: 2, MessageOverlayWidget.RejectRole: 3, } orderd = sorted(self._buttons, key=lambda slot: order.get(slot.role, -1)) prev = self.__textlabel for slot in orderd: self.layout().addWidget(slot.button) QWidget.setTabOrder(prev, slot.button)
class PreviewBrowser(QWidget): """A Preview Browser for recent/premade scheme selection. """ # Emitted when the current previewed item changes currentIndexChanged = Signal(int) # Emitted when an item is double clicked in the preview list. activated = Signal(int) def __init__(self, *args, heading="", previewMargins=12, **kwargs): super().__init__(*args) self.__model = None self.__currentIndex = -1 self.__template = DESCRIPTION_TEMPLATE self.__margin = previewMargins self.__setupUi() self.setHeading(heading) def __setupUi(self): vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) top_layout = QVBoxLayout(objectName="top-layout") margin = self.__margin top_layout.setContentsMargins(margin, margin, margin, margin) # Optional heading label self.__heading = QLabel( self, objectName="heading", visible=False ) # Horizontal row with full text description and a large preview # image. hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) self.__label = QLabel( self, objectName="description-label", wordWrap=True, alignment=Qt.AlignTop | Qt.AlignLeft ) self.__label.setWordWrap(True) self.__label.setFixedSize(220, PREVIEW_SIZE[1]) self.__label.setMinimumWidth(PREVIEW_SIZE[0] // 2) self.__label.setMaximumHeight(PREVIEW_SIZE[1]) self.__image = QSvgWidget(self, objectName="preview-image") self.__image.setFixedSize(*PREVIEW_SIZE) self.__imageFrame = DropShadowFrame(self) self.__imageFrame.setWidget(self.__image) hlayout.addWidget(self.__label) hlayout.addWidget(self.__image) # Path text below the description and image path_layout = QHBoxLayout() path_layout.setContentsMargins(0, 0, 0, 0) path_label = QLabel("<b>{0!s}</b>".format(self.tr("Path:")), self, objectName="path-label") self.__path = TextLabel(self, objectName="path-text") path_layout.addWidget(path_label) path_layout.addWidget(self.__path) top_layout.addWidget(self.__heading) top_layout.addLayout(hlayout) top_layout.addLayout(path_layout) vlayout.addLayout(top_layout) # An list view with small preview icons. self.__previewList = LinearIconView( objectName="preview-list-view", wordWrap=True ) self.__previewList.doubleClicked.connect(self.__onDoubleClicked) vlayout.addWidget(self.__previewList) self.setLayout(vlayout) def setHeading(self, text): self.__heading.setVisible(bool(text)) self.__heading.setText(text) def setPreviewMargins(self, margin): # type: (int) -> None """ Set the left, top and right margins of the top widget part (heading and description) Parameters ---------- margin : int Margin """ if margin != self.__margin: layout = self.layout().itemAt(0).layout() assert isinstance(layout, QVBoxLayout) assert layout.objectName() == "top-layout" layout.setContentsMargins(margin, margin, margin, 0) def setModel(self, model): """Set the item model for preview. """ if self.__model != model: if self.__model: s_model = self.__previewList.selectionModel() s_model.selectionChanged.disconnect(self.__onSelectionChanged) self.__model.dataChanged.disconnect(self.__onDataChanged) self.__model = model self.__previewList.setModel(model) if model: s_model = self.__previewList.selectionModel() s_model.selectionChanged.connect(self.__onSelectionChanged) self.__model.dataChanged.connect(self.__onDataChanged) if model and model.rowCount(): self.setCurrentIndex(0) def model(self): """Return the item model. """ return self.__model def setPreviewDelegate(self, delegate): """Set the delegate to render the preview images. """ raise NotImplementedError def setDescriptionTemplate(self, template): self.__template = template self.__update() def setCurrentIndex(self, index): """Set the selected preview item index. """ if self.__model is not None and self.__model.rowCount(): index = min(index, self.__model.rowCount() - 1) index = self.__model.index(index, 0) sel_model = self.__previewList.selectionModel() # This emits selectionChanged signal and triggers # __onSelectionChanged, currentIndex is updated there. sel_model.select(index, sel_model.ClearAndSelect) elif self.__currentIndex != -1: self.__currentIndex = -1 self.__update() self.currentIndexChanged.emit(-1) def currentIndex(self): """Return the current selected index. """ return self.__currentIndex def __onSelectionChanged(self, *args): """Selected item in the preview list has changed. Set the new description and large preview image. """ rows = self.__previewList.selectedIndexes() if rows: index = rows[0] self.__currentIndex = index.row() else: index = QModelIndex() self.__currentIndex = -1 self.__update() self.currentIndexChanged.emit(self.__currentIndex) def __onDataChanged(self, topleft, bottomRight): """Data changed, update the preview if current index in the changed range. """ if self.__currentIndex <= topleft.row() and \ self.__currentIndex >= bottomRight.row(): self.__update() def __onDoubleClicked(self, index): """Double click on an item in the preview item list. """ self.activated.emit(index.row()) def __update(self): """Update the current description. """ if self.__currentIndex != -1: index = self.model().index(self.__currentIndex, 0) else: index = QModelIndex() if not index.isValid(): description = "" name = "" path = "" svg = NO_PREVIEW_SVG else: description = index.data(Qt.WhatsThisRole) if description: description = description else: description = "No description." description = escape(description) description = description.replace("\n", "<br/>") name = index.data(Qt.DisplayRole) if name: name = name else: name = "Untitled" name = escape(name) path = str(index.data(Qt.StatusTipRole)) svg = str(index.data(previewmodel.ThumbnailSVGRole)) desc_text = self.__template.format(description=description, name=name) self.__label.setText(desc_text) self.__path.setText(contractuser(path)) if not svg: svg = NO_PREVIEW_SVG if svg: self.__image.load(QByteArray(svg.encode("utf-8")))
class MessageWidget(QWidget): """ A widget displaying a simple message to the user. This is an alternative to a full QMessageBox intended for inline modeless messages. [[icon] {Message text} (Ok) (Cancel)] """ #: Emitted when a button with the AcceptRole is clicked accepted = Signal() #: Emitted when a button with the RejectRole is clicked rejected = Signal() #: Emitted when a button with the HelpRole is clicked helpRequested = Signal() #: Emitted when a button is clicked clicked = Signal(QAbstractButton) class StandardButton(enum.IntEnum): NoButton, Ok, Close, Help = 0x0, 0x1, 0x2, 0x4 NoButton, Ok, Close, Help = list(StandardButton) class ButtonRole(enum.IntEnum): InvalidRole, AcceptRole, RejectRole, HelpRole = 0, 1, 2, 3 InvalidRole, AcceptRole, RejectRole, HelpRole = list(ButtonRole) _Button = namedtuple("_Button", ["button", "role", "stdbutton"]) def __init__(self, parent=None, icon=QIcon(), text="", wordWrap=False, textFormat=Qt.AutoText, standardButtons=NoButton, **kwargs): super().__init__(parent, **kwargs) self.__text = text self.__icon = QIcon() self.__wordWrap = wordWrap self.__standardButtons = MessageWidget.NoButton self.__buttons = [] layout = QHBoxLayout() layout.setContentsMargins(8, 0, 8, 0) self.__iconlabel = QLabel(objectName="icon-label") self.__iconlabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.__textlabel = QLabel(objectName="text-label", text=text, wordWrap=wordWrap, textFormat=textFormat) if sys.platform == "darwin": self.__textlabel.setAttribute(Qt.WA_MacSmallSize) layout.addWidget(self.__iconlabel) layout.addWidget(self.__textlabel) self.setLayout(layout) self.setIcon(icon) self.setStandardButtons(standardButtons) def setText(self, text): """ Set the current message text. :type message: str """ if self.__text != text: self.__text = text self.__textlabel.setText(text) def text(self): """ Return the current message text. :rtype: str """ return self.__text def setIcon(self, icon): """ Set the message icon. :type icon: QIcon | QPixmap | QString | QStyle.StandardPixmap """ if isinstance(icon, QStyle.StandardPixmap): icon = self.style().standardIcon(icon) else: icon = QIcon(icon) if self.__icon != icon: self.__icon = QIcon(icon) if not self.__icon.isNull(): size = self.style().pixelMetric( QStyle.PM_SmallIconSize, None, self) pm = self.__icon.pixmap(QSize(size, size)) else: pm = QPixmap() self.__iconlabel.setPixmap(pm) self.__iconlabel.setVisible(not pm.isNull()) def icon(self): """ Return the current icon. :rtype: QIcon """ return QIcon(self.__icon) def setWordWrap(self, wordWrap): """ Set the message text wrap property :type wordWrap: bool """ if self.__wordWrap != wordWrap: self.__wordWrap = wordWrap self.__textlabel.setWordWrap(wordWrap) def wordWrap(self): """ Return the message text wrap property. :rtype: bool """ return self.__wordWrap def setTextFormat(self, textFormat): """ Set message text format :type textFormat: Qt.TextFormat """ self.__textlabel.setTextFormat(textFormat) def textFormat(self): """ Return the message text format. :rtype: Qt.TextFormat """ return self.__textlabel.textFormat() def changeEvent(self, event): # reimplemented if event.type() == 177: # QEvent.MacSizeChange: ... super().changeEvent(event) def setStandardButtons(self, buttons): for button in MessageWidget.StandardButton: existing = self.button(button) if button & buttons and existing is None: self.addButton(button) elif existing is not None: self.removeButton(existing) def standardButtons(self): return functools.reduce( operator.ior, (slot.stdbutton for slot in self.__buttons if slot.stdbutton is not None), MessageWidget.NoButton) def addButton(self, button, *rolearg): """ addButton(QAbstractButton, ButtonRole) addButton(str, ButtonRole) addButton(StandardButton) Add and return a button """ stdbutton = None if isinstance(button, QAbstractButton): if len(rolearg) != 1: raise TypeError("Wrong number of arguments for " "addButton(QAbstractButton, role)") role = rolearg[0] elif isinstance(button, MessageWidget.StandardButton): if len(rolearg) != 0: raise TypeError("Wrong number of arguments for " "addButton(StandardButton)") stdbutton = button if button == MessageWidget.Ok: role = MessageWidget.AcceptRole button = QPushButton("Ok", default=False, autoDefault=False) elif button == MessageWidget.Close: role = MessageWidget.RejectRole # button = QPushButton( # default=False, autoDefault=False, flat=True, # icon=QIcon(self.style().standardIcon( # QStyle.SP_TitleBarCloseButton))) button = SimpleButton( icon=QIcon(self.style().standardIcon( QStyle.SP_TitleBarCloseButton))) elif button == MessageWidget.Help: role = MessageWidget.HelpRole button = QPushButton("Help", default=False, autoDefault=False) elif isinstance(button, str): if len(rolearg) != 1: raise TypeError("Wrong number of arguments for " "addButton(str, ButtonRole)") role = rolearg[0] button = QPushButton(button, default=False, autoDefault=False) if sys.platform == "darwin": button.setAttribute(Qt.WA_MacSmallSize) self.__buttons.append(MessageWidget._Button(button, role, stdbutton)) button.clicked.connect(self.__button_clicked) self.__relayout() return button def removeButton(self, button): """ Remove a `button`. :type button: QAbstractButton """ slot = [s for s in self.__buttons if s.button is button] if slot: slot = slot[0] self.__buttons.remove(slot) self.layout().removeWidget(slot.button) slot.button.setParent(None) def buttonRole(self, button): """ Return the ButtonRole for button :type button: QAbsstractButton """ for slot in self.__buttons: if slot.button is button: return slot.role else: return MessageWidget.InvalidRole def button(self, standardButton): """ Return the button for the StandardButton. :type standardButton: StandardButton """ for slot in self.__buttons: if slot.stdbutton == standardButton: return slot.button else: return None def __button_clicked(self): button = self.sender() role = self.buttonRole(button) self.clicked.emit(button) if role == MessageWidget.AcceptRole: self.accepted.emit() self.close() elif role == MessageWidget.RejectRole: self.rejected.emit() self.close() elif role == MessageWidget.HelpRole: self.helpRequested.emit() def __relayout(self): for slot in self.__buttons: self.layout().removeWidget(slot.button) order = { MessageOverlayWidget.HelpRole: 0, MessageOverlayWidget.AcceptRole: 2, MessageOverlayWidget.RejectRole: 3, } orderd = sorted(self.__buttons, key=lambda slot: order.get(slot.role, -1)) prev = self.__textlabel for slot in orderd: self.layout().addWidget(slot.button) QWidget.setTabOrder(prev, slot.button)
class NotificationMessageWidget(QWidget): #: Emitted when a button with the AcceptRole is clicked accepted = Signal() #: Emitted when a button with the RejectRole is clicked rejected = Signal() #: Emitted when a button is clicked clicked = Signal(QAbstractButton) class StandardButton(enum.IntEnum): NoButton, Ok, Close = 0x0, 0x1, 0x2 NoButton, Ok, Close = list(StandardButton) class ButtonRole(enum.IntEnum): InvalidRole, AcceptRole, RejectRole, DismissRole = 0, 1, 2, 3 InvalidRole, AcceptRole, RejectRole, DismissRole = list(ButtonRole) _Button = namedtuple("_Button", ["button", "role", "stdbutton"]) def __init__(self, parent=None, icon=QIcon(), title="", text="", wordWrap=False, textFormat=Qt.PlainText, standardButtons=NoButton, acceptLabel="Ok", rejectLabel="No", **kwargs): super().__init__(parent, **kwargs) self._title = title self._text = text self._icon = QIcon() self._wordWrap = wordWrap self._standardButtons = NotificationMessageWidget.NoButton self._buttons = [] self._acceptLabel = acceptLabel self._rejectLabel = rejectLabel self._iconlabel = QLabel(objectName="icon-label") self._titlelabel = QLabel(objectName="title-label", text=title, wordWrap=wordWrap, textFormat=textFormat) self._textlabel = QLabel(objectName="text-label", text=text, wordWrap=wordWrap, textFormat=textFormat) self._textlabel.setTextInteractionFlags(Qt.TextBrowserInteraction) self._textlabel.setOpenExternalLinks(True) if sys.platform == "darwin": self._titlelabel.setAttribute(Qt.WA_MacSmallSize) self._textlabel.setAttribute(Qt.WA_MacSmallSize) layout = QHBoxLayout() self._iconlabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) layout.addWidget(self._iconlabel) layout.setAlignment(self._iconlabel, Qt.AlignTop) message_layout = QVBoxLayout() self._titlelabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) if sys.platform == "darwin": self._titlelabel.setContentsMargins(0, 1, 0, 0) else: self._titlelabel.setContentsMargins(0, 0, 0, 0) message_layout.addWidget(self._titlelabel) self._textlabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) message_layout.addWidget(self._textlabel) self.button_layout = QHBoxLayout() self.button_layout.setAlignment(Qt.AlignLeft) message_layout.addLayout(self.button_layout) layout.addLayout(message_layout) layout.setSpacing(7) self.setLayout(layout) self.setIcon(icon) self.setStandardButtons(standardButtons) def setText(self, text): """ Set the current message text. :type message: str """ if self._text != text: self._text = text self._textlabel.setText(text) def text(self): """ Return the current message text. :rtype: str """ return self._text def setTitle(self, title): """ Set the current title text. :type title: str """ if self._title != title: self._title = title self._titleLabel.setText(title) def title(self): """ Return the current title text. :rtype: str """ return self._title def setIcon(self, icon): """ Set the message icon. :type icon: QIcon | QPixmap | QString | QStyle.StandardPixmap """ if isinstance(icon, QStyle.StandardPixmap): icon = self.style().standardIcon(icon) else: icon = QIcon(icon) if self._icon != icon: self._icon = QIcon(icon) if not self._icon.isNull(): size = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self) pm = self._icon.pixmap(QSize(size, size)) else: pm = QPixmap() self._iconlabel.setPixmap(pm) self._iconlabel.setVisible(not pm.isNull()) def icon(self): """ Return the current icon. :rtype: QIcon """ return QIcon(self._icon) def setWordWrap(self, wordWrap): """ Set the message text wrap property :type wordWrap: bool """ if self._wordWrap != wordWrap: self._wordWrap = wordWrap self._textlabel.setWordWrap(wordWrap) def wordWrap(self): """ Return the message text wrap property. :rtype: bool """ return self._wordWrap def setTextFormat(self, textFormat): """ Set message text format :type textFormat: Qt.TextFormat """ self._textlabel.setTextFormat(textFormat) def textFormat(self): """ Return the message text format. :rtype: Qt.TextFormat """ return self._textlabel.textFormat() def setAcceptLabel(self, label): """ Set the accept button label. :type label: str """ self._acceptLabel = label def acceptLabel(self): """ Return the accept button label. :rtype str """ return self._acceptLabel def setRejectLabel(self, label): """ Set the reject button label. :type label: str """ self._rejectLabel = label def rejectLabel(self): """ Return the reject button label. :rtype str """ return self._rejectLabel def setStandardButtons(self, buttons): for button in NotificationMessageWidget.StandardButton: existing = self.button(button) if button & buttons and existing is None: self.addButton(button) elif existing is not None: self.removeButton(existing) def standardButtons(self): return functools.reduce( operator.ior, (slot.stdbutton for slot in self._buttons if slot.stdbutton is not None), NotificationMessageWidget.NoButton) def addButton(self, button, *rolearg): """ addButton(QAbstractButton, ButtonRole) addButton(str, ButtonRole) addButton(StandardButton) Add and return a button """ stdbutton = None if isinstance(button, QAbstractButton): if len(rolearg) != 1: raise TypeError("Wrong number of arguments for " "addButton(QAbstractButton, role)") role = rolearg[0] elif isinstance(button, NotificationMessageWidget.StandardButton): if rolearg: raise TypeError("Wrong number of arguments for " "addButton(StandardButton)") stdbutton = button if button == NotificationMessageWidget.Ok: role = NotificationMessageWidget.AcceptRole button = QPushButton(self._acceptLabel, default=False, autoDefault=False) elif button == NotificationMessageWidget.Close: role = NotificationMessageWidget.RejectRole button = QPushButton(self._rejectLabel, default=False, autoDefault=False) elif isinstance(button, str): if len(rolearg) != 1: raise TypeError("Wrong number of arguments for " "addButton(str, ButtonRole)") role = rolearg[0] button = QPushButton(button, default=False, autoDefault=False) if sys.platform == "darwin": button.setAttribute(Qt.WA_MacSmallSize) self._buttons.append( NotificationMessageWidget._Button(button, role, stdbutton)) button.clicked.connect(self._button_clicked) self._relayout() return button def _relayout(self): for slot in self._buttons: self.button_layout.removeWidget(slot.button) order = { NotificationWidget.AcceptRole: 0, NotificationWidget.RejectRole: 1, } ordered = sorted([ b for b in self._buttons if self.buttonRole(b.button) != NotificationMessageWidget.DismissRole ], key=lambda slot: order.get(slot.role, -1)) prev = self._textlabel for slot in ordered: self.button_layout.addWidget(slot.button) QWidget.setTabOrder(prev, slot.button) def removeButton(self, button): """ Remove a `button`. :type button: QAbstractButton """ slot = [s for s in self._buttons if s.button is button] if slot: slot = slot[0] self._buttons.remove(slot) self.layout().removeWidget(slot.button) slot.button.setParent(None) def buttonRole(self, button): """ Return the ButtonRole for button :type button: QAbstractButton """ for slot in self._buttons: if slot.button is button: return slot.role return NotificationMessageWidget.InvalidRole def button(self, standardButton): """ Return the button for the StandardButton. :type standardButton: StandardButton """ for slot in self._buttons: if slot.stdbutton == standardButton: return slot.button return None def _button_clicked(self): button = self.sender() role = self.buttonRole(button) self.clicked.emit(button) if role == NotificationMessageWidget.AcceptRole: self.accepted.emit() self.close() elif role == NotificationMessageWidget.RejectRole: self.rejected.emit() self.close()
class PreviewBrowser(QWidget): """ A Preview Browser for recent/example workflow selection. """ # Emitted when the current previewed item changes currentIndexChanged = Signal(int) # Emitted when an item is double clicked in the preview list. activated = Signal(int) def __init__(self, *args, heading="", previewMargins=12, **kwargs): # type: (Any, str, int, Any) -> None super().__init__(*args, **kwargs) self.__model = None # type: Optional[QAbstractItemModel] self.__currentIndex = -1 self.__template = DESCRIPTION_TEMPLATE self.__margin = previewMargins vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) top_layout = QVBoxLayout(objectName="top-layout") margin = self.__margin top_layout.setContentsMargins(margin, margin, margin, margin) # Optional heading label self.__heading = QLabel(self, objectName="heading", visible=False) # Horizontal row with full text description and a large preview # image. hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) self.__label = QLabel(self, objectName="description-label", wordWrap=True, alignment=Qt.AlignTop | Qt.AlignLeft) self.__label.setWordWrap(True) self.__label.setFixedSize(220, PREVIEW_SIZE[1]) self.__label.setMinimumWidth(PREVIEW_SIZE[0] // 2) self.__label.setMaximumHeight(PREVIEW_SIZE[1]) self.__image = QSvgWidget(self, objectName="preview-image") self.__image.setFixedSize(*PREVIEW_SIZE) self.__imageFrame = DropShadowFrame(self) self.__imageFrame.setWidget(self.__image) hlayout.addWidget(self.__label) hlayout.addWidget(self.__image) # Path text below the description and image path_layout = QHBoxLayout() path_layout.setContentsMargins(0, 0, 0, 0) path_label = QLabel("<b>{0!s}</b>".format(self.tr("Path:")), self, objectName="path-label") self.__path = TextLabel(self, objectName="path-text") path_layout.addWidget(path_label) path_layout.addWidget(self.__path) top_layout.addWidget(self.__heading) top_layout.addLayout(hlayout) top_layout.addLayout(path_layout) vlayout.addLayout(top_layout) # An list view with small preview icons. self.__previewList = LinearIconView(objectName="preview-list-view", wordWrap=True) self.__previewList.doubleClicked.connect(self.__onDoubleClicked) vlayout.addWidget(self.__previewList) self.setLayout(vlayout) self.setHeading(heading) def setHeading(self, text): # type: (str) -> None """ Set the heading text. Parameters ---------- text: str The new heading text. If empty the heading is hidden. """ self.__heading.setVisible(bool(text)) self.__heading.setText(text) def setPreviewMargins(self, margin): # type: (int) -> None """ Set the left, top and right margins of the top widget part (heading and description) Parameters ---------- margin : int Margin """ if margin != self.__margin: layout = self.layout().itemAt(0).layout() assert isinstance(layout, QVBoxLayout) assert layout.objectName() == "top-layout" layout.setContentsMargins(margin, margin, margin, 0) def setModel(self, model): # type: (QAbstractItemModel) -> None """ Set the item model for preview. Parameters ---------- model : QAbstractItemModel """ if self.__model != model: if self.__model: s_model = self.__previewList.selectionModel() s_model.selectionChanged.disconnect(self.__onSelectionChanged) self.__model.dataChanged.disconnect(self.__onDataChanged) self.__model = model self.__previewList.setModel(model) if model: s_model = self.__previewList.selectionModel() s_model.selectionChanged.connect(self.__onSelectionChanged) self.__model.dataChanged.connect(self.__onDataChanged) if model and model.rowCount(): self.setCurrentIndex(0) def model(self): # type: () -> Optional[QAbstractItemModel] """ Return the item model. """ return self.__model def setDescriptionTemplate(self, template): self.__template = template self.__update() def setCurrentIndex(self, index): # type: (int) -> None """ Set the selected preview item index. Parameters ---------- index : int The current selected index. """ if self.__model is not None and self.__model.rowCount(): index = min(index, self.__model.rowCount() - 1) index = self.__model.index(index, 0) sel_model = self.__previewList.selectionModel() # This emits selectionChanged signal and triggers # __onSelectionChanged, currentIndex is updated there. sel_model.select(index, sel_model.ClearAndSelect) elif self.__currentIndex != -1: self.__currentIndex = -1 self.__update() self.currentIndexChanged.emit(-1) def currentIndex(self): # type: () -> int """ Return the current selected index. """ return self.__currentIndex def __onSelectionChanged(self): # type: () -> None """Selected item in the preview list has changed. Set the new description and large preview image. """ rows = self.__previewList.selectedIndexes() if rows: index = rows[0] self.__currentIndex = index.row() else: self.__currentIndex = -1 self.__update() self.currentIndexChanged.emit(self.__currentIndex) def __onDataChanged(self, topLeft, bottomRight): # type: (QModelIndex, QModelIndex) -> None """Data changed, update the preview if current index in the changed range. """ if topLeft.row() <= self.__currentIndex <= bottomRight.row(): self.__update() def __onDoubleClicked(self, index): # type: (QModelIndex) -> None """Double click on an item in the preview item list. """ self.activated.emit(index.row()) def __update(self): # type: () -> None """Update the current description. """ if self.__currentIndex != -1 and self.__model is not None: index = self.__model.index(self.__currentIndex, 0) else: index = QModelIndex() if not index.isValid(): description = "" name = "" path = "" svg = NO_PREVIEW_SVG else: description = index.data(Qt.WhatsThisRole) if description: description = description else: description = "没有说明。" description = escape(description) description = description.replace("\n", "<br/>") name = index.data(Qt.DisplayRole) if name: name = name else: name = "Untitled" name = escape(name) path = str(index.data(Qt.StatusTipRole)) svg = str(index.data(previewmodel.ThumbnailSVGRole)) desc_text = self.__template.format(description=description, name=name) self.__label.setText(desc_text) self.__path.setText(contractuser(path)) if not svg: svg = NO_PREVIEW_SVG if svg: self.__image.load(QByteArray(svg.encode("utf-8")))
class WelcomeDialog(QDialog): """ A welcome widget shown at startup presenting a series of buttons (actions) for a beginner to choose from. """ triggered = Signal(QAction) def __init__(self, *args, **kwargs): showAtStartup = kwargs.pop("showAtStartup", True) feedbackUrl = kwargs.pop("feedbackUrl", "") super().__init__(*args, **kwargs) self.__triggeredAction = None self.__showAtStartupCheck = None self.__mainLayout = None self.__feedbackUrl = None self.__feedbackLabel = None self.setupUi() self.setFeedbackUrl(feedbackUrl) self.setShowAtStartup(showAtStartup) def setupUi(self): self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(0) self.__mainLayout = QVBoxLayout() self.__mainLayout.setContentsMargins(0, 40, 0, 40) self.__mainLayout.setSpacing(65) self.layout().addLayout(self.__mainLayout) self.setStyleSheet(WELCOME_WIDGET_BUTTON_STYLE) bottom_bar = QWidget(objectName="bottom-bar") bottom_bar_layout = QHBoxLayout() bottom_bar_layout.setContentsMargins(20, 10, 20, 10) bottom_bar.setLayout(bottom_bar_layout) bottom_bar.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum) self.__showAtStartupCheck = QCheckBox( self.tr("Show at startup"), bottom_bar, checked=False ) self.__feedbackLabel = QLabel( textInteractionFlags=Qt.TextBrowserInteraction, openExternalLinks=True, visible=False, ) bottom_bar_layout.addWidget( self.__showAtStartupCheck, alignment=Qt.AlignVCenter | Qt.AlignLeft ) bottom_bar_layout.addWidget( self.__feedbackLabel, alignment=Qt.AlignVCenter | Qt.AlignRight ) self.layout().addWidget(bottom_bar, alignment=Qt.AlignBottom, stretch=1) self.setSizeGripEnabled(False) self.setFixedSize(620, 390) def setShowAtStartup(self, show): """ Set the 'Show at startup' check box state. """ if self.__showAtStartupCheck.isChecked() != show: self.__showAtStartupCheck.setChecked(show) def showAtStartup(self): """ Return the 'Show at startup' check box state. """ return self.__showAtStartupCheck.isChecked() def setFeedbackUrl(self, url): # type: (str) -> None """ Set an 'feedback' url. When set a link is displayed in the bottom row. """ self.__feedbackUrl = url if url: text = self.tr("Help us improve!") self.__feedbackLabel.setText( '<a href="{url}">{text}</a>'.format(url=url, text=escape(text)) ) else: self.__feedbackLabel.setText("") self.__feedbackLabel.setVisible(bool(url)) def addRow(self, actions, background="light-orange"): """Add a row with `actions`. """ count = self.__mainLayout.count() self.insertRow(count, actions, background) def insertRow(self, index, actions, background="light-orange"): """Insert a row with `actions` at `index`. """ widget = QWidget(objectName="icon-row") layout = QHBoxLayout() layout.setContentsMargins(40, 0, 40, 0) layout.setSpacing(65) widget.setLayout(layout) self.__mainLayout.insertWidget(index, widget, stretch=10, alignment=Qt.AlignCenter) for i, action in enumerate(actions): self.insertAction(index, i, action, background) def insertAction(self, row, index, action, background="light-orange"): """Insert `action` in `row` in position `index`. """ button = self.createButton(action, background) self.insertButton(row, index, button) def insertButton(self, row, index, button): """Insert `button` in `row` in position `index`. """ item = self.__mainLayout.itemAt(row) layout = item.widget().layout() layout.insertWidget(index, button) button.triggered.connect(self.__on_actionTriggered) def createButton(self, action, background="light-orange"): """Create a tool button for action. """ button = WelcomeActionButton(self) button.setDefaultAction(action) button.setText(action.iconText()) button.setIcon(decorate_welcome_icon(action.icon(), background)) button.setToolTip(action.toolTip()) button.setFixedSize(100, 100) button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) button.setVisible(action.isVisible()) font = QFont(button.font()) font.setPointSize(13) button.setFont(font) return button def buttonAt(self, i, j): """Return the button at i-t row and j-th column. """ item = self.__mainLayout.itemAt(i) row = item.widget() item = row.layout().itemAt(j) return item.widget() def triggeredAction(self): """Return the action that was triggered by the user. """ return self.__triggeredAction def showEvent(self, event): # Clear the triggered action before show. self.__triggeredAction = None super().showEvent(event) def __on_actionTriggered(self, action): """Called when the button action is triggered. """ self.triggered.emit(action) self.__triggeredAction = action