class timer(NodeBase): def __init__(self, name): super(timer, self).__init__(name) self.out = self.createOutputPin("OUT", 'ExecPin') self.beginPin = self.createInputPin("Begin", 'ExecPin', None, self.start) self.stopPin = self.createInputPin("Stop", 'ExecPin', None, self.stop) self.resetPin = self.createInputPin("Reset", 'ExecPin', None, self.reset) self.interval = self.createInputPin("Delta(ms)", 'FloatPin') self.interval.setDefaultValue(0.2) self._timer = QTimer() self._timer.timeout.connect(self.compute) def kill(self): self._timer.stop() self._timer.timeout.disconnect() NodeBase.kill(self) @staticmethod def pinTypeHints(): return {'inputs': ['FloatPin', 'ExecPin'], 'outputs': ['ExecPin']} def reset(self, *args, **kwargs): self.stop() self.start() def stop(self, *args, **kwargs): self._timer.stop() def start(self, *args, **kwargs): dt = self.interval.getData() * 1000.0 self._timer.start(dt) @staticmethod def category(): return 'Utils' def compute(self, *args, **kwargs): self.out.call(*args, **kwargs)
class retriggerableDelay(Node, NodeBase): def __init__(self, name, graph): super(retriggerableDelay, self).__init__(name, graph) self.inp0 = self.addInputPin('in0', DataTypes.Exec, self.compute, hideLabel=True) self.delay = self.addInputPin('Delay(s)', DataTypes.Float) self.delay.setDefaultValue(0.2) self.out0 = self.addOutputPin('out0', DataTypes.Exec, hideLabel=True) self.process = False self.timer = QTimer() self.timer.timeout.connect(self.callAndReset) def kill(self): self.timer.stop() self.timer.timeout.disconnect() Node.kill(self) @staticmethod def pinTypeHints(): return { 'inputs': [DataTypes.Exec, DataTypes.Float], 'outputs': [DataTypes.Exec] } @staticmethod def category(): return 'FlowControl' @staticmethod def keywords(): return [] @staticmethod def description(): return 'Delayed call. With ability to reset.' def callAndReset(self): self.out0.call() self.process = False self.timer.stop() def restart(self): delay = self.delay.getData() * 1000.0 self.timer.stop() self.timer.start(delay) def compute(self): self.restart()
class PlotMainWindow(QWidget): """Base class for plot main windows.""" def __init__(self, U, plot, length=1, title=None): super().__init__() layout = QVBoxLayout() if title: title = QLabel('<b>' + title + '</b>') title.setAlignment(Qt.AlignHCenter) layout.addWidget(title) layout.addWidget(plot) plot.set(U, 0) if length > 1: hlayout = QHBoxLayout() self.slider = QSlider(Qt.Horizontal) self.slider.setMinimum(0) self.slider.setMaximum(length - 1) self.slider.setTickPosition(QSlider.TicksBelow) hlayout.addWidget(self.slider) lcd = QLCDNumber(m.ceil(m.log10(length))) lcd.setDecMode() lcd.setSegmentStyle(QLCDNumber.Flat) hlayout.addWidget(lcd) layout.addLayout(hlayout) hlayout = QHBoxLayout() toolbar = QToolBar() self.a_play = QAction(self.style().standardIcon(QStyle.SP_MediaPlay), 'Play', self) self.a_play.setCheckable(True) self.a_rewind = QAction(self.style().standardIcon(QStyle.SP_MediaSeekBackward), 'Rewind', self) self.a_toend = QAction(self.style().standardIcon(QStyle.SP_MediaSeekForward), 'End', self) self.a_step_backward = QAction(self.style().standardIcon(QStyle.SP_MediaSkipBackward), 'Step Back', self) self.a_step_forward = QAction(self.style().standardIcon(QStyle.SP_MediaSkipForward), 'Step', self) self.a_loop = QAction(self.style().standardIcon(QStyle.SP_BrowserReload), 'Loop', self) self.a_loop.setCheckable(True) toolbar.addAction(self.a_play) toolbar.addAction(self.a_rewind) toolbar.addAction(self.a_toend) toolbar.addAction(self.a_step_backward) toolbar.addAction(self.a_step_forward) toolbar.addAction(self.a_loop) if hasattr(self, 'save'): self.a_save = QAction(self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save', self) toolbar.addAction(self.a_save) self.a_save.triggered.connect(self.save) hlayout.addWidget(toolbar) self.speed = QSlider(Qt.Horizontal) self.speed.setMinimum(0) self.speed.setMaximum(100) hlayout.addWidget(QLabel('Speed:')) hlayout.addWidget(self.speed) layout.addLayout(hlayout) self.timer = QTimer() self.timer.timeout.connect(self.update_solution) self.slider.valueChanged.connect(self.slider_changed) self.slider.valueChanged.connect(lcd.display) self.speed.valueChanged.connect(self.speed_changed) self.a_play.toggled.connect(self.toggle_play) self.a_rewind.triggered.connect(self.rewind) self.a_toend.triggered.connect(self.to_end) self.a_step_forward.triggered.connect(self.step_forward) self.a_step_backward.triggered.connect(self.step_backward) self.speed.setValue(50) elif hasattr(self, 'save'): hlayout = QHBoxLayout() toolbar = QToolBar() self.a_save = QAction(self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save', self) toolbar.addAction(self.a_save) hlayout.addWidget(toolbar) layout.addLayout(hlayout) self.a_save.triggered.connect(self.save) self.setLayout(layout) self.plot = plot self.U = U self.length = length def slider_changed(self, ind): self.plot.set(self.U, ind) def speed_changed(self, val): self.timer.setInterval(val * 20) def update_solution(self): ind = self.slider.value() + 1 if ind >= self.length: if self.a_loop.isChecked(): ind = 0 else: self.a_play.setChecked(False) return self.slider.setValue(ind) def toggle_play(self, checked): if checked: if self.slider.value() + 1 == self.length: self.slider.setValue(0) self.timer.start() else: self.timer.stop() def rewind(self): self.slider.setValue(0) def to_end(self): self.a_play.setChecked(False) self.slider.setValue(self.length - 1) def step_forward(self): self.a_play.setChecked(False) ind = self.slider.value() + 1 if ind == self.length and self.a_loop.isChecked(): ind = 0 if ind < self.length: self.slider.setValue(ind) def step_backward(self): self.a_play.setChecked(False) ind = self.slider.value() - 1 if ind == -1 and self.a_loop.isChecked(): ind = self.length - 1 if ind >= 0: self.slider.setValue(ind)
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()
class BaseAnimObject(object): _glow_pens = {} for index in range(1, 11): _glow_pens[index] = [QPen(QColor(0, 255, 0, 12 * index), 1, Qt.SolidLine), QPen(QColor(0, 255, 0, 5 * index), 3, Qt.SolidLine), QPen(QColor(0, 255, 0, 2 * index), 5, Qt.SolidLine), QPen(QColor(0, 255, 0, 25.5 * index), 1, Qt.SolidLine)] _pens_text = QPen(QColor(202, 207, 210), 1, Qt.SolidLine) _pens_shadow = QPen(QColor(9, 10, 12), 1, Qt.SolidLine) _pens_border = QPen(QColor(9, 10, 12), 2, Qt.SolidLine) _pens_clear = QPen(QColor(0, 0, 0, 0), 1, Qt.SolidLine) _pens_text_disabled = QPen(QColor(102, 107, 110), 1, Qt.SolidLine) _pens_shadow_disabled = QPen(QColor(0, 0, 0), 1, Qt.SolidLine) _brush_clear = QBrush(QColor(0, 0, 0, 0)) _brush_border = QBrush(QColor(9, 10, 12)) def __init__(self): font = QFont() font.setPointSize(8) font.setFamily("Calibri") self.setFont(font) self._hover = False self._glow_index = 0 self._anim_timer = QTimer() self._anim_timer.timeout.connect(self._animate_glow) def enterEvent(self, event): super(self.__class__, self).enterEvent(event) if not self.isEnabled(): return self._hover = True self._start_anim() def leaveEvent(self, event): super(self.__class__, self).leaveEvent(event) if not self.isEnabled(): return self._hover = False self._start_anim() def _animate_glow(self): if self._hover: if self._glow_index >= 10: self._glow_index = 10 self._anim_timer.stop() else: self._glow_index += 1 else: if self._glow_index <= 0: self._glow_index = 0 self._anim_timer.stop() else: self._glow_index -= 1 dcc.execute_deferred(self.update) def _start_anim(self): if self._anim_timer.isActive(): return self._anim_timer.start(20)
class PlotMainWindow(QWidget): """Base class for plot main windows.""" def __init__(self, U, plot, length=1, title=None): super().__init__() layout = QVBoxLayout() if title: title = QLabel('<b>' + title + '</b>') title.setAlignment(Qt.AlignHCenter) layout.addWidget(title) layout.addWidget(plot) plot.set(U, 0) if length > 1: hlayout = QHBoxLayout() self.slider = QSlider(Qt.Horizontal) self.slider.setMinimum(0) self.slider.setMaximum(length - 1) self.slider.setTickPosition(QSlider.TicksBelow) hlayout.addWidget(self.slider) lcd = QLCDNumber(m.ceil(m.log10(length))) lcd.setDecMode() lcd.setSegmentStyle(QLCDNumber.Flat) hlayout.addWidget(lcd) layout.addLayout(hlayout) hlayout = QHBoxLayout() toolbar = QToolBar() self.a_play = QAction( self.style().standardIcon(QStyle.SP_MediaPlay), 'Play', self) self.a_play.setCheckable(True) self.a_rewind = QAction( self.style().standardIcon(QStyle.SP_MediaSeekBackward), 'Rewind', self) self.a_toend = QAction( self.style().standardIcon(QStyle.SP_MediaSeekForward), 'End', self) self.a_step_backward = QAction( self.style().standardIcon(QStyle.SP_MediaSkipBackward), 'Step Back', self) self.a_step_forward = QAction( self.style().standardIcon(QStyle.SP_MediaSkipForward), 'Step', self) self.a_loop = QAction( self.style().standardIcon(QStyle.SP_BrowserReload), 'Loop', self) self.a_loop.setCheckable(True) toolbar.addAction(self.a_play) toolbar.addAction(self.a_rewind) toolbar.addAction(self.a_toend) toolbar.addAction(self.a_step_backward) toolbar.addAction(self.a_step_forward) toolbar.addAction(self.a_loop) if hasattr(self, 'save'): self.a_save = QAction( self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save', self) toolbar.addAction(self.a_save) self.a_save.triggered.connect(self.save) hlayout.addWidget(toolbar) self.speed = QSlider(Qt.Horizontal) self.speed.setMinimum(0) self.speed.setMaximum(100) hlayout.addWidget(QLabel('Speed:')) hlayout.addWidget(self.speed) layout.addLayout(hlayout) self.timer = QTimer() self.timer.timeout.connect(self.update_solution) self.slider.valueChanged.connect(self.slider_changed) self.slider.valueChanged.connect(lcd.display) self.speed.valueChanged.connect(self.speed_changed) self.a_play.toggled.connect(self.toggle_play) self.a_rewind.triggered.connect(self.rewind) self.a_toend.triggered.connect(self.to_end) self.a_step_forward.triggered.connect(self.step_forward) self.a_step_backward.triggered.connect(self.step_backward) self.speed.setValue(50) elif hasattr(self, 'save'): hlayout = QHBoxLayout() toolbar = QToolBar() self.a_save = QAction( self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save', self) toolbar.addAction(self.a_save) hlayout.addWidget(toolbar) layout.addLayout(hlayout) self.a_save.triggered.connect(self.save) self.setLayout(layout) self.plot = plot self.U = U self.length = length def slider_changed(self, ind): self.plot.set(self.U, ind) def speed_changed(self, val): self.timer.setInterval(val * 20) def update_solution(self): ind = self.slider.value() + 1 if ind >= self.length: if self.a_loop.isChecked(): ind = 0 else: self.a_play.setChecked(False) return self.slider.setValue(ind) def toggle_play(self, checked): if checked: if self.slider.value() + 1 == self.length: self.slider.setValue(0) self.timer.start() else: self.timer.stop() def rewind(self): self.slider.setValue(0) def to_end(self): self.a_play.setChecked(False) self.slider.setValue(self.length - 1) def step_forward(self): self.a_play.setChecked(False) ind = self.slider.value() + 1 if ind == self.length and self.a_loop.isChecked(): ind = 0 if ind < self.length: self.slider.setValue(ind) def step_backward(self): self.a_play.setChecked(False) ind = self.slider.value() - 1 if ind == -1 and self.a_loop.isChecked(): ind = self.length - 1 if ind >= 0: self.slider.setValue(ind)
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())
class StatusWidget(QFrame, object): DEFAULT_DISPLAY_TIME = 10000 # milliseconds -> 10 seconds def __init__(self, *args): super(StatusWidget, self).__init__(*args) self._status = None self._blocking = False self._timer = QTimer(self) self.setObjectName('StatusWidget') self.setFrameShape(QFrame.NoFrame) self.setFixedHeight(19) self.setMinimumWidth(5) self._label = label.BaseLabel('', parent=self) self._label.setStyleSheet('background-color: transparent;') self._label.setCursor(Qt.IBeamCursor) self._label.setTextInteractionFlags(Qt.TextSelectableByMouse) self._label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.label_image = label.BaseLabel(parent=self) self.label_image.setMaximumSize(QSize(17, 17)) self.label_image.hide() self.main_layout = QHBoxLayout(self) self.main_layout.setContentsMargins(1, 0, 0, 0) self.main_layout.addWidget(self.label_image) self.main_layout.addWidget(self._label) self.setLayout(self.main_layout) self._timer.timeout.connect(self._reset) # Force set to initialize default status Qt property self.status = '' def _get_status(self): return self._status def _set_status(self, value): self._status = str(value) self.polish() status = Property(str, _get_status, _set_status) def is_blocking(self): """ Returns True if the status widget is blocking, otherwise return False :return: bool """ return self._blocking def show_ok_message(self, message, msecs=None): """ Set an ok message to be displayed in the status widget :param message: str :param msecs: int """ if self.is_blocking(): return self.status = 'ok' icon = resources.icon('ok') self._show_message(message, icon, msecs) def show_info_message(self, message, msecs=None): """ Set an info message to be displayed in the status widget :param message: str :param msecs: int """ if self.is_blocking(): return self.status = 'info' icon = resources.icon('info') self._show_message(message, icon, msecs) def show_warning_message(self, message, msecs=None): """ Set a warning message to be displayed in the status widget :param message: str :param msecs: int """ if self.is_blocking(): return self.status = 'warning' icon = resources.icon('warning') self._show_message(message, icon, msecs) def show_error_message(self, message, msecs=None): """ Set an error message to be displayed in the status widget :param message: str :param msecs: int """ self.status = 'error' icon = resources.icon('error', extension='png') self._show_message(message, icon, msecs, blocking=True) def _reset(self): """ Called when the current animation has finished """ self._timer.stop() self.label_image.setVisible(False) self._label.setText('') icon = resources.pixmap('blank') self.label_image.setPixmap( icon) if icon else self.label_image.setPixmap(QPixmap()) self.setStyleSheet('') self._blocking = False self.status = '' def _show_message(self, message, icon, msecs=None, blocking=False): """ Set the given text to be displayed in the status widget :param message: str :param icon: QIcon :param msecs: int :param blocking: bool """ msecs = msecs or self.DEFAULT_DISPLAY_TIME self._blocking = blocking self.label_image.setStyleSheet('border: 0px;') if icon: self.label_image.setPixmap(icon.pixmap(QSize(17, 17))) self.label_image.show() else: self.label_image.hide() if message: self._label.setText(str(message)) self._timer.stop() self._timer.start(msecs) else: self._reset() self.update()