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 BaseDateEdit(QDateEdit, object): def __init__(self, parent=None): super(BaseDateEdit, self).__init__(parent=parent) self._size = self.theme_default_size() # ================================================================================================================= # PROPERTIES # ================================================================================================================= def _get_size(self): """ Returns the date edit height size :return: float """ return self._size def _set_size(self, value): """ Sets date edit height size :param value: float """ self._size = value self.style().polish(self) theme_size = Property(int, _get_size, _set_size) # ================================================================================================================= # BASE # ================================================================================================================= def tiny(self): """ Sets date 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 date 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 date 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 date 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 date edit to huge size """ widget_theme = self.theme() self.theme_size = widget_theme.huge if widget_theme else theme.Theme.Sizes.HUGE return self
class BaseButton(QPushButton, object): class Types(object): DEFAULT = 'default' PRIMARY = 'primary' SUCCESS = 'success' WARNING = 'warning' DANGER = 'danger' def __init__(self, text='', icon=None, elided=False, parent=None): self._text = text self._elided = elided if not icon: super(BaseButton, self).__init__(text=text, parent=parent) else: super(BaseButton, self).__init__(text=text, parent=parent, icon=icon) self._type = self.Types.DEFAULT self._size = self.theme_default_size() # NOTE: Without this, button will not call focusIn/out events when pressed self.setFocusPolicy(Qt.StrongFocus) # ================================================================================================================= # PROPERTIES # ================================================================================================================= def _get_type(self): """ Returns button type :return: float """ return self._type def _set_type(self, value): """ Sets button type :param value: str """ if value in [ self.Types.DEFAULT, self.Types.PRIMARY, self.Types.SUCCESS, self.Types.WARNING, self.Types.DANGER ]: self._type = value else: raise ValueError( 'Given button type: "{}" is not supported. Supported types ' 'are: default, primary, success, warning and danger'.format( value)) self.style().polish(self) def _get_size(self): """ Returns the button height size :return: float """ return self._size def _set_size(self, value): """ Sets button height size :param value: float """ self._size = value self.style().polish(self) theme_type = Property(str, _get_type, _set_type) theme_size = Property(int, _get_size, _set_size) # ================================================================================================================= # BASE # ================================================================================================================= def default(self): """ Sets button to default style """ self.theme_type = self.Types.DEFAULT return self def primary(self): """ Sets button to primary style """ self.theme_type = self.Types.PRIMARY return self def success(self): """ Sets button to success style """ self.theme_type = self.Types.SUCCESS return self def warning(self): """ Sets button to warning style """ self.theme_type = self.Types.WARNING return self def danger(self): """ Sets button to danger style """ self.theme_type = self.Types.DANGER return self def tiny(self): """ Sets button 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 button 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 button 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 button 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 button to huge size """ widget_theme = self.theme() self.theme_size = widget_theme.huge if widget_theme else theme.Theme.Sizes.HUGE return self def setText(self, text): self._text = text super(BaseButton, self).setText(text) def resizeEvent(self, event): if self._elided: has_icon = self.icon() and not self.icon().isNull() if has_icon: font_metrics = QFontMetrics(self.font()) elided = font_metrics.elidedText(self._text, Qt.ElideMiddle, self.width() - 30) super(BaseButton, self).setText(elided) super(BaseButton, self).resizeEvent(event)
class Badge(base.BaseWidget, object): """ Widget that can be located near notification or user avatars to display unread messages count We support 3 types of styles: 1. dof: show a dot 2. count: show a number 3. text: show a string """ def __init__(self, widget=None, parent=None): self._dot = None self._text = None self._count = None self._widget = widget self._overflow_count = 99 super(Badge, self).__init__(parent=parent) # ================================================================================================================= # PROPERTIES # ================================================================================================================= @property def overflow(self): """ Returns current overflow number :return: int """ return self._overflow_count @overflow.setter def overflow(self, value): """ Sets overflow number :param value: int """ self._overflow_count = value self._update_number() @property def count(self): """ Returns current badge count number :return: int """ return self._count @count.setter def count(self, value): """ Sets current badge count number :param value: int """ self._count = value self._update_number() @property def text(self): """ Returns current badge text :return: str """ return self._text @text.setter def text(self, value): """ Sets current badge text :param value: str """ self._text = value self._badge_btn.setText(self._text) self._badge_btn.setVisible(bool(self._text)) self._dot = None self.style().polish(self) def _get_dot(self): """ Returns whether or not current badge style is dot :return: bool """ return self._dot def _set_dot(self, flag): """ Sets whether or not current badge style is dot :param flag: bool """ self._dot = flag self._badge_btn.setText('') self._badge_btn.setVisible(flag) self.style().polish(self) dot = Property(bool, _get_dot, _set_dot) # ================================================================================================================= # OVERRIDES # ================================================================================================================= def get_main_layout(self): main_layout = layouts.GridLayout(margins=(0, 0, 0, 0)) return main_layout def ui(self): super(Badge, self).ui() self._badge_btn = QPushButton() self._badge_btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) if self._widget is not None: self.main_layout.addWidget(self._widget, 0, 0) self.main_layout.addWidget(self._badge_btn, 0, 0, Qt.AlignTop | Qt.AlignRight) # ================================================================================================================= # BASE # ================================================================================================================= @classmethod def create_dot(cls, show=False, widget=None, parent=None): """ Creates a new badge with dot style :param show: bool :param widget: QWidget :param parent: QWidget :return: Badge """ inst = cls(widget=widget, parent=parent) inst.dot = show return inst @classmethod def create_count(cls, count=0, widget=None, parent=None): """ Creates a new badge with count style :param count: int :param widget: QWidget :param parent: QWidget :return: Badge """ inst = cls(widget=widget, parent=parent) inst.count = count return inst @classmethod def create_text(cls, text='', widget=None, parent=None): """ Creates a new badge with dot style :param text: str :param widget: QWidget :param parent: QWidget :return: Badge """ inst = cls(widget=widget, parent=parent) inst.text = text return inst # ================================================================================================================= # INTERNAL # ================================================================================================================= def _update_number(self): """ Internal function that updates overflow number """ self._badge_btn.setText( formatters.overflow_format(self._count, self._overflow_count)) self._badge_btn.setVisible(self._count > 0) self._dot = None self.style().polish(self)
class ToolBar(QToolBar, object): """ Class that adds functionality to expand/collapse QToolBars """ DEFAULT_EXPANDED_HEIGHT = 32 DEFAULT_COLLAPSED_HEIGHT = 10 ICON_SIZE = 32 def __init__(self, *args, **kwargs): super(ToolBar, self).__init__(*args, **kwargs) self._dpi = 1 self._is_expanded = True self._expanded_height = self.DEFAULT_EXPANDED_HEIGHT self._collapsed_height = self.DEFAULT_COLLAPSED_HEIGHT self.setMinimumHeight(self.DEFAULT_EXPANDED_HEIGHT) def _get_expanded(self): return self._is_expanded def _set_expanded(self, flag): self._is_expanded = flag expanded = Property(bool, _get_expanded, _set_expanded) # ================================================================================================================= # OVERRIDES # ================================================================================================================= def mousePressEvent(self, event): if not self.is_expanded(): self.expand() def setFixedHeight(self, value): """ Overrides base QToolBar setFixedHeight Allows to also set the height for all child widgets of the menu bar :param value: float """ self.set_children_height(value) super(ToolBar, self).setFixedHeight(value) print(self.height()) def insertAction(self, before, action): """ Overrides base QToolBar insertAction function Support the before argument as string :param before: QAction or str :param action: QAction :return: QAction """ action.setParent(self) if python.is_string(before): before = self.find_action(before) action = super(ToolBar, self).insertAction(before, action) return action def actions(self): """ Overrides base QToolBar actions function Returns all the widgets that are a child of the menu bar widget :return: list(QWidget) """ actions = list() for child in self.children(): if isinstance(child, QAction): actions.append(child) return actions # ================================================================================================================= # DPI # ================================================================================================================= def dpi(self): """ Returns the zoom multiplier :return: float """ return self._dpi def set_dpi(self, dpi): """ Set the zoom multiplier :param : float """ self._dpi = dpi if self.is_expanded(): self.expand() else: self.collapse() # ================================================================================================================= # BASE # ================================================================================================================= def widgets(self): """ Returns all the widgets that are a child of the menu bar widget :return: list(QWidget) """ widgets = list() for i in range(self.layout().count()): w = self.layout().itemAt(i).widget() if isinstance(w, QWidget): widgets.append(w) return widgets def is_expanded(self): """ Returns whether the menu bar is expanded or not :return: bool """ return self._is_expanded def expand_height(self): """ Returns the height of menu bar when is expanded :return: float """ return int(self._expanded_height * self.dpi()) def collapse_height(self): """ Returns the height of widget when collapsed :return: int """ return int(self._collapsed_height * self.dpi()) def set_children_hidden(self, flag): """ Hide/Show all child widgets :param flag: bool """ for action in self.actions(): action.setVisible(not flag) # for w in self.widgets(): # w.setHidden(flag) def set_children_height(self, height): """ Set the height of all the child widgets to the given height :param height: int """ for w in self.widgets(): w.setFixedHeight(height) def expand(self): """ Expand the menu bar to the expand height """ self._is_expanded = True height = self.expand_height() self.setFixedHeight(height) self.set_children_hidden(False) icon_size = self.ICON_SIZE * self.dpi() self.setIconSize(QSize(icon_size, icon_size)) self.setStyleSheet(self.styleSheet()) def collapse(self): """ Collapse the menu bar to the collapse height """ self._is_expanded = False height = self.collapse_height() self.setFixedHeight(height) self.set_children_height(0) self.set_children_hidden(True) self.setIconSize(QSize(0, 0)) self.setStyleSheet(self.styleSheet()) def set_icon_color(self, color): """ Set the icon colors to the current foregroundRole :param color: QColor """ for action in self.actions(): action_icon = action.icon() action_icon = icon.Icon(action_icon) action_icon.set_color(color) action.setIcon(action_icon) def find_action(self, text): """ Find the action with the given text :param text: str :return: QAction or None """ for child in self.children(): if isinstance(child, QAction): if child.text() == text: return child def find_tool_button(self, text): """ Find the QToolButton with the given text :param text: str :return: QToolButton or None """ for child in self.children(): if isinstance(child, QAction): if child.text() == text: return self.widgetForAction(child)
class BaseToolButton(QToolButton, object): def __init__(self, parent=None): super(BaseToolButton, self).__init__(parent=parent) self._image = None self._image_theme = None self._size = self.theme_default_size() self.setAutoExclusive(False) self.setAutoRaise(True) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._polish_icon() self.toggled.connect(self._polish_icon) # ================================================================================================================= # PROPERTIES # ================================================================================================================= def _get_size(self): """ Returns the button height size :return: float """ return self._size def _set_size(self, value): """ Sets button height size :param value: float """ self._size = value self.style().polish(self) if self.toolButtonStyle() == Qt.ToolButtonIconOnly: self.setFixedSize(self._size, self._size) theme_size = Property(int, _get_size, _set_size) # ================================================================================================================= # OVERRIDES # ================================================================================================================= def enterEvent(self, event): if self._image: theme = self.theme() if theme: accent_color = theme.accent_color if self._image_theme: self.setIcon( resources.icon(self._image, theme=self._image_theme, color=accent_color)) else: self.setIcon( resources.icon(self._image, color=accent_color)) return super(BaseToolButton, self).enterEvent(event) def leaveEvent(self, event): self._polish_icon() return super(BaseToolButton, self).leaveEvent(event) def _polish_icon(self, checked=None, **kwargs): if self._image: image_theme = kwargs.get('theme', self._image_theme) if image_theme: kwargs['theme'] = image_theme if self.isCheckable() and self.isChecked(): self.setIcon(resources.icon(self._image, **kwargs)) else: self.setIcon(resources.icon(self._image, **kwargs)) # ================================================================================================================= # BASE # ================================================================================================================= def image(self, name, **kwargs): """ Sets the name of the icon to use by the tool button :param str name: :return: """ self._image = name self._image_theme = kwargs.get('theme', None) self._polish_icon(**kwargs) return self def tiny(self): """ Sets tool button 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 tool button 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 tool button 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 tool button 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 tool button to huge size """ widget_theme = self.theme() self.theme_size = widget_theme.huge if widget_theme else theme.Theme.Sizes.HUGE return self def icon_only(self): """ Sets tool button style to icon only """ self.setToolButtonStyle(Qt.ToolButtonIconOnly) self.setFixedSize(self._size, self._size) return self def text_only(self): """ Sets tool button style to icon only """ self.setToolButtonStyle(Qt.ToolButtonTextOnly) return self def text_beside_icon(self): """ Sets tool button style to text beside icon """ self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) return self def text_under_icon(self): """ Sets tool button style to text under icon """ self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) return self
class FilePickerWidget(QWidget): filenamePicked = Signal(str) filenameChanged = Signal(str) filenameEdited = Signal(str) def __init__(self, parent=None): self._correctBackground = QColor(156, 206, 156, 255) self._correctForeground = QColor(Qt.white) self._inCorrectBackground = QColor(210, 156, 156, 255) self._inCorrectForeground = QColor(Qt.white) self._defaultLocation = '' QWidget.__init__(self, parent) self.uiFilenameTXT = LineEdit(self) self.uiPickFileBTN = QToolButton(self) self.uiPickFileBTN.setText('...') self.uiPickFileBTN.setToolTip( '<html><head/><body><p>Browse to a file path.</p><p>Ctrl + LMB: Explore to current path.</p></body></html>' ) # Make this widget focusable and pass the widget focus to uiFilenameTXT self.setFocusProxy(self.uiFilenameTXT) self.setFocusPolicy(Qt.StrongFocus) layout = QHBoxLayout(self) layout.addWidget(self.uiFilenameTXT) layout.addWidget(self.uiPickFileBTN) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self._caption = "Pick file..." self._filters = "All Files (*.*)" self._pickFolder = False self._openFile = False self._resolvePath = False #self._imageSequence = False self._resolved = False self._chosenPath = None #self._imageSequenceFormat = '{pre}[{firstNum}:{lastNum}]{post}' self.uiFilenameTXT.textChanged.connect(self.emitFilenameChanged) self.uiFilenameTXT.editingFinished.connect(self.emitFilenameEdited) self.uiPickFileBTN.clicked.connect(self.pickPath) self.resolvedStylesheet = resolvedStylesheetDefault self.resolve() def caption(self): return self._caption def emitFilenameChanged(self): self.resolve() if (not self.signalsBlocked()): self.filenameChanged.emit(self.uiFilenameTXT.text()) def emitFilenameEdited(self): if (not self.signalsBlocked()): self.filenameEdited.emit(self.uiFilenameTXT.text()) def filePath(self): # if it's an image sequence, return the last chosen image path return self._chosenPath or self.uiFilenameTXT.text() def filters(self): return self._filters def isResolved(self): return self._resolved def openFile(self): return self._openFile def pickFolder(self): return self._pickFolder def pickPath(self): initialPath = self.uiFilenameTXT.text() or self.defaultLocation initialPath = str(initialPath) while not os.path.exists(initialPath): if os.path.dirname(initialPath) == initialPath: break else: initialPath = os.path.dirname(initialPath) if QApplication.keyboardModifiers() == Qt.ControlModifier: import blurdev blurdev.osystem.explore(initialPath) else: if self._pickFolder: filepath = QFileDialog.getExistingDirectory( self, self._caption, initialPath) elif self._openFile: filepath, _ = QtCompat.QFileDialog.getOpenFileName( self, self._caption, initialPath, self._filters) else: filepath, _ = QtCompat.QFileDialog.getSaveFileName( self, self._caption, initialPath, self._filters) if filepath: self.uiFilenameTXT.setText(filepath) if (not self.signalsBlocked()): self.filenamePicked.emit(filepath) def resolve(self): if self.resolvePath(): path = self.uiFilenameTXT.text() if self._pickFolder: valid = os.path.isdir(path) else: valid = os.path.isfile(path) if valid: fg = self.correctForeground bg = self.correctBackground self._resolved = True else: fg = self.inCorrectForeground bg = self.inCorrectBackground self._resolved = False style = self.resolvedStylesheet % { 'bg': bg.getRgb(), 'fg': fg.getRgb() } else: style = '' self._resolved = False self.uiFilenameTXT.setStyleSheet(style) def resolvePath(self): return self._resolvePath def setCaption(self, caption): self._caption = caption @Slot(str) def setFilePath(self, filePath): self.uiFilenameTXT.setText(filePath) self.resolve() def setFilters(self, filters): self._filters = filters def setOpenFile(self, state): self._openFile = state def setPickFolder(self, state): self._pickFolder = state @Slot(bool) def setNotResolvePath(self, state): """ Set resolvePath to the oposite of state. """ self.setResolvePath(not state) @Slot(bool) def setResolvePath(self, state): self._resolvePath = state self.resolve() pyCaption = Property("QString", caption, setCaption) pyFilters = Property("QString", filters, setFilters) pyPickFolder = Property("bool", pickFolder, setPickFolder) pyOpenFile = Property("bool", openFile, setOpenFile) pyResolvePath = Property("bool", resolvePath, setResolvePath) #pyImageSequence = Property( "bool", imageSequence, setImageSequence ) pyFilePath = Property("QString", filePath, setFilePath) # Load the colors from the stylesheets @Property(QColor) def correctBackground(self): return self._correctBackground @correctBackground.setter def correctBackground(self, color): self._correctBackground = color self.resolve() @Property(QColor) def correctForeground(self): return self._correctForeground @correctForeground.setter def correctForeground(self, color): self._correctForeground = color self.resolve() @Property(QColor) def inCorrectBackground(self): return self._inCorrectBackground @inCorrectBackground.setter def inCorrectBackground(self, color): self._inCorrectBackground = color self.resolve() @Property(QColor) def inCorrectForeground(self): return self._inCorrectForeground @inCorrectForeground.setter def inCorrectForeground(self, color): self._inCorrectForeground = color self.resolve() @Property("QString") def defaultLocation(self): return self._defaultLocation @defaultLocation.setter def defaultLocation(self, value): self._defaultLocation = str(value)
class BaseMessage(base.BaseWidget, object): def __init__(self, text='', parent=None): self._type = None self._text = '' super(BaseMessage, self).__init__(parent) self.setAttribute(Qt.WA_StyledBackground) self.set_show_icon(True) self.set_closable(False) self.theme_type = MessageTypes.INFO self.text = text # ================================================================================================================= # PROPERTIES # ================================================================================================================= def _get_text(self): """ Returns message text :return: str """ return self._text def _set_text(self, text): """ Sets message text content :param text: str """ self._text = str(text) self._content_label.setText(self._text) self.setVisible(bool(self._text)) def _get_type(self): """ Returns message type :return: float """ return self._type def _set_type(self, value): """ Sets message type :param value: str """ current_theme = self.theme() if value in [ MessageTypes.INFO, MessageTypes.SUCCESS, MessageTypes.WARNING, MessageTypes.ERROR ]: self._type = value else: raise ValueError( 'Given button type: "{}" is not supported. Supported types ' 'are: info, success, warning, error'.format(value)) if current_theme: self._icon_label.image = resources.pixmap( self._type, color=getattr(current_theme, '{}_color'.format(self._type))) else: self._icon_label.image = resources.pixmap(self._type) self.style().polish(self) text = Property(str, _get_text, _set_text) theme_type = Property(str, _get_type, _set_type) # ================================================================================================================= # OVERRIDES # ================================================================================================================= def get_main_layout(self): main_layout = layouts.HorizontalLayout(margins=(8, 8, 8, 8)) return main_layout def ui(self): super(BaseMessage, self).ui() current_theme = self.theme() self._icon_label = avatar.Avatar() self._icon_label.theme_size = current_theme.huge if current_theme else theme.Theme.Sizes.HUGE self._content_label = label.BaseLabel().secondary() self._close_btn = buttons.BaseToolButton().image( 'close', theme='window').large().icon_only() self.main_layout.addWidget(self._icon_label) self.main_layout.addWidget(self._content_label) self.main_layout.addStretch() self.main_layout.addWidget(self._close_btn) def setup_signals(self): self._close_btn.clicked.connect(partial(self.setVisible, False)) # ================================================================================================================= # BASE # ================================================================================================================= def set_closable(self, flag): """ Sets whether or not the message is closable :param flag: bool """ self._close_btn.setVisible(flag) return self def set_show_icon(self, flag): """ Sets whether or not the display information type icon is visible or not :param flag: bool """ self._icon_label.setVisible(flag) return self def info(self): """ Sets message to info type """ self.theme_type = MessageTypes.INFO return self def success(self): """ Sets message to success type """ self.theme_type = MessageTypes.SUCCESS return self def warning(self): """ Sets message to warning type """ self.theme_type = MessageTypes.WARNING return self def error(self): """ Sets message to error type """ self.theme_type = MessageTypes.ERROR return self def closable(self): """ Sets message to info type """ self.set_closable(True) return self
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()
class HoudiniDoubleSlider(QWidget, object): """ Slider that encapsulates a DoubleSlider and Houdini draggers linked together """ valueChanged = Signal(object) def __init__(self, parent, slider_type='float', style=0, name=None, slider_range=None, default_value=0.0, dragger_steps=None, main_color=None, *args): if slider_range is None: slider_range = (-100.0, 100.0) if dragger_steps is None: dragger_steps = FLOAT_SLIDER_DRAG_STEPS super(HoudiniDoubleSlider, self).__init__(parent=parent, *args) h = 20 self._parent = parent self._type = slider_type self._value = 0.0 self._label = None self._style_type = style theme = self.theme() if theme: theme_color = theme.accent_color if core_color.string_is_hex(theme_color): theme_color = core_color.hex_to_rgb(theme_color) main_color = QColor(*theme_color).getRgb() self._main_color = main_color or QColor(215, 128, 26).getRgb() self.setMaximumHeight(h) self.setMinimumHeight(h) self._main_layout = layouts.HorizontalLayout(margins=(10, 0, 0, 0)) self.setLayout(self._main_layout) self._input = DraggerSlider(slider_type=slider_type) self._input.setButtonSymbols(QAbstractSpinBox.NoButtons) self._input.setRange(slider_range[0], slider_range[1]) self._input.setContentsMargins(0, 0, 0, 0) self._input.setMinimumWidth(60 if self._type == 'float' else 40) self._input.setMaximumWidth(60 if self._type == 'float' else 40) self._input.setMinimumHeight(h) self._input.setMaximumHeight(h) self._input.valueIncremented.connect(self._on_increment_value) if self._type == 'float': self._slider = DoubleSlider(parent=self, default_value=default_value, slider_range=slider_range, dragger_steps=dragger_steps) else: self._slider = Slider(parent=self, slider_range=slider_range) self._slider.valueIncremented.connect(self._on_increment_value) self._slider.setContentsMargins(0, 0, 0, 0) self._slider.setMinimumHeight(h) self._slider.setMaximumHeight(h) self._slider.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) if name: self._label = label.BaseLabel(name + ' ', parent=self) self._main_layout.addWidget(self._label) self._main_layout.addWidget(self._input) self._main_layout.addWidget(self._slider) style_sheet = self._get_style_sheet(self._style_type) if self._style_type == 0: self._main_layout.setSpacing(0) self._slider.setStyleSheet(style_sheet) self._slider.valueChanged.connect(self._on_slider_value_changed) self._input.valueChanged.connect(self._on_houdini_slider_value_changed) def update(self): style_sheet = self._get_style_sheet(self._style_type) if self._style_type == 0: self._main_layout.setSpacing(0) self._slider.setStyleSheet(style_sheet) @property def minimum(self): return self._input.minimum() @property def maximum(self): return self._input.maximum() @property def _value_range(self): return self.maximum - self.minimum def _get_value(self): return self.value() def _set_value(self, value): with qt_contexts.block_signals(self): self.set_value(value) intValue = Property(int, _get_value, _set_value, user=True) floatValue = Property(float, _get_value, _set_value, user=True) def value(self): self._value = self._input.value() if self._type == 'int': self._value = int(self._value) return self._value def set_value(self, value): self._input.setValue(value) self._value = self._input.value() self.valueChanged.emit(self.value()) # self._on_houdini_slider_value_changed(0) def set_decimals(self, decimals): self._input.setDecimals(decimals) def set_single_step(self, step): self._input.setSingleStep(step) def hide_label(self): if self._label: self._label.hide() def show_label(self): if self._label: self._label.show() def hide_slider(self): self._slider.hide() def show_slider(self): self._slider.show() def set_range(self, minimum_value, maximum_value): self._input.setRange(minimum_value, maximum_value) def _on_increment_value(self, step): if step == 0.0: return old = self._input.value() new = old + step self._input.setValue(new) self.valueChanged.emit(new) def _on_slider_value_changed(self, value): out_value = utils.map_range_unclamped( value, self._slider.minimum(), self._slider.maximum(), self._input.minimum(), self._input.maximum()) with qt_contexts.block_signals(self._input): self._input.setValue(out_value) self.valueChanged.emit(out_value) def _on_houdini_slider_value_changed(self, value): in_value = utils.map_range_unclamped( self._input.value(), self._input.minimum(), self._input.maximum(), self._slider.minimum(), self._slider.maximum()) with qt_contexts.block_signals(self._slider): self._slider.setValue(int(in_value)) self.valueChanged.emit(value) def _get_style_sheet(self, style_type): if style_type == 0: return """ QWidget{ border: 1.25 solid black; } QSlider::groove:horizontal, QSlider::sub-page:horizontal { background: %s; } QSlider::add-page:horizontal, QSlider::sub-page:horizontal:disabled { background: rgb(32, 32, 32); } QSlider::add-page:horizontal:disabled { background: grey; } QSlider::handle:horizontal { width: 1px; } """ % "rgba%s" % str(self._main_color) else: return """ QSlider::groove:horizontal { border: 1px solid #bbb; background: white; height: 3px; border-radius: 2px; } QSlider::sub-page:horizontal { background: %s; border: 0px solid #777; height: 3px; border-radius: 2px; } QSlider::add-page:horizontal { background: #fff; border: 1px solid #777; height: 3px; border-radius: 2px; } QSlider::handle:horizontal { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #eee, stop:1 #ccc); border: 1px solid #777; width: 4px; margin-top: -8px; margin-bottom: -8px; border-radius: 2px; height : 10px; } QSlider::handle:horizontal:hover { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #fff, stop:1 #ddd); border: 1px solid #444; border-radius: 2px; } QSlider::sub-page:horizontal:disabled { background: #bbb; border-color: #999; } QSlider::add-page:horizontal:disabled { background: #eee; border-color: #999; } QSlider::handle:horizontal:disabled { background: #eee; border: 1px solid #aaa; border-radius: 2px; height : 10; } """ % "rgba%s" % str(self._main_color)
class Avatar(QLabel, object): """ Widget that can be used to represent users or objects """ def __init__(self, parent=None): super(Avatar, self).__init__(parent) self._default_pixmap = resources.pixmap('user') self._pixmap = self._default_pixmap self._size = 0 self._set_size(self.theme_default_size()) # ================================================================================================================= # PROPERTIES # ================================================================================================================= def _get_size(self): """ Returns the avatar height size :return: float """ return self._size def _set_size(self, value): """ Sets avatar height size :param value: float """ self._size = value self.setFixedSize(self._size, self._size) self.setPixmap(self._pixmap.scaledToWidth(self.height(), Qt.SmoothTransformation)) def _get_image(self): """ Returns avatar image :return: QPixmap """ return self._pixmap def _set_image(self, value): """ Sets avatar image :param value: QPixmap or None """ if value is None: self._pixmap = self._default_pixmap elif isinstance(value, QPixmap): self._pixmap = value else: raise TypeError('Input argument value should be QPixmap or None, but get "{}"'.format(type(value))) self.setPixmap(self._pixmap.scaledToWidth(self.height(), Qt.SmoothTransformation)) theme_size = Property(int, _get_size, _set_size) image = Property(QPixmap, _get_image, _set_image) # ================================================================================================================= # BASE # ================================================================================================================= @classmethod def tiny(cls, image=None, parent=None): """ Creates a new avatar widget with tiny size :param image: :param parent: :return: """ avatar_widget = cls(parent=parent) loading_theme = avatar_widget.theme() loading_size = loading_theme.tiny if loading_theme else theme.Theme.Sizes.TINY avatar_widget.theme_size = loading_size avatar_widget.image = image return avatar_widget @classmethod def small(cls, image=None, parent=None): """ Creates a new avatar widget with small size :param image: :param parent: :return: """ avatar_widget = cls(parent=parent) loading_theme = avatar_widget.theme() loading_size = loading_theme.small if loading_theme else theme.Theme.Sizes.SMALL avatar_widget.theme_size = loading_size avatar_widget.image = image return avatar_widget @classmethod def medium(cls, image=None, parent=None): """ Creates a new avatar widget with medium size :param image: :param parent: :return: """ avatar_widget = cls(parent=parent) loading_theme = avatar_widget.theme() loading_size = loading_theme.medium if loading_theme else theme.Theme.Sizes.MEDIUM avatar_widget.theme_size = loading_size avatar_widget.image = image return avatar_widget @classmethod def large(cls, image=None, parent=None): """ Creates a new avatar widget with large size :param image: :param parent: :return: """ avatar_widget = cls(parent=parent) loading_theme = avatar_widget.theme() loading_size = loading_theme.large if loading_theme else theme.Theme.Sizes.LARGE avatar_widget.theme_size = loading_size avatar_widget.image = image return avatar_widget @classmethod def huge(cls, image=None, parent=None): """ Creates a new avatar widget with huge size :param image: :param parent: :return: """ avatar_widget = cls(parent=parent) loading_theme = avatar_widget.theme() loading_size = loading_theme.huge if loading_theme else theme.Theme.Sizes.HUGE avatar_widget.theme_size = loading_size avatar_widget.image = image return avatar_widget