Пример #1
0
 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))
Пример #2
0
    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()
Пример #3
0
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()
Пример #4
0
    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()
Пример #5
0
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)
Пример #6
0
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)
Пример #7
0
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))
Пример #8
0
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())