code release

This commit is contained in:
iperov 2021-07-23 17:34:49 +04:00
commit a902f11f74
354 changed files with 826570 additions and 1 deletions

View file

@ -0,0 +1,27 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXCheckBox(QCheckBox, _part_QXWidget):
def __init__(self, text=None, color=None, clicked=None, toggled=None, font=None, size_policy=None, hided=False, enabled=True):
super().__init__()
if text is not None:
self.setText(text)
if color is not None:
self.setStyleSheet(f'QCheckBox {{ color: {color};}}')
_part_QXWidget.connect_signal(clicked, self.clicked)
_part_QXWidget.connect_signal(toggled, self.toggled)
_part_QXWidget.__init__(self, font=font, size_policy=size_policy, hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,89 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from .QXFrame import QXFrame
from .QXHBoxLayout import QXHBoxLayout
from .QXLabel import QXLabel
from .QXToolButton import QXToolButton
from .QXVBoxLayout import QXVBoxLayout
class QXCollapsibleSection(QXFrame):
"""
Collapsible section.
Open/close state is saved to app db.
"""
def __init__(self, title, content_layout, vertical=False, is_opened=True, allow_open_close=True):
super().__init__()
self._is_opened = is_opened
self._vertical = vertical
if vertical:
title = '\n'.join(title)
label_title = self.label_title = QXLabel(text=title)
btn = self.btn = QXToolButton(checkable=True)
btn.setStyleSheet('border: none;')
btn.setArrowType(Qt.ArrowType.RightArrow)
btn.setChecked(False)
if allow_open_close:
btn.toggled.connect(self.on_btn_toggled)
frame = self.frame = QXFrame(layout=content_layout, size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding), hided=True)
if vertical:
main_l = QXHBoxLayout([ ( QXFrame(layout=
QXVBoxLayout([ (btn, Qt.AlignmentFlag.AlignTop),
(label_title, Qt.AlignmentFlag.AlignCenter)
]),
size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) ), Qt.AlignmentFlag.AlignTop),
frame ])
else:
main_l = QXVBoxLayout( [ ( QXFrame(layout=
QXHBoxLayout([ (btn, Qt.AlignmentFlag.AlignTop),
(label_title, Qt.AlignmentFlag.AlignCenter)
]),
size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) ) , Qt.AlignmentFlag.AlignTop),
frame])
self.setLayout(main_l)
if self._is_opened:
self.open()
def _on_registered(self):
super()._on_registered()
self._is_opened = self.get_widget_data( (QXCollapsibleSection,'opened'), default_value=self._is_opened )
if self._is_opened:
self.open()
else:
self.close()
def is_opened(self):
return self.btn.isChecked()
def open(self):
self.set_widget_data( (QXCollapsibleSection,'opened'), True)
self.btn.setArrowType(Qt.ArrowType.DownArrow)
self.btn.setChecked(True)
self.frame.show()
def close(self):
self.set_widget_data( (QXCollapsibleSection,'opened'), False)
self.btn.setArrowType(Qt.ArrowType.RightArrow)
self.btn.setChecked(False)
self.frame.hide()
def on_btn_toggled(self):
if self.btn.isChecked():
self.open()
else:
self.close()

View file

