def _delay_run(times): if times <= 0: return timer = QTimer() all_timer.append(timer) timer.setSingleShot(True) timer.timeout.connect(_run) timer.timeout.connect(lambda: _delay_run(times - 1)) timer.start(random.randint(0, 300))
def __init__(self, text, duration=None, theme_type=None, closable=False, parent=None): self._text = text self._duration = duration self._theme_type = theme_type self._closable = closable super(PopupMessage, self).__init__(parent=parent) self.setAttribute(Qt.WA_TranslucentBackground) close_timer = QTimer(self) close_timer.setSingleShot(True) close_timer.timeout.connect(self.close) close_timer.timeout.connect(self.closed) close_timer.setInterval( (duration or self.DEFAULT_CONFIG['duration']) * 1000) anim_timer = QTimer(self) anim_timer.timeout.connect(self._on_fade_out) anim_timer.setInterval((duration or self.DEFAULT_CONFIG['duration']) * 1000 - 300) close_timer.start() anim_timer.start() self._pos_anim = QPropertyAnimation(self) self._pos_anim.setTargetObject(self) self._pos_anim.setEasingCurve(QEasingCurve.OutCubic) self._pos_anim.setDuration(300) self._pos_anim.setPropertyName(b'pos') self._opacity_anim = QPropertyAnimation() self._opacity_anim.setTargetObject(self) self._opacity_anim.setEasingCurve(QEasingCurve.OutCubic) self._opacity_anim.setDuration(300) self._opacity_anim.setPropertyName(b'windowOpacity') self._opacity_anim.setStartValue(0.0) self._opacity_anim.setEndValue(1.0) self._set_proper_position(parent) self._fade_in()
class ToolTipWidget(QWidget, object): hidden = Signal() def __init__(self, parent=None): super(ToolTipWidget, self).__init__(parent) self._layout = None self._content = None self._content_parent = None self._hide_timer = QTimer(self) self._init() def enterEvent(self, event): if self.hide_delay() > 0: self._hide_timer.stop() else: self.hide() def hideEvent(self, event): self._remove_widget() QTimer.singleShot(0, self.hidden.emit) def leaveEvent(self, event): self.hide() # def paintEvent(self, event): # painter = QStylePainter(self) # painter.setClipRegion(event.region()) # option = QStyleOptionFrame() # option.init(self) # painter.drawPrimitive(QStyle.PE_PanelTipLabel, option) # painter.end() # # super(ToolTipWidget, self).paintEvent(event) def show_at(self, pos, content, parent_window=None): """ Shows tooltip in given position and with given widget :param pos: QPoint :param content: QWidget :param parent_window: QWindow """ parent_window = parent_window or dcc.get_main_window().windowHandle() self._add_widget(content) self._show(pos, parent_window) def show_below(self, rect, content, parent_window=None): """ Shows tooltip below given rect and with given content :param rect: QRect :param content: QWidget :param parent_window: QWindow """ parent_window = parent_window or dcc.get_main_window().windowHandle() self._add_widget(content) margin_size = QSize( 2 * content.style().pixelMetric(QStyle.PM_DefaultTopLevelMargin), 2 * content.style().pixelMetric(QStyle.PM_DefaultTopLevelMargin) ) content.setMaximumSize(parent_window.screen().geometry().size() - margin_size) self._show(self._center_below(rect, parent_window.screen()), parent_window) def hide_delay(self): """ Returns timer hide interval :return: float """ return self._hide_timer.interval() def set_hide_delay(self, hide_delay_interval): """ Sets the delay timer value :param hide_delay_interval: float """ self._hide_timer.setInterval(hide_delay_interval) def hide_later(self): """ Hides tooltip if timer is over """ if not self.isVisible(): return if self.hide_delay() > 0: self._hide_timer.start() else: self.hide() def _init(self): """ Internal function that initializes tooltip widget """ self.setMouseTracking(True) self._layout = layouts.VerticalLayout(parent=self) self._hide_timer.setSingleShot(True) self._hide_timer.setInterval(500) self._hide_timer.timeout.connect(self._on_timer_timeout) # self.setAttribute(Qt.WA_TranslucentBackground) self.setWindowFlags(Qt.ToolTip | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint) def _add_widget(self, widget): """ Internal function that adds replaces current contained wiget with the given one :param widget: QWidget """ self._remove_widget() self._content = widget self._store_parent() self._layout.addWidget(self._content) widget.destroyed.connect(widget.hide) def _remove_widget(self): """ Internal function that removes current contained widget from the tooltip """ self._layout.removeWidget(self._content) self._restore_parent() def _show(self, pos, parent_window): if not pos or pos.isNull(): return offset_pos = QPoint(pos.x() - 5, pos.y() - 5) self.move(offset_pos) self.createWinId() self.windowHandle().setProperty('ENABLE_BLUR_BEHIND_HINT', True) self.windowHandle().setTransientParent(parent_window) self.show() def _store_parent(self): """ Internal function that stores parent of current contained widget """ if not self._content: return self._content_parent = self._content.parent() def _restore_parent(self): """ Internal function that reparent current contained widget to current tooltip parent widget """ if not self._content or not self._content_parent: return self._content.setParent(self._content_parent) def _center_below(self, rect, screen): """ Internal function that returns a position for the tooltip ensuring that: 1) The content is fully visible 2) The content is not drawn inside rect :param rect: QRect :param screen: QScreen :return: QPoint """ size = self.sizeHint() margin = self.style().pixelMetric(QStyle.PM_ToolTipLabelFrameWidth) screen_geometry = screen.geometry() has_room_to_left = (rect.left() - size.width() - margin >= screen_geometry.left()) has_room_to_right = (rect.right() + size.width() + margin <= screen_geometry.right()) has_room_above = (rect.top() - size.height() - margin >= screen_geometry.top()) has_room_below = (rect.bottom() + size.height() + margin <= screen_geometry.bottom()) if not has_room_above and not has_room_below and not has_room_to_left and not has_room_to_right: return QPoint() x = 0 y = 0 if has_room_below or has_room_above: x = max(screen_geometry.left(), rect.center().x() - size.width() / 2) if x + size.width() >= screen_geometry.right(): x = screen_geometry.right() - size.width() + 1 assert x >= 0 if has_room_below: y = rect.bottom() + margin else: y = rect.top() - size.height() - margin + 1 else: assert has_room_to_left or has_room_to_right if has_room_to_right: x = rect.right() + margin else: x = rect.left() - size.width() - margin + 1 # Put tooltip at the bottom of the screen. The x-coordinate has already been adjusted, # so no overlapping with rect occurs y = screen_geometry.bottom() - size.height() + 1 return QPoint(x, y) def _on_timer_timeout(self): self.hide()
def ui(self): super(BaseToast, self).ui() self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog | Qt.WA_TranslucentBackground | Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_StyledBackground) self.setFixedSize(QSize(120, 120)) icon_layout = layouts.HorizontalLayout() icon_layout.addStretch() widget_theme = self.theme() if self._toast_type == self.ToastTypes.LOADING: icon_layout.addWidget( loading.CircleLoading(size=widget_theme.huge, color=widget_theme.text_color_inverse)) else: icon_label = avatar.Avatar() icon_label.theme_size = 60 icon_label.image = resources.pixmap( self._toast_type or self.ToastTypes.INFO, color=widget_theme.text_color_inverse) icon_layout.addWidget(icon_label) icon_layout.addStretch() content_label = label.BaseLabel() content_label.setText(self._text or '') content_label.setAlignment(Qt.AlignCenter) self.main_layout.addStretch() self.main_layout.addLayout(icon_layout) self.main_layout.addSpacing(10) self.main_layout.addWidget(content_label) self.main_layout.addStretch() close_timer = QTimer(self) close_timer.setSingleShot(True) close_timer.timeout.connect(self.close) close_timer.timeout.connect(self.toastClosed.emit) close_timer.setInterval( (self._duration or self.DEFAULT_CONFIG.get('duration', 2)) * 1000) anim_timer = QTimer(self) anim_timer.timeout.connect(self._fade_out) anim_timer.setInterval( (self._duration or self.DEFAULT_CONFIG.get('duration', 2)) * 1000 - 300) self._opacity_anim = QPropertyAnimation() self._opacity_anim.setTargetObject(self) self._opacity_anim.setDuration(300) self._opacity_anim.setEasingCurve(QEasingCurve.OutCubic) self._opacity_anim.setPropertyName('windowOpacity') self._opacity_anim.setStartValue(0.0) self._opacity_anim.setEndValue(0.9) close_timer.start() anim_timer.start() self._get_center_position(self._parent) self._fade_in()
class ToastWidget(QLabel, object): """ Toast widget used to show quick messages to user """ DEFAULT_DURATION = 500 DEFAULT_PADDING = 30 def __init__(self, *args): super(ToastWidget, self).__init__(*args) self._timer = QTimer(self) self._timer.setSingleShot(True) self._timer.timeout.connect(self._on_fade_out) self._duration = self.DEFAULT_DURATION self.setMouseTracking(True) self.setAlignment(Qt.AlignCenter) self.setAttribute(Qt.WA_TransparentForMouseEvents) if self.parent(): self.parent().installEventFilter(self) # def eventFilter(self, obj, event): # """ # Overrides base QLabel eventFilter function # Updates the geometry when the parent widget changes size # :param obj: QWidget # :param event: QEvent # """ # # if event.type() == QEvent.Resize: # self.updateGeometry() # return super(ToastWidget, self).eventFilter(obj, event) def updateGeometry(self): """ Overrides base QLabel updateGeometry function Updates and aligns the geometry to the parent widget """ padding = self.DEFAULT_PADDING widget = self.parent() width = self.text_width() + padding height = self.text_height() + padding x = widget.width() * 0.5 - width * 0.5 y = (widget.height() - height) / 1.2 self.setGeometry(x, y, width, height) def setText(self, *args, **kwargs): """ Overrides base QLabel setText function Updates the size depending on the text width :param text: str """ super(ToastWidget, self).setText(*args, **kwargs) self.updateGeometry() def show(self): """ Overrides base QLabel show function Starts the timer to hide the toast """ duration = self.duration() self._timer.stop() self._timer.start(duration) if not self.isVisible(): animation.fade_in_widget(self, duration=0) super(ToastWidget, self).show() def duration(self): """ Returns duration :return: int """ return self._duration def set_duration(self, duration): """ Sets how long to show the toast (in milliseconds) :param duration: int """ self._duration = duration def text_rect(self): """ Returns the bounding box rect for the text :return: QRect """ text = self.text() font = self.font() metrics = QFontMetricsF(font) return metrics.boundingRect(text) def text_width(self): """ Returns the width of the text :return: int """ text_width = self.text_rect().width() return max(0, text_width) def text_height(self): """ Returns the height of the text :return: int """ text_height = self.text_rect().height() return max(0, text_height) def _on_fade_out(self, duration=250): """ Internal callback function that fades out the toast message :param duration: int """ animation.fade_out_widget(self, duration=duration, on_finished=self.hide)
class ImageSequence(QObject, object): DEFAULT_FPS = 24 frameChanged = Signal(int) def __init__(self, path, *args): super(ImageSequence, self).__init__(*args) self._fps = self.DEFAULT_FPS self._timer = None self._frame = 0 self._frames = list() self._dirname = None self._paused = False if path: self.set_dirname(path) def set_path(self, path): """ Sets s single frame image sequence :param path: str """ if not path: return if os.path.isfile(path): self._frame = 0 self._frames = [path] elif os.path.isdir(path): self.set_dirname(path) def dirname(self): """ Return the location to the image sequence in disk :return: str """ return self._dirname def set_dirname(self, dirname): """ Set the location where image sequence files are located :param dirname: str """ def natural_sort_items(items): """ Sort the given list in the expected way :param items: list(str) """ def _convert(text): return int(text) if text.isdigit() else text def _alphanum_key(key): return [_convert(c) for c in re.split('([0-9]+)', key)] items.sort(key=_alphanum_key) self._dirname = dirname if os.path.isdir(dirname): self._frames = [ dirname + '/' + filename for filename in os.listdir(dirname) ] natural_sort_items(self._frames) def first_frame(self): """ Returns the path of the first frame of the sequence :return: str """ if not self._frames: return '' return self._frames[0] def start(self): """ Starts the image sequence """ self.reset() if self._timer: self._timer.start(1000.0 / self._fps) def pause(self): """ Pause image sequence """ self._paused = True if self._timer: self._timer.stop() def resume(self): """ Play image sequence after Pause """ if self._paused: self._paused = False if self._timer: self._timer.start() def stop(self): """ Stop the image sequence """ if self._timer: self._timer.stop() def reset(self): """ Stop and reset the current frame to 0 """ if not self._timer: self._timer = QTimer(self.parent()) self._timer.setSingleShot(False) self._timer.timeout.connect(self._on_frame_changed) if not self._paused: self._frame = 0 self._timer.stop() def frames(self): """ Return all the filenames in the image sequence :return: list(str) """ return self._frames def percent(self): """ Return the current frame position as a percentage :return: float """ if len(self._frames) == self._frame + 1: _percent = 1 else: _percent = float( (len(self._frames) + self._frame)) / len(self._frames) - 1 return _percent def frame_count(self): """ Returns the number of frames :return: int """ return len(self._frames) def current_frame_number(self): """ Returns the current frame :return: int """ return self._frame def current_filename(self): """ Returns the current file name :return: str """ try: return self._frames[self.current_frame_number()] except IndexError: pass def current_icon(self): """ Returns the current frames as QIcon :return: QIcon """ return QIcon(self.current_filename()) def current_pixmap(self): """ Returns the current frame as QPixmap :return: QPixmap """ return QPixmap(self.current_filename()) def jump_to_frame(self, frame): """ Set the current frame :param frame: int """ if frame >= self.frame_count(): frame = 0 self._frame = frame self.frameChanged.emit(frame) def _on_frame_changed(self): """ Internal callback function that is called when the current frame changes """ if not self._frames: return frame = self._frame frame += 1 self.jump_to_frame(frame)
class SliderPanel(base.BaseWidget, object): """ Panel that slides in from the edge of the window """ closed = Signal() closeButtonClicked = Signal() def __init__(self, title, position=SliderPanelPositions.RIGHT, closable=True, parent=None): self._title = title self._position = position self._closable = closable self._is_first_close = True super(SliderPanel, self).__init__(parent) self.setObjectName('sliderPanel') self.setWindowFlags(Qt.Popup) self.setAttribute(Qt.WA_StyledBackground) self._close_timer = QTimer(self) self._close_timer.setInterval(300) self._close_timer.setSingleShot(True) self._close_timer.timeout.connect(self.close) self._close_timer.timeout.connect(self.closed.emit) self._pos_anim = QPropertyAnimation(self) self._pos_anim.setTargetObject(self) self._pos_anim.setEasingCurve(QEasingCurve.OutCubic) self._pos_anim.setDuration(300) self._pos_anim.setPropertyName(b'pos') self._opacity_anim = QPropertyAnimation() self._opacity_anim.setTargetObject(self) self._opacity_anim.setDuration(300) self._opacity_anim.setEasingCurve(QEasingCurve.OutCubic) self._opacity_anim.setPropertyName(b'windowOpacity') self._opacity_anim.setStartValue(0.0) self._opacity_anim.setEndValue(1.0) # ================================================================================================================= # PROPERTIES # ================================================================================================================= @property def position(self): """ Returns the placement of the panel in parent window :return: str """ return self._position @position.setter def position(self, value): """ Sets the position of the panel in parent window ('top', 'right', 'bottom' or 'left'). :param value: str """ self._position = value if value in [SliderPanelPositions.BOTTOM, SliderPanelPositions.TOP]: self.setFixedHeight(200) else: self.setFixedWidth(200) # ================================================================================================================= # OVERRIDES # ================================================================================================================= def ui(self): super(SliderPanel, self).ui() self._title_label = label.BaseLabel(parent=self).h4() self._title_label.setText(self._title) self._close_btn = buttons.BaseToolButton( parent=self).icon_only().image('close', theme='window').small() self._close_btn.setVisible(self._closable or False) title_layout = layouts.HorizontalLayout() title_layout.addWidget(self._title_label) title_layout.addStretch() title_layout.addWidget(self._close_btn) self._button_layout = layouts.HorizontalLayout() self._button_layout.addStretch() self._scroll_area = QScrollArea() self.main_layout.addLayout(title_layout) self.main_layout.addWidget(dividers.Divider()) self.main_layout.addWidget(self._scroll_area) self.main_layout.addWidget(dividers.Divider()) self.main_layout.addLayout(self._button_layout) def setup_signals(self): self._close_btn.clicked.connect(self.close) self._close_btn.clicked.connect(self.closeButtonClicked.emit) def show(self): self._update_position() self._fade_in() return super(SliderPanel, self).show() def closeEvent(self, event): if self._is_first_close: self._is_first_close = False self._close_timer.stop() self._fade_out() self.closed.emit() event.ignore() else: event.accept() # ================================================================================================================= # BASE # ================================================================================================================= def set_widget(self, widget): """ Sets the widget that will be contained inside the panel :param widget: QWidget """ self._scroll_area.setWidget(widget) def add_button(self, button): """ Adds a new button to the bottom part of the panel :param button: QPushButton """ self._button_layout.addWidget(button) def left(self): """ Sets the panel's placement to left :return: SliderPanel """ self.position = SliderPanelPositions.LEFT return self def right(self): """ Sets the panel's placement to right :return: SliderPanel """ self.position = SliderPanelPositions.RIGHT return self def top(self): """ Sets the panel's placement to top :return: SliderPanel """ self.position = SliderPanelPositions.TOP return self def bottom(self): """ Sets the panel's placement to bottom :return: SliderPanel """ self.position = SliderPanelPositions.BOTTOM return self # ================================================================================================================= # INTERNAL # ================================================================================================================= def _fade_in(self): """ Internal function that fades in the panel """ self._pos_anim.start() self._opacity_anim.start() def _fade_out(self): """ Internal function that fades out the panel """ self._pos_anim.setDirection(QAbstractAnimation.Backward) self._pos_anim.start() self._opacity_anim.setDirection(QAbstractAnimation.Backward) self._opacity_anim.start() def _update_position(self): """ Internal function that makes sure that panel is positioned in the proper place """ parent = self.parent() parent_parent = parent.parent() dcc_win = dcc.get_main_window() dcc_window = parent_parent == dcc_win if parent_parent and dcc_win: dcc_window = dcc_window or parent_parent.objectName( ) == dcc_win.objectName() parent_geo = parent.geometry() if self._position == SliderPanelPositions.LEFT: pos = parent_geo.topLeft() if dcc_window else parent.mapToGlobal( parent_geo.topLeft()) target_x = pos.x() target_y = pos.y() self.setFixedHeight(parent_geo.height()) self._pos_anim.setStartValue( QPoint(target_x - self.width(), target_y)) self._pos_anim.setEndValue(QPoint(target_x, target_y)) if self._position == SliderPanelPositions.RIGHT: pos = parent_geo.topRight() if dcc_window else parent.mapToGlobal( parent_geo.topRight()) self.setFixedHeight(parent_geo.height()) target_x = pos.x() - self.width() target_y = pos.y() self._pos_anim.setStartValue( QPoint(target_x + self.width(), target_y)) self._pos_anim.setEndValue(QPoint(target_x, target_y)) if self._position == SliderPanelPositions.TOP: pos = parent_geo.topLeft( ) if dcc_window or 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_anim.setStartValue( QPoint(target_x, target_y - self.height())) self._pos_anim.setEndValue(QPoint(target_x, target_y)) if self._position == SliderPanelPositions.BOTTOM: pos = parent_geo.bottomLeft( ) if dcc_window else parent.mapToGlobal(parent_geo.bottomLeft()) self.setFixedWidth(parent_geo.width()) target_x = pos.x() target_y = pos.y() - self.height() self._pos_anim.setStartValue( QPoint(target_x, target_y + self.height())) self._pos_anim.setEndValue(QPoint(target_x, target_y))
class BaseLineEdit(QLineEdit, object): """ Basic line edit """ delayTextChanged = Signal(str) def __init__(self, text='', input_mode=None, parent=None): super(BaseLineEdit, self).__init__(text, parent) self._prefix_widget = None self._suffix_widget = None self._size = self.theme_default_size() self._main_layout = layouts.HorizontalLayout() self._main_layout.setContentsMargins(0, 0, 0, 0) self._main_layout.addStretch() self.setLayout(self._main_layout) self.setProperty('history', self.property('text')) self.setTextMargins(2, 0, 2, 0) if input_mode == 'float': self.setValidator(QDoubleValidator()) elif input_mode == 'int': self.setValidator(QIntValidator()) self._delay_timer = QTimer() self._delay_timer.setInterval(500) self._delay_timer.setSingleShot(True) self._delay_timer.timeout.connect(self._on_delay_text_changed) # ================================================================================================================= # PROPERTIES # ================================================================================================================= def _get_text(self): return self.text() def _set_text(self, value): with qt_contexts.block_signals(self): self.setText(value) def _get_size(self): """ Returns the spin box height size :return: float """ return self._size def _set_size(self, value): """ Sets spin box height size :param value: float """ self._size = value if hasattr(self._prefix_widget, 'theme_size'): self._prefix_widget.theme_size = self._size if hasattr(self._suffix_widget, 'theme_size'): self._suffix_widget.theme_size = self._size self.style().polish(self) theme_size = Property(int, _get_size, _set_size) line_text = Property(str, _get_text, _set_text) # ================================================================================================================= # OVERRIDES # ================================================================================================================= def setText(self, text): """ Overrides base QLineEdit setText base function. Save history :param text: str """ self.setProperty('history', '{}\n{}'.format(self.property('history'), text)) return super(BaseLineEdit, self).setText(text) def clear(self): """ Overrides base QLineEdit clear function :return: """ self.setProperty('history', '') return super(BaseLineEdit, self).clear() def keyPressEvent(self, event): """ Overrides base QLineEdit keyPressEvent function :param event: QKeyEvent """ 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(BaseLineEdit, self).keyPressEvent(event) # ================================================================================================================= # BASE # ================================================================================================================= def set_delay_duration(self, ms): """ Sets the delay timer duration :param ms: float """ self._delay_timer.setInterval(ms) def get_prefix_widget(self): """ Returns prefix widget for user to edit :return: QWidget """ return self._prefix_widget def set_prefix_widget(self, widget): """ Sets the edit line left start widget :param widget: QWidget :return: QWidget """ if self._prefix_widget: index = self._main_layout.indexOf(self._prefix_widget) self._main_layout.takeAt(index) self._prefix_widget.setVisible(False) self._prefix_widget.deleteLater() widget.setProperty('combine', 'horizontal') widget.setProperty('position', 'left') if hasattr(widget, 'theme_size'): widget.them_size = self.theme_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): """ Returns suffix widget for user to edit :return: QWidget """ return self._suffix_widget def set_suffix_widget(self, widget): """ Sets the edit line right start widget :param widget: QWidget :return: QWidget """ if self._suffix_widget: index = self._main_layout.indexOf(self._suffix_widget) self._main_layout.takeAt(index) self._suffix_widget.setVisible(False) self._suffix_widget.deleteLater() widget.setProperty('combine', 'horizontal') widget.setProperty('position', 'right') if hasattr(widget, 'theme_size'): widget.them_size = self.theme_size margin = self.textMargins() margin.setRight(margin.right() + widget.width()) self.setTextMargins(margin) self._main_layout.addWidget(widget) self._prefix_widget = widget return widget def search(self): """ Adds a search icon button for line edit :return: self """ prefix_btn = buttons.BaseToolButton().image('search').icon_only() suffix_btn = buttons.BaseToolButton().image('close').icon_only() suffix_btn.clicked.connect(self.clear) self.set_prefix_widget(prefix_btn) self.set_suffix_widget(suffix_btn) self.setPlaceholderText('Enter keyword to search ...') return self def search_engine(self, text='Search'): """ Adds a search push button to line edit :param text: str :return: self """ _suffix_btn = buttons.BaseButton(text).primary() _suffix_btn.clicked.connect(self.returnPressed) _suffix_btn.setFixedWidth(100) self.set_suffix_widget(_suffix_btn) self.setPlaceholderText('Enter keyword to search ...') return self def file(self, filters=None): """ Adds a ClickBrowserFileToolButton to line edit :param filters: :return: self """ _suffix_btn = browser.ClickBrowserFileToolButton() _suffix_btn.fileChanged.connect(self.setText) _suffix_btn.filters = filters self.textChanged.connect(_suffix_btn.set_path) self.set_suffix_widget(_suffix_btn) self.setPlaceholderText('Click button to browse files') return self def save_file(self, filters=None): """ Adds a ClickSaveFileToolButton to line edit :param filters: :return: self """ _suffix_button = browser.ClickSaveFileToolButton() _suffix_button.fileChanged.connect(self.setText) _suffix_button.filters = filters or list() self.textChanged.connect(_suffix_button.set_path) self.set_suffix_widget(_suffix_button) self.setPlaceholderText('Click button to set save file') return self def folder(self): """ Adds a ClickBrowserFileToolButton to line edit :return: self """ _suffix_btn = browser.ClickBrowserFolderToolButton() _suffix_btn.folderChanged.connect(self.setText) self.textChanged.connect(_suffix_btn.set_path) self.set_suffix_widget(_suffix_btn) self.setPlaceholderText('Click button to browse folder') return self def error(self): """ Shows error in line edit with red style :return: self """ def _on_show_detail(self): dlg = QTextEdit(self) dlg.setReadOnly(True) geo = QApplication.desktop().screenGeometry() dlg.setGeometry(geo.width() / 2, geo.height() / 2, geo.width() / 4, geo.height() / 4) dlg.setWindowTitle('Error Detail Information') dlg.setText(self.property('history')) dlg.setWindowFlags(Qt.Dialog) dlg.show() self.setProperty('theme_type', 'error') self.setReadOnly(True) _suffix_btn = buttons.BaseToolButton().image( 'delete_message').icon_only() _suffix_btn.clicked.connect(partial(_on_show_detail, self)) self.set_suffix_widget(_suffix_btn) self.setPlaceholderText('Error information will be here ...') return self def tiny(self): """ Sets line edit to tiny size """ widget_theme = self.theme() self.theme_size = widget_theme.tiny if widget_theme else theme.Theme.Sizes.TINY return self def small(self): """ Sets line edit to small size """ widget_theme = self.theme() self.theme_size = widget_theme.small if widget_theme else theme.Theme.Sizes.SMALL return self def medium(self): """ Sets line edit to medium size """ widget_theme = self.theme() self.theme_size = widget_theme.medium if widget_theme else theme.Theme.Sizes.MEDIUM return self def large(self): """ Sets line edit to large size """ widget_theme = self.theme() self.theme_size = widget_theme.large if widget_theme else theme.Theme.Sizes.LARGE return self def huge(self): """ Sets line edit to huge size """ widget_theme = self.theme() self.theme_size = widget_theme.huge if widget_theme else theme.Theme.Sizes.HUGE return self def password(self): """ Sets line edit password mode """ self.setEchoMode(QLineEdit.Password) return self # ================================================================================================================= # CALLBACKS # ================================================================================================================= def _on_delay_text_changed(self): """ Internal callback function that is called when delay timer is completed """ self.delayTextChanged.emit(self.text())