Source code for schrodinger.ui.qt.standard_widgets.buttons

"""
Module containing classes for buttons styled for use in Schrodinger panels.
"""

import enum

from schrodinger.Qt import QtWidgets
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt.QtWidgets import QSizePolicy
from schrodinger.ui.qt import style
from schrodinger.ui.qt.standard.colors import LightModeColors


[docs]def apply_style_sheet_to_button(button, style, margin_height=0, margin_width=3): """ Apply a Schrodinger color scheme CSS to the given QPushButton object. :param button: Button to apply the styling to. :type button: QtWidgets.QPushButton :param style: Style to use :type style: `StyledButton.Style` :param margin_height: Margin height of the PushButton in pixels :type margin_height: Union[float, int] :param margin_width: Margin width of the PushButton in pixels :type margin_width: Union[float, int] """ background_gradient = LightModeColors.STANDARD_BUTTON_BACKGROUND pressed_bg_color = LightModeColors.STANDARD_PRESSED_BUTTON_BACKGROUND_COLOR if style == StyledButton.Style.Highlighted: background_gradient = LightModeColors.HIGHLIGHTED_BUTTON_BACKGROUND pressed_bg_color = LightModeColors.HIGHLIGHTED_PRESSED_BUTTON_BACKGROUND_COLOR text_color = LightModeColors.HIGHLIGHTED_BUTTON_COLOR elif style == StyledButton.Style.HighlightedText: text_color = LightModeColors.GOOD_TEXT else: if style != StyledButton.Style.Standard: raise ValueError('Invalid style specified') text_color = LightModeColors.STANDARD_BUTTON_COLOR css = f''' QPushButton {{ padding: -5px 20px; /* spacing WITHIN; make button shorter & wider */ margin: {margin_height}px {margin_width}px; /* spacing between buttons */ height: 28px; color: {text_color}; background-color: {background_gradient}; border-radius: 2px; border: 1px solid {LightModeColors.STANDARD_BUTTON_BORDER_ENABLED}; }} QPushButton:!enabled {{ color: {LightModeColors.DISABLED_BUTTON_COLOR}; background-color: {LightModeColors.DISABLED_BUTTON_BACKGROUND}; border: 1px solid {LightModeColors.DISABLED_BUTTON_BORDER} }} QPushButton:pressed {{ color: {LightModeColors.PRESSED_BUTTON_COLOR}; background-color: {pressed_bg_color}; }} ''' button.setStyleSheet(css)
[docs]class DoubleClickButtonMixin: """ Mixin that includes a double click signal for push button. Include this mixin with a QPushButton subclass and singleClicked and doubleClicked will work out of the box. :ivar DOUBLE_CLICK_TIME: the amount of time to trigger double click (ms) :vartype DOUBLE_CLICK_TIME: int :ivar doubleClicked: a signal that the button was double clicked :vartype doubleClicked: QtCore.pyqtSignal :ivar singleClicked: a signal that the button was single clicked :vartype singleClicked: QtCore.pyqtSignal """ doubleClicked = QtCore.pyqtSignal() singleClicked = QtCore.pyqtSignal()
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.click_timer = QtCore.QTimer() self.click_timer.setSingleShot(True) self.click_timer.timeout.connect(self.singleClicked) super().clicked.connect(self._checkClickType)
@QtCore.pyqtSlot() def _checkClickType(self): double_click_time = QtWidgets.QApplication.instance( ).doubleClickInterval() self.click_timer.start(double_click_time)
[docs] def mouseDoubleClickEvent(self, event: QtGui.QMouseEvent): if event.button() == QtCore.Qt.LeftButton: self.doubleClicked.emit() self.click_timer.stop()
[docs]class StyledButton(QtWidgets.QPushButton): """ A QPushButton that uses custom CSS, and offers ability to be "highlighted" with a green background (e.g. for Run buttons). """
[docs] class Style(enum.Enum): # Standard button - gray background, black text Standard = 'Standard' # gray background, black text # Green background, white text - similar to "defualt" button, except # that "Enter" key is not mapped to it. Highlighted = 'Highlighted' # Standard button with green text: HighlightedText = 'HighlightedText'
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._style = self.Style.Standard self.updateStyleSheet()
[docs] def setStyle(self, style): """ Set the style of the button to one of available styles. :param style: Style to use. :type stle: `StyledButton.Style` """ self._style = style self.updateStyleSheet()
[docs] def setHighlighted(self, highlighted): """ Whether to make the button background green, to make the button "jump out" to the user as the next likely action to take. Similar to a "default" button state (often used in dialog boxes), except that it doesn't activate on pressing of the "Enter" key. """ self._style = self.Style.Highlighted if highlighted else self.Style.Standard self.updateStyleSheet()
[docs] def updateStyleSheet(self): apply_style_sheet_to_button(self, self._style)
[docs]class SplitStyledButton(StyledButton): """ A StyledButton with no margin used for adjacent alignment. Create a split button by placing multiple in an individual HBoxLayout with no spacing. """
[docs] def updateStyleSheet(self): apply_style_sheet_to_button(self, self._style, margin_width=0)
[docs]class DoubleClickSplitStyledButton(DoubleClickButtonMixin, SplitStyledButton): """ Button with split styling and double click signal. """
[docs]class FlatButton(QtWidgets.QToolButton): """ A flat icon toolbutton. To use this, it's necessary to call setIconPath with the appropriate path to get the icon to show up. If a different icon is to be used for hover/pressed states, call setHoverIconPath. The size of the icon can be specified using `setIconSize_` (there is already a Qt setIconSize method on the class) """
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._normal_path = "" self._hover_path = "" self._disabled_path = "" self._pressed_path = "" self._icon_width = 20 self._icon_height = 20 self.updateStyleSheet()
[docs] def setIconPath(self, normal_path): self._normal_path = normal_path self.updateStyleSheet()
[docs] def setHoverIconPath(self, hover_path): self._hover_path = hover_path self.updateStyleSheet()
[docs] def setDisabledIconPath(self, disabled_path): self._disabled_path = disabled_path self.updateStyleSheet()
[docs] def setPressedIconPath(self, pressed_path): self._pressed_path = pressed_path self.updateStyleSheet()
[docs] def setIconSize_(self, width=None, height=None): if width: self._icon_width = width if height: self._icon_height = height self.updateStyleSheet()
[docs] def updateStyleSheet(self): ss = """ QToolButton { width: """ + str(self._icon_width) + """px; height: """ + str(self._icon_height) + """px; border: 1px solid transparent; background: none; padding: 0px 0px 0px 0px; }""" if self._normal_path: ss += """ QToolButton { image: url(""" + self._normal_path + """); }""" if self._hover_path: ss += """ QToolButton:hover, QToolButton:pressed { image: url(""" + self._hover_path + """); }""" if self._disabled_path: ss += """ QToolButton:disabled { image: url(""" + self._disabled_path + """); }""" if self._pressed_path: ss += """ QToolButton:pressed { image: url(""" + self._pressed_path + """); }""" self.setStyleSheet(ss)
# ============================================================================== # PANELX BUTTONS # ============================================================================== """ The PanelX design system includes 6 standard buttons. Classes are defined here and should be styled through the parent panel by applying the PanelX stylesheet to the panel. """ class _DropdownMixin: """ A temporary mixin to test out PanelX menu visuals. Eventually we can get rid of this. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.dropdown_menu = QtWidgets.QMenu(parent=self) self.setMenu(self.dropdown_menu) self.dropdown_menu.addAction('foo') self.dropdown_menu.addAction('bar') self.dropdown_menu.addAction('baz') class _PanelXPushButton(QtWidgets.QPushButton): """ Push button with fixed size policy for use in all PanelX buttons. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
[docs]class PrimaryPushButton(_PanelXPushButton): """ Subclass to apply primary button styling. """ pass
[docs]class SecondaryPushButton(_PanelXPushButton): """ Subclass to apply secondary button styling. """ pass
[docs]class TertiaryPushButton(_PanelXPushButton): """ Subclass to apply tertiary button styling. """ pass
[docs]class BaseSplitPushButton(_PanelXPushButton): """ """ pass
class _LeftSplitPushButton(PrimaryPushButton): """ PanelX button with no horizontal margin and rounded left corners. This the primary button of a SplitPushButton. """ pass class _RightSplitPushButton(PrimaryPushButton, _DropdownMixin): """ PanelX button with no horizontal margin and rounded right corners. This the secondary button of a SplitPushButton. """ pass
[docs]class SplitPushButton(QtWidgets.QWidget): """ PanelX split push button. Contains both the left and right buttons """ INTERBUTTON_SPACING = 1 # TODO: implement button functionality once we get a spec (maybe using # taskbar redesign spec?)
[docs] def __init__(self, text: str = '', *args, **kwargs): super().__init__(*args, **kwargs) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) # Manually apply stylesheet since this is only a QWidget # Does not inherit styleSheet otherwise style.apply_panelx_style(self) self._text = text self.left_btn = _LeftSplitPushButton(self._text) self.right_btn = _RightSplitPushButton('') self._initLayOut()
def _initLayOut(self): """ Lay out all children. Not to be confused with `InitMixin.initLayOut()` """ self._buttons_layout = QtWidgets.QHBoxLayout() self._buttons_layout.setContentsMargins(0, 0, 0, 0) self._buttons_layout.setSpacing(self.INTERBUTTON_SPACING) self._buttons_layout.addWidget(self.left_btn) self._buttons_layout.addWidget(self.right_btn) self.setLayout(self._buttons_layout)