@ -0,0 +1,33 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
from typing import List
class QXComboBox(QComboBox, _part_QXWidget):
def __init__(self, choices : List[str] = None,
on_index_changed=None,
font=None, tooltip_text=None,
minimum_width=None, maximum_width=None, fixed_width=None, minimum_height=None, maximum_height=None, fixed_height=None, size_policy=None, hided=False, enabled=True):
super().__init__()
if choices is not None:
for choice in choices:
self.addItem(choice)
_part_QXWidget.connect_signal(on_index_changed, self.currentIndexChanged)
_part_QXWidget.__init__(self, font=font, tooltip_text=tooltip_text,
size_policy=size_policy,
minimum_width=minimum_width, maximum_width=maximum_width,
minimum_height=minimum_height, maximum_height=maximum_height,
fixed_width=fixed_width, fixed_height=fixed_height,
hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,14 @@
from PyQt6.QtWidgets import *
class QXDirDialog(QFileDialog):
def __init__(self, parent=None, caption : str = None, directory : str = None, accepted=None):
super().__init__(parent=parent, directory=directory)
self.setOption(QFileDialog.Option.DontUseNativeDialog)
self.setOption(QFileDialog.Option.ShowDirsOnly, True)
self.setFileMode(QFileDialog.FileMode.Directory)
if caption is not None:
self.setWindowTitle(caption)
if accepted is not None:
self.accepted.connect(accepted)

View file

@ -0,0 +1,42 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXDoubleSpinBox(QDoubleSpinBox, _part_QXWidget):
def __init__(self, min=None, max=None, step=None, decimals=None, readonly=False, special_value_text=None, color=None, alignment=None, editingFinished=None, valueChanged=None, font=None, size_policy=None, hided=False, enabled=True):
super().__init__()
if min is not None:
self.setMinimum(min)
if max is not None:
self.setMaximum(max)
if step is not None:
self.setSingleStep(step)
if decimals is not None:
self.setDecimals(decimals)
if special_value_text is not None:
self.setSpecialValueText(special_value_text)
if alignment is not None:
self.setAlignment(alignment)
self.setReadOnly(readonly)
if color is not None:
self.setStyleSheet(f'QDoubleSpinBox {{ color: {color};}}\n QDoubleSpinBox::disabled {{ color: dark-gray;}}')
_part_QXWidget.connect_signal(editingFinished, self.editingFinished)
_part_QXWidget.connect_signal(valueChanged, self.valueChanged)
_part_QXWidget.__init__(self, font=font, size_policy=size_policy, hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,27 @@
from PyQt6.QtWidgets import *
class QXFileDialog(QFileDialog):
def __init__(self, parent=None,
multi_files=False,
existing_only=False,
is_save=False,
filter=None,
accepted=None):
super().__init__(parent=parent, filter=filter)
self.setOption(QFileDialog.Option.DontUseNativeDialog)
if is_save:
self.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
if multi_files:
self.setFileMode(QFileDialog.FileMode.ExistingFiles)
else:
if existing_only:
self.setFileMode(QFileDialog.FileMode.ExistingFile)
else:
self.setFileMode(QFileDialog.FileMode.AnyFile)
if accepted is not None:
self.accepted.connect(accepted)

View file

@ -0,0 +1,80 @@
from typing import List
import numpy as np
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib.image import ImageProcessor
from ..gui.from_np import QPixmap_from_np
from .QXWidget import QXWidget
class QXFixedLayeredImages(QXWidget):
"""
A widget to show multiple stacked images in fixed area
"""
def __init__(self, fixed_width, fixed_height):
super().__init__()
self._fixed_width = fixed_width
self._fixed_height = fixed_height
self._qp = QPainter()
self._pixmaps : List[QPixmap] = []
def clear_images(self):
self._pixmaps : List[QPixmap] = []
self.update()
def add_image(self, image, name=None):
"""
image np.ndarray
QPixmap
all images must have the same aspect ratio
"""
if isinstance(image, np.ndarray):
ip = ImageProcessor(image)
ip.fit_in(self._fixed_width, self._fixed_height)
image = ip.get_image('HWC')
q_pixmap = QPixmap_from_np(image)
elif isinstance(image, QPixmap):
q_pixmap = image
else:
raise ValueError(f'Unsupported type of image {image.__class__}')
self._pixmaps.append(q_pixmap)
self.update()
def sizeHint(self):
return QSize(self._fixed_width, self._fixed_height)
def paintEvent(self, event):
super().paintEvent(event)
qp = self._qp
qp.begin(self)
#qp.setRenderHint(QPainter.RenderHint.Antialiasing)
qp.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
w = self._fixed_width
h = self._fixed_height
w_half = w /2
h_half = h /2
a = w/h
for pixmap in self._pixmaps:
size = pixmap.size()
ap = size.width() / size.height()
if ap > a:
ph_fit = h * (a / ap)
rect = QRect(0, h_half-ph_fit/2, w, ph_fit )
elif ap < a:
pw_fit = w * (ap / a)
rect = QRect(w_half-pw_fit/2, 0, pw_fit, h )
else:
rect = self.rect()
qp.drawPixmap(rect, pixmap, pixmap.rect())
qp.end()

View file

@ -0,0 +1,46 @@
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
from .QXMainApplication import QXMainApplication
class QXFrame(QFrame, _part_QXWidget):
def __init__(self, bg_color=None, layout=None, minimum_width=None, maximum_width=None, fixed_width=None, minimum_height=None, maximum_height=None, fixed_height=None, size_policy=None, hided=False, enabled=True):
QFrame.__init__(self)
_part_QXWidget.__init__(self, layout=layout,
size_policy=size_policy,
minimum_width=minimum_width, maximum_width=maximum_width,
minimum_height=minimum_height, maximum_height=maximum_height,
fixed_width=fixed_width, fixed_height=fixed_height,
hided=hided, enabled=enabled )
pal = QXMainApplication.get_singleton().palette()
if bg_color is not None:
bg_color = QColor(bg_color)
else:
bg_color = pal.color(QPalette.ColorRole.Window)
bg_color = QColor(bg_color.red()+12,bg_color.green()+12,bg_color.blue()+12,255)
self._bg_color = bg_color
self._qp = QPainter()
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)
def paintEvent(self, ev : QPaintEvent):
rect = self.rect()
qp = self._qp
qp.begin(self)
qp.fillRect(rect, self._bg_color )
qp.end()

View file

@ -0,0 +1,21 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ..core.widget import set_contents_margins
class QXGridLayout(QGridLayout):
def __init__(self, contents_margins=0, spacing=None, horizontal_spacing=None, vertical_spacing=None):
super().__init__()
set_contents_margins(self, contents_margins)
if spacing is not None:
self.setSpacing(spacing)
if horizontal_spacing is not None:
self.setHorizontalSpacing(horizontal_spacing)
if vertical_spacing is not None:
self.setVerticalSpacing(vertical_spacing)

View file

@ -0,0 +1,33 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ..core.widget import set_contents_margins
class QXHBoxLayout(QHBoxLayout):
def __init__(self, widgets=None, contents_margins=0, spacing=0):
super().__init__()
set_contents_margins(self, contents_margins)
if widgets is not None:
for widget in widgets:
alignment = None
if isinstance(widget, int):
thickness=widget
widget = QWidget()
widget.setFixedWidth(thickness)
widget.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
if isinstance(widget, (tuple,list)):
widget, alignment = widget
if isinstance(widget, QLayout):
self.addLayout(widget)
else:
self.addWidget(widget)
if alignment is not None:
self.setAlignment(widget, alignment)
if spacing is not None:
self.setSpacing(spacing)

View file

@ -0,0 +1,13 @@
from PyQt6.QtWidgets import *
from .QXLabel import QXLabel
class QXHorizontalLine(QXLabel):
def __init__(self, thickness=1,
color=None):
super().__init__(size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed),
fixed_height=thickness )
if color is not None:
self.setStyleSheet(f'background: {color};')

