class MPushButtonGroup(MButtonGroupBase): def __init__(self, orientation=Qt.Horizontal, parent=None): super(MPushButtonGroup, self).__init__(orientation=orientation, parent=parent) self.set_spacing(1) self._dayu_type = MPushButton.PrimaryType self._dayu_size = dayu_theme.default_size self._button_group.setExclusive(False) def create_button(self, data_dict): button = MPushButton() button.set_dayu_size(data_dict.get('dayu_size', self._dayu_size)) button.set_dayu_type(data_dict.get('dayu_type', self._dayu_type)) return button def get_dayu_size(self): return self._dayu_size def get_dayu_type(self): return self._dayu_type def set_dayu_size(self, value): self._dayu_size = value def set_dayu_type(self, value): self._dayu_type = value dayu_size = Property(int, get_dayu_size, set_dayu_size) dayu_type = Property(str, get_dayu_type, set_dayu_type)
class MClickBrowserFolderToolButton(MToolButton): """A Clickable tool button to browser folders""" sig_folder_changed = Signal(str) sig_folders_changed = Signal(list) slot_browser_folder = _slot_browser_folder def __init__(self, multiple=False, parent=None): super(MClickBrowserFolderToolButton, self).__init__(parent=parent) self.set_dayu_svg('folder_line.svg') self.icon_only() self.clicked.connect(self.slot_browser_folder) self.setToolTip(self.tr('Click to browser folder')) self._path = None self._multiple = multiple def get_dayu_path(self): """ Get last browser file path :return: str """ return self._path def set_dayu_path(self, value): """ Set browser file start path :param value: str :return: None """ self._path = value def get_dayu_multiple(self): """ Get browser can select multiple file or not :return: bool """ return self._multiple def set_dayu_multiple(self, value): """ Set browser can select multiple file or not :param value: bool :return: None """ self._multiple = value dayu_multiple = Property(bool, get_dayu_multiple, set_dayu_multiple) dayu_path = Property(basestring, get_dayu_path, set_dayu_path)
class MClickSaveFileToolButton(MToolButton): """A Clickable tool button to browser files""" sig_file_changed = Signal(str) slot_browser_file = _slot_save_file def __init__(self, multiple=False, parent=None): super(MClickSaveFileToolButton, self).__init__(parent=parent) self.set_dayu_svg('save_line.svg') self.icon_only() self.clicked.connect(self.slot_browser_file) self.setToolTip(self.tr('Click to save file')) self._path = None self._multiple = multiple self._filters = [] def get_dayu_filters(self): """ Get browser's format filters :return: list """ return self._filters def set_dayu_filters(self, value): """ Set browser file format filters :param value: :return: None """ self._filters = value def get_dayu_path(self): """ Get last browser file path :return: str """ return self._path def set_dayu_path(self, value): """ Set browser file start path :param value: str :return: None """ self._path = value dayu_path = Property(basestring, get_dayu_path, set_dayu_path) dayu_filters = Property(list, get_dayu_filters, set_dayu_filters)
class MUnderlineButtonGroup(MButtonGroupBase): """MUnderlineButtonGroup""" sig_checked_changed = Signal(int) def __init__(self, parent=None): super(MUnderlineButtonGroup, self).__init__(parent=parent) self.set_spacing(1) self._button_group.setExclusive(True) self._button_group.buttonClicked[int].connect(self.sig_checked_changed) def create_button(self, data_dict): button = MUnderlineButton(parent=self) if data_dict.get('svg'): button.svg(data_dict.get('svg')) if data_dict.get('text'): if data_dict.get('svg') or data_dict.get('icon'): button.text_beside_icon() else: button.text_only() else: button.icon_only() return button def set_dayu_checked(self, value): """Set current checked button's id""" button = self._button_group.button(value) button.setChecked(True) self.sig_checked_changed.emit(value) def get_dayu_checked(self): """Get current checked button's id""" return self._button_group.checkedId() dayu_checked = Property(int, get_dayu_checked, set_dayu_checked, notify=sig_checked_changed)
class MRadioButtonGroup(MButtonGroupBase): """ Property: dayu_checked """ sig_checked_changed = Signal(int) def __init__(self, orientation=Qt.Horizontal, parent=None): super(MRadioButtonGroup, self).__init__(orientation=orientation, parent=parent) self.set_spacing(15) self._button_group.setExclusive(True) self._button_group.buttonClicked[int].connect(self.sig_checked_changed) def create_button(self, data_dict): return MRadioButton() def set_dayu_checked(self, value): if value == self.get_dayu_checked(): return button = self._button_group.button(value) if button: button.setChecked(True) self.sig_checked_changed.emit(value) else: print 'error' def get_dayu_checked(self): return self._button_group.checkedId() dayu_checked = Property(int, get_dayu_checked, set_dayu_checked, notify=sig_checked_changed)
class MLoadingWrapper(QWidget): """ A wrapper widget to show the loading widget or hide. Property: dayu_loading: bool. current loading state. """ def __init__(self, widget, loading=True, parent=None): super(MLoadingWrapper, self).__init__(parent) self._widget = widget self._mask_widget = QFrame() self._mask_widget.setObjectName('mask') self._mask_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._loading_widget = MLoading() self._loading_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._main_lay = QGridLayout() self._main_lay.setContentsMargins(0, 0, 0, 0) self._main_lay.addWidget(widget, 0, 0) self._main_lay.addWidget(self._mask_widget, 0, 0) self._main_lay.addWidget(self._loading_widget, 0, 0, Qt.AlignCenter) self.setLayout(self._main_lay) self._loading = None self.set_dayu_loading(loading) def _set_loading(self): self._loading_widget.setVisible(self._loading) self._mask_widget.setVisible(self._loading) def set_dayu_loading(self, loading): """ Set current state to loading or not :param loading: bool :return: None """ self._loading = loading self._set_loading() def get_dayu_loading(self): """ Get current loading widget is loading or not. :return: bool """ return self._loading dayu_loading = Property(bool, get_dayu_loading, set_dayu_loading)
class MProgressBar(QProgressBar): ''' props: status: str ''' ErrorStatus = 'error' NormalStatus = 'primary' SuccessStatus = 'success' def __init__(self, parent=None): super(MProgressBar, self).__init__(parent=parent) self.setAlignment(Qt.AlignCenter) self._status = MProgressBar.NormalStatus def auto_color(self): self.valueChanged.connect(self._update_color) return self @Slot(int) def _update_color(self, value): if value >= self.maximum(): self.set_dayu_status(MProgressBar.SuccessStatus) else: self.set_dayu_status(MProgressBar.NormalStatus) def get_dayu_status(self): return self._status def set_dayu_status(self, value): self._status = value self.style().polish(self) dayu_status = Property(str, get_dayu_status, set_dayu_status) def normal(self): self.set_dayu_status(MProgressBar.NormalStatus) return self def error(self): self.set_dayu_status(MProgressBar.ErrorStatus) return self def success(self): self.set_dayu_status(MProgressBar.SuccessStatus) return self
class MToolButtonGroup(MButtonGroupBase): sig_checked_changed = Signal(int) def __init__(self, size=None, type=None, exclusive=False, orientation=Qt.Horizontal, parent=None): super(MToolButtonGroup, self).__init__(orientation=orientation, parent=parent) self.set_spacing(1) self._button_group.setExclusive(exclusive) self._size = size self._type = type self._button_group.buttonClicked[int].connect(self.sig_checked_changed) def create_button(self, data_dict): button = MToolButton() if data_dict.get('svg'): button.svg(data_dict.get('svg')) if data_dict.get('text'): if data_dict.get('svg') or data_dict.get('icon'): button.text_beside_icon() else: button.text_only() else: button.icon_only() return button def set_dayu_checked(self, value): if value == self.get_dayu_checked(): return button = self._button_group.button(value) if button: button.setChecked(True) self.sig_checked_changed.emit(value) else: print 'error' def get_dayu_checked(self): return self._button_group.checkedId() dayu_checked = Property(int, get_dayu_checked, set_dayu_checked, notify=sig_checked_changed)
class MDragFileButton(MToolButton): """A Clickable and draggable tool button to upload files""" sig_file_changed = Signal(str) sig_files_changed = Signal(list) slot_browser_file = _slot_browser_file def __init__(self, text='', multiple=False, parent=None): super(MDragFileButton, self).__init__(parent=parent) self.setAcceptDrops(True) self.setMouseTracking(True) self.text_under_icon() self.setText(text) self.set_dayu_size(60) self.set_dayu_svg('cloud_line.svg') self.setIconSize(QSize(60, 60)) self.clicked.connect(self.slot_browser_file) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setToolTip(self.tr('Click to browser file')) self._path = None self._multiple = multiple self._filters = [] def get_dayu_filters(self): """ Get browser's format filters :return: list """ return self._filters def set_dayu_filters(self, value): """ Set browser file format filters :param value: :return: None """ self._filters = value def get_dayu_path(self): """ Get last browser file path :return: str """ return self._path def set_dayu_path(self, value): """ Set browser file start path :param value: str :return: None """ self._path = value def get_dayu_multiple(self): """ Get browser can select multiple file or not :return: bool """ return self._multiple def set_dayu_multiple(self, value): """ Set browser can select multiple file or not :param value: bool :return: None """ self._multiple = value dayu_multiple = Property(bool, get_dayu_multiple, set_dayu_multiple) dayu_path = Property(basestring, get_dayu_path, set_dayu_path) dayu_filters = Property(list, get_dayu_filters, set_dayu_filters) def dragEnterEvent(self, event): """Override dragEnterEvent. Validate dragged files""" if event.mimeData().hasFormat("text/uri-list"): file_list = self._get_valid_file_list(event.mimeData().urls()) count = len(file_list) if count == 1 or (count > 1 and self.get_dayu_multiple()): event.acceptProposedAction() return def dropEvent(self, event): """Override dropEvent to accept the dropped files""" file_list = self._get_valid_file_list(event.mimeData().urls()) if self.get_dayu_multiple(): self.sig_files_changed.emit(file_list) self.set_dayu_path(file_list) else: self.sig_file_changed.emit(file_list[0]) self.set_dayu_path(file_list[0]) def _get_valid_file_list(self, url_list): import subprocess import sys file_list = [] for url in url_list: file_name = url.toLocalFile() if sys.platform == 'darwin': sub_process = subprocess.Popen( 'osascript -e \'get posix path of posix file \"file://{}\" -- kthxbai\'' .format(file_name), stdout=subprocess.PIPE, shell=True) # print sub_process.communicate()[0].strip() file_name = sub_process.communicate()[0].strip() sub_process.wait() if os.path.isfile(file_name): if self.get_dayu_filters(): if os.path.splitext( file_name)[-1] in self.get_dayu_filters(): file_list.append(file_name) else: file_list.append(file_name) return file_list
class MClickBrowserFilePushButton(MPushButton): """A Clickable push button to browser files""" sig_file_changed = Signal(str) sig_files_changed = Signal(list) slot_browser_file = _slot_browser_file def __init__(self, text='Browser', multiple=False, parent=None): super(MClickBrowserFilePushButton, self).__init__(text=text, parent=parent) self.setProperty('multiple', multiple) self.clicked.connect(self.slot_browser_file) self.setToolTip(self.tr('Click to browser file')) self._path = None self._multiple = multiple self._filters = [] def get_dayu_filters(self): """ Get browser's format filters :return: list """ return self._filters def set_dayu_filters(self, value): """ Set browser file format filters :param value: :return: None """ self._filters = value def get_dayu_path(self): """ Get last browser file path :return: str """ return self._path def set_dayu_path(self, value): """ Set browser file start path :param value: str :return: None """ self._path = value def get_dayu_multiple(self): """ Get browser can select multiple file or not :return: bool """ return self._multiple def set_dayu_multiple(self, value): """ Set browser can select multiple file or not :param value: bool :return: None """ self._multiple = value dayu_multiple = Property(bool, get_dayu_multiple, set_dayu_multiple) dayu_path = Property(basestring, get_dayu_path, set_dayu_path) dayu_filters = Property(list, get_dayu_filters, set_dayu_filters)
class MLoading(QWidget): """ Show a loading animation image. """ def __init__(self, size=None, color=None, parent=None): super(MLoading, self).__init__(parent) size = size or dayu_theme.default_size self.setFixedSize(QSize(size, size)) self.pix = MPixmap('loading.svg', color or dayu_theme.primary_color) \ .scaledToWidth(size, Qt.SmoothTransformation) self._rotation = 0 self._loading_ani = QPropertyAnimation() self._loading_ani.setTargetObject(self) # self.loading_ani.setEasingCurve(QEasingCurve.InOutQuad) self._loading_ani.setDuration(1000) self._loading_ani.setPropertyName('rotation') self._loading_ani.setStartValue(0) self._loading_ani.setEndValue(360) self._loading_ani.setLoopCount(-1) self._loading_ani.start() def _set_rotation(self, value): self._rotation = value self.update() def _get_rotation(self): return self._rotation rotation = Property(int, _get_rotation, _set_rotation) def paintEvent(self, event): """override the paint event to paint the 1/4 circle image.""" painter = QPainter(self) painter.setRenderHint(QPainter.SmoothPixmapTransform) painter.translate(self.pix.width() / 2, self.pix.height() / 2) painter.rotate(self._rotation) painter.drawPixmap(-self.pix.width() / 2, -self.pix.height() / 2, self.pix.width(), self.pix.height(), self.pix) painter.end() return super(MLoading, self).paintEvent(event) @classmethod def huge(cls, color=None): """Create a MLoading with huge size""" return cls(dayu_theme.huge, color) @classmethod def large(cls, color=None): """Create a MLoading with large size""" return cls(dayu_theme.large, color) @classmethod def medium(cls, color=None): """Create a MLoading with medium size""" return cls(dayu_theme.medium, color) @classmethod def small(cls, color=None): """Create a MLoading with small size""" return cls(dayu_theme.small, color) @classmethod def tiny(cls, color=None): """Create a MLoading with tiny size""" return cls(dayu_theme.tiny, color)
class MProgressCircle(QProgressBar): """ MProgressCircle: Display the current progress of an operation flow. When you need to display the completion percentage of an operation. Property: dayu_width: int dayu_color: str """ def __init__(self, dashboard=False, parent=None): super(MProgressCircle, self).__init__(parent) self._main_lay = QHBoxLayout() self._default_label = MLabel().h3() self._default_label.setAlignment(Qt.AlignCenter) self._main_lay.addWidget(self._default_label) self.setLayout(self._main_lay) self._color = None self._width = None self._start_angle = 90 * 16 self._max_delta_angle = 360 * 16 self._height_factor = 1.0 self._width_factor = 1.0 if dashboard: self._start_angle = 225 * 16 self._max_delta_angle = 270 * 16 self._height_factor = (2 + pow(2, 0.5)) / 4 + 0.03 self.set_dayu_width(120) self.set_dayu_color(dayu_theme.primary_color) def set_widget(self, widget): """ Set a custom widget to show on the circle's inner center and replace the default percent label :param widget: QWidget :return: None """ self.setTextVisible(False) self._main_lay.addWidget(widget) def get_dayu_width(self): """ Get current circle fixed width :return: int """ return self._width def set_dayu_width(self, value): """ Set current circle fixed width :param value: int :return: None """ self._width = value self.setFixedSize( QSize(self._width * self._width_factor, self._width * self._height_factor)) def get_dayu_color(self): """ Get current circle foreground color :return: str """ return self._color def set_dayu_color(self, value): """ Set current circle's foreground color :param value: str :return: """ self._color = value self.update() dayu_color = Property(str, get_dayu_color, set_dayu_color) dayu_width = Property(int, get_dayu_width, set_dayu_width) def paintEvent(self, event): """Override QProgressBar's paintEvent.""" if self.text() != self._default_label.text(): self._default_label.setText(self.text()) if self.isTextVisible() != self._default_label.isVisible(): self._default_label.setVisible(self.isTextVisible()) percent = utils.get_percent(self.value(), self.minimum(), self.maximum()) total_width = self.get_dayu_width() pen_width = int(3 * total_width / 50.0) radius = total_width - pen_width - 1 painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing) # draw background circle pen_background = QPen() pen_background.setWidth(pen_width) pen_background.setColor(dayu_theme.background_selected_color) pen_background.setCapStyle(Qt.RoundCap) painter.setPen(pen_background) painter.drawArc(pen_width / 2.0 + 1, pen_width / 2.0 + 1, radius, radius, self._start_angle, -self._max_delta_angle) # draw foreground circle pen_foreground = QPen() pen_foreground.setWidth(pen_width) pen_foreground.setColor(self._color) pen_foreground.setCapStyle(Qt.RoundCap) painter.setPen(pen_foreground) painter.drawArc(pen_width / 2.0 + 1, pen_width / 2.0 + 1, radius, radius, self._start_angle, -percent * 0.01 * self._max_delta_angle) painter.end() @classmethod def dashboard(cls, parent=None): """Create a dashboard style MCircle""" return MProgressCircle(dashboard=True, parent=parent)
class MDivider(QWidget): ''' A divider line separates different content. Property: dayu_text: basestring ''' _alignment_map = { Qt.AlignCenter: 50, Qt.AlignLeft: 20, Qt.AlignRight: 80, } def __init__(self, text='', orientation=Qt.Horizontal, alignment=Qt.AlignCenter, parent=None): super(MDivider, self).__init__(parent) self._orient = orientation self._text_label = MLabel().secondary() self._left_frame = QFrame() self._right_frame = QFrame() self._main_lay = QHBoxLayout() self._main_lay.setContentsMargins(0, 0, 0, 0) self._main_lay.setSpacing(0) self._main_lay.addWidget(self._left_frame) self._main_lay.addWidget(self._text_label) self._main_lay.addWidget(self._right_frame) self.setLayout(self._main_lay) if orientation == Qt.Horizontal: self._left_frame.setFrameShape(QFrame.HLine) self._left_frame.setFrameShadow(QFrame.Sunken) self._right_frame.setFrameShape(QFrame.HLine) self._right_frame.setFrameShadow(QFrame.Sunken) else: self._text_label.setVisible(False) self._right_frame.setVisible(False) self._left_frame.setFrameShape(QFrame.VLine) self._left_frame.setFrameShadow(QFrame.Plain) self.setFixedWidth(2) self._main_lay.setStretchFactor(self._left_frame, self._alignment_map.get(alignment, 50)) self._main_lay.setStretchFactor( self._right_frame, 100 - self._alignment_map.get(alignment, 50)) self._text = None self.set_dayu_text(text) def set_dayu_text(self, value): """ Set the divider's text. When text is empty, hide the text_label and right_frame to ensure the divider not has a gap. :param value: basestring :return: None """ self._text = value self._text_label.setText(value) if self._orient == Qt.Horizontal: self._text_label.setVisible(bool(value)) self._right_frame.setVisible(bool(value)) def get_dayu_text(self): """ Get current text :return: basestring """ return self._text dayu_text = Property(basestring, get_dayu_text, set_dayu_text) @classmethod def left(cls, text=''): """Create a horizontal divider with text at left.""" return cls(text, alignment=Qt.AlignLeft) @classmethod def right(cls, text=''): """Create a horizontal divider with text at right.""" return cls(text, alignment=Qt.AlignRight) @classmethod def center(cls, text=''): """Create a horizontal divider with text at center.""" return cls(text, alignment=Qt.AlignCenter) @classmethod def vertical(cls): """Create a vertical divider""" return cls(orientation=Qt.Vertical)
class MPushButton(QPushButton): """ QPushButton. Property: dayu_size: The size of push button dayu_type: The type of push button. """ DefaultType = 'default' PrimaryType = 'primary' SuccessType = 'success' WarningType = 'warning' DangerType = 'danger' def __init__(self, text='', icon=None, parent=None): if icon is None: super(MPushButton, self).__init__(text=text, parent=parent) else: super(MPushButton, self).__init__(icon=icon, text=text, parent=parent) self._dayu_type = MPushButton.DefaultType self._dayu_size = dayu_theme.default_size def get_dayu_size(self): """ Get the push button height :return: integer """ return self._dayu_size def set_dayu_size(self, value): """ Set the avatar size. :param value: integer :return: None """ self._dayu_size = value self.style().polish(self) def get_dayu_type(self): """ Get the push button type. :return: string. """ return self._dayu_type def set_dayu_type(self, value): """ Set the push button type. :return: None """ if value in [ MPushButton.DefaultType, MPushButton.PrimaryType, MPushButton.SuccessType, MPushButton.WarningType, MPushButton.DangerType ]: self._dayu_type = value else: raise ValueError("Input argument 'value' should be one of " "default/primary/success/warning/danger string.") self.style().polish(self) dayu_type = Property(str, get_dayu_type, set_dayu_type) dayu_size = Property(int, get_dayu_size, set_dayu_size) def primary(self): """Set MPushButton to PrimaryType""" self.set_dayu_type(MPushButton.PrimaryType) return self def success(self): """Set MPushButton to SuccessType""" self.set_dayu_type(MPushButton.SuccessType) return self def warning(self): """Set MPushButton to WarningType""" self.set_dayu_type(MPushButton.WarningType) return self def danger(self): """Set MPushButton to DangerType""" self.set_dayu_type(MPushButton.DangerType) return self def huge(self): """Set MPushButton to huge size""" self.set_dayu_size(dayu_theme.huge) return self def large(self): """Set MPushButton to large size""" self.set_dayu_size(dayu_theme.large) return self def medium(self): """Set MPushButton to medium""" self.set_dayu_size(dayu_theme.medium) return self def small(self): """Set MPushButton to small size""" self.set_dayu_size(dayu_theme.small) return self def tiny(self): """Set MPushButton to tiny size""" self.set_dayu_size(dayu_theme.tiny) return self
class MLabel(QLabel): """ Display title in different level. Property: dayu_level: integer dayu_type: str """ SecondaryType = 'secondary' WarningType = 'warning' DangerType = 'danger' H1Level = 1 H2Level = 2 H3Level = 3 H4Level = 4 def __init__(self, text='', parent=None, flags=0): super(MLabel, self).__init__(text, parent, flags) self.setTextInteractionFlags(Qt.TextBrowserInteraction | Qt.LinksAccessibleByMouse) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) self._actual_text = text self._dayu_type = '' self._dayu_underline = False self._dayu_mark = False self._dayu_delete = False self._dayu_strong = False self._dayu_code = False self._dayu_level = 0 self._elide_mode = Qt.ElideNone self.setProperty('dayu_text', text) def get_dayu_level(self): """Get MLabel level.""" return self._dayu_level def set_dayu_level(self, value): """Set MLabel level""" self._dayu_level = value self.style().polish(self) def set_dayu_underline(self, value): """Set MLabel underline style.""" self._dayu_underline = value self.style().polish(self) def get_dayu_underline(self): return self._dayu_underline def set_dayu_delete(self, value): """Set MLabel a delete line style.""" self._dayu_delete = value self.style().polish(self) def get_dayu_delete(self): return self._dayu_delete def set_dayu_strong(self, value): """Set MLabel bold style.""" self._dayu_strong = value self.style().polish(self) def get_dayu_strong(self): return self._dayu_strong def set_dayu_mark(self, value): """Set MLabel mark style.""" self._dayu_mark = value self.style().polish(self) def get_dayu_mark(self): return self._dayu_mark def set_dayu_code(self, value): """Set MLabel code style.""" self._dayu_code = value self.style().polish(self) def get_dayu_code(self): return self._dayu_code def get_elide_mode(self): return self._elide_mode def set_elide_mode(self, value): """Set MLabel elide mode. Only accepted Qt.ElideLeft/Qt.ElideMiddle/Qt.ElideRight/Qt.ElideNone""" self._elide_mode = value self._update_elided_text() def get_dayu_type(self): return self._dayu_type def set_dayu_type(self, value): self._dayu_type = value self.style().polish(self) dayu_level = Property(int, get_dayu_level, set_dayu_level) dayu_type = Property(str, get_dayu_type, set_dayu_type) dayu_underline = Property(bool, get_dayu_underline, set_dayu_underline) dayu_delete = Property(bool, get_dayu_delete, set_dayu_delete) dayu_strong = Property(bool, get_dayu_strong, set_dayu_strong) dayu_mark = Property(bool, get_dayu_mark, set_dayu_mark) dayu_code = Property(bool, get_dayu_code, set_dayu_code) dayu_elide_mod = Property(Qt.TextElideMode, get_dayu_code, set_dayu_code) def minimumSizeHint(self): return QSize(1, self.fontMetrics().height()) def text(self): """ Overridden base method to return the original unmodified text :returns: The original unmodified text """ return self._actual_text def setText(self, text): """ Overridden base method to set the text on the label :param text: The text to set on the label """ self._actual_text = text self._update_elided_text() self.setToolTip(text) def _update_elided_text(self): """ Update the elided text on the label """ _font_metrics = self.fontMetrics() _elided_text = _font_metrics.elidedText(self._actual_text, self._elide_mode, self.width() - 2 * 2) super(MLabel, self).setText(_elided_text) def resizeEvent(self, event): """ Overridden base method called when the widget is resized. :param event: The resize event """ self._update_elided_text() def h1(self): """Set QLabel with h1 type.""" self.set_dayu_level(MLabel.H1Level) return self def h2(self): """Set QLabel with h2 type.""" self.set_dayu_level(MLabel.H2Level) return self def h3(self): """Set QLabel with h3 type.""" self.set_dayu_level(MLabel.H3Level) return self def h4(self): """Set QLabel with h4 type.""" self.set_dayu_level(MLabel.H4Level) return self def secondary(self): """Set QLabel with secondary type.""" self.set_dayu_type(MLabel.SecondaryType) return self def warning(self): """Set QLabel with warning type.""" self.set_dayu_type(MLabel.WarningType) return self def danger(self): """Set QLabel with danger type.""" self.set_dayu_type(MLabel.DangerType) return self def strong(self): """Set QLabel with strong style.""" self.set_dayu_strong(True) return self def mark(self): """Set QLabel with mark style.""" self.set_dayu_mark(True) return self def code(self): """Set QLabel with code style.""" self.set_dayu_code(True) return self def delete(self): """Set QLabel with delete style.""" self.set_dayu_delete(True) return self def underline(self): """Set QLabel with underline style.""" self.set_dayu_underline(True) return self def event(self, event): if event.type() == QEvent.DynamicPropertyChange and event.propertyName( ) == 'dayu_text': self.setText(self.property('dayu_text')) return super(MLabel, self).event(event)
class MDragFolderButton(MToolButton): """A Clickable and draggable tool button to browser folders""" sig_folder_changed = Signal(str) sig_folders_changed = Signal(list) slot_browser_folder = _slot_browser_folder def __init__(self, multiple=False, parent=None): super(MDragFolderButton, self).__init__(parent=parent) self.setAcceptDrops(True) self.setMouseTracking(True) self.text_under_icon() self.set_dayu_svg('folder_line.svg') self.set_dayu_size(60) self.setIconSize(QSize(60, 60)) self.setText(self.tr('Click or drag folder here')) self.clicked.connect(self.slot_browser_folder) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setToolTip(self.tr('Click to browser folder or drag folder here')) self._path = None self._multiple = multiple def get_dayu_path(self): """ Get last browser file path :return: str """ return self._path def set_dayu_path(self, value): """ Set browser file start path :param value: str :return: None """ self._path = value def get_dayu_multiple(self): """ Get browser can select multiple file or not :return: bool """ return self._multiple def set_dayu_multiple(self, value): """ Set browser can select multiple file or not :param value: bool :return: None """ self._multiple = value dayu_multiple = Property(bool, get_dayu_multiple, set_dayu_multiple) dayu_path = Property(bool, get_dayu_path, set_dayu_path) def dragEnterEvent(self, event): """Override dragEnterEvent. Validate dragged folders""" if event.mimeData().hasFormat("text/uri-list"): folder_list = [ url.toLocalFile() for url in event.mimeData().urls() if os.path.isdir(url.toLocalFile()) ] count = len(folder_list) if count == 1 or (count > 1 and self.get_dayu_multiple()): event.acceptProposedAction() return def dropEvent(self, event): """Override dropEvent to accept the dropped folders""" folder_list = [ url.toLocalFile() for url in event.mimeData().urls() if os.path.isdir(url.toLocalFile()) ] if self.get_dayu_multiple(): self.sig_folders_changed.emit(folder_list) else: self.sig_folder_changed.emit(folder_list[0]) self.set_dayu_path(folder_list[0])
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
class MComboBox(QComboBox): Separator = '/' sig_value_changed = Signal(list) def __init__(self, parent=None): super(MComboBox, self).__init__(parent) self._root_menu = None self._display_formatter = utils.display_formatter self.setEditable(True) line_edit = self.lineEdit() line_edit.setReadOnly(True) line_edit.setTextMargins(4, 0, 4, 0) line_edit.setStyleSheet('background-color:transparent') # line_edit.setCursor(Qt.PointingHandCursor) line_edit.installEventFilter(self) self._has_custom_view = False self.set_value('') self.set_placeholder(self.tr('Please Select')) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self._dayu_size = dayu_theme.default_size def get_dayu_size(self): """ Get the push button height :return: integer """ return self._dayu_size def set_dayu_size(self, value): """ Set the avatar size. :param value: integer :return: None """ self._dayu_size = value self.lineEdit().setProperty('dayu_size', value) self.style().polish(self) dayu_size = Property(int, get_dayu_size, set_dayu_size) def set_formatter(self, func): self._display_formatter = func def set_placeholder(self, text): """Display the text when no item selected.""" self.lineEdit().setPlaceholderText(text) def set_value(self, value): self.setProperty('value', value) def _set_value(self, value): self.lineEdit().setProperty('text', self._display_formatter(value)) if self._root_menu: self._root_menu.set_value(value) def set_menu(self, menu): self._root_menu = menu self._root_menu.sig_value_changed.connect(self.sig_value_changed) self._root_menu.sig_value_changed.connect(self.set_value) def setView(self, *args, **kwargs): """Override setView to flag _has_custom_view variable.""" self._has_custom_view = True super(MComboBox, self).setView(*args, **kwargs) def showPopup(self): """Override default showPopup. When set custom menu, show the menu instead.""" if self._has_custom_view or self._root_menu is None: super(MComboBox, self).showPopup() else: QComboBox.hidePopup(self) self._root_menu.popup(self.mapToGlobal(QPoint(0, self.height()))) # def setCurrentIndex(self, index): # raise NotImplementedError def eventFilter(self, widget, event): if widget is self.lineEdit(): if event.type() == QEvent.MouseButtonPress: self.showPopup() return super(MComboBox, self).eventFilter(widget, event) def huge(self): """Set MComboBox to huge size""" self.set_dayu_size(dayu_theme.huge) return self def large(self): """Set MComboBox to large size""" self.set_dayu_size(dayu_theme.large) return self def medium(self): """Set MComboBox to medium""" self.set_dayu_size(dayu_theme.medium) return self def small(self): """Set MComboBox to small size""" self.set_dayu_size(dayu_theme.small) return self def tiny(self): """Set MComboBox to tiny size""" self.set_dayu_size(dayu_theme.tiny) return self
class MAvatar(QLabel): """ Avatar component. It can be used to represent people or object. Property: image: avatar image, should be QPixmap. dayu_size: the size of image. """ def __init__(self, parent=None, flags=0): super(MAvatar, self).__init__(parent, flags) self._default_pix = MPixmap('user_fill.svg') self._pixmap = self._default_pix self._dayu_size = 0 self.set_dayu_size(dayu_theme.default_size) def set_dayu_size(self, value): """ Set the avatar size. :param value: integer :return: None """ self._dayu_size = value self._set_dayu_size() def _set_dayu_size(self): self.setFixedSize(QSize(self._dayu_size, self._dayu_size)) self._set_dayu_image() def _set_dayu_image(self): self.setPixmap( self._pixmap.scaledToWidth(self.height(), Qt.SmoothTransformation)) def set_dayu_image(self, value): """ Set avatar image. :param value: QPixmap or None. :return: None """ if value is None: self._pixmap = self._default_pix elif isinstance(value, QPixmap): self._pixmap = value else: raise TypeError( "Input argument 'value' should be QPixmap or None, " "but get {}".format(type(value))) self._set_dayu_image() def get_dayu_image(self): """ Get the avatar image. :return: QPixmap """ return self._pixmap def get_dayu_size(self): """ Get the avatar size :return: integer """ return self._dayu_size dayu_image = Property(QPixmap, get_dayu_image, set_dayu_image) dayu_size = Property(int, get_dayu_size, set_dayu_size) @classmethod def huge(cls, image=None): """Create a MAvatar with huge size""" inst = cls() inst.set_dayu_size(dayu_theme.huge) inst.set_dayu_image(image) return inst @classmethod def large(cls, image=None): """Create a MAvatar with large size""" inst = cls() inst.set_dayu_size(dayu_theme.large) inst.set_dayu_image(image) return inst @classmethod def medium(cls, image=None): """Create a MAvatar with medium size""" inst = cls() inst.set_dayu_size(dayu_theme.medium) inst.set_dayu_image(image) return inst @classmethod def small(cls, image=None): """Create a MAvatar with small size""" inst = cls() inst.set_dayu_size(dayu_theme.small) inst.set_dayu_image(image) return inst @classmethod def tiny(cls, image=None): """Create a MAvatar with tiny size""" inst = cls() inst.set_dayu_size(dayu_theme.tiny) inst.set_dayu_image(image) return inst
class MCheckBoxGroup(MButtonGroupBase): sig_checked_changed = Signal(list) def __init__(self, orientation=Qt.Horizontal, parent=None): super(MCheckBoxGroup, self).__init__(orientation=orientation, parent=parent) self.set_spacing(15) self._button_group.setExclusive(False) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self._slot_context_menu) self._button_group.buttonClicked[int].connect(self._slot_map_signal) self._dayu_checked = [] def create_button(self, data_dict): return MCheckBox() @Slot(QPoint) def _slot_context_menu(self, point): context_menu = MMenu(parent=self) action_select_all = context_menu.addAction('Select All') action_select_none = context_menu.addAction('Select None') action_select_invert = context_menu.addAction('Select Invert') action_select_all.triggered.connect(functools.partial(self._slot_set_select, True)) action_select_none.triggered.connect(functools.partial(self._slot_set_select, False)) action_select_invert.triggered.connect(functools.partial(self._slot_set_select, None)) context_menu.exec_(QCursor.pos() + QPoint(10, 10)) @Slot(bool) def _slot_set_select(self, state): for check_box in self._button_group.buttons(): if state is None: old_state = check_box.isChecked() check_box.setChecked(not old_state) else: check_box.setChecked(state) self._slot_map_signal() @Slot(int) def _slot_map_signal(self, state=None): self.sig_checked_changed.emit( [check_box.text() for check_box in self._button_group.buttons() if check_box.isChecked()]) def set_dayu_checked(self, value): if not isinstance(value, list): value = [value] if value == self.get_dayu_checked(): return self._dayu_checked = value for check_box in self._button_group.buttons(): flag = Qt.Checked if check_box.text() in value else Qt.Unchecked if flag != check_box.checkState(): check_box.setCheckState(flag) self.sig_checked_changed.emit(value) def get_dayu_checked(self): return [check_box.text() for check_box in self._button_group.buttons() if check_box.isChecked()] # TODO: pyside 的 Property 不直接支持 list,需要寻求解决办法 dayu_checked = Property(list, get_dayu_checked, set_dayu_checked, notify=sig_checked_changed)
class MBadge(QWidget): """ Badge normally appears in proximity to notifications or user avatars with eye-catching appeal, typically displaying unread messages count. Show something at the wrapped widget top right. There is 3 type styles: dot: show a dot count: show a number at text: show a string Property: dayu_dot: bool dayu_text: basestring dayu_count: int dayu_overflow: int """ def __init__(self, widget=None, parent=None): super(MBadge, self).__init__(parent) self._widget = widget self._overflow_count = 99 self._dot = None self._text = None self._count = None self._badge_button = QPushButton() self._badge_button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._main_lay = QGridLayout() self._main_lay.setContentsMargins(0, 0, 0, 0) if widget is not None: self._main_lay.addWidget(widget, 0, 0) self._main_lay.addWidget(self._badge_button, 0, 0, Qt.AlignTop | Qt.AlignRight) self.setLayout(self._main_lay) def get_dayu_overflow(self): """ Get current overflow number :return: int """ return self._overflow_count def set_dayu_overflow(self, num): """ Set the overflow number :param num: new max number :return: None """ self._overflow_count = num self._update_number() def get_dayu_dot(self): """ Get current style is dot or not and dot is show or not :return: bool """ return self._dot def set_dayu_dot(self, show): """ Set dot style and weather show the dot or not :param show: bool :return: None """ self._dot = show self._badge_button.setText('') self._badge_button.setVisible(show) self.style().polish(self) def get_dayu_count(self): """ Get actual count number :return: int """ return self._count def set_dayu_count(self, num): """ Set current style to show a number :param num: int :return: None """ self._count = num self._update_number() def _update_number(self): self._badge_button.setText( utils.overflow_format(self._count, self._overflow_count)) self._badge_button.setVisible(self._count > 0) self._dot = None self.style().polish(self) def get_dayu_text(self): """ Get current showed text :return: basestring """ return self._text def set_dayu_text(self, text): """ Set current style to show a text. :param text: basestring :return: None """ self._text = text self._badge_button.setText(self._text) self._badge_button.setVisible(bool(self._text)) self._dot = None self.style().polish(self) dayu_overflow = Property(int, get_dayu_overflow, set_dayu_overflow) dayu_dot = Property(bool, get_dayu_dot, set_dayu_dot) dayu_count = Property(int, get_dayu_count, set_dayu_count) dayu_text = Property(basestring, get_dayu_text, set_dayu_text) @classmethod def dot(cls, show=False, widget=None): """ Create a Badge with dot style. :param show: bool :param widget: the wrapped widget :return: instance badge """ inst = cls(widget=widget) inst.set_dayu_dot(show) return inst @classmethod def count(cls, count=0, widget=None): """ Create a Badge with number style. :param count: int :param widget: the wrapped widget :return: instance badge """ inst = cls(widget=widget) inst.set_dayu_count(count) return inst @classmethod def text(cls, text='', widget=None): """ Create a Badge with text style. :param text: basestring :param widget: the wrapped widget :return: instance badge """ inst = cls(widget=widget) inst.set_dayu_text(text) return inst
class MSwitch(QRadioButton): """ Switching Selector. Property: dayu_size: the size of switch widget. int """ def __init__(self, parent=None): super(MSwitch, self).__init__(parent) self._dayu_size = dayu_theme.default_size self.setAutoExclusive(False) def minimumSizeHint(self): """ Override the QRadioButton minimum size hint. We don't need the text space. :return: """ height = self._dayu_size * 1.2 return QSize(height, height / 2) def get_dayu_size(self): """ Get the switch size. :return: int """ return self._dayu_size def set_dayu_size(self, value): """ Set the switch size. :param value: int :return: None """ self._dayu_size = value self.style().polish(self) dayu_size = Property(int, get_dayu_size, set_dayu_size) def huge(self): """Set MSwitch to huge size""" self.set_dayu_size(dayu_theme.huge) return self def large(self): """Set MSwitch to large size""" self.set_dayu_size(dayu_theme.large) return self def medium(self): """Set MSwitch to medium size""" self.set_dayu_size(dayu_theme.medium) return self def small(self): """Set MSwitch to small size""" self.set_dayu_size(dayu_theme.small) return self def tiny(self): """Set MSwitch to tiny size""" self.set_dayu_size(dayu_theme.tiny) return self
class MToolButton(QToolButton): """MToolButton""" def __init__(self, parent=None): super(MToolButton, self).__init__(parent=parent) self._dayu_svg = None self.setAutoExclusive(False) self.setAutoRaise(True) self._polish_icon() self.toggled.connect(self._polish_icon) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._dayu_size = dayu_theme.default_size @Slot(bool) def _polish_icon(self, checked=None): if self._dayu_svg: if self.isCheckable() and self.isChecked(): self.setIcon(MIcon(self._dayu_svg, dayu_theme.primary_color)) else: self.setIcon(MIcon(self._dayu_svg)) def enterEvent(self, event): """Override enter event to highlight the icon""" if self._dayu_svg: self.setIcon(MIcon(self._dayu_svg, dayu_theme.primary_color)) return super(MToolButton, self).enterEvent(event) def leaveEvent(self, event): """Override leave event to recover the icon""" self._polish_icon() return super(MToolButton, self).leaveEvent(event) def get_dayu_size(self): """ Get the push button height :return: integer """ return self._dayu_size def set_dayu_size(self, value): """ Set the avatar size. :param value: integer :return: None """ self._dayu_size = value self.style().polish(self) if self.toolButtonStyle() == Qt.ToolButtonIconOnly: self.setFixedSize(QSize(self._dayu_size, self._dayu_size)) def get_dayu_svg(self): """Get current svg path""" return self._dayu_svg def set_dayu_svg(self, path): """Set current svg path""" self._dayu_svg = path self._polish_icon() dayu_size = Property(int, get_dayu_size, set_dayu_size) def huge(self): """Set MPushButton to PrimaryType""" self.set_dayu_size(dayu_theme.huge) return self def large(self): """Set MPushButton to SuccessType""" self.set_dayu_size(dayu_theme.large) return self def medium(self): """Set MPushButton to WarningType""" self.set_dayu_size(dayu_theme.medium) return self def small(self): """Set MPushButton to DangerType""" self.set_dayu_size(dayu_theme.small) return self def tiny(self): """Set MPushButton to DangerType""" self.set_dayu_size(dayu_theme.tiny) return self def svg(self, path): """Set current svg path""" self.set_dayu_svg(path) return self def icon_only(self): """Set tool button style to icon only""" self.setToolButtonStyle(Qt.ToolButtonIconOnly) self.setFixedSize(QSize(self._dayu_size, self._dayu_size)) return self def text_only(self): """Set tool button style to text only""" self.setToolButtonStyle(Qt.ToolButtonTextOnly) return self def text_beside_icon(self): """Set tool button style to text beside icon""" self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) return self def text_under_icon(self): """Set tool button style to text under icon""" self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) return self
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 MLineEdit(QLineEdit): """MLineEdit""" sig_delay_text_changed = Signal(basestring) def __init__(self, text='', parent=None): super(MLineEdit, self).__init__(text, parent) self._main_layout = QHBoxLayout() self._main_layout.setContentsMargins(0, 0, 0, 0) self._main_layout.addStretch() self._prefix_widget = None self._suffix_widget = None self.setLayout(self._main_layout) self.setProperty('history', self.property('text')) self.setTextMargins(2, 0, 2, 0) self._delay_timer = QTimer() self._delay_timer.setInterval(500) self._delay_timer.setSingleShot(True) self._delay_timer.timeout.connect(self._slot_delay_text_changed) self._dayu_size = dayu_theme.default_size def get_dayu_size(self): """ Get the push button height :return: integer """ return self._dayu_size def set_dayu_size(self, value): """ Set the avatar size. :param value: integer :return: None """ self._dayu_size = value if hasattr(self._prefix_widget, 'set_dayu_size'): self._prefix_widget.set_dayu_size(self._dayu_size) if hasattr(self._suffix_widget, 'set_dayu_size'): self._suffix_widget.set_dayu_size(self._dayu_size) self.style().polish(self) dayu_size = Property(int, get_dayu_size, set_dayu_size) def set_delay_duration(self, millisecond): """Set delay timer's timeout duration.""" self._delay_timer.setInterval(millisecond) @Slot() def _slot_delay_text_changed(self): self.sig_delay_text_changed.emit(self.text()) def get_prefix_widget(self): """Get the prefix widget for user to edit""" return self._prefix_widget def set_prefix_widget(self, widget): """Set the line edit left start widget""" if self._prefix_widget: index = self._main_layout.indexOf(self._prefix_widget) self._main_layout.takeAt(index) self._prefix_widget.setVisible(False) # if isinstance(widget, MPushButton): widget.setProperty('combine', 'horizontal') widget.setProperty('position', 'left') if hasattr(widget, 'set_dayu_size'): widget.set_dayu_size(self._dayu_size) margin = self.textMargins() margin.setLeft(margin.left() + widget.width()) self.setTextMargins(margin) self._main_layout.insertWidget(0, widget) self._prefix_widget = widget return widget def get_suffix_widget(self): """Get the suffix widget for user to edit""" return self._suffix_widget def set_suffix_widget(self, widget): """Set the line edit right end widget""" if self._suffix_widget: index = self._main_layout.indexOf(self._suffix_widget) self._main_layout.takeAt(index) self._suffix_widget.setVisible(False) # if isinstance(widget, MPushButton): widget.setProperty('combine', 'horizontal') widget.setProperty('position', 'right') if hasattr(widget, 'set_dayu_size'): widget.set_dayu_size(self._dayu_size) margin = self.textMargins() margin.setRight(margin.right() + widget.width()) self.setTextMargins(margin) self._main_layout.addWidget(widget) self._suffix_widget = widget return widget def setText(self, text): """Override setText save text to history""" self.setProperty('history', u'{}\n{}'.format(self.property('history'), text)) return super(MLineEdit, self).setText(text) def clear(self): """Override clear to clear history""" self.setProperty('history', '') return super(MLineEdit, self).clear() def keyPressEvent(self, event): """Override keyPressEvent to start delay timer""" if event.key() not in [Qt.Key_Enter, Qt.Key_Tab]: if self._delay_timer.isActive(): self._delay_timer.stop() self._delay_timer.start() super(MLineEdit, self).keyPressEvent(event) def search(self): """Add a search icon button for MLineEdit.""" suffix_button = MToolButton().icon_only().svg('close_line.svg') suffix_button.clicked.connect(self.clear) self.set_suffix_widget(suffix_button) self.setPlaceholderText(self.tr('Enter key word to search...')) return self def error(self): """A a toolset to MLineEdit to store error info with red style""" @Slot() def _slot_show_detail(self): dialog = QTextEdit(self) dialog.setReadOnly(True) geo = QApplication.desktop().screenGeometry() dialog.setGeometry(geo.width() / 2, geo.height() / 2, geo.width() / 4, geo.height() / 4) dialog.setWindowTitle(self.tr('Error Detail Information')) dialog.setText(self.property('history')) dialog.setWindowFlags(Qt.Dialog) dialog.show() self.setProperty('dayu_type', 'error') self.setReadOnly(True) _suffix_button = MToolButton().icon_only().svg('detail_line.svg') _suffix_button.clicked.connect( functools.partial(_slot_show_detail, self)) self.set_suffix_widget(_suffix_button) self.setPlaceholderText(self.tr('Error information will be here...')) return self def search_engine(self, text='Search'): """Add a MPushButton to suffix for MLineEdit""" _suffix_button = MPushButton(text=text).primary() _suffix_button.clicked.connect(self.returnPressed) _suffix_button.setFixedWidth(100) self.set_suffix_widget(_suffix_button) self.setPlaceholderText(self.tr('Enter key word to search...')) return self def file(self, filters=None): """Add a MClickBrowserFileToolButton for MLineEdit to select file""" _suffix_button = MClickBrowserFileToolButton() _suffix_button.sig_file_changed.connect(self.setText) _suffix_button.set_dayu_filters(filters or []) self.textChanged.connect(_suffix_button.set_dayu_path) self.set_suffix_widget(_suffix_button) self.setPlaceholderText(self.tr('Click button to browser files')) return self def save_file(self, filters=None): """Add a MClickSaveFileToolButton for MLineEdit to set save file""" _suffix_button = MClickSaveFileToolButton() _suffix_button.sig_file_changed.connect(self.setText) _suffix_button.set_dayu_filters(filters or []) self.textChanged.connect(_suffix_button.set_dayu_path) self.set_suffix_widget(_suffix_button) self.setPlaceholderText(self.tr('Click button to set save file')) return self def folder(self): """Add a MClickBrowserFolderToolButton for MLineEdit to select folder""" _suffix_button = MClickBrowserFolderToolButton() _suffix_button.sig_folder_changed.connect(self.setText) self.textChanged.connect(_suffix_button.set_dayu_path) self.set_suffix_widget(_suffix_button) self.setPlaceholderText(self.tr('Click button to browser folder')) return self def huge(self): """Set MLineEdit to huge size""" self.set_dayu_size(dayu_theme.huge) return self def large(self): """Set MLineEdit to large size""" self.set_dayu_size(dayu_theme.large) return self def medium(self): """Set MLineEdit to medium""" self.set_dayu_size(dayu_theme.medium) return self def small(self): """Set MLineEdit to small size""" self.set_dayu_size(dayu_theme.small) return self def tiny(self): """Set MLineEdit to tiny size""" self.set_dayu_size(dayu_theme.tiny) return self def password(self): """Set MLineEdit to password echo mode""" self.setEchoMode(QLineEdit.Password) return self