Source code for qtdraw.widget.custom_widget

"""
Custom widget.

This module provides customuized widgets.
"""

from PySide6.QtWidgets import (
    QWidget,
    QLabel,
    QGridLayout,
    QSizePolicy,
    QLineEdit,
    QFrame,
    QPushButton,
    QComboBox,
    QSpinBox,
    QDoubleSpinBox,
    QCheckBox,
    QSpacerItem,
    QApplication,
)
from PySide6.QtGui import QFont, Qt, QPixmap, QColor, QIcon
from PySide6.QtCore import Signal
from gcoreutils.color_palette import all_colors
from qtdraw.util.color_selector_util import _color2pixmap
from qtdraw.widget.validator import (
    validator_int,
    validator_float,
    validator_sympy_float,
    validator_sympy,
    validator_ilist,
    validator_list,
    validator_site,
    validator_bond,
    validator_site_bond,
    validator_vector_site_bond,
    validator_orbital_site_bond,
)
from qtdraw.util.latex_to_png import latex_to_png
from qtdraw.util.color_selector_util import color2pixmap


# ==================================================
[docs] class Layout(QGridLayout): # ================================================== def __init__(self, parent=None): """ Layout widget. Args: parent (QWidget, optional): parent. """ super().__init__(parent) self.setContentsMargins(0, 0, 0, 0) self.setHorizontalSpacing(2) self.setVerticalSpacing(5)
# ==================================================
[docs] class Panel(QWidget): # ================================================== def __init__(self, parent=None): """ Panel widget. Args: parent (QWidget, optional): parent. """ super().__init__(parent) self.layout = Layout(self)
# ==================================================
[docs] class Color(QColor): # ================================================== def __init__(self, color): """ Color Args: color (str): color name. """ super().__init__(all_colors[color][0])
# ==================================================
[docs] class Label(QLabel): # ================================================== def __init__(self, parent=None, text="", bold=False, color="black", size=10, math=False, dpi=120): """ Label widget. Args: parent (QWidget, optional): parent. text (str, optional): text. bold (bool, optional): bold font ? color (str, optional): font color. size (int, optional): font size. math (bool, optional): for math ? dpi (int, optional): DPI for math. Note: - in math mode, text is given in LaTeX code without $. """ super().__init__(parent=parent) policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setSizePolicy(policy) self.setContentsMargins(0, 0, 0, 0) font = QFont() font.setBold(bold) self.setFont(font) self.setFocusPolicy(Qt.NoFocus) self.setIndent(6) if math: self.to_png = lambda text: latex_to_png(text, True, color, size, dpi) else: self.to_png = None self.setText(text) # ==================================================
[docs] def setText(self, text): """ Set text. Args: text (xtr): text. """ if self.to_png is None: super().setText(text) else: s = self.to_png(text) if s is None: return else: pixmap = QPixmap() pixmap.setDevicePixelRatio(1.0) pixmap.loadFromData(s) super().setPixmap(pixmap)
# ==================================================
[docs] class ColorLabel(Panel): # ================================================== def __init__(self, parent=None, color="", bold=False): """ Color label widget. Args: parent (QWidget, optional): parent. color (str, optional): color/colormap name. bold (bool, optional): bold font ? """ super().__init__(parent) label = Label(self, color, bold) size = label.font().pointSize() colorbox = _color2pixmap(color, "color_both", size) icon = Label(self) icon.setPixmap(colorbox) icon.setFrameStyle(QFrame.Box) icon.setFixedSize(colorbox.size()) self.layout.addWidget(icon, 0, 0) self.layout.addWidget(label, 0, 1)
# ==================================================
[docs] class LineEdit(QLineEdit): _valid_style = "padding-left: 3px; background: none;" _invalid_style = "padding-left: 3px; background: pink;" _read_only_style = "padding-left: 3px; background: lightgray;" focusOut = Signal() # ================================================== def __init__(self, parent=None, text="", validator=None): """ Line editor. Args: parent (QWidget, optional): parent. text (str, optional): text. validator (tuple, optional): (validator_type, option). """ super().__init__(text, parent) policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.setSizePolicy(policy) self.setContentsMargins(0, 0, 0, 0) self.set_validator(validator) self.set_read_only(False) self._raw = text self._backup = text self._valid = True self.setText(text) # ================================================== @property def validator(self): """ Validator type. Returns: - (str) -- validator type. """ return self._validator_type # ================================================== @property def read_only(self): """ Read only ? Returns: - (bool) -- read only ? """ return self._read_only # ==================================================
[docs] def close_edit(self): """ Close editor. """ if self._valid: self.returnPressed.emit() self.clearFocus()
# ==================================================
[docs] def raw_text(self): """ Raw text. Returns: - (str) -- raw text. """ return self._raw
# ==================================================
[docs] def setText(self, text): """ Set text. Args: text (str): text. """ if self._validator is None: s = text else: s = self._validator(text) if s is None: self.setStyleSheet(self._invalid_style) self._valid = False else: if self.read_only: super().setText(s) return self.setStyleSheet(self._valid_style) self._backup = text self._raw = text self._valid = True super().setText(s)
# ==================================================
[docs] def set_validator(self, validator): """ Set validator. Args: validator (tuple): (validator_type, option). """ validator_dict = { "int": validator_int, "float": validator_float, "sympy_float": validator_sympy_float, "sympy": validator_sympy, "ilist": validator_ilist, "list": validator_list, "site": validator_site, "bond": validator_bond, "site_bond": validator_site_bond, "vector_site_bond": validator_vector_site_bond, "orbital_site_bond": validator_orbital_site_bond, } if validator is None: self._validator = None else: self._validator_type, option = validator self._validator = lambda text: validator_dict[self._validator_type](text, option)
# ==================================================
[docs] def set_read_only(self, flag): """ Set read only. Args: flag (bool): read only ? """ self._read_only = flag if flag: self.setStyleSheet(self._read_only_style) else: self.setStyleSheet(self._valid_style)
# ==================================================
[docs] def focusInEvent(self, event): """ Focus-out event. """ super().setText(self._raw) super().focusInEvent(event) self.selectAll()
# ==================================================
[docs] def focusOutEvent(self, event): """ Focus-out event. """ self.clearFocus() super().focusOutEvent(event) self.focusOut.emit()
# ==================================================
[docs] def clearFocus(self): """ Clear focus. """ if not self.read_only: if not self._valid: self.setStyleSheet(self._valid_style) self.setText(self._backup) super().clearFocus()
# ==================================================
[docs] def keyPressEvent(self, event): """ Key event. Args: event (str): event. """ k = event.key() if k == Qt.Key_Escape: self.clearFocus() self.setFocus() elif k == Qt.Key_Return: self.setText(self.text()) self.close_edit() else: super().keyPressEvent(event)
# ==================================================
[docs] class HBar(QFrame): # ================================================== def __init__(self, parent=None): """ Horizontal bar item. Args: parent (QWidget, optional): parent. """ super().__init__(parent) self.setMinimumSize(0, 10) self.setFrameShape(QFrame.HLine) self.setFrameShadow(QFrame.Sunken) self.setContentsMargins(0, 0, 0, 0) self.setFocusPolicy(Qt.NoFocus)
# ==================================================
[docs] class Button(QPushButton): # ================================================== def __init__(self, parent=None, text="", toggle=False): """ Button widget. Args: parent (QWidget, optional): parent. text (str, optional): text. toggle (bool, optional): toggle button ? """ super().__init__(text, parent) self.setCheckable(toggle) self.setContentsMargins(0, 0, 0, 0) self.setFocusPolicy(Qt.NoFocus)
# ==================================================
[docs] class Combo(QComboBox): # ================================================== def __init__(self, parent=None, item=[], init=None): """ Combo widget. Args: parent (QWidget, optional): parent. item (list, optional): list of items, [str]. init (str, optional): initial value. """ super().__init__(parent) self.setContentsMargins(0, 0, 0, 0) self.setFocusPolicy(Qt.NoFocus) self.set_item(item) if init is not None: self.setCurrentText(init) self.setSizeAdjustPolicy(QComboBox.AdjustToContents) # ==================================================
[docs] def get_item(self): """ Get item. Returns: - (list) -- item list. """ lst = [self.itemText(i) for i in range(self.count())] return lst
# ==================================================
[docs] def set_item(self, item): """ Set item. Args: item (list): item list. """ self.blockSignals(True) self.clear() self.addItems(item) self.blockSignals(False)
# ==================================================
[docs] def find_index(self, key): """ Find index. Args: key (str): item key. Returns: - (int) -- index. """ item = self.get_item() index = [idx for idx, s in enumerate(item) if key in s] return index
# ==================================================
[docs] class Spin(QSpinBox): # ================================================== def __init__(self, parent=None, min=0, max=1): """ Spin widget. Args: parent (QWidget, optional): parent. min (int, optional): minimum value. max (int, optional): maximum value. """ super().__init__(parent) self.setMinimum(min) self.setMaximum(max) self.setContentsMargins(0, 0, 0, 0) self.setFocusPolicy(Qt.NoFocus)
# ==================================================
[docs] class DSpin(QDoubleSpinBox): # ================================================== def __init__(self, parent=None, min=0.0, max=1.0, step=0.1): """ Spin widget. Args: parent (QWidget, optional): parent. min (float, optional): minimum value. max (float, optioanl): maximum value. step (float, optional): step value. """ super().__init__(parent) self.setMinimum(min) self.setMaximum(max) self.setSingleStep(step) self.setContentsMargins(0, 0, 0, 0) self.setFocusPolicy(Qt.NoFocus)
# ==================================================
[docs] class Check(QCheckBox): # ================================================== def __init__(self, parent=None, text=""): """ Check widget. Args: parent (QWidget, optional): parent. text (str, optional): text. """ super().__init__(text, parent) self.setContentsMargins(0, 0, 0, 0) self.setFocusPolicy(Qt.NoFocus) # ==================================================
[docs] def is_checked(self): """ Is checked ? Returns: - (bool) -- checked ? """ return self.checkState() == Qt.Checked
# ==================================================
[docs] class VSpacer(QSpacerItem): # ================================================== def __init__(self): """ Vertical spacer. """ super().__init__(1, 1, QSizePolicy.Minimum, QSizePolicy.Expanding)
# ==================================================
[docs] class HSpacer(QSpacerItem): # ================================================== def __init__(self): """ Horizontal spacer. """ super().__init__(1, 1, QSizePolicy.Expanding, QSizePolicy.Minimum)
# ==================================================
[docs] class Editor(Panel): returnPressed = Signal(str) # ================================================== def __init__(self, parent=None, text="", validator=None, color="black", size=10, dpi=120): """ Math equation label widget. Args: parent (QWidget, optional): parent. text (str, optional): text. validator (tuple, optional): (validator_type, option). color (str, optioanl): color name. size (int, optional): font size. dpi (int, optional): DPI. Note: - following validators can be used. - int: (min, max). - float: (min, max, digit). - sympy_float: (digit). - sympy: (variable list). - ilist: (shape). - list: (shape, variable list, digit). - site: (use variable?). - bond: (use variable?). - site_bond: (use variable?). - vector_site_bond: (use variable?). - orbital_site_bond: (use variable?). """ super().__init__(parent) math = False if validator is not None and validator[0] == "sympy": math = True self._editor = LineEdit(None, text, validator) self._display = Label(None, self._editor.text(), color=color, size=size, math=math, dpi=dpi) self.layout.addWidget(self._display, 0, 0) self.layout.addWidget(self._editor, 0, 0) self._editor.hide() self._in_edit = False self._editor.returnPressed.connect(self.close_editor) self._editor.focusOut.connect(self.clearFocus) # ==================================================
[docs] def close_editor(self): """ Close editor. """ self.clearFocus() if self._editor._valid: self.returnPressed.emit(self._editor.raw_text())
# ==================================================
[docs] def clearFocus(self): """ Clear focus. """ if self._editor._valid: self._display.setText(self._editor.text()) self._editor.clearFocus() self._editor.hide() self._display.show() self._in_edit = False
# ==================================================
[docs] def mouseDoubleClickEvent(self, event): """ Mouse double-click event. """ if not self._in_edit and not self._editor.read_only: self._in_edit = True self._display.hide() self._editor.show() self._editor.setFocus()
# ==================================================
[docs] def mousePressEvent(self, event): """ Mouse click event. """ current = QApplication.focusWidget() if current: current.clearFocus() super().mousePressEvent(event)
# ==================================================
[docs] def text(self): """ Text. Returns: - (str) -- text. """ if self._editor._validator_type == "sympy": return self._editor.raw_text() else: return self._display.text()
# ==================================================
[docs] def setText(self, text): """ Set text. Args: text (str): text. """ self._editor.setText(text) if self._editor._valid: self._display.setText(self._editor.text())
# ==================================================
[docs] def raw_text(self): """ Raw text. Returns: - (str) -- raw text. """ return self._editor.raw_text()
# ==================================================
[docs] def setCurrentText(self, text): """ Set current text. Args: text (str): text. """ self._editor.setText(text) if self._editor._valid: self._display.setText(self._editor.text())
# ==================================================
[docs] def currentText(self): """ Get current text. Returns: - (str) -- current text. """ return self._editor.raw_text()
# ==================================================
[docs] class ColorSelector(QComboBox): # ================================================== def __init__(self, parent=None, current="", color_type="color"): """ Color selector widget. Args: parent (QWidget, optional): parent. current (str, optional): default color. color_type (str, optional): color/colormap/color_both Notes: - connect currentTextChanged. """ super().__init__(parent=parent) self.setContentsMargins(0, 0, 0, 0) self.setFocusPolicy(Qt.NoFocus) size = self.font().pointSize() color_pixmap, separator = color2pixmap(color_type, size) names = list(color_pixmap.keys()) if current == "": current = names[0] try: current = names.index(current) except ValueError: current = 0 self.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.blockSignals(True) for color, pixmap in color_pixmap.items(): self.addItem(QIcon(pixmap), color, self) self.blockSignals(False) self.setCurrentIndex(current) size = next(iter(color_pixmap.values())).size() self.setIconSize(size) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.setFixedHeight(size.height() * 2.8) for i in separator: self.insertSeparator(i)