View file

@ -0,0 +1,74 @@
from typing import Union, Any
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ..gui import QXImage
from ._part_QXWidget import _part_QXWidget
class QXLabel(QLabel, _part_QXWidget):
def __init__(self, text = None,
color = None,
image : QXImage = None,
movie = None,
word_wrap = False, scaled_contents = False,
font=None, tooltip_text=None,
size_policy=None,
minimum_size=None, minimum_width=None, minimum_height=None,
maximum_size=None, maximum_width=None, maximum_height=None,
fixed_size=None, fixed_width=None, fixed_height=None,
hided=False, enabled=True
):
super().__init__()
self._default_pal = QPalette( self.palette() )
if text is not None:
self.setText(text)
if movie is not None:
self.setMovie(movie)
if image is not None:
self.setPixmap(image.as_QXPixmap())
if word_wrap:
self.setWordWrap(True)
self.setScaledContents(scaled_contents)
self.set_color(color)
_part_QXWidget.__init__(self, font=font, tooltip_text=tooltip_text,
size_policy=size_policy,
minimum_size=minimum_size, minimum_width=minimum_width, minimum_height=minimum_height,
maximum_size=maximum_size, maximum_width=maximum_width, maximum_height=maximum_height,
fixed_size=fixed_size, fixed_width=fixed_width, fixed_height=fixed_height,
hided=hided, enabled=enabled )
def _update_color(self):
if self._color is not None:
pal = QPalette(self._default_pal)
pal.setColor( QPalette.ColorRole.WindowText, self._color )
self.setPalette(pal)
else:
self.setPalette(self._default_pal)
def set_color(self, color : Union[Any,None] ):
self._color = QColor(color) if color is not None else None
self._update_color()
def changeEvent(self, ev : QEvent):
super().changeEvent(ev)
if ev.type() == QEvent.Type.EnabledChange:
self._update_color()
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,33 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXLineEdit(QLineEdit, _part_QXWidget):
def __init__(self, placeholder_text=None,
style_sheet=None,
read_only=False,
editingFinished=None,
font=None, size_policy=None, hided=False, enabled=True):
super().__init__()
if placeholder_text is not None:
self.setPlaceholderText(placeholder_text)
if style_sheet is not None:
self.setStyleSheet(style_sheet)
if read_only:
self.setReadOnly(True)
_part_QXWidget.connect_signal(editingFinished, self.editingFinished)
_part_QXWidget.__init__(self, font=font, size_policy=size_policy, hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,164 @@
from pathlib import Path
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from xlib.db import KeyValueDB
from .forward_declarations import forward_declarations
class QXMainApplication(QApplication):
"""
base class for MainApplication
QXMainApplication.inst - singleton instance
settings_dirpath(None) where the data will be saved
"""
inst : 'QXMainApplication' = None
@staticmethod
def get_singleton() -> 'QXMainApplication':
if QXMainApplication.inst is None:
raise Exception('QXMainApplication must be instantiated')
return QXMainApplication.inst
def __init__(self, app_name=None, settings_dirpath : Path = None):
super().__init__([])
if QXMainApplication.inst is not None:
raise Exception('Only one singleton QXMainApplication is allowed')
QXMainApplication.inst = self
self._settings_dirpath = settings_dirpath
if settings_dirpath is not None:
self._app_data_path = settings_dirpath / 'app.dat'
else:
self._app_data_path = None
self._hierarchy_name_count = {}
self._app_db = KeyValueDB(self._app_data_path)
if app_name is not None:
self.setApplicationName(app_name)
self.setStyle('Fusion')
text_color = QColor(200,200,200)
self.setStyleSheet(f"""
QRadioButton::disabled {{
color: gray;
}}
""")
pal = QPalette()
pal.setColor(QPalette.ColorRole.Window, QColor(56, 56, 56))
pal.setColor(QPalette.ColorRole.Base, QColor(25, 25, 25))
pal.setColor(QPalette.ColorRole.AlternateBase, QColor(56, 56, 56))
pal.setColor(QPalette.ColorRole.ToolTipBase, text_color )
pal.setColor(QPalette.ColorRole.ToolTipText, text_color )
pal.setColor(QPalette.ColorRole.Text, text_color )
pal.setColor(QPalette.ColorRole.Button, QColor(56, 56, 56))
pal.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.white)
pal.setColor(QPalette.ColorRole.PlaceholderText, Qt.GlobalColor.darkGray)
pal.setColor(QPalette.ColorGroup.Active, QPalette.ColorRole.ButtonText, text_color)
pal.setColor(QPalette.ColorGroup.Inactive, QPalette.ColorRole.ButtonText, text_color)
pal.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, Qt.GlobalColor.gray)
pal.setColor(QPalette.ColorRole.WindowText, text_color )
pal.setColor(QPalette.ColorGroup.Active, QPalette.ColorRole.WindowText, text_color)
pal.setColor(QPalette.ColorGroup.Inactive, QPalette.ColorRole.WindowText, text_color)
pal.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, Qt.GlobalColor.gray)
pal.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, Qt.GlobalColor.gray)
pal.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red)
pal.setColor(QPalette.ColorRole.Link, QColor(42, 130, 218))
pal.setColor(QPalette.ColorRole.Highlight, QColor(42, 130, 218))
pal.setColor(QPalette.ColorRole.HighlightedText, Qt.GlobalColor.black)
self.setPalette(pal)
self._reinitialize = False
self._timer = lib_qt.QXTimer(interval=10, timeout=self._on_10ms_timer, start=True)
def _on_10ms_timer(self):
self._app_db.process_messages()
if self._reinitialize:
self._reinitialize = False
self.on_reinitialize()
def register_QXWidget(self, widget) -> str:
"""
registers QXWidget, checks validity, returns an unique name
"""
hierarchy = []
iter_widget = widget
while True:
hierarchy.insert(0, iter_widget.__class__.__name__)
iter_parent_widget = iter_widget.parentWidget()
if iter_parent_widget is None:
break
iter_widget = iter_parent_widget
if not isinstance(iter_widget, forward_declarations.QXWindow):
raise Exception('Top widget must be a class of QXWindow')
if len(hierarchy) == 1:
# top level widgets(Windows) has no numerification
return hierarchy[0]
else:
hierarchy_name = '.'.join(hierarchy)
num = self._hierarchy_name_count.get(hierarchy_name, -1)
num = self._hierarchy_name_count[hierarchy_name] = num + 1
return f'{hierarchy_name}:{num}'
def clear_app_data(self):
"""
clear app data and reinitialize()
"""
self._app_db.clear()
self.reinitialize()
def get_app_data(self, key, default_value=None):
"""
returns picklable data by picklable key stored in app db
returns default_value if no data
"""
return self._app_db.get_value(key, default_value=default_value)
def set_app_data(self, key, value):
"""
set picklable data by picklable key stored to app db
"""
self._app_db.set_value(key, value )
def run(self):
"""
run the app
"""
self.exec()
self._app_db.finish_pending_jobs()
def reinitialize(self):
self._reinitialize = True
def on_reinitialize(self):
raise NotImplementedError()
def get_language(self) -> str:
return self.get_app_data('__app_language', 'en-US')
def set_language(self, lang : str) -> str:
return self.set_app_data('__app_language', lang)

