class MPushButtonGroup(MButtonGroupBase): def __init__(self, orientation=QtCore.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 = QtCore.Property(int, get_dayu_size, set_dayu_size) dayu_type = QtCore.Property(str, get_dayu_type, set_dayu_type)
class MClickBrowserFolderToolButton(MToolButton): """A Clickable tool button to browser folders""" sig_folder_changed = QtCore.Signal(str) sig_folders_changed = QtCore.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 = QtCore.Property(bool, get_dayu_multiple, set_dayu_multiple) dayu_path = QtCore.Property(six.string_types[0], get_dayu_path, set_dayu_path)
class MClickSaveFileToolButton(MToolButton): """A Clickable tool button to browser files""" sig_file_changed = QtCore.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 = QtCore.Property(six.string_types[0], get_dayu_path, set_dayu_path) dayu_filters = QtCore.Property(list, get_dayu_filters, set_dayu_filters)
class MTimeEdit(QtWidgets.QTimeEdit): """ MTimeEdit just use stylesheet and add dayu_size. No more extend. Property: dayu_size: The height of MTimeEdit """ def __init__(self, time=None, parent=None): if time is None: super(MTimeEdit, self).__init__(parent=parent) else: super(MTimeEdit, self).__init__(time, parent=parent) self._dayu_size = dayu_theme.default_size def get_dayu_size(self): """ Get the MTimeEdit height :return: integer """ return self._dayu_size def set_dayu_size(self, value): """ Set the MTimeEdit size. :param value: integer :return: None """ self._dayu_size = value self.style().polish(self) dayu_size = QtCore.Property(int, get_dayu_size, set_dayu_size) def huge(self): """Set MTimeEdit to huge size""" self.set_dayu_size(dayu_theme.huge) return self def large(self): """Set MTimeEdit to large size""" self.set_dayu_size(dayu_theme.large) return self def medium(self): """Set MTimeEdit to medium""" self.set_dayu_size(dayu_theme.medium) return self def small(self): """Set MTimeEdit to small size""" self.set_dayu_size(dayu_theme.small) return self def tiny(self): """Set MTimeEdit to tiny size""" self.set_dayu_size(dayu_theme.tiny) return self
def bind(typ,objectName, propertyName ,getter = None ,setter = None): def _getter(self): print ("getter objectName ",objectName) return self.findChild(QtCore.QObject, objectName).property(propertyName) def _setter(self, value): print ("setter objectName ",objectName,value) self.findChild(QtCore.QObject, objectName).setProperty(propertyName, value) return QtCore.Property(typ,_getter,_setter)
class MDoubleSpinBox(QtWidgets.QDoubleSpinBox): """ MDoubleSpinBox just use stylesheet and add dayu_size. No more extend. Property: dayu_size: The height of MDoubleSpinBox """ def __init__(self, parent=None): super(MDoubleSpinBox, self).__init__(parent=parent) self._dayu_size = dayu_theme.default_size def get_dayu_size(self): """ Get the MDoubleSpinBox height :return: integer """ return self._dayu_size def set_dayu_size(self, value): """ Set the MDoubleSpinBox size. :param value: integer :return: None """ self._dayu_size = value self.style().polish(self) dayu_size = QtCore.Property(int, get_dayu_size, set_dayu_size) def huge(self): """Set MDoubleSpinBox to huge size""" self.set_dayu_size(dayu_theme.huge) return self def large(self): """Set MDoubleSpinBox to large size""" self.set_dayu_size(dayu_theme.large) return self def medium(self): """Set MDoubleSpinBox to medium""" self.set_dayu_size(dayu_theme.medium) return self def small(self): """Set MDoubleSpinBox to small size""" self.set_dayu_size(dayu_theme.small) return self def tiny(self): """Set MDoubleSpinBox to tiny size""" self.set_dayu_size(dayu_theme.tiny) return self
class MToolButtonGroup(MButtonGroupBase): sig_checked_changed = QtCore.Signal(int) def __init__( self, size=None, type=None, exclusive=False, orientation=QtCore.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 = QtCore.Property(int, get_dayu_checked, set_dayu_checked, notify=sig_checked_changed)
class MProgressBar(QtWidgets.QProgressBar): """ props: status: str """ ErrorStatus = "error" NormalStatus = "primary" SuccessStatus = "success" def __init__(self, parent=None): super(MProgressBar, self).__init__(parent=parent) self.setAlignment(QtCore.Qt.AlignCenter) self._status = MProgressBar.NormalStatus def auto_color(self): self.valueChanged.connect(self._update_color) return self @QtCore.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 = QtCore.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 TestLabel(QtWidgets.QLabel): def __init__(self, parent=None): super(TestLabel, self).__init__(parent) self.text3 = "123" def getString(self): print("getString call") return self.text() def setString(self, text): self.setText("text2: %s" % text) text2 = QtCore.Property(str, getString, setString) @property def text3(self): return self.text() @text3.setter def text3(self, text): self.setText("text3: %s" % text)
class MRadioButtonGroup(MButtonGroupBase): """ Property: dayu_checked """ sig_checked_changed = QtCore.Signal(int) def __init__(self, orientation=QtCore.Qt.Horizontal, parent=None): super(MRadioButtonGroup, self).__init__(orientation=orientation, parent=parent) scale_x, _ = get_scale_factor() self.set_spacing(15 * scale_x) 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 = QtCore.Property(int, get_dayu_checked, set_dayu_checked, notify=sig_checked_changed)
class ScreenMarquee(QtWidgets.QDialog): """Dialog to interactively define screen area. This allows to select a screen area through a marquee selection. """ def __init__(self, parent=None): """Constructor""" super(ScreenMarquee, self).__init__(parent=parent) self._opacity = 1 self._click_pos = None self._capture_rect = QtCore.QRect() self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.CustomizeWindowHint | QtCore.Qt.Tool) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setCursor(QtCore.Qt.CrossCursor) self.setMouseTracking(True) desktop = QtWidgets.QApplication.desktop() desktop.resized.connect(self._fit_screen_geometry) desktop.screenCountChanged.connect(self._fit_screen_geometry) @property def capture_rect(self): """The resulting QRect from a previous capture operation.""" return self._capture_rect def paintEvent(self, event): """Paint event""" # Convert click and current mouse positions to local space. mouse_pos = self.mapFromGlobal(QtGui.QCursor.pos()) click_pos = None if self._click_pos is not None: click_pos = self.mapFromGlobal(self._click_pos) painter = QtGui.QPainter(self) # Draw background. Aside from aesthetics, this makes the full # tool region accept mouse events. painter.setBrush(QtGui.QColor(0, 0, 0, self._opacity)) painter.setPen(QtCore.Qt.NoPen) painter.drawRect(event.rect()) # Clear the capture area if click_pos is not None: capture_rect = QtCore.QRect(click_pos, mouse_pos) painter.setCompositionMode(painter.CompositionMode_Clear) painter.drawRect(capture_rect) painter.setCompositionMode(painter.CompositionMode_SourceOver) pen_color = QtGui.QColor(255, 255, 255, 64) pen = QtGui.QPen(pen_color, 1, QtCore.Qt.DotLine) painter.setPen(pen) # Draw cropping markers at click position rect = event.rect() if click_pos is not None: painter.drawLine(rect.left(), click_pos.y(), rect.right(), click_pos.y()) painter.drawLine(click_pos.x(), rect.top(), click_pos.x(), rect.bottom()) # Draw cropping markers at current mouse position painter.drawLine(rect.left(), mouse_pos.y(), rect.right(), mouse_pos.y()) painter.drawLine(mouse_pos.x(), rect.top(), mouse_pos.x(), rect.bottom()) def mousePressEvent(self, event): """Mouse click event""" if event.button() == QtCore.Qt.LeftButton: # Begin click drag operation self._click_pos = event.globalPos() def mouseReleaseEvent(self, event): """Mouse release event""" if event.button( ) == QtCore.Qt.LeftButton and self._click_pos is not None: # End click drag operation and commit the current capture rect self._capture_rect = QtCore.QRect(self._click_pos, event.globalPos()).normalized() self._click_pos = None self.close() def mouseMoveEvent(self, event): """Mouse move event""" self.repaint() @classmethod def capture_pixmap(cls): """Modally capture screen with marquee into pixmap. Returns: QtGui.QPixmap: Captured pixmap image """ tool = cls() tool.exec_() return get_desktop_pixmap(tool.capture_rect) @classmethod def capture_file(cls, filepath=None): if filepath is None: filepath = tempfile.NamedTemporaryFile(prefix="screenshot_", suffix=".png", delete=False).name pixmap = cls.capture_pixmap() pixmap.save(filepath) return filepath def showEvent(self, event): """ Show event """ self._fit_screen_geometry() # Start fade in animation fade_anim = QtCore.QPropertyAnimation(self, "_opacity_anim_prop", self) fade_anim.setStartValue(self._opacity) fade_anim.setEndValue(50) fade_anim.setDuration(200) fade_anim.setEasingCurve(QtCore.QEasingCurve.OutCubic) fade_anim.start(QtCore.QAbstractAnimation.DeleteWhenStopped) def _set_opacity(self, value): """ Animation callback for opacity """ self._opacity = value self.repaint() def _get_opacity(self): """ Animation callback for opacity """ return self._opacity _opacity_anim_prop = QtCore.Property(int, _get_opacity, _set_opacity) def _fit_screen_geometry(self): # Compute the union of all screen geometries, and resize to fit. desktop = QtWidgets.QApplication.desktop() workspace_rect = QtCore.QRect() for i in range(desktop.screenCount()): workspace_rect = workspace_rect.united(desktop.screenGeometry(i)) self.setGeometry(workspace_rect)
class TestContainer(QtWidgets.QWidget): msg = QtCore.Property( str, self.label.text, lambda instance, val: self.label.setText("test : %s" % val))
class MDivider(QtWidgets.QWidget): """ A divider line separates different content. Property: dayu_text: six.string_types """ _alignment_map = { QtCore.Qt.AlignCenter: 50, QtCore.Qt.AlignLeft: 20, QtCore.Qt.AlignRight: 80, } def __init__( self, text="", orientation=QtCore.Qt.Horizontal, alignment=QtCore.Qt.AlignCenter, parent=None, ): super(MDivider, self).__init__(parent) self._orient = orientation self._text_label = MLabel().secondary() self._left_frame = QtWidgets.QFrame() self._right_frame = QtWidgets.QFrame() self._main_lay = QtWidgets.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 == QtCore.Qt.Horizontal: self._left_frame.setFrameShape(QtWidgets.QFrame.HLine) self._left_frame.setFrameShadow(QtWidgets.QFrame.Sunken) self._right_frame.setFrameShape(QtWidgets.QFrame.HLine) self._right_frame.setFrameShadow(QtWidgets.QFrame.Sunken) else: self._text_label.setVisible(False) self._right_frame.setVisible(False) self._left_frame.setFrameShape(QtWidgets.QFrame.VLine) self._left_frame.setFrameShadow(QtWidgets.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: six.string_types :return: None """ self._text = value self._text_label.setText(value) if self._orient == QtCore.Qt.Horizontal: self._text_label.setVisible(bool(value)) self._right_frame.setVisible(bool(value)) def get_dayu_text(self): """ Get current text :return: six.string_types """ return self._text dayu_text = QtCore.Property(six.string_types[0], get_dayu_text, set_dayu_text) @classmethod def left(cls, text=""): """Create a horizontal divider with text at left.""" return cls(text, alignment=QtCore.Qt.AlignLeft) @classmethod def right(cls, text=""): """Create a horizontal divider with text at right.""" return cls(text, alignment=QtCore.Qt.AlignRight) @classmethod def center(cls, text=""): """Create a horizontal divider with text at center.""" return cls(text, alignment=QtCore.Qt.AlignCenter) @classmethod def vertical(cls): """Create a vertical divider""" return cls(orientation=QtCore.Qt.Vertical)
class MAvatar(QtWidgets.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=QtCore.Qt.Widget): 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(QtCore.QSize(self._dayu_size, self._dayu_size)) self._set_dayu_image() def _set_dayu_image(self): self.setPixmap( self._pixmap.scaledToWidth(self.height(), QtCore.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, QtGui.QPixmap): self._pixmap = self._default_pix if value.isNull() else 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 = QtCore.Property(QtGui.QPixmap, get_dayu_image, set_dayu_image) dayu_size = QtCore.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 MClickBrowserFilePushButton(MPushButton): """A Clickable push button to browser files""" sig_file_changed = QtCore.Signal(str) sig_files_changed = QtCore.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 = QtCore.Property(bool, get_dayu_multiple, set_dayu_multiple) dayu_path = QtCore.Property(six.string_types[0], get_dayu_path, set_dayu_path) dayu_filters = QtCore.Property(list, get_dayu_filters, set_dayu_filters)
class MDragFolderButton(MToolButton): """A Clickable and draggable tool button to browser folders""" sig_folder_changed = QtCore.Signal(str) sig_folders_changed = QtCore.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") size = dayu_theme.drag_size self.set_dayu_size(size) self.setIconSize(QtCore.QSize(size, size)) self.setText(self.tr("Click or drag folder here")) self.clicked.connect(self.slot_browser_folder) self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.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 = QtCore.Property(bool, get_dayu_multiple, set_dayu_multiple) dayu_path = QtCore.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])