class MMessage(QWidget): """ Display global messages as feedback in response to user operations. """ InfoType = 'info' SuccessType = 'success' WarningType = 'warning' ErrorType = 'error' LoadingType = 'loading' default_config = {'duration': 2, 'top': 24} sig_closed = Signal() def __init__(self, text, duration=None, dayu_type=None, closable=False, parent=None): super(MMessage, self).__init__(parent) self.setObjectName('message') self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog | Qt.WA_TranslucentBackground | Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_StyledBackground) if dayu_type == MMessage.LoadingType: _icon_label = MLoading.tiny() else: _icon_label = MAvatar.tiny() current_type = dayu_type or MMessage.InfoType _icon_label.set_dayu_image( MPixmap('{}_fill.svg'.format(current_type), vars(dayu_theme).get(current_type + '_color'))) self._content_label = MLabel(parent=self) # self._content_label.set_elide_mode(Qt.ElideMiddle) self._content_label.setText(text) self._close_button = MToolButton( parent=self).icon_only().svg('close_line.svg').tiny() self._close_button.clicked.connect(self.close) self._close_button.setVisible(closable or False) self._main_lay = QHBoxLayout() self._main_lay.addWidget(_icon_label) self._main_lay.addWidget(self._content_label) self._main_lay.addStretch() self._main_lay.addWidget(self._close_button) self.setLayout(self._main_lay) _close_timer = QTimer(self) _close_timer.setSingleShot(True) _close_timer.timeout.connect(self.close) _close_timer.timeout.connect(self.sig_closed) _close_timer.setInterval( (duration or self.default_config.get('duration')) * 1000) _ani_timer = QTimer(self) _ani_timer.timeout.connect(self._fade_out) _ani_timer.setInterval( (duration or self.default_config.get('duration')) * 1000 - 300) _close_timer.start() _ani_timer.start() self._pos_ani = QPropertyAnimation(self) self._pos_ani.setTargetObject(self) self._pos_ani.setEasingCurve(QEasingCurve.OutCubic) self._pos_ani.setDuration(300) self._pos_ani.setPropertyName('pos') self._opacity_ani = QPropertyAnimation() self._opacity_ani.setTargetObject(self) self._opacity_ani.setDuration(300) self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic) self._opacity_ani.setPropertyName('windowOpacity') self._opacity_ani.setStartValue(0.0) self._opacity_ani.setEndValue(1.0) self._set_proper_position(parent) self._fade_int() def _fade_out(self): self._pos_ani.setDirection(QAbstractAnimation.Backward) self._pos_ani.start() self._opacity_ani.setDirection(QAbstractAnimation.Backward) self._opacity_ani.start() def _fade_int(self): self._pos_ani.start() self._opacity_ani.start() def _set_proper_position(self, parent): parent_geo = parent.geometry() pos = parent_geo.topLeft( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.topLeft()) offset = 0 for child in parent.children(): if isinstance(child, MMessage) and child.isVisible(): offset = max(offset, child.y()) base = pos.y() + MMessage.default_config.get('top') target_x = pos.x() + parent_geo.width() / 2 - 100 target_y = (offset + 50) if offset else base self._pos_ani.setStartValue(QPoint(target_x, target_y - 40)) self._pos_ani.setEndValue(QPoint(target_x, target_y)) @classmethod def info(cls, text, parent, duration=None, closable=None): """Show a normal message""" inst = cls(text, dayu_type=MMessage.InfoType, duration=duration, closable=closable, parent=parent) inst.show() return inst @classmethod def success(cls, text, parent, duration=None, closable=None): """Show a success message""" inst = cls(text, dayu_type=MMessage.SuccessType, duration=duration, closable=closable, parent=parent) inst.show() return inst @classmethod def warning(cls, text, parent, duration=None, closable=None): """Show a warning message""" inst = cls(text, dayu_type=MMessage.WarningType, duration=duration, closable=closable, parent=parent) inst.show() return inst @classmethod def error(cls, text, parent, duration=None, closable=None): """Show an error message""" inst = cls(text, dayu_type=MMessage.ErrorType, duration=duration, closable=closable, parent=parent) inst.show() return inst @classmethod def loading(cls, text, parent): """Show a message with loading animation""" inst = cls(text, dayu_type=MMessage.LoadingType, parent=parent) inst.show() return inst @classmethod def config(cls, duration=None, top=None): """ Config the global MMessage duration and top setting. :param duration: int (unit is second) :param top: int (unit is px) :return: None """ if duration is not None: cls.default_config['duration'] = duration if top is not None: cls.default_config['top'] = top
class MDrawer(QWidget): """ A panel which slides in from the edge of the screen. """ LeftPos = 'left' RightPos = 'right' TopPos = 'top' BottomPos = 'bottom' sig_closed = Signal() def __init__(self, title, position='right', closable=True, parent=None): super(MDrawer, self).__init__(parent) self.setObjectName('message') self.setWindowFlags(Qt.Popup) # self.setWindowFlags( # Qt.FramelessWindowHint | Qt.Popup | Qt.WA_TranslucentBackground) self.setAttribute(Qt.WA_StyledBackground) self._title_label = MLabel(parent=self).h4() # self._title_label.set_elide_mode(Qt.ElideRight) self._title_label.setText(title) self._close_button = MToolButton( parent=self).icon_only().svg('close_line.svg').small() self._close_button.clicked.connect(self.close) self._close_button.setVisible(closable or False) _title_lay = QHBoxLayout() _title_lay.addWidget(self._title_label) _title_lay.addStretch() _title_lay.addWidget(self._close_button) self._button_lay = QHBoxLayout() self._button_lay.addStretch() self._scroll_area = QScrollArea() self._main_lay = QVBoxLayout() self._main_lay.addLayout(_title_lay) self._main_lay.addWidget(MDivider()) self._main_lay.addWidget(self._scroll_area) self._main_lay.addWidget(MDivider()) self._main_lay.addLayout(self._button_lay) self.setLayout(self._main_lay) self._position = position self._close_timer = QTimer(self) self._close_timer.setSingleShot(True) self._close_timer.timeout.connect(self.close) self._close_timer.timeout.connect(self.sig_closed) self._close_timer.setInterval(300) self._is_first_close = True self._pos_ani = QPropertyAnimation(self) self._pos_ani.setTargetObject(self) self._pos_ani.setEasingCurve(QEasingCurve.OutCubic) self._pos_ani.setDuration(300) self._pos_ani.setPropertyName('pos') self._opacity_ani = QPropertyAnimation() self._opacity_ani.setTargetObject(self) self._opacity_ani.setDuration(300) self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic) self._opacity_ani.setPropertyName('windowOpacity') self._opacity_ani.setStartValue(0.0) self._opacity_ani.setEndValue(1.0) # self._shadow_effect = QGraphicsDropShadowEffect(self) # color = dayu_theme.red # self._shadow_effect.setColor(color) # self._shadow_effect.setOffset(0, 0) # self._shadow_effect.setBlurRadius(5) # self._shadow_effect.setEnabled(False) # self.setGraphicsEffect(self._shadow_effect) def set_widget(self, widget): self._scroll_area.setWidget(widget) def add_button(self, button): self._button_lay.addWidget(button) def _fade_out(self): self._pos_ani.setDirection(QAbstractAnimation.Backward) self._pos_ani.start() self._opacity_ani.setDirection(QAbstractAnimation.Backward) self._opacity_ani.start() def _fade_int(self): self._pos_ani.start() self._opacity_ani.start() def _set_proper_position(self): parent = self.parent() parent_geo = parent.geometry() if self._position == MDrawer.LeftPos: pos = parent_geo.topLeft( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.topLeft()) target_x = pos.x() target_y = pos.y() self.setFixedHeight(parent_geo.height()) self._pos_ani.setStartValue( QPoint(target_x - self.width(), target_y)) self._pos_ani.setEndValue(QPoint(target_x, target_y)) if self._position == MDrawer.RightPos: pos = parent_geo.topRight( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.topRight()) self.setFixedHeight(parent_geo.height()) target_x = pos.x() - self.width() target_y = pos.y() self._pos_ani.setStartValue( QPoint(target_x + self.width(), target_y)) self._pos_ani.setEndValue(QPoint(target_x, target_y)) if self._position == MDrawer.TopPos: pos = parent_geo.topLeft( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.topLeft()) self.setFixedWidth(parent_geo.width()) target_x = pos.x() target_y = pos.y() self._pos_ani.setStartValue( QPoint(target_x, target_y - self.height())) self._pos_ani.setEndValue(QPoint(target_x, target_y)) if self._position == MDrawer.BottomPos: pos = parent_geo.bottomLeft( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.bottomLeft()) self.setFixedWidth(parent_geo.width()) target_x = pos.x() target_y = pos.y() - self.height() self._pos_ani.setStartValue( QPoint(target_x, target_y + self.height())) self._pos_ani.setEndValue(QPoint(target_x, target_y)) def set_dayu_position(self, value): """ Set the placement of the MDrawer. top/right/bottom/left, default is right :param value: str :return: None """ self._position = value if value in [MDrawer.BottomPos, MDrawer.TopPos]: self.setFixedHeight(200) else: self.setFixedWidth(200) def get_dayu_position(self): """ Get the placement of the MDrawer :return: str """ return self._position dayu_position = Property(str, get_dayu_position, set_dayu_position) def left(self): """Set drawer's placement to left""" self.set_dayu_position(MDrawer.LeftPos) return self def right(self): """Set drawer's placement to right""" self.set_dayu_position(MDrawer.RightPos) return self def top(self): """Set drawer's placement to top""" self.set_dayu_position(MDrawer.TopPos) return self def bottom(self): """Set drawer's placement to bottom""" self.set_dayu_position(MDrawer.BottomPos) return self def show(self): self._set_proper_position() self._fade_int() return super(MDrawer, self).show() def closeEvent(self, event): if self._is_first_close: self._is_first_close = False self._close_timer.start() self._fade_out() event.ignore() else: event.accept()
class MSectionItem(QWidget): sig_context_menu = Signal(object) def __init__(self, title='', expand=False, widget=None, closeable=False, parent=None): super(MSectionItem, self).__init__(parent) self._central_widget = None self.setAttribute(Qt.WA_StyledBackground) self.title_label = MLabel(parent=self) self.expand_icon = MLabel(parent=self) self.expand_icon.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._close_button = MToolButton().icon_only().tiny().svg( 'close_line.svg') self._close_button.clicked.connect(self.close) header_lay = QHBoxLayout() header_lay.addWidget(self.expand_icon) header_lay.addWidget(self.title_label) header_lay.addStretch() header_lay.addWidget(self._close_button) self.header_widget = QWidget(parent=self) self.header_widget.setAttribute(Qt.WA_StyledBackground) self.header_widget.setObjectName('title') self.header_widget.setLayout(header_lay) self.header_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.header_widget.setCursor(Qt.PointingHandCursor) self.title_label.setCursor(Qt.PointingHandCursor) self.header_widget.installEventFilter(self) self.title_label.installEventFilter(self) self.content_widget = QWidget(parent=self) self.content_layout = QHBoxLayout() self.content_widget.setLayout(self.content_layout) self.main_lay = QVBoxLayout() self.main_lay.setContentsMargins(0, 0, 0, 0) self.main_lay.setSpacing(0) self.main_lay.addWidget(self.header_widget) self.main_lay.addWidget(self.content_widget) self.setLayout(self.main_lay) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self.setMouseTracking(True) self.set_title(title) self.set_closeable(closeable) if widget: self.set_content(widget) self.set_expand(expand) def set_content(self, widget): if self._central_widget: self.content_layout.removeWidget(self._central_widget) self._central_widget.close() self.content_layout.addWidget(widget) self._central_widget = widget def get_content(self): return self._central_widget def set_closeable(self, value): self.setProperty('closeable', value) def _set_closeable(self, value): self.content_widget.setVisible(value) self._close_button.setVisible(value) def set_expand(self, value): self.setProperty('expand', value) def _set_expand(self, value): self.content_widget.setVisible(value) self.expand_icon.setPixmap( MPixmap('down_line.svg' if value else 'right_line.svg'). scaledToHeight(12)) def set_title(self, value): self.setProperty('title', value) def _set_title(self, value): self.title_label.setText(value) def eventFilter(self, widget, event): if widget in [self.header_widget, self.title_label]: if event.type() == QEvent.MouseButtonRelease: self.set_expand(not self.property('expand')) return super(QWidget, self).eventFilter(widget, event)
class MMeta(QWidget): def __init__(self, cover=None, avatar=None, title=None, description=None, extra=False, parent=None): super(MMeta, self).__init__(parent) self.setAttribute(Qt.WA_StyledBackground) self._cover_label = QLabel() self._avatar = MAvatar() self._title_label = MLabel().h4() self._description_label = MLabel().secondary() self._description_label.setWordWrap(True) self._description_label.set_elide_mode(Qt.ElideRight) self._title_layout = QHBoxLayout() self._title_layout.addWidget(self._title_label) self._title_layout.addStretch() self._extra_button = MToolButton( parent=self).icon_only().svg('more.svg') self._title_layout.addWidget(self._extra_button) self._extra_button.setVisible(extra) content_lay = QFormLayout() content_lay.setContentsMargins(5, 5, 5, 5) content_lay.addRow(self._avatar, self._title_layout) content_lay.addRow(self._description_label) self._button_layout = QHBoxLayout() main_lay = QVBoxLayout() main_lay.setSpacing(0) main_lay.setContentsMargins(1, 1, 1, 1) main_lay.addWidget(self._cover_label) main_lay.addLayout(content_lay) main_lay.addLayout(self._button_layout) main_lay.addStretch() self.setLayout(main_lay) self._cover_label.setFixedSize(QSize(200, 200)) # self.setFixedWidth(200) def get_more_button(self): return self._extra_button def setup_data(self, data_dict): if data_dict.get('title'): self._title_label.setText(data_dict.get('title')) self._title_label.setVisible(True) else: self._title_label.setVisible(False) if data_dict.get('description'): self._description_label.setText(data_dict.get('description')) self._description_label.setVisible(True) else: self._description_label.setVisible(False) if data_dict.get('avatar'): self._avatar.set_dayu_image(data_dict.get('avatar')) self._avatar.setVisible(True) else: self._avatar.setVisible(False) if data_dict.get('cover'): fixed_height = self._cover_label.width() self._cover_label.setPixmap( data_dict.get('cover').scaledToWidth(fixed_height, Qt.SmoothTransformation)) self._cover_label.setVisible(True) else: self._cover_label.setVisible(False)
class MAlert(QWidget): """ Alert component for feedback. Property: dayu_type: The feedback type with different color container. dayu_text: The feedback string showed in container. """ InfoType = 'info' SuccessType = 'success' WarningType = 'warning' ErrorType = 'error' def __init__(self, text='', parent=None, flags=0): super(MAlert, self).__init__(parent, flags) self.setAttribute(Qt.WA_StyledBackground) self._icon_label = MAvatar() self._icon_label.set_dayu_size(dayu_theme.tiny) self._content_label = MLabel().secondary() self._close_button = MToolButton().svg( 'close_line.svg').tiny().icon_only() self._close_button.clicked.connect( functools.partial(self.setVisible, False)) self._main_lay = QHBoxLayout() self._main_lay.setContentsMargins(8, 8, 8, 8) self._main_lay.addWidget(self._icon_label) self._main_lay.addWidget(self._content_label) self._main_lay.addStretch() self._main_lay.addWidget(self._close_button) self.setLayout(self._main_lay) self.set_show_icon(True) self.set_closeable(False) self._dayu_type = None self._dayu_text = None self.set_dayu_type(MAlert.InfoType) self.set_dayu_text(text) def set_closeable(self, closeable): """Display the close icon button or not.""" self._close_button.setVisible(closeable) def set_show_icon(self, show_icon): """Display the information type icon or not.""" self._icon_label.setVisible(show_icon) def _set_dayu_text(self): self._content_label.setText(self._dayu_text) self.setVisible(bool(self._dayu_text)) def set_dayu_text(self, value): """Set the feedback content.""" if isinstance(value, basestring): self._dayu_text = value else: raise TypeError("Input argument 'value' should be string type, " "but get {}".format(type(value))) self._set_dayu_text() def _set_dayu_type(self): self._icon_label.set_dayu_image( MPixmap('{}_fill.svg'.format(self._dayu_type), vars(dayu_theme).get(self._dayu_type + '_color'))) self.style().polish(self) def set_dayu_type(self, value): """Set feedback type.""" if value in [ MAlert.InfoType, MAlert.SuccessType, MAlert.WarningType, MAlert.ErrorType ]: self._dayu_type = value else: raise ValueError("Input argument 'value' should be one of " "info/success/warning/error string.") self._set_dayu_type() def get_dayu_type(self): """ Get MAlert feedback type. :return: str """ return self._dayu_type def get_dayu_text(self): """ Get MAlert feedback message. :return: basestring """ return self._dayu_text dayu_text = Property(unicode, get_dayu_text, set_dayu_text) dayu_type = Property(str, get_dayu_type, set_dayu_type) def info(self): """Set MAlert to InfoType""" self.set_dayu_type(MAlert.InfoType) return self def success(self): """Set MAlert to SuccessType""" self.set_dayu_type(MAlert.SuccessType) return self def warning(self): """Set MAlert to WarningType""" self.set_dayu_type(MAlert.WarningType) return self def error(self): """Set MAlert to ErrorType""" self.set_dayu_type(MAlert.ErrorType) return self def closable(self): """Set MAlert closebale is True""" self.set_closeable(True) return self