View file

@ -0,0 +1,32 @@
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXMenuBar(QMenuBar, _part_QXWidget):
def __init__(self,
font=None, size_policy=None, minimum_width=None, maximum_width=None, fixed_width=None, minimum_height=None, maximum_height=None, fixed_height=None, hided=False, enabled=True):
QMenuBar.__init__(self)
_part_QXWidget.__init__(self, font=font,
size_policy=size_policy,
minimum_width=minimum_width, maximum_width=maximum_width,
minimum_height=minimum_height, maximum_height=maximum_height,
fixed_width=fixed_width, fixed_height=fixed_height,
hided=hided, enabled=enabled )
self.setStyleSheet(f"""
QMenuBar {{
border: 0px;
background-color: #444444;
}}
""")
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,28 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXProgressBar(QProgressBar, _part_QXWidget):
def __init__(self, min=None,
max=None,
valueChanged=None,
font=None, size_policy=None, hided=False, enabled=True, ):
super().__init__()
if min is not None:
self.setMinimum(min)
if max is not None:
self.setMaximum(max)
_part_QXWidget.connect_signal(valueChanged, self.valueChanged)
_part_QXWidget.__init__(self, font=font, size_policy=size_policy, hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,132 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ..core.QXTimeLine import QXTimeLine
from ..gui import QXImage, QXImageSequence
from ._part_QXWidget import _part_QXWidget
class QXPushButton(QPushButton, _part_QXWidget):
def __init__(self, image : QXImage = None, flat=False,
text=None, padding=4, checkable=False,
toggled=None, released=None,
font=None, tooltip_text=None, size_policy=None,
minimum_size=None, minimum_width=None, minimum_height=None,
fixed_size=None, fixed_width=None, fixed_height=None, hided=False, enabled=True
):
super().__init__()
self._image = None
self._image_sequence = None
self._tl = None
if size_policy is None:
size_policy = (QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
if text is not None:
self.setText(text)
if image is not None:
self._set_image(image)
self.setCheckable(checkable)
if flat:
self.setStyleSheet(f"""
QPushButton {{
border: 0px;
background-color: #434343;
padding: {padding}px;
}}
QPushButton:hover {{
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #434343, stop: 0.3 #515151, stop: 0.6 #515151, stop: 1.0 #434343);
}}
QPushButton:pressed {{
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #434343, stop: 0.3 #353535, stop: 0.6 #353535, stop: 1.0 #434343);
}}
""")
_part_QXWidget.connect_signal(released, self.released)
_part_QXWidget.connect_signal(toggled, self.toggled)
_part_QXWidget.__init__(self, font=font, tooltip_text=tooltip_text,
size_policy=size_policy, minimum_size=minimum_size, minimum_width=minimum_width, minimum_height=minimum_height,
fixed_size=fixed_size, fixed_width=fixed_width, fixed_height=fixed_height,
hided=hided, enabled=enabled )
def sizeHint(self) -> QSize:
return QSize(0,0)
def setText(self, text):
QPushButton.setText(self, text)
self.setMinimumWidth(self.fontMetrics().horizontalAdvance(text)+8)
new_min_height = self.fontMetrics().height()+4
min_height = self.minimumHeight()
if new_min_height > min_height:
self.setMinimumHeight(new_min_height)
def _update_icon_size(self):
if self._image is not None:
rect = self.rect()
image = self._image
w, h = rect.width(), rect.height()
rect_aspect = w / h
size = image.size()
pixmap_aspect = size.width() / size.height()
if pixmap_aspect != rect_aspect:
if pixmap_aspect > rect_aspect:
pw, ph = w, int(h * (rect_aspect / pixmap_aspect))
px, py = 0, h/2-ph/2
elif pixmap_aspect < rect_aspect:
pw, ph = int( w * (pixmap_aspect / rect_aspect) ), h
px, py = w/2-pw/2, 0
else:
px, py, pw, ph = 0, 0, w, h
self.setIconSize( QSize(pw-4,ph-4) )
def _set_image(self, image : QXImage ):
self._image = image
self.setIcon( image.as_QIcon() )
def set_image(self, image : QXImage ):
self.stop_image_sequence()
self._set_image(image)
def set_image_sequence(self, image_sequence : QXImageSequence, loop_count : int = 1):
"""
set and play pixmap sequence
"""
self._image_sequence = image_sequence
self._tl = QXTimeLine( duration=image_sequence.get_duration(),
frame_range=(0, image_sequence.get_frame_count()-1),
loop_count=0,
update_interval=int( (1.0/image_sequence.get_fps()) * 1000),
frameChanged=self._tl_frameChanged,
start=True )
def stop_image_sequence(self):
if self._tl is not None:
self._tl.stop()
self._tl = None
self._image_sequence = None
def _tl_frameChanged(self, frame_id):
self._set_image(self._image_sequence.get_frame(frame_id))
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)
self._update_icon_size()

View file

@ -0,0 +1,36 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXRadioButton(QRadioButton, _part_QXWidget):
def __init__(self, text=None,
disabled_color=None,
auto_exclusive=False,
clicked=None, toggled=None,
font=None, size_policy=None, hided=False, enabled=True):
super().__init__()
if text is not None:
self.setText(text)
self.setAutoExclusive(auto_exclusive)
style_sheet = ''
if disabled_color is not None:
style_sheet += f'QRadioButton::disabled {{color: {disabled_color};}}'
if len(style_sheet) != 0:
self.setStyleSheet(style_sheet)
_part_QXWidget.connect_signal(clicked, self.clicked)
_part_QXWidget.connect_signal(toggled, self.toggled)
_part_QXWidget.__init__(self, font=font, size_policy=size_policy, hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,47 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from typing import Iterable
from .QXComboBox import QXComboBox
from .QXMainApplication import QXMainApplication
from ..core.widget import BlockSignals
class QXSaveableComboBox(QXComboBox):
"""
a saveable QXComboBox
"""
def __init__(self, db_key, choices : Iterable, default_choice, choices_names=None, on_choice_selected = None):
self._choices = [x for x in choices]
self._default_choice = default_choice
if choices_names is None:
choices_names = [str(x) for x in choices]
self._choices_names = choices_names
if len(self._choices) != len(self._choices_names):
raise ValueError('mismatch len of choices and choices_names')
self._db_key = db_key
self._on_choice_selected = on_choice_selected
super().__init__(choices=choices_names, on_index_changed=self._index_changed)
self.set_choice( QXMainApplication.get_singleton().get_app_data (db_key) )
def set_choice(self, choice):
if choice not in self._choices:
choice = self._default_choice
QXMainApplication.get_singleton().set_app_data(self._db_key, choice)
idx = self._choices.index(choice)
if self._on_choice_selected is not None:
self._on_choice_selected(self._choices[idx], self._choices_names[idx])
with BlockSignals(self):
self.setCurrentIndex(idx)
def _index_changed(self, idx):
self.set_choice( self._choices[idx] )

View file

@ -0,0 +1,24 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXScrollArea(QScrollArea, _part_QXWidget):
def __init__(self,
size_policy=None, hided=False, enabled=True):
super().__init__()
_part_QXWidget.__init__(self, size_policy=size_policy, hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,46 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXSlider(QSlider, _part_QXWidget):
def __init__(self, orientation=None,
min=None, max=None,
tick_position=None,
tick_interval=None,
valueChanged=None,
sliderMoved=None,
sliderPressed=None,
sliderReleased=None,
size_policy=None, hided=False, enabled=True):
if orientation is not None:
super().__init__(orientation)
else:
super().__init__()
if min is not None:
self.setMinimum(min)
if max is not None:
self.setMaximum(max)
if tick_position is not None:
self.setTickPosition(tick_position)
if tick_interval is not None:
self.setTickInterval(tick_interval)
_part_QXWidget.connect_signal(valueChanged, self.valueChanged)
_part_QXWidget.connect_signal(sliderMoved, self.sliderMoved)
_part_QXWidget.connect_signal(sliderPressed, self.sliderPressed)
_part_QXWidget.connect_signal(sliderReleased, self.sliderReleased)
_part_QXWidget.__init__(self, size_policy=size_policy, hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,48 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXSpinBox(QSpinBox, _part_QXWidget):
def __init__(self, min=None,
max=None,
step=None,
special_value_text=None,
color=None,
alignment=None,
button_symbols=None, readonly=False,
editingFinished=None, textChanged=None, valueChanged=None,
font=None, size_policy=None, hided=False, enabled=True):
super().__init__()
if min is not None:
self.setMinimum(min)
if max is not None:
self.setMaximum(max)
if step is not None:
self.setSingleStep(step)
if special_value_text is not None:
self.setSpecialValueText(special_value_text)
if alignment is not None:
self.setAlignment(alignment)
if button_symbols is not None:
self.setButtonSymbols(button_symbols)
self.setReadOnly(readonly)
if color is not None:
self.setStyleSheet(f'QSpinBox {{ color: {color};}}')
_part_QXWidget.connect_signal(editingFinished, self.editingFinished)
_part_QXWidget.connect_signal(textChanged, self.textChanged)
_part_QXWidget.connect_signal(valueChanged, self.valueChanged)
_part_QXWidget.__init__(self, font=font, size_policy=size_policy, hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,32 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXToolButton(QToolButton, _part_QXWidget):
def __init__(self, text=None,
checkable=False,
toggled=None, released=None,
font=None, size_policy=None, hided=False, enabled=True):
super().__init__()
if text is not None:
self.setText(text)
self.setCheckable(checkable)
_part_QXWidget.connect_signal(released, self.released)
_part_QXWidget.connect_signal(toggled, self.toggled)
_part_QXWidget.__init__(self, font=font, size_policy=size_policy, hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,31 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ..core.widget import set_contents_margins
class QXVBoxLayout(QVBoxLayout):
def __init__(self, widgets=None, contents_margins=0, spacing=0):
super().__init__()
set_contents_margins(self, contents_margins)
if widgets is not None:
for widget in widgets:
alignment = None
if isinstance(widget, int):
thickness=widget
widget = QWidget()
widget.setFixedHeight(thickness)
widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
if isinstance(widget, (tuple,list)):
widget, alignment = widget
if isinstance(widget, QLayout):
self.addLayout(widget)
else:
self.addWidget(widget)
if alignment is not None:
self.setAlignment(widget, alignment)
if spacing is not None:
self.setSpacing(spacing)

View file

@ -0,0 +1,11 @@
from PyQt6.QtWidgets import *
from .QXLabel import QXLabel
class QXVerticalLine(QXLabel):
def __init__(self, thickness=1, color=None):
super().__init__(size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding),
fixed_width=thickness)
if color is not None:
self.setStyleSheet(f'background: {color.name()};')

View file

@ -0,0 +1,30 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from ._part_QXWidget import _part_QXWidget
class QXWidget(QWidget, _part_QXWidget):
"""
"""
def __init__(self, layout=None, font=None, tooltip_text=None,
minimum_width=None, maximum_width=None, fixed_width=None, minimum_height=None, maximum_height=None, fixed_height=None, size_policy=None, hided=False, enabled=True):
super().__init__()
_part_QXWidget.__init__(self, layout=layout, font=font, tooltip_text=tooltip_text,
size_policy=size_policy,
minimum_width=minimum_width, maximum_width=maximum_width,
minimum_height=minimum_height, maximum_height=maximum_height,
fixed_width=fixed_width, fixed_height=fixed_height,
hided=hided, enabled=enabled )
def focusInEvent(self, ev : QFocusEvent):
super().focusInEvent(ev)
_part_QXWidget.focusInEvent(self, ev)
def resizeEvent(self, ev : QResizeEvent):
super().resizeEvent(ev)
_part_QXWidget.resizeEvent(self, ev)

View file

@ -0,0 +1,99 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib.python import EventListener
from .forward_declarations import forward_declarations
from .QXMainApplication import QXMainApplication
from .QXWidget import QXWidget
class QXWindow(QXWidget):
"""
represents top widget which has no parent
"""
def __init__(self, save_load_state=False,
size_policy=None):
super().__init__(size_policy=size_policy)
self._save_load_state = save_load_state
#QXMainApplication.get_singleton().register_QXWindow(self)
#self.keyPressEvent_listeners = []
#self.keyReleaseEvent_listeners = []
self._QXW = True
self._closeEvent_ev = EventListener()
self.setFocusPolicy(Qt.FocusPolicy.WheelFocus)
self._qp = QPainter()
pal = QXMainApplication.get_singleton().palette()
self._bg_color = pal.color(QPalette.ColorRole.Window)
def call_on_closeEvent(self, func_or_list):
self._closeEvent_ev.add(func_or_list)
# def add_closeEvent_func(self, func):
# self.closeEvent_funcs.append (func)
# def add_keyPressEvent_listener(self, func):
# self.keyPressEvent_listeners.append (func)
# def add_keyReleaseEvent_listener(self, func):
# self.keyReleaseEvent_listeners.append (func)
def center_on_screen(self):
widget_width, widget_height = self.size().width(), self.size().height()
screen_size = QXMainApplication.get_singleton().primaryScreen().size()
self.move( (screen_size.width() - widget_width) // 2, (screen_size.height() - widget_height) // 2 )
#def resizeEvent(self, ev : QResizeEvent):
# super().resizeEvent(ev)
def showEvent(self, ev: QShowEvent):
super().showEvent(ev)
if self._save_load_state:
geo = self.get_widget_data('geometry')
if geo is not None:
pos, size = geo
self.move(pos)
self.resize(size)
else:
self.center_on_screen()
def hideEvent(self, ev: QHideEvent):
super().hideEvent(ev)
if self._save_load_state:
self.set_widget_data('geometry', ( self.pos(), self.size() ) )
def closeEvent(self, ev : QCloseEvent):
super().closeEvent(ev)
if ev.isAccepted():
self._closeEvent_ev.call()
def is_minimized(self) -> bool:
state = self.windowState()
return (state & Qt.WindowState.WindowMinimized) == Qt.WindowState.WindowMinimized
def paintEvent(self, ev : QPaintEvent):
qp = self._qp
qp.begin(self)
qp.fillRect(self.rect(), self._bg_color )
qp.end()
# def keyPressEvent(self, ev):
# super().keyPressEvent(ev)
# for func in self.keyPressEvent_listeners:
# func(ev)
# def keyReleaseEvent(self, ev):
# super().keyReleaseEvent(ev)
# for func in self.keyReleaseEvent_listeners:
# func(ev)
forward_declarations.QXWindow = QXWindow

View file

@ -0,0 +1,119 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from ..core.widget import BlockSignals
from .QXMainApplication import QXMainApplication
class _part_QXWidget:
def __init__(self, layout=None,
font=None,
tooltip_text=None,
size_policy=None,
minimum_size=None, minimum_width=None, minimum_height=None,
maximum_size=None, maximum_width=None, maximum_height=None,
fixed_size=None, fixed_width=None, fixed_height=None,
hided=False, enabled=True):
self._registered = False
self._name_id = None
self._top_QXWindow = None
if font is not None:
self.setFont(font)
if tooltip_text is not None:
self.setToolTip(tooltip_text)
if size_policy is not None:
self.setSizePolicy(*size_policy)
if layout is not None:
self.setLayout(layout)
if minimum_size is not None:
minimum_width, minimum_height = minimum_size
if minimum_width is not None:
self.setMinimumWidth(minimum_width)
if minimum_height is not None:
self.setMinimumHeight(minimum_height)
if maximum_size is not None:
maximum_width, maximum_height = maximum_size
if maximum_width is not None:
self.setMaximumWidth(maximum_width)
if maximum_height is not None:
self.setMaximumHeight(maximum_height)
if fixed_size is not None:
fixed_width, fixed_height = fixed_size
if fixed_width is not None:
self.setFixedWidth(fixed_width)
if fixed_height is not None:
self.setFixedHeight(fixed_height)
if hided:
self.hide()
self.setEnabled(enabled)
def get_top_QXWindow(self) -> 'QXWindow':
if self._top_QXWindow is not None:
return self._top_QXWindow
obj = self
while True:
obj = obj.parentWidget()
if getattr(obj, '_QXW', False):
self._top_QXWindow = obj
return obj
if obj is None:
raise Exception('top_QXWindow is not found.')
def get_name_id(self) -> str:
"""
returns name_id of widget
"""
return self._name
def get_widget_data(self, key, default_value=None):
"""
Get picklable data by picklable key from widget's storage
if widget is not registered, default_value will be returned
"""
if not self._registered:
return default_value
return QXMainApplication.get_singleton().get_app_data ( (self._name_id, key), default_value=default_value )
def set_widget_data(self, key, data):
"""
Set picklable data by picklable key to widget's storage
if widget is not registered, nothing will be happened
"""
QXMainApplication.get_singleton().set_app_data ( (self._name_id, key), data )
def focusInEvent(self, ev : QFocusEvent):
if ev.reason() == Qt.FocusReason.TabFocusReason:
with BlockSignals(self):
self.clearFocus()
def resizeEvent(self, ev : QResizeEvent):
if not self._registered:
self._registered = True
self._name_id = QXMainApplication.get_singleton().register_QXWidget(self)
self._on_registered()
def _on_registered(self):
"""
called when widget is registered on QXMainApplication.
At this point you can use widget's storage.
"""
@staticmethod
def connect_signal(funcs, qt_signal):
if funcs is not None:
if not isinstance(funcs, (tuple,list)):
funcs = [funcs]
for func in funcs:
qt_signal.connect(func)

View file

@ -0,0 +1,2 @@
class forward_declarations:
QXWindow : 'QXWindow'= None