mirror of
https://github.com/iperov/DeepFaceLive
synced 2025-08-21 05:53:25 -07:00
code release
This commit is contained in:
parent
b941ba41a3
commit
a902f11f74
354 changed files with 826570 additions and 1 deletions
36
xlib/qt/__init__.py
Normal file
36
xlib/qt/__init__.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from .core.QXTimeLine import QXTimeLine
|
||||
from .core.QXTimer import QXTimer
|
||||
from .core.widget import (BlockSignals, disable, enable, hide,
|
||||
hide_and_disable, show, show_and_enable)
|
||||
from .gui.from_file import (QIcon_from_file, QPixmap_from_file,
|
||||
QXImage_from_file, QXPixmap_from_file)
|
||||
from .gui.from_np import QPixmap_from_np
|
||||
from .gui.QXImageSequence import QXImageSequence
|
||||
from .gui.QXPixmap import QXPixmap
|
||||
from .widgets.QXCheckBox import QXCheckBox
|
||||
from .widgets.QXCollapsibleSection import QXCollapsibleSection
|
||||
from .widgets.QXComboBox import QXComboBox
|
||||
from .widgets.QXDirDialog import QXDirDialog
|
||||
from .widgets.QXDoubleSpinBox import QXDoubleSpinBox
|
||||
from .widgets.QXFileDialog import QXFileDialog
|
||||
from .widgets.QXFixedLayeredImages import QXFixedLayeredImages
|
||||
from .widgets.QXFrame import QXFrame
|
||||
from .widgets.QXGridLayout import QXGridLayout
|
||||
from .widgets.QXHBoxLayout import QXHBoxLayout
|
||||
from .widgets.QXHorizontalLine import QXHorizontalLine
|
||||
from .widgets.QXLabel import QXLabel
|
||||
from .widgets.QXLineEdit import QXLineEdit
|
||||
from .widgets.QXMainApplication import QXMainApplication
|
||||
from .widgets.QXMenuBar import QXMenuBar
|
||||
from .widgets.QXProgressBar import QXProgressBar
|
||||
from .widgets.QXPushButton import QXPushButton
|
||||
from .widgets.QXRadioButton import QXRadioButton
|
||||
from .widgets.QXSaveableComboBox import QXSaveableComboBox
|
||||
from .widgets.QXScrollArea import QXScrollArea
|
||||
from .widgets.QXSlider import QXSlider
|
||||
from .widgets.QXSpinBox import QXSpinBox
|
||||
from .widgets.QXToolButton import QXToolButton
|
||||
from .widgets.QXVBoxLayout import QXVBoxLayout
|
||||
from .widgets.QXVerticalLine import QXVerticalLine
|
||||
from .widgets.QXWidget import QXWidget
|
||||
from .widgets.QXWindow import QXWindow
|
261
xlib/qt/_unused/QSubprocessor.py
Normal file
261
xlib/qt/_unused/QSubprocessor.py
Normal file
|
@ -0,0 +1,261 @@
|
|||
import multiprocessing
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
|
||||
from .qtex import *
|
||||
|
||||
class QSubprocessor(object):
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
class Cli(object):
|
||||
def __init__ ( self, client_dict ):
|
||||
s2c = multiprocessing.Queue()
|
||||
c2s = multiprocessing.Queue()
|
||||
self.p = multiprocessing.Process(target=self._subprocess_run, args=(client_dict,s2c,c2s) )
|
||||
self.s2c = s2c
|
||||
self.c2s = c2s
|
||||
self.p.daemon = True
|
||||
self.p.start()
|
||||
|
||||
self.state = None
|
||||
self.sent_time = None
|
||||
self.sent_data = None
|
||||
self.name = None
|
||||
self.host_dict = None
|
||||
|
||||
def kill(self):
|
||||
self.p.terminate()
|
||||
self.p.join()
|
||||
|
||||
#overridable optional
|
||||
def on_initialize(self, client_dict):
|
||||
#initialize your subprocess here using client_dict
|
||||
pass
|
||||
|
||||
#overridable optional
|
||||
def on_finalize(self):
|
||||
#finalize your subprocess here
|
||||
pass
|
||||
|
||||
#overridable
|
||||
def process_data(self, data):
|
||||
#process 'data' given from host and return result
|
||||
raise NotImplementedError
|
||||
|
||||
#overridable optional
|
||||
def get_data_name (self, data):
|
||||
#return string identificator of your 'data'
|
||||
return "undefined"
|
||||
|
||||
def log_info(self, msg): self.c2s.put ( {'op': 'log_info', 'msg':msg } )
|
||||
def log_err(self, msg): self.c2s.put ( {'op': 'log_err' , 'msg':msg } )
|
||||
def progress_bar_inc(self, c): self.c2s.put ( {'op': 'progress_bar_inc' , 'c':c } )
|
||||
|
||||
def _subprocess_run(self, client_dict, s2c, c2s):
|
||||
self.c2s = c2s
|
||||
data = None
|
||||
try:
|
||||
self.on_initialize(client_dict)
|
||||
c2s.put ( {'op': 'init_ok'} )
|
||||
while True:
|
||||
msg = s2c.get()
|
||||
op = msg.get('op','')
|
||||
if op == 'data':
|
||||
data = msg['data']
|
||||
result = self.process_data (data)
|
||||
c2s.put ( {'op': 'success', 'data' : data, 'result' : result} )
|
||||
data = None
|
||||
elif op == 'close':
|
||||
break
|
||||
time.sleep(0.001)
|
||||
self.on_finalize()
|
||||
c2s.put ( {'op': 'finalized'} )
|
||||
except Exception as e:
|
||||
c2s.put ( {'op': 'error', 'data' : data} )
|
||||
if data is not None:
|
||||
print ('Exception while process data [%s]: %s' % (self.get_data_name(data), traceback.format_exc()) )
|
||||
else:
|
||||
print ('Exception: %s' % (traceback.format_exc()) )
|
||||
c2s.close()
|
||||
s2c.close()
|
||||
self.c2s = None
|
||||
|
||||
# disable pickling
|
||||
def __getstate__(self):
|
||||
return dict()
|
||||
def __setstate__(self, d):
|
||||
self.__dict__.update(d)
|
||||
|
||||
#overridable
|
||||
def __init__(self, name, SubprocessorCli_class, no_response_time_sec = 0, io_loop_sleep_time=0.005):
|
||||
if not issubclass(SubprocessorCli_class, QSubprocessor.Cli):
|
||||
raise ValueError("SubprocessorCli_class must be subclass of QSubprocessor.Cli")
|
||||
|
||||
self.name = name
|
||||
self.SubprocessorCli_class = SubprocessorCli_class
|
||||
self.no_response_time_sec = no_response_time_sec
|
||||
self.io_loop_sleep_time = io_loop_sleep_time
|
||||
|
||||
self.clis = []
|
||||
|
||||
#getting info about name of subprocesses, host and client dicts, and spawning them
|
||||
for name, host_dict, client_dict in self.process_info_generator():
|
||||
try:
|
||||
cli = self.SubprocessorCli_class(client_dict)
|
||||
cli.state = 1
|
||||
cli.sent_time = 0
|
||||
cli.sent_data = None
|
||||
cli.name = name
|
||||
cli.host_dict = host_dict
|
||||
|
||||
self.clis.append (cli)
|
||||
except:
|
||||
raise Exception (f"Unable to start subprocess {name}. Error: {traceback.format_exc()}")
|
||||
|
||||
if len(self.clis) == 0:
|
||||
raise Exception ("Unable to start QSubprocessor '%s' " % (self.name))
|
||||
|
||||
#waiting subprocesses their success(or not) initialization
|
||||
while True:
|
||||
for cli in self.clis[:]:
|
||||
while not cli.c2s.empty():
|
||||
obj = cli.c2s.get()
|
||||
op = obj.get('op','')
|
||||
if op == 'init_ok':
|
||||
cli.state = 0
|
||||
elif op == 'log_info':
|
||||
print(obj['msg'])
|
||||
elif op == 'log_err':
|
||||
print(obj['msg'])
|
||||
elif op == 'error':
|
||||
cli.kill()
|
||||
self.clis.remove(cli)
|
||||
break
|
||||
if all ([cli.state == 0 for cli in self.clis]):
|
||||
break
|
||||
time.sleep(0.005)
|
||||
|
||||
if len(self.clis) == 0:
|
||||
raise Exception ( "Unable to start subprocesses." )
|
||||
|
||||
#ok some processes survived, initialize host logic
|
||||
self.on_clients_initialized()
|
||||
|
||||
self.q_timer = QTimer()
|
||||
self.q_timer.timeout.connect(self.tick)
|
||||
self.q_timer.start(5)
|
||||
|
||||
#overridable
|
||||
def process_info_generator(self):
|
||||
#yield per process (name, host_dict, client_dict)
|
||||
for i in range(min(multiprocessing.cpu_count(), 8) ):
|
||||
yield 'CPU%d' % (i), {}, {}
|
||||
|
||||
#overridable optional
|
||||
def on_clients_initialized(self):
|
||||
#logic when all subprocesses initialized and ready
|
||||
pass
|
||||
|
||||
#overridable optional
|
||||
def on_clients_finalized(self):
|
||||
#logic when all subprocess finalized
|
||||
pass
|
||||
|
||||
#overridable
|
||||
def get_data(self, host_dict):
|
||||
#return data for processing here
|
||||
raise NotImplementedError
|
||||
|
||||
#overridable
|
||||
def on_data_return (self, host_dict, data):
|
||||
#you have to place returned 'data' back to your queue
|
||||
raise NotImplementedError
|
||||
|
||||
#overridable
|
||||
def on_result (self, host_dict, data, result):
|
||||
#your logic what to do with 'result' of 'data'
|
||||
raise NotImplementedError
|
||||
|
||||
def tick(self):
|
||||
for cli in self.clis[:]:
|
||||
while not cli.c2s.empty():
|
||||
obj = cli.c2s.get()
|
||||
op = obj.get('op','')
|
||||
if op == 'success':
|
||||
#success processed data, return data and result to on_result
|
||||
self.on_result (cli.host_dict, obj['data'], obj['result'])
|
||||
self.sent_data = None
|
||||
cli.state = 0
|
||||
elif op == 'error':
|
||||
#some error occured while process data, returning chunk to on_data_return
|
||||
if 'data' in obj.keys():
|
||||
self.on_data_return (cli.host_dict, obj['data'] )
|
||||
#and killing process
|
||||
cli.kill()
|
||||
self.clis.remove(cli)
|
||||
elif op == 'log_info':
|
||||
print(obj['msg'])
|
||||
elif op == 'log_err':
|
||||
print(obj['msg'])
|
||||
elif op == 'progress_bar_inc':
|
||||
...
|
||||
#io.progress_bar_inc(obj['c'])
|
||||
|
||||
for cli in self.clis[:]:
|
||||
if cli.state == 1:
|
||||
if cli.sent_time != 0 and self.no_response_time_sec != 0 and (time.time() - cli.sent_time) > self.no_response_time_sec:
|
||||
#subprocess busy too long
|
||||
print ( '%s doesnt response, terminating it.' % (cli.name) )
|
||||
self.on_data_return (cli.host_dict, cli.sent_data )
|
||||
cli.kill()
|
||||
self.clis.remove(cli)
|
||||
|
||||
for cli in self.clis[:]:
|
||||
if cli.state == 0:
|
||||
#free state of subprocess, get some data from get_data
|
||||
data = self.get_data(cli.host_dict)
|
||||
if data is not None:
|
||||
#and send it to subprocess
|
||||
cli.s2c.put ( {'op': 'data', 'data' : data} )
|
||||
cli.sent_time = time.time()
|
||||
cli.sent_data = data
|
||||
cli.state = 1
|
||||
|
||||
if all ([cli.state == 0 for cli in self.clis]):
|
||||
#gracefully terminating subprocesses
|
||||
for cli in self.clis[:]:
|
||||
cli.s2c.put ( {'op': 'close'} )
|
||||
cli.sent_time = time.time()
|
||||
|
||||
while True:
|
||||
for cli in self.clis[:]:
|
||||
terminate_it = False
|
||||
while not cli.c2s.empty():
|
||||
obj = cli.c2s.get()
|
||||
obj_op = obj['op']
|
||||
if obj_op == 'finalized':
|
||||
terminate_it = True
|
||||
break
|
||||
|
||||
if (time.time() - cli.sent_time) > 30:
|
||||
terminate_it = True
|
||||
|
||||
if terminate_it:
|
||||
cli.state = 2
|
||||
cli.kill()
|
||||
|
||||
if all ([cli.state == 2 for cli in self.clis]):
|
||||
break
|
||||
|
||||
#finalizing host logic
|
||||
self.q_timer.stop()
|
||||
self.q_timer = None
|
||||
self.on_clients_finalized()
|
||||
|
235
xlib/qt/_unused/_unused.py
Normal file
235
xlib/qt/_unused/_unused.py
Normal file
|
@ -0,0 +1,235 @@
|
|||
# from PyQt6.QtCore import *
|
||||
# from PyQt6.QtGui import *
|
||||
# from PyQt6.QtWidgets import *
|
||||
|
||||
# #from localization import StringsDB
|
||||
# from .QXMainWindow import *
|
||||
|
||||
# class QXIconButton(QPushButton):
|
||||
# """
|
||||
# Custom Icon button that works through keyEvent system, without shortcut of QAction
|
||||
# works only with QXMainWindow as global window class
|
||||
# currently works only with one-key shortcut
|
||||
# """
|
||||
|
||||
# def __init__(self, icon,
|
||||
# tooltip=None,
|
||||
# shortcut=None,
|
||||
# click_func=None,
|
||||
# first_repeat_delay=300,
|
||||
# repeat_delay=20,
|
||||
# ):
|
||||
|
||||
# super().__init__(icon, "")
|
||||
|
||||
# self.setIcon(icon)
|
||||
|
||||
# if shortcut is not None:
|
||||
# tooltip = f"{tooltip} ( S_HOT_KEY: {shortcut} )"
|
||||
|
||||
# self.setToolTip(tooltip)
|
||||
|
||||
|
||||
|
||||
# self.seq = QKeySequence(shortcut) if shortcut is not None else None
|
||||
|
||||
# QXMainWindow.inst.add_keyPressEvent_listener ( self.on_keyPressEvent )
|
||||
# QXMainWindow.inst.add_keyReleaseEvent_listener ( self.on_keyReleaseEvent )
|
||||
|
||||
# self.click_func = click_func
|
||||
# self.first_repeat_delay = first_repeat_delay
|
||||
# self.repeat_delay = repeat_delay
|
||||
# self.repeat_timer = None
|
||||
|
||||
# self.op_device = None
|
||||
|
||||
# self.pressed.connect( lambda : self.action(is_pressed=True) )
|
||||
# self.released.connect( lambda : self.action(is_pressed=False) )
|
||||
|
||||
# def action(self, is_pressed=None, op_device=None):
|
||||
# if self.click_func is None:
|
||||
# return
|
||||
|
||||
# if is_pressed is not None:
|
||||
# if is_pressed:
|
||||
# if self.repeat_timer is None:
|
||||
# self.click_func()
|
||||
# self.repeat_timer = QTimer()
|
||||
# self.repeat_timer.timeout.connect(self.action)
|
||||
# self.repeat_timer.start(self.first_repeat_delay)
|
||||
# else:
|
||||
# if self.repeat_timer is not None:
|
||||
# self.repeat_timer.stop()
|
||||
# self.repeat_timer = None
|
||||
# else:
|
||||
# self.click_func()
|
||||
# if self.repeat_timer is not None:
|
||||
# self.repeat_timer.setInterval(self.repeat_delay)
|
||||
|
||||
# def on_keyPressEvent(self, ev):
|
||||
# key = ev.nativeVirtualKey()
|
||||
# if ev.isAutoRepeat():
|
||||
# return
|
||||
|
||||
# if self.seq is not None:
|
||||
# if key == self.seq[0]:
|
||||
# self.action(is_pressed=True)
|
||||
|
||||
# def on_keyReleaseEvent(self, ev):
|
||||
# key = ev.nativeVirtualKey()
|
||||
# if ev.isAutoRepeat():
|
||||
# return
|
||||
# if self.seq is not None:
|
||||
# if key == self.seq[0]:
|
||||
# self.action(is_pressed=False)
|
||||
|
||||
|
||||
############################
|
||||
############################
|
||||
############################
|
||||
############################
|
||||
############################
|
||||
|
||||
# class QXTabWidget(QTabWidget):
|
||||
# def __init__(self, tabs=None, tab_shape=None, size_policy=None, maximum_width=None, hided=False, enabled=True):
|
||||
# super().__init__()
|
||||
# if tabs is not None:
|
||||
# for tab,icon,name in tabs:
|
||||
# self.addTab(tab, icon, name)
|
||||
# if tab_shape is not None:
|
||||
# self.setTabShape(tab_shape)
|
||||
|
||||
# if maximum_width is not None:
|
||||
# self.setMaximumWidth(maximum_width)
|
||||
|
||||
# if size_policy is not None:
|
||||
# self.setSizePolicy(*size_policy)
|
||||
# if hided:
|
||||
# self.hide()
|
||||
# self.setEnabled(enabled)
|
||||
|
||||
|
||||
|
||||
|
||||
# class QXComboObjectBox(QXComboBox):
|
||||
# """
|
||||
# as QComboBox but str'able Iterable of objects
|
||||
# and more functionality
|
||||
# """
|
||||
|
||||
# def __init__(self, choices : Iterable, none_choice=None, font=None, size_policy=None, maximum_width=None, hided=False, enabled=True, on_choosed=None):
|
||||
# super().__init__(font=font, size_policy=size_policy, maximum_width=maximum_width, hided=hided, enabled=enabled)
|
||||
|
||||
# self.choices = tuple(choices)
|
||||
# if len(self.choices) == 0:
|
||||
# raise ValueError('Number of choices are 0')
|
||||
# self.none_choice = none_choice
|
||||
# self.on_choosed = on_choosed
|
||||
|
||||
# if none_choice is not None:
|
||||
# self.addItem( QIcon(), str(none_choice) )
|
||||
# for i, choice in enumerate(choices):
|
||||
# self.addItem( QIcon(), str(choice) )
|
||||
|
||||
# self.setCurrentIndex(0)
|
||||
# self.currentIndexChanged.connect(self.on_toggled)
|
||||
|
||||
# def get_choices(self):
|
||||
# return self.choices
|
||||
|
||||
# def get_selected_choice(self):
|
||||
# idx = self.currentIndex()
|
||||
# if self.none_choice is not None:
|
||||
# idx -= 1
|
||||
# if idx == -1:
|
||||
# return None
|
||||
|
||||
# return self.choices[idx]
|
||||
|
||||
# def unselect(self, block_signals : bool = False):
|
||||
# if self.none_choice is not None:
|
||||
# with BlockSignals(self, block_signals=block_signals):
|
||||
# self.setCurrentIndex(0)
|
||||
|
||||
|
||||
# def set_selected_index(self, index, block_signals : bool = False):
|
||||
# if index >= 0 and index < len(self.choices):
|
||||
# if self.none_choice is not None:
|
||||
# index += 1
|
||||
|
||||
# with BlockSignals(self, block_signals=block_signals):
|
||||
# self.setCurrentIndex(index)
|
||||
|
||||
# def set_selected_choice(self, choice, block_signals : bool = False):
|
||||
# with BlockSignals(self, block_signals=block_signals):
|
||||
# if choice is None:
|
||||
# if self.none_choice is not None:
|
||||
# self.setCurrentIndex(0)
|
||||
# else:
|
||||
# raise ValueError('unable to change to None with none_choice=False')
|
||||
# else:
|
||||
# for i, schoice in enumerate(self.choices):
|
||||
# if schoice == choice:
|
||||
# self.setCurrentIndex(i+1)
|
||||
# break
|
||||
|
||||
# def on_toggled(self, idx):
|
||||
# if self.on_choosed is not None:
|
||||
# self.on_choosed( self.get_selected_choice() )
|
||||
|
||||
|
||||
|
||||
# class QXCollapsibleSection(QWidget):
|
||||
# def __init__(self, title, content_layout, is_opened=False, allow_open_close=True, show_content_frame=True):
|
||||
# super().__init__()
|
||||
|
||||
# btn = self.btn = QToolButton()
|
||||
# btn.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
|
||||
# btn.setStyleSheet('border: none;')
|
||||
# btn.setArrowType(Qt.ArrowType.RightArrow)
|
||||
# btn.setText(title)
|
||||
# #btn.setIconSize( QSize(8,8))
|
||||
# btn.setCheckable(True)
|
||||
# btn.setChecked(False)
|
||||
|
||||
# if allow_open_close:
|
||||
# btn.toggled.connect(self.on_btn_toggled)
|
||||
|
||||
# line = QXFrame( size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum) )
|
||||
# line.setFrameShape(QFrame.Shape.HLine)
|
||||
|
||||
# frame = self.frame = QXFrame( size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed), layout=content_layout, hided=True)
|
||||
# if show_content_frame:
|
||||
# frame.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
|
||||
# main_l = QXGridLayout( contents_margins=0 )
|
||||
|
||||
# main_l.addWidget(btn, 0, 0, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||
# main_l.addWidget(QXHorizontalLine() , 0, 1)
|
||||
|
||||
# main_l.addWidget(frame, 1, 0, 1, 2)
|
||||
|
||||
|
||||
# self.setLayout(main_l)
|
||||
|
||||
# if is_opened:
|
||||
# self.open()
|
||||
# else:
|
||||
# self.close()
|
||||
|
||||
# def is_opened(self):
|
||||
# return self.frame.isVisible()
|
||||
|
||||
# def open(self):
|
||||
# self.btn.setArrowType(Qt.ArrowType.DownArrow)
|
||||
# self.frame.show()
|
||||
|
||||
# def close(self):
|
||||
# self.btn.setArrowType(Qt.ArrowType.RightArrow)
|
||||
# self.frame.hide()
|
||||
|
||||
# def on_btn_toggled(self):
|
||||
# if self.btn.isChecked():
|
||||
# self.open()
|
||||
# else:
|
||||
# self.close()
|
62
xlib/qt/_unused/pyqt5ex.py
Normal file
62
xlib/qt/_unused/pyqt5ex.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
import numpy as np
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
|
||||
|
||||
# class QActionEx(QAction):
|
||||
# def __init__(self, icon, text, shortcut=None, trigger_func=None, shortcut_in_tooltip=False, is_checkable=False, is_auto_repeat=False ):
|
||||
# super().__init__(icon, text)
|
||||
# if shortcut is not None:
|
||||
# self.setShortcut(shortcut)
|
||||
# if shortcut_in_tooltip:
|
||||
|
||||
# self.setToolTip( f"{text} ( S_HOT_KEY : {shortcut} )")
|
||||
|
||||
# if trigger_func is not None:
|
||||
# self.triggered.connect(trigger_func)
|
||||
# if is_checkable:
|
||||
# self.setCheckable(True)
|
||||
# self.setAutoRepeat(is_auto_repeat)
|
||||
|
||||
# def QImage_from_np(img):
|
||||
# if img.dtype != np.uint8:
|
||||
# raise ValueError("img should be in np.uint8 format")
|
||||
|
||||
# h,w,c = img.shape
|
||||
# if c == 1:
|
||||
# fmt = QImage.Format_Grayscale8
|
||||
# elif c == 3:
|
||||
# fmt = QImage.Format_BGR888
|
||||
# elif c == 4:
|
||||
# fmt = QImage.Format_ARGB32
|
||||
# else:
|
||||
# raise ValueError("unsupported channel count")
|
||||
|
||||
# return QImage(img.data, w, h, c*w, fmt )
|
||||
|
||||
# def QImage_to_np(q_img, fmt=QImage.Format_BGR888):
|
||||
# q_img = q_img.convertToFormat(fmt)
|
||||
|
||||
# width = q_img.width()
|
||||
# height = q_img.height()
|
||||
|
||||
# b = q_img.constBits()
|
||||
# b.setsize(height * width * 3)
|
||||
# arr = np.frombuffer(b, np.uint8).reshape((height, width, 3))
|
||||
# return arr#[::-1]
|
||||
|
||||
# def QPixmap_from_np(img):
|
||||
# return QPixmap.fromImage(QImage_from_np(img))
|
||||
|
||||
# def QPoint_from_np(n):
|
||||
# return QPoint(*n.astype(np.int))
|
||||
|
||||
# def QPoint_to_np(q):
|
||||
# return np.int32( [q.x(), q.y()] )
|
||||
|
||||
# def QSize_to_np(q):
|
||||
# return np.int32( [q.width(), q.height()] )
|
||||
|
||||
|
||||
|
43
xlib/qt/core/QXTimeLine.py
Normal file
43
xlib/qt/core/QXTimeLine.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from typing import Tuple
|
||||
|
||||
from PyQt6.QtCore import *
|
||||
|
||||
_linear_easing_curve = QEasingCurve(QEasingCurve.Type.Linear)
|
||||
class QXTimeLine(QTimeLine):
|
||||
"""
|
||||
QXTimeLine with default linear curve
|
||||
|
||||
|
||||
frame_range(None) (int,int) start,end
|
||||
"""
|
||||
def __init__(self, duration,
|
||||
frame_range : Tuple[int,int] = None,
|
||||
loop_count=1,
|
||||
update_interval : int = None,
|
||||
easing_curve=None,
|
||||
frameChanged=None,
|
||||
stateChanged=None,
|
||||
start=False):
|
||||
|
||||
super().__init__(duration)
|
||||
|
||||
if frame_range is not None:
|
||||
self.setFrameRange(*frame_range)
|
||||
|
||||
self.setLoopCount(loop_count)
|
||||
|
||||
if update_interval is not None:
|
||||
self.setUpdateInterval(update_interval)
|
||||
|
||||
if easing_curve is None:
|
||||
easing_curve = _linear_easing_curve
|
||||
self.setEasingCurve(easing_curve)
|
||||
|
||||
if frameChanged is not None:
|
||||
self.frameChanged.connect(frameChanged)
|
||||
|
||||
if stateChanged is not None:
|
||||
self.stateChanged.connect(stateChanged)
|
||||
|
||||
if start:
|
||||
self.start()
|
19
xlib/qt/core/QXTimer.py
Normal file
19
xlib/qt/core/QXTimer.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from PyQt6.QtCore import *
|
||||
|
||||
|
||||
class QXTimer(QTimer):
|
||||
|
||||
def __init__(self, interval=None, timeout=None, single_shot=False, start=False):
|
||||
super().__init__()
|
||||
|
||||
if interval is not None:
|
||||
self.setInterval(interval)
|
||||
|
||||
if timeout is not None:
|
||||
self.timeout.connect(timeout)
|
||||
|
||||
if single_shot:
|
||||
self.setSingleShot(True)
|
||||
|
||||
if start:
|
||||
self.start()
|
88
xlib/qt/core/widget.py
Normal file
88
xlib/qt/core/widget.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
from collections import Iterable
|
||||
|
||||
from PyQt6.QtCore import *
|
||||
|
||||
|
||||
class BlockSignals:
|
||||
def __init__(self, qt_widget_or_list, block_signals=True):
|
||||
if not isinstance(qt_widget_or_list, (tuple,list)):
|
||||
qt_widget_or_list = [qt_widget_or_list]
|
||||
self.qt_widget_or_list = qt_widget_or_list
|
||||
self.block_signals = block_signals
|
||||
|
||||
def __enter__(self):
|
||||
if self.block_signals:
|
||||
for qt_widget in self.qt_widget_or_list:
|
||||
qt_widget.blockSignals(True)
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, *_):
|
||||
if self.block_signals:
|
||||
for qt_widget in self.qt_widget_or_list:
|
||||
qt_widget.blockSignals(False)
|
||||
|
||||
def enable(widget_or_list):
|
||||
if not isinstance(widget_or_list, (tuple,list)):
|
||||
widget_or_list = [widget_or_list]
|
||||
for widget in widget_or_list:
|
||||
if isinstance(widget, (tuple,list)):
|
||||
enable(widget)
|
||||
else:
|
||||
widget.setEnabled(True)
|
||||
|
||||
def disable(widget_or_list):
|
||||
if not isinstance(widget_or_list, (tuple,list)):
|
||||
widget_or_list = [widget_or_list]
|
||||
for widget in widget_or_list:
|
||||
if isinstance(widget, (tuple,list)):
|
||||
disable(widget)
|
||||
else:
|
||||
widget.setEnabled(False)
|
||||
|
||||
def hide(widget_or_list):
|
||||
if not isinstance(widget_or_list, (tuple,list)):
|
||||
widget_or_list = [widget_or_list]
|
||||
for widget in widget_or_list:
|
||||
if isinstance(widget, (tuple,list)):
|
||||
hide(widget)
|
||||
else:
|
||||
widget.hide()
|
||||
|
||||
def show(widget_or_list):
|
||||
if not isinstance(widget_or_list, (tuple,list)):
|
||||
widget_or_list = [widget_or_list]
|
||||
for widget in widget_or_list:
|
||||
if isinstance(widget, (tuple,list)):
|
||||
show(widget)
|
||||
else:
|
||||
widget.show()
|
||||
|
||||
def show_and_enable(widget_or_list):
|
||||
if not isinstance(widget_or_list, (tuple,list)):
|
||||
widget_or_list = [widget_or_list]
|
||||
for widget in widget_or_list:
|
||||
if isinstance(widget, (tuple,list)):
|
||||
show_and_enable(widget)
|
||||
else:
|
||||
widget.show()
|
||||
widget.setEnabled(True)
|
||||
|
||||
def hide_and_disable(widget_or_list):
|
||||
if not isinstance(widget_or_list, (tuple,list)):
|
||||
widget_or_list = [widget_or_list]
|
||||
for widget in widget_or_list:
|
||||
if isinstance(widget, (tuple,list)):
|
||||
hide_and_disable(widget)
|
||||
else:
|
||||
widget.hide()
|
||||
widget.setEnabled(False)
|
||||
|
||||
def set_contents_margins(obj, contents_margins):
|
||||
|
||||
if contents_margins is not None:
|
||||
if isinstance(contents_margins, int):
|
||||
contents_margins = (contents_margins,)*4
|
||||
|
||||
if isinstance(contents_margins, Iterable):
|
||||
obj.setContentsMargins(*contents_margins)
|
43
xlib/qt/gui/QXImage.py
Normal file
43
xlib/qt/gui/QXImage.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
|
||||
from .QXPixmap import QXPixmap
|
||||
|
||||
|
||||
class QXImage(QImage):
|
||||
"""
|
||||
extension of QImage
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._cache = {}
|
||||
|
||||
def as_QXPixmap(self) -> QXPixmap:
|
||||
pixmap = self._cache.get(QXPixmap, None )
|
||||
if pixmap is None:
|
||||
pixmap = self._cache[QXPixmap] = QXPixmap(QPixmap.fromImage(self))
|
||||
return pixmap
|
||||
|
||||
def as_QIcon(self) -> QIcon:
|
||||
icon = self._cache.get(QIcon, None )
|
||||
if icon is None:
|
||||
icon = self._cache[QIcon] = QIcon(self.as_QXPixmap())
|
||||
return icon
|
||||
|
||||
def colored(self, color) -> 'QXImage':
|
||||
"""
|
||||
get colored version from cache or create.
|
||||
"""
|
||||
image = self._cache.get(color, None)
|
||||
if image is None:
|
||||
pixmap = self.as_QXPixmap()
|
||||
qp = QPainter(pixmap)
|
||||
qp.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
|
||||
qp.fillRect( pixmap.rect(), QColor(color) )
|
||||
qp.end()
|
||||
|
||||
|
||||
image = self._cache[color] = QXImage( pixmap.toImage() )
|
||||
|
||||
return image
|
24
xlib/qt/gui/QXImageSequence.py
Normal file
24
xlib/qt/gui/QXImageSequence.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from typing import List
|
||||
|
||||
from .QXImage import QXImage
|
||||
|
||||
|
||||
class QXImageSequence:
|
||||
"""
|
||||
contains a list of QXImage with defined FPS
|
||||
"""
|
||||
|
||||
def __init__(self, frames : List[QXImage], fps : float):
|
||||
super().__init__()
|
||||
self._frames = frames
|
||||
self._fps = fps
|
||||
self._frame_count = len(frames)
|
||||
|
||||
def get_fps(self) -> float: return self._fps
|
||||
def get_frame_count(self) -> int: return self._frame_count
|
||||
def get_frame(self, i) -> QXImage: return self._frames[i]
|
||||
def get_duration(self) -> int:
|
||||
"""
|
||||
return duration in ms
|
||||
"""
|
||||
return int( (self._frame_count / self._fps) * 1000 )
|
51
xlib/qt/gui/QXPixmap.py
Normal file
51
xlib/qt/gui/QXPixmap.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
|
||||
|
||||
class QXPixmap(QPixmap):
|
||||
"""
|
||||
extension of QPixmap
|
||||
|
||||
contains cached scaled versions
|
||||
cached grayscaled
|
||||
cached QIcon
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._cache = {}
|
||||
|
||||
def scaled_cached(self, width: int, height: int, aspectRatioMode: Qt.AspectRatioMode = Qt.AspectRatioMode.KeepAspectRatio) -> 'QPixmap':
|
||||
"""
|
||||
get scaled version from cache or create.
|
||||
"""
|
||||
key = (width, height)
|
||||
pixmap = self._cache.get(key, None)
|
||||
|
||||
if pixmap is None:
|
||||
pixmap = self._cache[key] = QXPixmap( self.scaled(width, height, aspectRatioMode=aspectRatioMode, transformMode=Qt.TransformationMode.SmoothTransformation) )
|
||||
|
||||
return pixmap
|
||||
|
||||
|
||||
def as_QIcon(self) -> QIcon:
|
||||
icon = self._cache.get( QIcon, None )
|
||||
if icon is None:
|
||||
icon = self._cache[QIcon] = QIcon(self)
|
||||
return icon
|
||||
|
||||
|
||||
def grayscaled_cached(self) -> 'QXPixmap':
|
||||
"""
|
||||
get grayscaled version from cache or create.
|
||||
"""
|
||||
key = 'grayscaled'
|
||||
pixmap = self._cache.get(key, None)
|
||||
if pixmap is None:
|
||||
pixmap = QXPixmap(self)
|
||||
qp = QPainter(pixmap)
|
||||
qp.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
|
||||
qp.fillRect( pixmap.rect(), QColor(127,127,127,255) )
|
||||
qp.end()
|
||||
pixmap = self._cache[key] = pixmap
|
||||
|
||||
return pixmap
|
32
xlib/qt/gui/from_file.py
Normal file
32
xlib/qt/gui/from_file.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from PyQt6.QtGui import *
|
||||
|
||||
from .QXImage import QXImage
|
||||
from .QXPixmap import QXPixmap
|
||||
|
||||
|
||||
def QPixmap_from_file(filepath, color=None):
|
||||
img = QPixmap(str(filepath))
|
||||
|
||||
if color is not None:
|
||||
qp = QPainter(img)
|
||||
qp.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
|
||||
qp.fillRect( img.rect(), QColor(color) )
|
||||
qp.end()
|
||||
return img
|
||||
|
||||
def QXPixmap_from_file(filepath, color=None):
|
||||
img = QXPixmap(str(filepath))
|
||||
|
||||
if color is not None:
|
||||
qp = QPainter(img)
|
||||
qp.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
|
||||
qp.fillRect( img.rect(), QColor(color) )
|
||||
qp.end()
|
||||
return img
|
||||
|
||||
def QXImage_from_file(filepath, color=None):
|
||||
return QXImage(QPixmap_from_file(filepath, color).toImage())
|
||||
|
||||
|
||||
def QIcon_from_file(filepath, color='black'):
|
||||
return QIcon(QPixmap_from_file(filepath,color=color))
|
25
xlib/qt/gui/from_np.py
Normal file
25
xlib/qt/gui/from_np.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import numpy as np
|
||||
from PyQt6.QtGui import *
|
||||
from xlib.image import ImageProcessor
|
||||
|
||||
|
||||
def QPixmap_from_np(image : np.ndarray):
|
||||
ip = ImageProcessor(image).to_uint8()
|
||||
N,H,W,C = ip.get_dims()
|
||||
|
||||
if N > 1:
|
||||
raise ValueError(f'N dim must be == 1')
|
||||
|
||||
if C == 1:
|
||||
format = QImage.Format.Format_Grayscale8
|
||||
elif C == 3:
|
||||
format = QImage.Format.Format_BGR888
|
||||
elif C == 4:
|
||||
format = QImage.Format.Format_ARGB32
|
||||
else:
|
||||
raise ValueError(f'Unsupported channels {C}')
|
||||
|
||||
image = ip.get_image('HWC')
|
||||
q_image = QImage(image.data, W, H, W*C, format)
|
||||
q_pixmap = QPixmap.fromImage(q_image)
|
||||
return q_pixmap
|
27
xlib/qt/widgets/QXCheckBox.py
Normal file
27
xlib/qt/widgets/QXCheckBox.py
Normal 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)
|
89
xlib/qt/widgets/QXCollapsibleSection.py
Normal file
89
xlib/qt/widgets/QXCollapsibleSection.py
Normal 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()
|
33
xlib/qt/widgets/QXComboBox.py
Normal file
33
xlib/qt/widgets/QXComboBox.py
Normal 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)
|
14
xlib/qt/widgets/QXDirDialog.py
Normal file
14
xlib/qt/widgets/QXDirDialog.py
Normal 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)
|
||||
|
42
xlib/qt/widgets/QXDoubleSpinBox.py
Normal file
42
xlib/qt/widgets/QXDoubleSpinBox.py
Normal 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)
|
27
xlib/qt/widgets/QXFileDialog.py
Normal file
27
xlib/qt/widgets/QXFileDialog.py
Normal 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)
|
80
xlib/qt/widgets/QXFixedLayeredImages.py
Normal file
80
xlib/qt/widgets/QXFixedLayeredImages.py
Normal 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()
|
46
xlib/qt/widgets/QXFrame.py
Normal file
46
xlib/qt/widgets/QXFrame.py
Normal 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()
|
21
xlib/qt/widgets/QXGridLayout.py
Normal file
21
xlib/qt/widgets/QXGridLayout.py
Normal 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)
|
||||
|
33
xlib/qt/widgets/QXHBoxLayout.py
Normal file
33
xlib/qt/widgets/QXHBoxLayout.py
Normal 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)
|
13
xlib/qt/widgets/QXHorizontalLine.py
Normal file
13
xlib/qt/widgets/QXHorizontalLine.py
Normal 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};')
|
74
xlib/qt/widgets/QXLabel.py
Normal file
74
xlib/qt/widgets/QXLabel.py
Normal 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)
|
33
xlib/qt/widgets/QXLineEdit.py
Normal file
33
xlib/qt/widgets/QXLineEdit.py
Normal 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)
|
164
xlib/qt/widgets/QXMainApplication.py
Normal file
164
xlib/qt/widgets/QXMainApplication.py
Normal 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)
|
32
xlib/qt/widgets/QXMenuBar.py
Normal file
32
xlib/qt/widgets/QXMenuBar.py
Normal 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)
|
28
xlib/qt/widgets/QXProgressBar.py
Normal file
28
xlib/qt/widgets/QXProgressBar.py
Normal 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)
|
132
xlib/qt/widgets/QXPushButton.py
Normal file
132
xlib/qt/widgets/QXPushButton.py
Normal 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()
|
36
xlib/qt/widgets/QXRadioButton.py
Normal file
36
xlib/qt/widgets/QXRadioButton.py
Normal 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)
|
47
xlib/qt/widgets/QXSaveableComboBox.py
Normal file
47
xlib/qt/widgets/QXSaveableComboBox.py
Normal 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] )
|
24
xlib/qt/widgets/QXScrollArea.py
Normal file
24
xlib/qt/widgets/QXScrollArea.py
Normal 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)
|
46
xlib/qt/widgets/QXSlider.py
Normal file
46
xlib/qt/widgets/QXSlider.py
Normal 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)
|
48
xlib/qt/widgets/QXSpinBox.py
Normal file
48
xlib/qt/widgets/QXSpinBox.py
Normal 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)
|
32
xlib/qt/widgets/QXToolButton.py
Normal file
32
xlib/qt/widgets/QXToolButton.py
Normal 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)
|
31
xlib/qt/widgets/QXVBoxLayout.py
Normal file
31
xlib/qt/widgets/QXVBoxLayout.py
Normal 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)
|
11
xlib/qt/widgets/QXVerticalLine.py
Normal file
11
xlib/qt/widgets/QXVerticalLine.py
Normal 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()};')
|
30
xlib/qt/widgets/QXWidget.py
Normal file
30
xlib/qt/widgets/QXWidget.py
Normal 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)
|
99
xlib/qt/widgets/QXWindow.py
Normal file
99
xlib/qt/widgets/QXWindow.py
Normal 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
|
119
xlib/qt/widgets/_part_QXWidget.py
Normal file
119
xlib/qt/widgets/_part_QXWidget.py
Normal 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)
|
2
xlib/qt/widgets/forward_declarations.py
Normal file
2
xlib/qt/widgets/forward_declarations.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
class forward_declarations:
|
||||
QXWindow : 'QXWindow'= None
|
Loading…
Add table
Add a link
Reference in a new issue