diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..51c968a0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,67 @@ +--- +name: Pull Request +about: Propose code changes +title: '' +labels: '' +assignees: '' + +--- + +# PR Checklist (Delete the section after checking everything) + +- [ ] Make sure you are requesting to PR **feature/bugfix/refactor branch**. +- [ ] Make sure you are making the pull request against the **dev** branch. +- [ ] Make sure you include labels to the PR +- [ ] Request a reviewer for the current PR +- [ ] After the PR is published, make sure you view the tests conducted by github actions, download artifacts and inspect them. Failure to pass the tests will result in a request for changes. + + + +# Description + +Select the type: +- [ ] Feature +- [ ] Bug fix +- [ ] Code refactor +- [ ] Documentation + +Include a summary of the changes made in this PR and which issue is fixed. + +If the current PR is related to a bug introduced in another PR please insert the reference of the previous PR with **#**. + + +Add a concise checklist of the implemented changes, as exemplified below. + +- Change 1. +- Change 2. +- Change 3. +- ... + + +**Depending on the type of change the current PR relates to, delete sections that are not applicable.** + + +# Motivation + +Include a detailed explanation for the current PR. Delete section if not applicable. + +# Tests +Please describe the conducted tests, and include logs, reports on the tests. + +Also include test configuration. + + +Delete section if not applicable. +# Screenshots + +Include screenshots of the changes. Mostly used for UI changes. + +Delete section if not applicable. + + +# Future work + +Emphasize future tasks and improvements tied directly to this pull request. +Include only non-breaking changes to this pull request + +Delete section if not applicable. \ No newline at end of file diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml new file mode 100644 index 00000000..556119c2 --- /dev/null +++ b/.github/workflows/dev-ci.yml @@ -0,0 +1,109 @@ +name: CI-dev-pipeline + +on: + pull_request: + branches: + - dev + paths-ignore: + - "scripts/**" + - "BlocksScreen/lib/ui/**" + - "extras/**" +jobs: + ci-checks: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.11.2"] + test-type: [ruff, pylint, pytest, docstrcov, security] + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: scripts/requirements-dev.txt + + - name: Install dependencies + run: | + echo "Installing dependencies" + python -m pip install --upgrade pip + pip install -r scripts/requirements-dev.txt + + - name: Run Test ${{ matrix.test-type }} + run: | + echo "Starting test runs" + if [ "${{ matrix.test-type }}" == "ruff" ]; then + echo "Running Formatting Test" + ruff check --output-format=github --target-version=py311 --config=pyproject.toml > ruff-output.txt 2>&1 + ruff format --diff --target-version=py311 --config=pyproject.toml >> ruff-output.txt 2>&1 + echo "Ruff finished" + fi + if [ "${{ matrix.test-type }}" == "pylint" ]; then + echo "Running Pylint Code Test" + pylint -j$(nproc) --recursive=y BlocksScreen/ > pylint-output.txt 2>&1 + echo "Pylint finished" + fi + + if [ "${{ matrix.test-type }}" == "pytest" ]; then + if [ -d "tests/" ] && [ "$(ls -A tests/)" ]; then + echo "Running Python unit tests" + pytest tests/ --doctest-modules --junitxml=junit/test-results.xml --cov=BlocksScreen/ --cov-report=xml --cov-report=html > pytest-output.txt 2>&1 + else + echo "No tests directory no need to proceed with tests" + fi + fi + + if [ "${{ matrix.test-type }}" == "docstrcov" ]; then + echo "Running docstring coverage test" + docstr-coverage BlocksScreen/ --exclude '.*/BlocksScreen/lib/ui/.*?$' --fail-under=80 --skip-magic --skip-init --skip-private --skip-property > docstr-cov-output.txt 2>&1 + fi + + if [ "${{matrix.test-type }}" == "security" ]; then + echo "Running bandit security test" + bandit -c pyproject.toml -r . -f json -o bandit-output.json 2>&1 + fi + + - name: Upload ruff artifact + if: always() && matrix.test-type == 'ruff' + uses: actions/upload-artifact@v4 + with: + name: ruff-results + path: ruff-output.txt + + - name: Upload Pylint Artifacts + if: always() && matrix.test-type == 'pylint' + uses: actions/upload-artifact@v4 + with: + name: pylint-results + path: pylint-output.txt + + - name: Upload Pytest Artifacts + if: always() && matrix.test-type == 'pytest' + uses: actions/upload-artifact@v4 + with: + name: pytest-results + path: | + pytest-output.txt + junit/test-results.xml + coverage.xml + htmlcov/ + continue-on-error: true + + - name: Upload docstr coverage report + if: always() && matrix.test-type == 'docstrcov' + uses: actions/upload-artifact@v4 + with: + name: docstr-coverage + path: docstr-cov-output.txt + + - name: Upload bandit security report + if: always() && matrix.test-type == 'security' + uses: actions/upload-artifact@v4 + with: + name: bandit-output + path: bandit-output.json diff --git a/.github/workflows/stage-ci.yml b/.github/workflows/stage-ci.yml new file mode 100644 index 00000000..6d8de6be --- /dev/null +++ b/.github/workflows/stage-ci.yml @@ -0,0 +1,23 @@ +name: stage-ci + +on: + branches: + - stage + paths-ignore: + - "scripts/**" + - "BlocksScreen/lib/ui/**" + - "extras/**" + workflow_run: + workflows: ["dev-test-code"] + types: + - completed + jobs: + ci-stage: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Run staging pipeline + run: echo "Running staging integration tests..." \ No newline at end of file diff --git a/.pylintrc.dev b/.pylintrc.dev new file mode 100644 index 00000000..6c910bac --- /dev/null +++ b/.pylintrc.dev @@ -0,0 +1,11 @@ +[MAIN] +fail-under=7 +jobs=16 +ignore=tests,scripts,ui,extras +ignore-paths=BlocksScreen/lib/ui +py-version=3.11 + + +[FORMAT] +max-line-length=88 + diff --git a/BlocksScreen/BlocksScreen.py b/BlocksScreen/BlocksScreen.py index 83ec0a3b..a7a2098e 100644 --- a/BlocksScreen/BlocksScreen.py +++ b/BlocksScreen/BlocksScreen.py @@ -22,16 +22,15 @@ RESET = "\033[0m" -def setup_working_dir(): ... - - def setup_app_loggers(): - ql = logger.create_logger(name="logs/BlocksScreen.log", level=logging.DEBUG) + """Setup logger""" + _ = logger.create_logger(name="logs/BlocksScreen.log", level=logging.DEBUG) _logger = logging.getLogger(name="logs/BlocksScreen.log") _logger.info("============ BlocksScreen Initializing ============") def show_splash(window: typing.Optional[QtWidgets.QWidget] = None): + """Show splash screen on app initialization""" logo = QtGui.QPixmap("BlocksScreen/BlocksScreen/lib/ui/resources/logoblocks.png") splash = QtWidgets.QSplashScreen(pixmap=logo) splash.setGeometry(QtCore.QRect(0, 0, 400, 200)) diff --git a/BlocksScreen/configfile.py b/BlocksScreen/configfile.py index 53ad0579..981ac4b2 100644 --- a/BlocksScreen/configfile.py +++ b/BlocksScreen/configfile.py @@ -47,10 +47,14 @@ class Sentinel(enum.Enum): + """Sentinel value to signify missing condition, absence of value""" + MISSING = object class ConfigError(Exception): + """Exception raised when Configfile errors exist""" + def __init__(self, msg) -> None: super().__init__(msg) self.msg = msg @@ -59,24 +63,10 @@ def __init__(self, msg) -> None: class BlocksScreenConfig: config = configparser.ConfigParser( allow_no_value=True, - # interpolation=configparser.ExtendedInterpolation(), - # delimiters=(":"), - # inline_comment_prefixes=("#"), - # comment_prefixes=("#", "#~#"), - # empty_lines_in_values=True, ) - update_pending: bool = False - _instance = None - # def __new__( - # cls, *args, **kwargs - # ) -> BlocksScreenConfig: # Singleton pattern - # if not cls._instance: - # cls._instance = super(BlocksScreenConfig, cls).__new__(cls) - # return cls._instance - def __init__( self, configfile: typing.Union[str, pathlib.Path], section: str ) -> None: @@ -93,24 +83,41 @@ def __contains__(self, key): return key in self.config def sections(self) -> typing.List[str]: + """Returns list of all sections""" return self.config.sections() def get_section( self, section: str, fallback: typing.Optional[T] = None ) -> BlocksScreenConfig: + """Get configfile section""" if not self.config.has_section(section): - raise configparser.NoSectionError( - f"No section with name: {section}" - ) + raise configparser.NoSectionError(f"No section with name: {section}") return BlocksScreenConfig(self.configfile, section) def get_options(self) -> list: + """Get section options""" return self.config.options(self.section) def has_section(self, section: str) -> bool: + """Check if config file has a section + + Args: + section (str): section name + + Returns: + bool: true if section exists, false otherwise + """ return bool(self.config.has_section(section)) def has_option(self, option: str) -> bool: + """Check if section has a option + + Args: + option (str): option name + + Returns: + bool: true if section exists, false otherwise + """ return bool(self.config.has_option(self.section, option)) def get( @@ -119,10 +126,18 @@ def get( parser: type = str, default: typing.Union[Sentinel, str, T] = Sentinel.MISSING, ) -> typing.Union[Sentinel, str]: + """Get option value + + Args: + option (str): option name + parser (type, optional): bool, float, int. Defaults to str. + default (typing.Union[Sentinel, str, T], optional): Default value for specified option. Defaults to Sentinel.MISSING. + + Returns: + typing.Union[Sentinel, str]: Requested option. Defaults to the specified default value + """ return parser( - self.config.get( - section=self.section, option=option, fallback=default - ) + self.config.get(section=self.section, option=option, fallback=default) ) def getint( @@ -130,15 +145,31 @@ def getint( option: str, default: typing.Union[Sentinel, int] = Sentinel.MISSING, ) -> typing.Union[Sentinel, int]: - return self.config.getint( - section=self.section, option=option, fallback=default - ) + """Get option value + + Args: + option (str): option name + default (typing.Union[Sentinel, int], optional): Default value for specified option. Defaults to Sentinel.MISSING. + + Returns: + typing.Union[Sentinel, int]: Requested option. + """ + return self.config.getint(section=self.section, option=option, fallback=default) def getfloat( self, option: str, default: typing.Union[Sentinel, float] = Sentinel.MISSING, ) -> typing.Union[Sentinel, float]: + """Get the value for the specified option + + Args: + option (str): option name + default (typing.Union[Sentinel, float], optional): Default value for specified option. Defaults to Sentinel.MISSING. + + Returns: + typing.Union[Sentinel, float]: _description_ + """ return self.config.getfloat( section=self.section, option=option, fallback=default ) @@ -148,6 +179,15 @@ def getboolean( option: str, default: typing.Union[Sentinel, bool] = Sentinel.MISSING, ) -> typing.Union[Sentinel, bool]: + """Get option value + + Args: + option (str): option name + default (typing.Union[Sentinel, bool], optional): Default value for specified option. Defaults to Sentinel.MISSING. + + Returns: + typing.Union[Sentinel, bool]: _description_ + """ return self.config.getboolean( section=self.section, option=option, fallback=default ) @@ -156,9 +196,7 @@ def _find_section_index(self, section: str) -> int: try: return self.raw_config.index("[" + section + "]") except ValueError as e: - raise configparser.Error( - f'Section "{section}" does not exist: {e}' - ) + raise configparser.Error(f'Section "{section}" does not exist: {e}') def _find_section_limits(self, section: str) -> typing.Tuple: try: @@ -189,6 +227,14 @@ def _find_option_index( ) def add_section(self, section: str) -> None: + """Add a section to configuration file + + Args: + section (str): section name + + Raises: + configparser.DuplicateSectionError: Exception thrown when section is duplicated + """ try: with self.file_lock: sec_string = f"[{section}]" @@ -209,9 +255,7 @@ def add_section(self, section: str) -> None: except configparser.DuplicateSectionError as e: logging.error(f'Section "{section}" already exists. {e}') except configparser.Error as e: - logging.error( - f'Unable to add "{section}" section to configuration: {e}' - ) + logging.error(f'Unable to add "{section}" section to configuration: {e}') def add_option( self, @@ -219,6 +263,13 @@ def add_option( option: str, value: typing.Union[str, None] = None, ) -> None: + """Add option with a value to a section + + Args: + section (str): section name + option (str): option name + value (typing.Union[str, None], optional): value for the specified option. Defaults to None. + """ try: with self.file_lock: section_start, section_end = self._find_section_limits(section) @@ -239,13 +290,12 @@ def add_option( ) def save_configuration(self) -> None: + """Save teh configuration to file""" try: if not self.update_pending: return with self.file_lock: - self.configfile.write_text( - "\n".join(self.raw_config), encoding="utf-8" - ) + self.configfile.write_text("\n".join(self.raw_config), encoding="utf-8") sio = io.StringIO() sio.writelines(self.raw_config) self.config.write(sio) @@ -257,13 +307,8 @@ def save_configuration(self) -> None: finally: self.update_pending = False - def _do_save(self, data) -> bool: - try: - return True - except Exception as e: - return False - def load_config(self): + """Load configuration file""" try: self.raw_config.clear() self.config.clear() # Reset configparser @@ -330,6 +375,7 @@ def _parse_file(self) -> typing.Tuple[typing.List[str], typing.Dict]: def get_configparser() -> BlocksScreenConfig: + """Loads configuration from file and returns that configuration""" wanted_target = os.path.join(DEFAULT_CONFIGFILE_PATH, "BlocksScreen.cfg") fallback = os.path.join(WORKING_DIR, "BlocksScreen.cfg") configfile = ( @@ -337,13 +383,9 @@ def get_configparser() -> BlocksScreenConfig: if check_file_on_path(DEFAULT_CONFIGFILE_PATH, "BlocksScreen.cfg") else fallback ) - try: - config_object = BlocksScreenConfig( - configfile=configfile, section="server" - ) - config_object.load_config() - if not config_object.has_section("server"): - raise ConfigError("Section [server] is missing from configuration") - except ConfigError: + config_object = BlocksScreenConfig(configfile=configfile, section="server") + config_object.load_config() + if not config_object.has_section("server"): logging.error("Error loading configuration file for the application.") + raise ConfigError("Section [server] is missing from configuration") return BlocksScreenConfig(configfile=configfile, section="server") diff --git a/BlocksScreen/events.py b/BlocksScreen/events.py index 05c5d28f..20a870e4 100644 --- a/BlocksScreen/events.py +++ b/BlocksScreen/events.py @@ -1,3 +1,5 @@ +"""Collection of all custom events used by the application""" + import typing from PyQt6.QtCore import QEvent @@ -21,6 +23,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(WebSocketConnecting.WebsocketConnectingEvent) @@ -48,9 +51,8 @@ def __init__( @staticmethod def type() -> QEvent.Type: - return QEvent.Type( - WebSocketMessageReceived.WebsocketMessageReceivedEvent - ) + """Return event type""" + return QEvent.Type(WebSocketMessageReceived.WebsocketMessageReceivedEvent) class WebSocketOpen(QEvent): @@ -70,6 +72,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(WebSocketOpen.WebsocketOpenEvent) @@ -83,15 +86,14 @@ class WebSocketError(QEvent): WebsocketErrorEvent = QEvent.Type(QEvent.registerEventType()) def __init__(self, data, *args, **kwargs): - super(WebSocketError, self).__init__( - WebSocketError.WebsocketErrorEvent - ) + super(WebSocketError, self).__init__(WebSocketError.WebsocketErrorEvent) self.data = data self.args = args self.kwargs = kwargs @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(WebSocketError.WebsocketErrorEvent) @@ -114,6 +116,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(WebSocketDisconnected.WebsocketDisconnectedEvent) @@ -128,15 +131,14 @@ class WebSocketClose(QEvent): WebsocketCloseEvent = QEvent.Type(QEvent.registerEventType()) def __init__(self, data, *args, **kwargs): - super(WebSocketClose, self).__init__( - WebSocketClose.WebsocketCloseEvent - ) + super(WebSocketClose, self).__init__(WebSocketClose.WebsocketCloseEvent) self.data = data self.args = args self.kwargs = kwargs @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(WebSocketClose.WebsocketCloseEvent) @@ -151,15 +153,14 @@ class KlippyShutdown(QEvent): def __init__(self, data, *args, **kwargs): QEvent.__instancecheck__(self) - super(KlippyShutdown, self).__init__( - KlippyShutdown.KlippyShutdownEvent - ) + super(KlippyShutdown, self).__init__(KlippyShutdown.KlippyShutdownEvent) self.data = data self.args = args self.kwargs = kwargs @staticmethod def type() -> QEvent.Type: + """Return event type""" return KlippyShutdown.KlippyShutdownEvent # def __instancecheck__(self, instance: Any) -> bool: @@ -186,6 +187,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(KlippyReady.KlippyReadyEvent) @@ -208,6 +210,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(KlippyDisconnected.KlippyDisconnectedEvent) @@ -227,6 +230,7 @@ def __init__(self, data, message, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(KlippyError.KlippyErrorEvent) @@ -244,9 +248,7 @@ class ReceivedFileData(QEvent): def __init__( self, data, method, params, /, *args, **kwargs ): # Positional-only arguments "data", "method", "params", these need to be inserted in order or it wont work - super(ReceivedFileData, self).__init__( - ReceivedFileData.ReceivedFileDataEvent - ) + super(ReceivedFileData, self).__init__(ReceivedFileData.ReceivedFileDataEvent) self.data = data self.method = method self.params = params @@ -255,6 +257,7 @@ def __init__( @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(ReceivedFileData.ReceivedFileDataEvent) @@ -276,6 +279,7 @@ def __init__(self, filename, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(PrintStart.PrintStartEvent) @@ -296,6 +300,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(PrintComplete.PrintCompleteEvent) @@ -317,6 +322,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(PrintPause.PrintPauseEvent) @@ -338,6 +344,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(PrintResume.PrintResumeEvent) @@ -351,9 +358,7 @@ class PrintCancelled(QEvent): PrintCancelledEvent = QEvent.Type(QEvent.registerEventType()) def __init__(self, data, *args, **kwargs): - super(PrintCancelled, self).__init__( - PrintCancelled.PrintCancelledEvent - ) + super(PrintCancelled, self).__init__(PrintCancelled.PrintCancelledEvent) self.data = data self.args = args @@ -361,6 +366,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(PrintCancelled.PrintCancelledEvent) @@ -381,6 +387,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(PrintError.PrintErrorEvent) @@ -401,6 +408,7 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(NetworkAdded.NetworkAddedEvent) @@ -414,15 +422,14 @@ class NetworkDeleted(QEvent): NetworkDeletedEvent = QEvent.Type(QEvent.registerEventType()) def __init__(self, data, *args, **kwargs): - super(NetworkDeleted, self).__init__( - NetworkDeleted.NetworkDeletedEvent - ) + super(NetworkDeleted, self).__init__(NetworkDeleted.NetworkDeletedEvent) self.data = data self.args = args self.kwargs = kwargs @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(NetworkDeleted) @@ -443,4 +450,5 @@ def __init__(self, data, *args, **kwargs): @staticmethod def type() -> QEvent.Type: + """Return event type""" return QEvent.Type(NetworkScan.NetworkScanEvent) diff --git a/BlocksScreen/helper_methods.py b/BlocksScreen/helper_methods.py index b086c751..b4dafc0f 100644 --- a/BlocksScreen/helper_methods.py +++ b/BlocksScreen/helper_methods.py @@ -1,23 +1,26 @@ +# Collection of useful methods +# # This file contains some methods derived from KlipperScreen # Original source: https://github.com/KlipperScreen/KlipperScreen # License: GNU General Public License v3 -# Modifications made by Hugo Costa (2025) for BlocksScreen +# Modifications made by Hugo Costa (2025) for BlocksScreen import ctypes -import os import enum import logging +import os import pathlib import struct import typing - try: ctypes.cdll.LoadLibrary("libXext.so.6") libxext = ctypes.CDLL("libXext.so.6") class DPMSState(enum.Enum): + """Available DPMS states""" + FAIL = -1 ON = 0 STANDBY = 1 @@ -35,6 +38,7 @@ class DPMSState(enum.Enum): libxext.DPMSForceLevel.restype = ctypes.c_int def get_dpms_state(): + """Gets and returns DPMS state""" _dpms_state = DPMSState.FAIL _display_name = ctypes.c_char_p(b":0") libxext.XOpenDisplay.restype = ctypes.c_void_p @@ -59,6 +63,11 @@ def get_dpms_state(): return _dpms_state def set_dpms_mode(mode: DPMSState) -> None: + """Set DPMS state + + Args: + mode (DPMSState): State to set DPMS. Check available state on `DPMSState` + """ _display_name = ctypes.c_char_p(b":0") libxext.XOpenDisplay.restype = ctypes.c_void_p display = ctypes.c_void_p( @@ -76,6 +85,7 @@ def set_dpms_mode(mode: DPMSState) -> None: libxext.XCloseDisplay(display) def get_dpms_timeouts() -> typing.Dict: + """Get current DPMS timeouts""" _display_name = ctypes.c_char_p(b":0") libxext.XOpenDisplay.restype = ctypes.c_void_p display = ctypes.c_void_p( @@ -93,9 +103,7 @@ def get_dpms_timeouts() -> typing.Dict: suspend_p = ctypes.create_string_buffer(2) off_p = ctypes.create_string_buffer(2) - if libxext.DPMSGetTimeouts( - display, standby_p, suspend_p, off_p - ): + if libxext.DPMSGetTimeouts(display, standby_p, suspend_p, off_p): _standby_timeout = struct.unpack("H", standby_p.raw)[0] _suspend_timeout = struct.unpack("H", suspend_p.raw)[0] _off_timeout = struct.unpack("H", off_p.raw)[0] @@ -111,6 +119,7 @@ def get_dpms_timeouts() -> typing.Dict: def set_dpms_timeouts( suspend: int = 0, standby: int = 0, off: int = 0 ) -> typing.Dict: + """Set DPMS timeout""" _display_name = ctypes.c_char_p(b":0") libxext.XOpenDisplay.restype = ctypes.c_void_p display = ctypes.c_void_p( @@ -131,9 +140,7 @@ def set_dpms_timeouts( suspend_p = ctypes.create_string_buffer(2) off_p = ctypes.create_string_buffer(2) - if libxext.DPMSGetTimeouts( - display, standby_p, suspend_p, off_p - ): + if libxext.DPMSGetTimeouts(display, standby_p, suspend_p, off_p): _standby_timeout = struct.unpack("H", standby_p.raw)[0] _suspend_timeout = struct.unpack("H", suspend_p.raw)[0] _off_timeout = struct.unpack("H", off_p.raw)[0] @@ -147,6 +154,11 @@ def set_dpms_timeouts( } def get_dpms_info() -> typing.Dict: + """Get DPMS information + + Returns: + typing.Dict: Dpms state + """ _dpms_state = DPMSState.FAIL onoff = 0 _display_name = ctypes.c_char_p(b":0") @@ -176,6 +188,12 @@ def get_dpms_info() -> typing.Dict: return {"power_level": onoff, "state": DPMSState(_dpms_state)} def check_dpms_capable(display: int): + """Check if device has DPMS + + Args: + display (int): Display index + + """ _display_name = ctypes.c_char_p(b":%d" % (display)) libxext.XOpenDisplay.restype = ctypes.c_void_p @@ -198,6 +216,7 @@ def check_dpms_capable(display: int): return _capable def disable_dpms() -> None: + """Disable DPMS""" set_dpms_mode(DPMSState.OFF) except OSError as e: @@ -234,7 +253,10 @@ def calculate_current_layer( """ if z_position == 0: return -1 - _current_layer = 1 + (z_position - first_layer_height) / layer_height + if z_position <= first_layer_height: + return 1 + + _current_layer = (z_position) / layer_height return int(_current_layer) @@ -255,6 +277,7 @@ def estimate_print_time(seconds: int) -> list: def normalize(value, r_min=0.0, r_max=1.0, t_min=0.0, t_max=100): + """Normalize values between a rage""" # https://stats.stackexchange.com/questions/281162/scale-a-number-between-a-range c1 = (value - r_min) / (r_max - r_min) c2 = (t_max - t_min) + t_min @@ -290,6 +313,7 @@ def check_filepath_permission(filepath, access_type: int = os.R_OK) -> bool: def check_dir_existence( directory: typing.Union[str, pathlib.Path], ) -> bool: + """Check if a directory exists. Returns a true if it exists""" if isinstance(directory, pathlib.Path): return bool(directory.is_dir()) return bool(os.path.isdir(directory)) @@ -299,12 +323,28 @@ def check_file_on_path( path: typing.Union[typing.LiteralString, pathlib.Path], filename: typing.Union[typing.LiteralString, pathlib.Path], ) -> bool: + """Check if file exists on path. Returns true if file exists on that specified directory""" _filepath = os.path.join(path, filename) return os.path.exists(_filepath) -def get_file_loc(filename) -> pathlib.Path: - ... +def get_file_loc(filename) -> pathlib.Path: ... + + +def get_file_name(filename: typing.Optional[str]) -> str: + # If filename is None or empty, return empty string instead of None + if not filename: + return "" + # Remove trailing slashes or backslashes + filename = filename.rstrip("/\\") + + # Normalize Windows backslashes to forward slashes + filename = filename.replace("\\", "/") + + parts = filename.split("/") + + # Split and return the last path component + return parts[-1] if filename else "" # def get_hash(data) -> hashlib._Hash: @@ -314,5 +354,4 @@ def get_file_loc(filename) -> pathlib.Path: # return hash - def digest_hash() -> None: ... diff --git a/BlocksScreen/lib/async_network_monitor.py b/BlocksScreen/lib/async_network_monitor.py deleted file mode 100644 index 6063be30..00000000 --- a/BlocksScreen/lib/async_network_monitor.py +++ /dev/null @@ -1,157 +0,0 @@ -import asyncio -import logging -import threading -import typing - -import sdbus -from PyQt6 import QtCore -from sdbus_async import networkmanager - - -class SdbusNMMonitor(QtCore.QObject): - state_change: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( - str, name="nm-state-changed" - ) - prop_changed: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( - name="nm-properties-changed" - ) - added_conn: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( - str, name="nm-conn-added" - ) - rem_con: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( - str, name="nm-conn-added" - ) - - def __init__(self) -> None: - super().__init__() - - self._running: bool = False # control - # Run on separate thread - self.thread: threading.Thread = threading.Thread( - name="asyncio.NMonitor.run_forever", - target=self._run_loop, - ) - self.thread.daemon = False # Do not exit the thread - - # Create a new asyncio loop - self.loop = asyncio.new_event_loop() - - # Asyncio Event - self.stop_event = asyncio.Event() - self.stop_event.clear() - # open asd set system sdbus - self.system_dbus = sdbus.sd_bus_open_system() - if not self.system_dbus: - logging.info("No dbus found, async network monitor exiting") - del self - return - sdbus.set_default_bus(self.system_dbus) - - # Instantiate NetworkManager - self.nm = networkmanager.NetworkManager() - - # Start thread - self.thread.start() - - if self.thread.is_alive(): - logging.info( - f"Sdbus NetworkManager Monitor Thread {self.thread.name} Running" - ) - - def close(self) -> None: - self._running = False - if hasattr(self, "state_listener_task"): - self.state_listener_task.cancel() - if hasattr(self, "added_ap_listener_task"): - self.added_ap_listener_task.cancel() - if hasattr(self, "rem_ap_listener_task"): - self.rem_ap_listener_task.cancel() - if hasattr(self, "prop_changed_listener_task"): - self.prop_changed_listener_task.cancel() - try: - self.loop.run_until_complete(self.state_listener_task) - self.loop.run_until_complete(self.added_ap_listener_task) - self.loop.run_until_complete(self.rem_ap_listener_task) - except asyncio.CancelledError as e: - logging.error(f"Exception while cancelling {e}") - self.stop_event.set() - self.loop.call_soon_threadsafe(self.stop_event.set) - self.loop.close() - self.thread.join() - - def _run_loop(self) -> None: - try: - asyncio.set_event_loop(self.loop) - self.loop.run_until_complete(asyncio.gather(self.monitor())) - except Exception as e: - logging.error(f"Exception on loop coroutine: {e}") - - async def monitor(self) -> None: - try: - self._running = True - self.state_listener_task = self.loop.create_task( - self._state_change_listener() - ) - self.added_ap_listener_task = self.loop.create_task( - self._access_added_listener() - ) - self.rem_ap_listener_task = self.loop.create_task( - self._access_rem_listener() - ) - self.prop_changed_listener_task = self.loop.create_task( - self._properties_changed_listener() - ) - await ( - self.stop_event.wait() - ) # Wait until .set() is done on self.stop_event - except Exception as e: - logging.error(f"Exception on monitor coroutine: {e}") - - async def _state_change_listener(self) -> None: - while self._running: - try: - logging.debug( - "Listening coroutine for NetworkManager state signal..." - ) - async for state in self.nm.state_changed: - enum_state = networkmanager.NetworkManagerState(state) - logging.debug( - f"NM State Changed: {enum_state.name} ({state})" - ) - self.state_change.emit(state) - except Exception as e: - logging.error(f"Exception on NM state listener: {e}") - - async def _properties_changed_listener(self) -> None: - while self._running: - try: - logging.debug( - "Listening coroutine for NetworkManager state signal..." - ) - async for state in self.nm.properties_changed: - enum_state = networkmanager.NetworkManagerState(state) - logging.debug( - f"NM State Changed: {enum_state.name} ({state})" - ) - self.state_change.emit(state) - except Exception as e: - logging.error(f"Exception on NM state listener: {e}") - - async def _access_added_listener(self) -> None: - while self._running: - try: - logging.debug("Listening coroutine for added access points") - async for ac in self.nm.device_added: - logging.debug(f"Signal for device added received {ac}") - self.added_conn.emit(ac) - except Exception as e: - logging.error(f"Error for added access points listener: {e}") - - async def _access_rem_listener(self) -> None: - while self._running: - try: - logging.debug("Listening coroutine for removed access points") - async for ac in self.nm.device_removed: - self.rem_con.emit(ac) - except Exception as e: - logging.error(f"Error for removed access points listener: {e}") diff --git a/BlocksScreen/lib/filament.py b/BlocksScreen/lib/filament.py index cafb053e..cb4c0232 100644 --- a/BlocksScreen/lib/filament.py +++ b/BlocksScreen/lib/filament.py @@ -1,20 +1,23 @@ -from typing import Optional - +# Class that represents a filament spool +from typing import Optional import enum -# typing.Optional[type()] == typing.Union[type(), None] - - class Filament: + """Filament spool""" + class SpoolBaseWeights(enum.Enum): # XXX This enum will probably be unnecessary + """Spool base weights""" + MINI = 750 BASE = 1000 BIG = 3000 JUMBO = 5000 class SpoolMaterial(enum.Flag): + """Spool material types""" + PLASTIC = enum.auto() PAPER = enum.auto() UNKNOWN = -1 @@ -80,10 +83,4 @@ def spool_type(self, new): raise ValueError( "Spool Material type is invalid" ) # Correct type but invalid option - else: - raise TypeError("") # TODO: Finish this type raise self._spool_type = new - - def calc_remaining_weight(self): ... # TODO calculate remaining spool weight - - def calc_initial_weight(self): ... # TODO calculate initial spool weight diff --git a/BlocksScreen/lib/files.py b/BlocksScreen/lib/files.py index 02849191..0eda561d 100644 --- a/BlocksScreen/lib/files.py +++ b/BlocksScreen/lib/files.py @@ -1,3 +1,6 @@ +# +# Gcode File manager +# from __future__ import annotations import os @@ -15,9 +18,7 @@ class Files(QtCore.QObject): [], [str], [str, bool], name="api-get-dir-info" ) request_file_metadata = QtCore.pyqtSignal([str], name="get_file_metadata") - request_files_thumbnails = QtCore.pyqtSignal( - [str], name="request_files_thumbnail" - ) + request_files_thumbnails = QtCore.pyqtSignal([str], name="request_files_thumbnail") request_file_download = QtCore.pyqtSignal([str, str], name="file_download") on_dirs: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( list, name="on-dirs" @@ -44,33 +45,24 @@ def __init__( self.request_file_list.connect(slot=self.ws.api.get_file_list) self.request_file_list[str].connect(slot=self.ws.api.get_file_list) self.request_dir_info.connect(slot=self.ws.api.get_dir_information) - self.request_dir_info[str, bool].connect( - self.ws.api.get_dir_information - ) - self.request_dir_info[str].connect( - slot=self.ws.api.get_dir_information - ) + self.request_dir_info[str, bool].connect(self.ws.api.get_dir_information) + self.request_dir_info[str].connect(slot=self.ws.api.get_dir_information) self.request_file_metadata.connect(slot=self.ws.api.get_gcode_metadata) - self.request_files_thumbnails.connect( - slot=self.ws.api.get_gcode_thumbnail - ) + self.request_files_thumbnails.connect(slot=self.ws.api.get_gcode_thumbnail) self.request_file_download.connect(slot=self.ws.api.download_file) QtWidgets.QApplication.instance().installEventFilter(self) # type: ignore @property def file_list(self): + """Available files list""" return self.files def handle_message_received(self, method: str, data, params: dict) -> None: + """Handle file related messages received by moonraker""" if "server.files.list" in method: - # Get all files in root and its subdirectories and - # request their metadata self.files.clear() self.files = data - [ - self.request_file_metadata.emit(item["path"]) - for item in self.files - ] + [self.request_file_metadata.emit(item["path"]) for item in self.files] elif "server.files.metadata" in method: if data["filename"] in self.files_metadata.keys(): if not data.get("filename", None): @@ -79,18 +71,32 @@ def handle_message_received(self, method: str, data, params: dict) -> None: else: self.files_metadata[data["filename"]] = data elif "server.files.get_directory" in method: - # Emit here the files for each directory so the - # ui can build the files list self.directories = data.get("dirs", {}) self.files.clear() self.files = data.get("files", []) self.on_file_list[list].emit(self.files) self.on_dirs[list].emit(self.directories) + @QtCore.pyqtSlot(str, str, name="on_request_delete_file") + def on_request_delete_file(self, filename: str, directory: str = "gcodes") -> None: + """Requests deletion of a file + + Args: + filename (str): file to delete + directory (str): root directory where the file is located + """ + if not directory: + self.ws.api.delete_file(filename) + return + self.ws.api.delete_file(filename, directory) # Use the root directory 'gcodes' + @QtCore.pyqtSlot(str, name="on_request_fileinfo") def on_request_fileinfo(self, filename: str) -> None: - # if not filename: - # return + """Requests metadata for a file + + Args: + filename (str): file to get metadata from + """ _data: dict = { "thumbnail_images": list, "filament_total": dict, @@ -122,34 +128,17 @@ def on_request_fileinfo(self, filename: str) -> None: _thumbnails, ) ) - _thumbnail_images = list( - map(lambda path: QtGui.QImage(path), _thumbnail_paths) - ) + _thumbnail_images = list(map(lambda path: QtGui.QImage(path), _thumbnail_paths)) _data.update({"thumbnail_images": _thumbnail_images}) - - _data.update( - {"filament_total": _file_metadata.get("filament_total", "?")} - ) - _data.update( - {"estimated_time": _file_metadata.get("estimated_time", 0)} - ) + _data.update({"filament_total": _file_metadata.get("filament_total", "?")}) + _data.update({"estimated_time": _file_metadata.get("estimated_time", 0)}) _data.update({"layer_count": _file_metadata.get("layer_count", -1.0)}) _data.update({"total_layer": _file_metadata.get("total_layer", -1.0)}) + _data.update({"object_height": _file_metadata.get("object_height", -1.0)}) + _data.update({"nozzle_diameter": _file_metadata.get("nozzle_diameter", -1.0)}) + _data.update({"layer_height": _file_metadata.get("layer_height", -1.0)}) _data.update( - {"object_height": _file_metadata.get("object_height", -1.0)} - ) - _data.update( - {"nozzle_diameter": _file_metadata.get("nozzle_diameter", -1.0)} - ) - _data.update( - {"layer_height": _file_metadata.get("layer_height", -1.0)} - ) - _data.update( - { - "first_layer_height": _file_metadata.get( - "first_layer_height", -1.0 - ) - } + {"first_layer_height": _file_metadata.get("first_layer_height", -1.0)} ) _data.update( { @@ -159,46 +148,31 @@ def on_request_fileinfo(self, filename: str) -> None: } ) _data.update( - { - "first_layer_bed_temp": _file_metadata.get( - "first_layer_bed_temp", -1.0 - ) - } - ) - _data.update( - {"chamber_temp": _file_metadata.get("chamber_temp", -1.0)} - ) - _data.update( - {"filament_name": _file_metadata.get("filament_name", -1.0)} - ) - _data.update( - {"filament_type": _file_metadata.get("filament_type", -1.0)} + {"first_layer_bed_temp": _file_metadata.get("first_layer_bed_temp", -1.0)} ) + _data.update({"chamber_temp": _file_metadata.get("chamber_temp", -1.0)}) + _data.update({"filament_name": _file_metadata.get("filament_name", -1.0)}) + _data.update({"filament_type": _file_metadata.get("filament_type", -1.0)}) _data.update( - { - "filament_weight_total": _file_metadata.get( - "filament_weight_total", -1.0 - ) - } + {"filament_weight_total": _file_metadata.get("filament_weight_total", -1.0)} ) _data.update({"slicer": _file_metadata.get("slicer", -1.0)}) self.fileinfo.emit(_data) def eventFilter(self, a0: QtCore.QObject, a1: QtCore.QEvent) -> bool: + """Handle Websocket and Klippy events""" + if a1.type() == events.WebSocketOpen.type(): + self.request_file_list.emit() + self.request_dir_info[str, bool].emit("", False) + return False if a1.type() == events.KlippyDisconnected.type(): self.files_metadata.clear() self.files.clear() return False - elif a1.type() == events.KlippyReady.type(): - # Request all files including in subdirectories - # in order to get all metadata - self.request_file_list.emit() - # List and directory build is depended only on this signal - self.request_dir_info[str, bool].emit("", False) - return False return super().eventFilter(a0, a1) def event(self, a0: QtCore.QEvent) -> bool: + """Filter ReceivedFileData event""" if a0.type() == ReceivedFileData.type(): if isinstance(a0, ReceivedFileData): self.handle_message_received(a0.method, a0.data, a0.params) diff --git a/BlocksScreen/lib/machine.py b/BlocksScreen/lib/machine.py index 6258a94b..e1c4a0ca 100644 --- a/BlocksScreen/lib/machine.py +++ b/BlocksScreen/lib/machine.py @@ -1,45 +1,55 @@ +# +# Machine manager +# import logging -import subprocess +import shlex +import subprocess # nosec: B404 import typing -from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot +from PyQt6 import QtCore -class MachineControl(QObject): - service_restart = pyqtSignal(str, name="service-restart") +class MachineControl(QtCore.QObject): + service_restart = QtCore.pyqtSignal(str, name="service-restart") - def __init__(self, parent: typing.Optional["QObject"]) -> None: + def __init__(self, parent: typing.Optional["QtCore.QObject"]) -> None: super(MachineControl, self).__init__(parent) self.setObjectName("MachineControl") - - @pyqtSlot(name="machine_restart") + + @QtCore.pyqtSlot(name="machine_restart") def machine_restart(self): + """Reboot machine""" return self._run_command("sudo reboot now") - @pyqtSlot(name="machine_shutdown") + @QtCore.pyqtSlot(name="machine_shutdown") def machine_shutdown(self): + """Shutdown machine""" return self._run_command("sudo shutdown now") - @pyqtSlot(name="restart_klipper_service") + @QtCore.pyqtSlot(name="restart_klipper_service") def restart_klipper_service(self): - # self.service_restart.emit("restart-klipper-service") + """Restart klipper service""" return self._run_command("sudo systemctl stop klipper.service") - - @pyqtSlot(name="restart_moonraker_service") + + @QtCore.pyqtSlot(name="restart_moonraker_service") def restart_moonraker_service(self): - # self.service_restart.emit("restart-moonraker-service") + """Restart moonraker service""" return self._run_command("sudo systemctl restart moonraker.service") - def restart_bo_service(self): - # TODO: Restart Blocks Screen service, implement it later on - pass - def check_service_state(self, service_name: str): + """Check service status + + Args: + service_name (str): service name + + Returns: + _type_: output of the command `systemctl is-active ` + """ if service_name is None: return None return self._run_command(f"systemctl is-active {service_name}") - - def _run_command(self, command): + + def _run_command(self, command: str): """Runs a shell command. Args: @@ -50,18 +60,26 @@ def _run_command(self, command): """ try: - # REVIEW: Safe way to run bash commands - # * Old way, it didn't let me use grep commands or use | on the command - # cmd = shlex.split(command,posix=False) - # exec = cmd[0] - # exec_options = cmd[1:] - # output = subprocess.run( - # ([exec] + exec_options), capture_output=True) - # TEST: is this safe to use like this, or is it susceptible to attacks and stuff - p = subprocess.Popen( - command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + # Split command into a list of strings + cmd = shlex.split(command) + p = subprocess.run( # nosec: B603 + cmd, check=True, capture_output=True, text=True, timeout=5 + ) + return p.stdout.strip() + "\n" + p.stderr.strip() + except ValueError as e: + logging.error("Failed to parse command string '%s': '%s'", command, e) + raise RuntimeError(f"Invalid command format: {e}") from e + except subprocess.CalledProcessError as e: + logging.error( + "Caught exception (exit code %d) failed to run command: %s \nStderr: %s", + e.returncode, + command, + e.stderr.strip(), ) - output, e = p.communicate() - return output - except subprocess.SubprocessError: - logging.error("Error running commas : %s", command) + raise + except ( + subprocess.SubprocessError, + subprocess.TimeoutExpired, + FileNotFoundError, + ): + logging.error("Caught exception failed to run command %s", command) diff --git a/BlocksScreen/lib/moonrakerComm.py b/BlocksScreen/lib/moonrakerComm.py index 3519cf01..ba298ba7 100644 --- a/BlocksScreen/lib/moonrakerComm.py +++ b/BlocksScreen/lib/moonrakerComm.py @@ -1,11 +1,10 @@ +# Moonraker api import json import logging import threading import websocket from events import ( - KlippyDisconnected, - KlippyShutdown, WebSocketDisconnected, WebSocketError, WebSocketMessageReceived, @@ -17,11 +16,6 @@ _logger = logging.getLogger(name="logs/BlocksScreen.log") -RED = "\033[31m" -GREEN = "\033[32m" -YELLOW = "\033[33m" -RESET = "\033[0m" - class OneShotTokenError(Exception): """Raised when unable to get oneshot token to connect to a websocket""" @@ -77,17 +71,20 @@ def __init__(self, parent: QtCore.QObject) -> None: @QtCore.pyqtSlot(name="retry_wb_conn") def retry_wb_conn(self): + """Retry websocket connection""" if self.connecting is True and self.connected is False: return False self._reconnect_count = 0 self.try_connection() def try_connection(self): + """Try connecting to websocket""" self.connecting = True self._retry_timer = RepeatedTimer(self.timeout, self.reconnect) return self.connect() def reconnect(self): + """Reconnect to websocket""" if self.connected: return True @@ -115,6 +112,7 @@ def reconnect(self): return self.connect() def connect(self) -> bool: + """Connect to websocket""" if self.connected: _logger.info("Connection established") return True @@ -320,7 +318,6 @@ def send_request(self, method: str, params: dict = {}) -> bool: return False self._request_id += 1 - # REVIEW: This data structure could be better, think about other implementations self.request_table[self._request_id] = [method, params] packet = { "jsonrpc": "2.0", @@ -331,18 +328,6 @@ def send_request(self, method: str, params: dict = {}) -> bool: self.ws.send(json.dumps(packet)) return True - # def customEvent(self, event: QtCore.QEvent | None) -> None: - # if not event: - # return - - # if ( - # event.type() == KlippyDisconnected.type() - # or event.type() == KlippyShutdown.type() - # ): - # # * Received notify_klippy_disconnected, start querying server information again to check if klipper is available - # self.evaluate_klippy_status() - # return super().customEvent(event) - class MoonAPI(QtCore.QObject): def __init__(self, ws: MoonWebSocket): @@ -351,12 +336,13 @@ def __init__(self, ws: MoonWebSocket): @QtCore.pyqtSlot(name="api_query_server_info") def api_query_server_info(self): - _logger.debug("Requested server.info") + """Query server information""" return self._ws.send_request(method="server.info") def identify_connection( self, client_name, version, type, url, access_token, api_key ): + """Request moonraker to identify connection""" return self._ws.send_request( method="server.connection.identify", params={ @@ -370,6 +356,7 @@ def identify_connection( ) def request_temperature_cached_data(self, include_monitors: bool = False): + """Request stored temperature monitors""" return self._ws.send_request( method="server.temperature_store", params={"include_monitors": include_monitors}, @@ -377,30 +364,36 @@ def request_temperature_cached_data(self, include_monitors: bool = False): @QtCore.pyqtSlot(name="query_printer_info") def request_printer_info(self): + """Requested printer information""" return self._ws.send_request(method="printer.info") @QtCore.pyqtSlot(name="get_available_objects") def get_available_objects(self): + """Request available printer objects""" return self._ws.send_request(method="printer.objects.list") @QtCore.pyqtSlot(dict, name="query_object") def object_query(self, objects: dict): + """Query printer object""" return self._ws.send_request( method="printer.objects.query", params={"objects": objects} ) @QtCore.pyqtSlot(dict, name="object_subscription") def object_subscription(self, objects: dict): + """Subscribe to printer object""" return self._ws.send_request( method="printer.objects.subscribe", params={"objects": objects} ) @QtCore.pyqtSlot(name="ws_query_endstops") def query_endstops(self): + """Query printer endstops""" return self._ws.send_request(method="printer.query_endstops.status") @QtCore.pyqtSlot(str, name="run_gcode") def run_gcode(self, gcode: str): + """Run Gcode""" if isinstance(gcode, str) is False or gcode is None: return False return self._ws.send_request( @@ -408,36 +401,45 @@ def run_gcode(self, gcode: str): ) def gcode_help(self): + """Request Gcode information""" return self._ws.send_request(method="printer.gcode.help") @QtCore.pyqtSlot(str, name="start_print") def start_print(self, filename): + """Start print job""" return self._ws.send_request( method="printer.print.start", params={"filename": filename} ) @QtCore.pyqtSlot(name="pause_print") def pause_print(self): + """Pause print job""" return self._ws.send_request(method="printer.print.pause") @QtCore.pyqtSlot(name="resume_print") def resume_print(self): + """Resume print job""" return self._ws.send_request(method="printer.print.resume") @QtCore.pyqtSlot(name="stop_print") def cancel_print(self): + """Cancel print job""" return self._ws.send_request(method="printer.print.cancel") - def machine_system(self): + def machine_shutdown(self): + """Request machine shutdown""" return self._ws.send_request(method="machine.shutdown") def machine_reboot(self): + """Request machine reboot""" return self._ws.send_request(method="machine.reboot") def restart_server(self): + """Request server restart""" return self._ws.send_request(method="server.restart") def restart_service(self, service): + """Request service restart""" if service is None or isinstance(service, str) is False: return False return self._ws.send_request( @@ -446,21 +448,16 @@ def restart_service(self, service): @QtCore.pyqtSlot(name="firmware_restart") def firmware_restart(self): - """firmware_restart + """Request Klipper firmware restart HTTP_REQUEST: POST /printer/firmware_restart JSON_RPC_REQUEST: printer.firmware_restart - Returns: - _type_: _description_ """ - # REVIEW: Whether i should send a websocket request or a post with http - # return self._ws._moonRest.firmware_restart() # With HTTP - return self._ws.send_request( - method="printer.firmware_restart" - ) # With Websocket + return self._ws.send_request(method="printer.firmware_restart") def stop_service(self, service): + """Request service stop""" if service is None or isinstance(service, str) is False: return False return self._ws.send_request( @@ -468,6 +465,7 @@ def stop_service(self, service): ) def start_service(self, service): + """Request service start""" if service is None or isinstance(service, str) is False: return False return self._ws.send_request( @@ -475,6 +473,7 @@ def start_service(self, service): ) def get_sudo_info(self, permission: bool = False): + """Request sudo privileges information""" if isinstance(permission, bool) is False: return False return self._ws.send_request( @@ -482,15 +481,19 @@ def get_sudo_info(self, permission: bool = False): ) def get_usb_devices(self): + """Request available usb devices""" return self._ws.send_request(method="machine.peripherals.usb") def get_serial_devices(self): + """Request available serial devices""" return self._ws.send_request(method="machine.peripherals.serial") def get_video_devices(self): + """Request available video devices""" return self._ws.send_request(method="machine.peripherals.video") def get_cabus_devices(self, interface: str = "can0"): + """Request available CAN devices""" return self._ws.send_request( method="machine.peripherals.canbus", params={"interface": interface}, @@ -499,16 +502,19 @@ def get_cabus_devices(self, interface: str = "can0"): @QtCore.pyqtSlot(name="api-request-file-list") @QtCore.pyqtSlot(str, name="api-request-file-list") def get_file_list(self, root_folder: str = "gcodes"): + """Get available files""" return self._ws.send_request( method="server.files.list", params={"root": root_folder} ) @QtCore.pyqtSlot(name="api-list-roots") def list_registered_roots(self): + """Get available root directories""" return self._ws.send_request(method="server.files.roots") @QtCore.pyqtSlot(str, name="api_request_file_list") def get_gcode_metadata(self, filename_dir: str): + """Request gcode metadata""" if not isinstance(filename_dir, str) or not filename_dir: return False return self._ws.send_request( @@ -517,6 +523,7 @@ def get_gcode_metadata(self, filename_dir: str): @QtCore.pyqtSlot(str, name="api-scan-gcode-metadata") def scan_gcode_metadata(self, filename_dir: str): + """Scan gcode metadata""" if isinstance(filename_dir, str) is False or filename_dir is None: return False return self._ws.send_request( @@ -525,6 +532,7 @@ def scan_gcode_metadata(self, filename_dir: str): @QtCore.pyqtSlot(name="api_get_gcode_thumbnail") def get_gcode_thumbnail(self, filename_dir: str): + """Request gcode thumbnail""" if isinstance(filename_dir, str) is False or filename_dir is None: return False return self._ws.send_request( @@ -534,7 +542,9 @@ def get_gcode_thumbnail(self, filename_dir: str): @QtCore.pyqtSlot(str, str, name="api-delete-file") @QtCore.pyqtSlot(str, name="api-delete-file") def delete_file(self, filename: str, root_dir: str = "gcodes"): + """Request file deletion""" filepath = f"{root_dir}/{filename}" + filepath = f"gcodes/{root_dir}/{filename}" if root_dir != "gcodes" else filepath return self._ws.send_request( method="server.files.delete_file", params={"path": filepath}, @@ -542,7 +552,7 @@ def delete_file(self, filename: str, root_dir: str = "gcodes"): @QtCore.pyqtSlot(str, str, name="api-file_download") def download_file(self, root: str, filename: str): - """download_file Retrieves file *filename* at root *root*, the filename must include the relative path if + """Retrieves file *filename* at root *root*, the filename must include the relative path if it is not in the root folder Args: @@ -561,6 +571,7 @@ def download_file(self, root: str, filename: str): @QtCore.pyqtSlot(str, name="api-get-dir-info") @QtCore.pyqtSlot(str, bool, name="api-get-dir-info") def get_dir_information(self, directory: str = "", extended: bool = True): + """Request directory information""" if not isinstance(directory, str): return False return self._ws.send_request( @@ -569,6 +580,7 @@ def get_dir_information(self, directory: str = "", extended: bool = True): ) def create_directory(self, directory: str): + """Create directory""" if isinstance(directory, str) is False or directory is None: return False return self._ws.send_request( @@ -579,6 +591,7 @@ def create_directory(self, directory: str): ) def delete_directory(self, directory: str): + """Delete directory""" if isinstance(directory, str) is False or directory is None: return False return self._ws.send_request( @@ -589,6 +602,7 @@ def delete_directory(self, directory: str): ) def move_file(self, source_dir: str, dest_dir: str): + """Move file""" if ( isinstance(source_dir, str) is False or isinstance(dest_dir, str) is False @@ -602,6 +616,7 @@ def move_file(self, source_dir: str, dest_dir: str): ) def copy_file(self, source_dir: str, dest_dir: str): + """Copy file""" if ( isinstance(source_dir, str) is False or isinstance(dest_dir, str) is False @@ -614,21 +629,19 @@ def copy_file(self, source_dir: str, dest_dir: str): params={"source": source_dir, "dest": dest_dir}, ) - def zip_archive(self, items: list): - raise NotImplementedError() - - # !Can implement a jog queueu - def list_announcements(self, include_dismissed: bool = False): + """Request available announcements""" return self._ws.send_request( method="server.announcements.list", params={"include_dismissed": include_dismissed}, ) def update_announcements(self): + """Request announcements update to moonraker""" return self._ws.send_request(method="server.announcements.update") def dismiss_announcements(self, entry_id: str, wake_time: int = 600): + """Dismiss announcements""" if ( isinstance(entry_id, str) is False or entry_id is None @@ -641,9 +654,11 @@ def dismiss_announcements(self, entry_id: str, wake_time: int = 600): ) def list_announcements_feeds(self): + """List announcement feeds""" return self._ws.send_request(method="server.announcements.feeds") def post_announcement_feed(self, announcement_name: str): + """Post annoucement feeds""" if isinstance(announcement_name, str) is False or announcement_name is None: return False return self._ws.send_request( @@ -652,6 +667,7 @@ def post_announcement_feed(self, announcement_name: str): ) def delete_announcement_feed(self, announcement_name: str): + """Delete announcement feeds""" if isinstance(announcement_name, str) is False or announcement_name is None: return False return self._ws.send_request( @@ -659,21 +675,20 @@ def delete_announcement_feed(self, announcement_name: str): params={"name": announcement_name}, ) - # * WEBCAM - def list_webcams(self): + """List available webcams""" return self._ws.send_request(method="server.webcams.list") def get_webcam_info(self, uid: str): + """Get webcamera information""" if isinstance(uid, str) is False or uid is None: return False return self._ws.send_request( method="server.webcams.get_info", params={"uid": uid} ) - # TODO: Can create a class that irs a URL type like i've done before to validate the links - # TODO: There are more options in this section, alot more options, later see if it's worth to implement or not def add_update_webcam(self, cam_name: str, snapshot_url: str, stream_url: str): + """Add or update webcamera""" if ( isinstance(cam_name, str) is False or isinstance(snapshot_url, str) is False @@ -693,6 +708,7 @@ def add_update_webcam(self, cam_name: str, snapshot_url: str, stream_url: str): ) def delete_webcam(self, uid: str): + """Delete webcamera""" if isinstance(uid, str) is False or uid is None: return False return self._ws.send_request( @@ -700,53 +716,66 @@ def delete_webcam(self, uid: str): ) def test_webcam(self, uid: str): + """Test webcamera connection""" if isinstance(uid, str) is False or uid is None: return False return self._ws.send_request(method="server.webcams.test", params={"uid": uid}) def list_notifiers(self): + """List configured notifiers""" return self._ws.send_request(method="server.notifiers.list") @QtCore.pyqtSlot(bool, name="update-status") def update_status(self, refresh: bool = False) -> bool: + """Get packages state""" return self._ws.send_request( method="machine.update.status", params={"refresh": refresh} ) @QtCore.pyqtSlot(name="update-refresh") @QtCore.pyqtSlot(str, name="update-refresh") - def refresh_update_status(self, name: str = "") -> bool: - if not isinstance(name, str) or not name: - return False - return self._ws.send_request( - method="machine.update.refresh", params={"name": name} - ) + def refresh_update_status(self, name: str = None) -> bool: + """Refresh packages state""" + if isinstance(name, str): + return self._ws.send_request( + method="machine.update.refresh", params={"name": name} + ) + else: + return self._ws.send_request( + method="machine.update.refresh", + ) @QtCore.pyqtSlot(name="update-full") def full_update(self) -> bool: + """Issue full upgrade to all packages""" return self._ws.send_request(method="machine.update.full") @QtCore.pyqtSlot(name="update-moonraker") def update_moonraker(self) -> bool: + """Issue moonraker update""" return self._ws.send_request(method="machine.update.moonraker") @QtCore.pyqtSlot(name="update-klipper") def update_klipper(self) -> bool: + """Issue klipper update""" return self._ws.send_request(method="machine.update.klipper") @QtCore.pyqtSlot(str, name="update-client") def update_client(self, client_name: str = "") -> bool: + """Issue client update""" if not isinstance(client_name, str) or not client_name: return False return self._ws.send_request(method="machine.update.client") @QtCore.pyqtSlot(name="update-system") def update_system(self): + """Issue system update""" return self._ws.send_request(method="machine.update.system") @QtCore.pyqtSlot(str, name="recover-repo") @QtCore.pyqtSlot(str, bool, name="recover-repo") def recover_corrupt_repo(self, name: str, hard: bool = False): + """Issue package recovery""" if isinstance(name, str) is False or name is None: return False return self._ws.send_request( @@ -756,54 +785,29 @@ def recover_corrupt_repo(self, name: str, hard: bool = False): @QtCore.pyqtSlot(str, name="rollback-update") def rollback_update(self, name: str): + """Issue rollback update""" if not isinstance(name, str) or not name: return False return self._ws.send_request( method="machine,update.rollback", params={"name": name} ) - # If moonraker [history] is configured def history_list(self, limit, start, since, before, order): - # TODO: + """Request Job history list""" raise NotImplementedError - return self._ws.send_request( - method="server.history.list", - params={ - "limit": limit, - "start": start, - "since": since, - "before": before, - "order": order, - }, - ) def history_job_totals(self): + """Request total job history""" raise NotImplementedError - return self._ws.send_request(method="server.history.totals") def history_reset_totals(self): + """Request history reset""" raise NotImplementedError - return self._ws.send_request(method="server.history.reset_totals") def history_get_job(self, uid: str): + """Request job history""" raise NotImplementedError - return self._ws.send_request( - method="server.history.get_job", params={"uid": uid} - ) def history_delete_job(self, uid: str): + """Request delete job history""" raise NotImplementedError - # It is possible to replace the uid argument with all=true to delete all jobs in the history database. - return self._ws.send_request( - method="server.history.delete_job", params={"uid": uid} - ) - - -############################################################################################################################ -# TODO: WEBSOCKET NOTIFICATIONS - -# TODO: Pass the logger object instanteation to another class so that the main window defines and calls it -# TODO: make host, port and websocket name not static but a argument that can be feed in the class -# TODO: Create websocket connection for each user login, which means different api keys for each user - -# TEST: Try and use multiprocessing as it sidesteps the GIL diff --git a/BlocksScreen/lib/moonrest.py b/BlocksScreen/lib/moonrest.py index d85b5425..1e43552a 100644 --- a/BlocksScreen/lib/moonrest.py +++ b/BlocksScreen/lib/moonrest.py @@ -57,6 +57,7 @@ def __init__(self, host: str = "localhost", port: int = 7125, api_key=False): @property def build_endpoint(self): + """Build connection endpoint""" return f"http://{self._host}:{self._port}" def get_oneshot_token(self): @@ -93,11 +94,8 @@ def firmware_restart(self): """ return self.post_request(method="printer/firmware_restart") - def delete_request(self): - # TODO: Create a delete request, so the user is able to delete files from the pi, can also be made with websockets - pass - def post_request(self, method, data=None, json=None, json_response=True): + """POST request""" return self._request( request_type="post", method=method, @@ -107,6 +105,7 @@ def post_request(self, method, data=None, json=None, json_response=True): ) def get_request(self, method, json=True, timeout=timeout): + """GET request""" return self._request( request_type="get", method=method, @@ -123,8 +122,6 @@ def _request( json_response=True, timeout=timeout, ): - # TODO: Need to check if the header is actually correct or not - # TEST: Test the reliability of this _url = f"{self.build_endpoint}/{method}" _headers = {"x-api-key": self._api_key} if self._api_key else {} try: diff --git a/BlocksScreen/lib/network.py b/BlocksScreen/lib/network.py index ca8a5ded..51b87074 100644 --- a/BlocksScreen/lib/network.py +++ b/BlocksScreen/lib/network.py @@ -1,6 +1,5 @@ import asyncio import enum -import hashlib import logging import threading import typing @@ -23,6 +22,8 @@ def __init__(self, error): class SdbusNetworkManagerAsync(QtCore.QObject): class ConnectionPriority(enum.Enum): + """Connection priorities""" + HIGH = 90 MEDIUM = 50 LOW = 20 @@ -111,6 +112,7 @@ def close(self) -> None: self.loop.close() async def listener_monitor(self) -> None: + """Monitor for NetworkManager properties""" try: self._listeners_running = True @@ -152,6 +154,7 @@ async def _nm_properties_listener(self) -> None: logging.error(f"Exception on Network Manager state listener: {e}") def check_nm_state(self) -> typing.Union[str, None]: + """Check NetworkManager state""" if not self.nm: return future = asyncio.run_coroutine_threadsafe(self.nm.state.get_async(), self.loop) @@ -194,6 +197,11 @@ def check_connectivity(self) -> str: return "" def check_wifi_interface(self) -> bool: + """Check if wifi interface is set + + Returns: + bool: true if it is. False otherwise + """ return bool(self.primary_wifi_interface) def get_available_interfaces(self) -> typing.Union[typing.List[str], None]: @@ -266,6 +274,7 @@ async def _toggle_networking(self, value: bool = True) -> None: logger.error(f"Exception Caught when toggling network : {result}") def disable_networking(self) -> None: + """Disable networking""" if not (self.primary_wifi_interface and self.primary_wired_interface): return if self.primary_wifi_interface == "/" and self.primary_wired_interface == "/": @@ -273,6 +282,7 @@ def disable_networking(self) -> None: asyncio.run_coroutine_threadsafe(self._toggle_networking(False), self.loop) def activate_networking(self) -> None: + """Activate networking""" if not (self.primary_wifi_interface and self.primary_wired_interface): return if self.primary_wifi_interface == "/" and self.primary_wired_interface == "/": @@ -327,7 +337,6 @@ def hotspot_enabled(self) -> typing.Optional["bool"]: Returns: bool: True if Hotspot is activated, False otherwise. """ - # REFACTOR: untested for all cases return bool(self.hotspot_ssid == self.get_current_ssid()) def get_wired_interfaces(self) -> typing.List[dbusNm.NetworkDeviceWired]: @@ -415,6 +424,11 @@ async def _gather_ssid(self) -> str: return "" def get_current_ssid(self) -> str: + """Get current ssid + + Returns: + str: ssid address + """ try: future = asyncio.run_coroutine_threadsafe(self._gather_ssid(), self.loop) return future.result(timeout=5) @@ -457,7 +471,7 @@ def get_current_ip_addr(self) -> str: addr_data = addr_data_fut.result(timeout=2) return [address_data["address"][1] for address_data in addr_data][0] except IndexError as e: - logger.error(f"List out of index %s", e) + logger.error("List out of index %s", e) return "" async def _gather_primary_interface( @@ -508,7 +522,6 @@ def get_primary_interface( If there is no wireless interface and no active connection return the first wired interface that is not (lo). - ### `TODO: Completely blocking and should be refactored` Returns: typing.List: """ @@ -607,6 +620,7 @@ async def _get_available_networks(self) -> typing.Union[typing.Dict, None]: return {} def get_available_networks(self) -> typing.Union[typing.Dict, None]: + """Get available networks""" future = asyncio.run_coroutine_threadsafe( self._get_available_networks(), self.loop ) @@ -717,6 +731,9 @@ def get_saved_networks( "mode": network_properties["802-11-wireless"][ "mode" ], + "priority": network_properties["connection"].get( + "autoconnect-priority", (None, None) + )[1], } if network_properties["connection"]["type"][1] == "802-11-wireless" @@ -1244,9 +1261,11 @@ def delete_network(self, ssid: str) -> None: logging.debug(f"Caught Exception while deleting network {ssid}: {e}") def get_hotspot_ssid(self) -> str: + """Get current hotspot ssid""" return self.hotspot_ssid def deactivate_connection(self, connection_path) -> None: + """Deactivate a connection, by connection path""" if not self.nm: return if not self.primary_wifi_interface: @@ -1269,6 +1288,7 @@ def deactivate_connection(self, connection_path) -> None: ) def deactivate_connection_by_ssid(self, ssid: str) -> None: + """Deactivate connection by ssid""" if not self.nm: return if not self.primary_wifi_interface: @@ -1287,6 +1307,12 @@ def deactivate_connection_by_ssid(self, ssid: str) -> None: def create_hotspot( self, ssid: str = "PrinterHotspot", password: str = "123456789" ) -> None: + """Create hostpot + + Args: + ssid (str, optional): Hotspot ssid. Defaults to "PrinterHotspot". + password (str, optional): connection password. Defaults to "123456789". + """ if self.is_known(ssid): self.delete_network(ssid) logger.debug("old hotspot deleted") @@ -1339,6 +1365,12 @@ def create_hotspot( def set_network_priority( self, ssid: str, priority: ConnectionPriority = ConnectionPriority.LOW ) -> None: + """Set network priority + + Args: + ssid (str): connection ssid + priority (ConnectionPriority, optional): Priority. Defaults to ConnectionPriority.LOW. + """ if not self.nm: return if not self.is_known(ssid): @@ -1408,4 +1440,4 @@ def update_connection_settings( if password != self.hotspot_password and password: self.hotspot_password = password except Exception as e: - logger.error(f"Caught Exception while updating network: %s", e) + logger.error("Caught Exception while updating network: %s", e) diff --git a/BlocksScreen/lib/panels/controlTab.py b/BlocksScreen/lib/panels/controlTab.py index a66ba1e6..5d1b9b80 100644 --- a/BlocksScreen/lib/panels/controlTab.py +++ b/BlocksScreen/lib/panels/controlTab.py @@ -1,18 +1,24 @@ from __future__ import annotations +import re import typing from functools import partial -import re + +from helper_methods import normalize from lib.moonrakerComm import MoonWebSocket -from lib.panels.widgets.loadPage import LoadScreen +from lib.panels.widgets.basePopup import BasePopup +from lib.panels.widgets.loadWidget import LoadingOverlayWidget from lib.panels.widgets.numpadPage import CustomNumpad +from lib.panels.widgets.optionCardWidget import OptionCard +from lib.panels.widgets.popupDialogWidget import Popup from lib.panels.widgets.printcorePage import SwapPrintcorePage from lib.panels.widgets.probeHelperPage import ProbeHelper +from lib.panels.widgets.slider_selector_page import SliderPage from lib.printer import Printer from lib.ui.controlStackedWidget_ui import Ui_controlStackedWidget +from lib.utils.display_button import DisplayButton from PyQt6 import QtCore, QtGui, QtWidgets -from lib.panels.widgets.popupDialogWidget import Popup class ControlTab(QtWidgets.QStackedWidget): """Printer Control Stacked Widget""" @@ -41,6 +47,8 @@ class ControlTab(QtWidgets.QStackedWidget): request_file_info: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( str, name="request-file-info" ) + tune_display_buttons: dict = {} + card_options: dict = {} def __init__( self, @@ -71,8 +79,17 @@ def __init__( self.addWidget(self.probe_helper_page) self.printcores_page = SwapPrintcorePage(self) self.addWidget(self.printcores_page) - self.loadpage = LoadScreen(self, LoadScreen.AnimationGIF.DEFAULT) - self.addWidget(self.loadpage) + + self.loadscreen = BasePopup(self, floating=False, dialog=False) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.loadscreen.add_widget(self.loadwidget) + + self.sliderPage = SliderPage(self) + self.addWidget(self.sliderPage) + self.sliderPage.request_back.connect(self.back_button) + self.probe_helper_page.request_page_view.connect( partial(self.change_page, self.indexOf(self.probe_helper_page)) ) @@ -107,13 +124,11 @@ def __init__( self.panel.cp_temperature_btn.clicked.connect( partial(self.change_page, self.indexOf(self.panel.temperature_page)) ) + self.panel.cp_fans_btn.clicked.connect( + partial(self.change_page, self.indexOf(self.panel.fans_page)) + ) + self.panel.fans_back_btn.clicked.connect(self.back_button) self.panel.cp_switch_print_core_btn.clicked.connect(self.show_swapcore) - # self.panel.cp_printer_settings_btn.clicked.connect( - # partial( - # self.change_page, - # self.indexOf(self.panel.printer_settings_page), - # ) - # ) self.panel.cp_nozzles_calibration_btn.clicked.connect( partial(self.change_page, self.indexOf(self.probe_helper_page)) ) @@ -258,9 +273,13 @@ def __init__( ) ) - self.panel.cp_z_tilt_btn.clicked.connect( - lambda: self.handle_ztilt() - ) + self.path = { + "fan_cage": QtGui.QPixmap(":/fan_related/media/btn_icons/fan_cage.svg"), + "blower": QtGui.QPixmap(":/fan_related/media/btn_icons/blower.svg"), + "fan": QtGui.QPixmap(":/fan_related/media/btn_icons/fan.svg"), + } + + self.panel.cp_z_tilt_btn.clicked.connect(lambda: self.handle_ztilt()) self.printcores_page.pc_accept.clicked.connect(self.handle_swapcore) @@ -274,28 +293,143 @@ def __init__( self.panel.cooldown_btn.hide() self.panel.cp_switch_print_core_btn.hide() + self.printer.fan_update[str, str, float].connect(self.on_fan_object_update) + self.printer.fan_update[str, str, int].connect(self.on_fan_object_update) + + @QtCore.pyqtSlot(str, str, float, name="on_fan_update") + @QtCore.pyqtSlot(str, str, int, name="on_fan_update") + def on_fan_object_update( + self, name: str, field: str, new_value: int | float + ) -> None: + """Slot that receives updates from fan objects. - def handle_printcoreupdate(self, value:dict): + Args: + name (str): Fan object name + field (str): Field name + new_value (int | float): New value for the field + """ + if "speed" not in field: + return + fields = name.split() + first_field = fields[0] + second_field = fields[1] if len(fields) > 1 else None + name = second_field.replace("_", " ") if second_field else name + + fan_card = self.tune_display_buttons.get(name) + if fan_card is None and first_field in ( + "fan", + "fan_generic", + ): + icon = self.path.get("fan") + if second_field: + second_field = second_field.lower() + pattern_blower = r"(?:^|_)(?:blower|auxiliary)(?:_|$)" + pattern_exhaust = r"(?:^|_)exhaust(?:_|$)" + if re.search(pattern_blower, second_field): + icon = self.path.get("blower") + elif re.search(pattern_exhaust, second_field): + icon = self.path.get("fan_cage") + + card = OptionCard(self, name, str(name), icon) # type: ignore + card.setObjectName(str(name)) + + # Add card to layout and record reference + self.card_options[name] = card + self.panel.fans_content_layout.addWidget(card) + + # If the card doesn't have expected UI properties, discard it + if not hasattr(card, "continue_clicked"): + del card + self.card_options.pop(name, None) + return + + card.setMode(True) + card.secondtext.setText(f"{new_value}%") + card.continue_clicked.connect( + lambda: self.on_slidePage_request( + str(name), + card.secondtext.text().replace("%", ""), + self.on_slider_change, + 0, + 100, + ) + ) + + self.tune_display_buttons[name] = card + self.update() + fan_card = card + + if fan_card: + value_percent = new_value * 100 if new_value <= 1 else new_value + fan_card.secondtext.setText(f"{value_percent:.0f}%") + + @QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on_slidePage_request") + @QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on_slidePage_request") + def on_slidePage_request( + self, + name: str, + current_value: int, + callback, + min_value: int = 0, + max_value: int = 100, + ) -> None: + self.sliderPage.value_selected.connect(callback) + self.sliderPage.set_name(name) + self.sliderPage.set_slider_position(int(current_value)) + self.sliderPage.set_slider_minimum(min_value) + self.sliderPage.set_slider_maximum(max_value) + self.change_page(self.indexOf(self.sliderPage)) + + @QtCore.pyqtSlot(str, int, name="on_slider_change") + def on_slider_change(self, name: str, new_value: int) -> None: + if "speed" in name.lower(): + self.speed_factor_override = new_value / 100 + self.run_gcode_signal.emit(f"M220 S{new_value}") + if name.lower() == "fan": + self.run_gcode_signal.emit( + f"M106 S{int(round((normalize(float(new_value / 100), 0.0, 1.0, 0, 255))))}" + ) # [0, 255] Range + else: + name = name.replace(" ", "_") + self.run_gcode_signal.emit( + f'SET_FAN_SPEED FAN="{name}" SPEED={float(new_value / 100.00)}' + ) # [0.0, 1.0] Range + + def create_display_button(self, name: str) -> DisplayButton: + """Create and return a DisplayButton + + Args: + name (str): Name for the display button + + Returns: + DisplayButton: The created DisplayButton object + """ + display_button = DisplayButton() + display_button.setObjectName(str(name + "_display")) + display_button.setMinimumSize(QtCore.QSize(150, 50)) + display_button.setMaximumSize(QtCore.QSize(150, 80)) + font = QtGui.QFont() + font.setPointSize(16) + display_button.setFont(font) + return display_button + + def handle_printcoreupdate(self, value: dict): if value["swapping"] == "idle": return if value["swapping"] == "in_pos": - self.loadpage.hide() + self.loadscreen.hide() self.printcores_page.show() self.disable_popups.emit(True) self.printcores_page.setText( "Please Insert Print Core \n \n Afterwards click continue" ) if value["swapping"] == "unloading": - self.loadpage.set_status_message("Unloading print core") - - if value["swapping"] == "cleaning": - self.loadpage.set_status_message("Cleaning print core") - - - + self.loadwidget.set_status_message("Unloading print core") + if value["swapping"] == "cleaning": + self.loadwidget.set_status_message("Cleaning print core") def _handle_gcode_response(self, messages: list): """Handle gcode response for Z-tilt adjustment""" @@ -305,10 +439,12 @@ def _handle_gcode_response(self, messages: list): if not msg_list: continue - if "Retries:" in msg_list and "range:" in msg_list and "tolerance:" in msg_list: - print("Match candidate:", msg_list) + if ( + "Retries:" in msg_list + and "range:" in msg_list + and "tolerance:" in msg_list + ): match = re.search(pattern, msg_list) - print("Regex match:", match) if match: retries_done = int(match.group(1)) @@ -316,22 +452,23 @@ def _handle_gcode_response(self, messages: list): probed_range = float(match.group(3)) tolerance = float(match.group(4)) if retries_done == retries_total: - self.loadpage.hide() + self.loadscreen.hide() return if probed_range < tolerance: - self.loadpage.hide() + self.loadscreen.hide() return - self.loadpage.set_status_message( + self.loadwidget.set_status_message( f"Retries: {retries_done}/{retries_total} | Range: {probed_range:.6f} | Tolerance: {tolerance:.6f}" ) - def handle_ztilt(self): """Handle Z-Tilt Adjustment""" - self.loadpage.show() - self.loadpage.set_status_message("Please wait, performing Z-axis calibration.") + self.loadscreen.show() + self.loadwidget.set_status_message( + "Please wait, performing Z-axis calibration." + ) self.run_gcode_signal.emit("G28\nM400\nZ_TILT_ADJUST") @QtCore.pyqtSlot(str, name="on-klippy-status") @@ -348,9 +485,8 @@ def on_klippy_status(self, state: str): def show_swapcore(self): """Show swap printcore""" self.run_gcode_signal.emit("CHANGE_PRINTCORES") - self.loadpage.show() - self.loadpage.set_status_message("Preparing to swap print core") - + self.loadscreen.show() + self.loadwidget.set_status_message("Preparing to swap print core") def handle_swapcore(self): """Handle swap printcore routine finish""" @@ -521,8 +657,8 @@ def on_toolhead_update(self, field: str, values: list) -> None: self.panel.mva_y_value_label.setText(f"{values[1]:.2f}") self.panel.mva_z_value_label.setText(f"{values[2]:.3f}") - if values[0] == "252,50" and values[1] == "250" and values[2] == "50": - self.loadpage.hide + if values[0] == "252,50" and values[1] == "250" and values[2] == "50": + self.loadscreen.hide self.toolhead_info.update({f"{field}": values}) @QtCore.pyqtSlot(str, str, float, name="on-extruder-update") diff --git a/BlocksScreen/lib/panels/filamentTab.py b/BlocksScreen/lib/panels/filamentTab.py index ceb77a2a..1271375e 100644 --- a/BlocksScreen/lib/panels/filamentTab.py +++ b/BlocksScreen/lib/panels/filamentTab.py @@ -1,5 +1,4 @@ import enum -import typing from functools import partial @@ -7,7 +6,8 @@ from lib.filament import Filament from lib.ui.filamentStackedWidget_ui import Ui_filamentStackedWidget -from lib.panels.widgets.loadPage import LoadScreen +from lib.panels.widgets.loadWidget import LoadingOverlayWidget +from lib.panels.widgets.basePopup import BasePopup from lib.panels.widgets.popupDialogWidget import Popup from PyQt6 import QtCore, QtGui, QtWidgets @@ -42,8 +42,11 @@ def __init__(self, parent: QtWidgets.QWidget, printer: Printer, ws, /) -> None: self.target_temp: int = 0 self.current_temp: int = 0 self.popup = Popup(self) - self.loadscreen = LoadScreen(self, LoadScreen.AnimationGIF.DEFAULT) - self.addWidget(self.loadscreen) + self.loadscreen = BasePopup(self, floating=False, dialog=False) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.loadscreen.add_widget(self.loadwidget) self.has_load_unload_objects = None self._filament_state = self.FilamentStates.UNKNOWN self._sensor_states = {} @@ -52,7 +55,6 @@ def __init__(self, parent: QtWidgets.QWidget, printer: Printer, ws, /) -> None: partial(self.change_page, self.indexOf(self.panel.load_page)) ) self.panel.custom_filament_header_back_btn.clicked.connect(self.back_button) - # REFACTOR self.panel.load_custom_btn.clicked.connect(partial(self.change_page, 2)) self.panel.load_custom_btn.hide() self.panel.load_header_back_button.clicked.connect(self.back_button) self.panel.load_pla_btn.clicked.connect( @@ -95,9 +97,7 @@ def __init__(self, parent: QtWidgets.QWidget, printer: Printer, ws, /) -> None: @QtCore.pyqtSlot(str, float, name="on_print_stats_update") @QtCore.pyqtSlot(str, str, name="on_print_stats_update") def on_print_stats_update(self, field: str, value: dict | float | str) -> None: - """ - unblocks tabs if on standby - """ + """Handle print stats object update""" if isinstance(value, str): if "state" in field: if value in ("standby"): @@ -106,6 +106,7 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None: @QtCore.pyqtSlot(str, str, bool, name="on_filament_sensor_update") def on_filament_sensor_update(self, sensor_name: str, parameter: str, value: bool): + """Handle filament sensor object update""" if parameter == "filament_detected": if not isinstance(value, bool): self._filament_state = self.FilamentStates.UNKNOWN @@ -126,24 +127,26 @@ def on_filament_sensor_update(self, sensor_name: str, parameter: str, value: boo def on_extruder_update( self, extruder_name: str, field: str, new_value: float ) -> None: + """Handle extruder update""" if not self.isVisible: return if self.target_temp != 0: if self.current_temp == self.target_temp: - self.loadscreen.set_status_message("Extruder heated up \n Please wait") + self.loadwidget.set_status_message("Extruder heated up \n Please wait") return if field == "temperature": - self.current_temp = round(new_value, 0) # somehow this works - self.loadscreen.set_status_message( + self.current_temp = round(new_value, 0) + self.loadwidget.set_status_message( f"Heating up ({new_value}/{self.target_temp}) \n Please wait" ) if field == "target": - self.target_temp = round(new_value, 0) # somehow this works again - self.loadscreen.set_status_message("Heating up \n Please wait") + self.target_temp = round(new_value, 0) + self.loadwidget.set_status_message("Heating up \n Please wait") @QtCore.pyqtSlot(bool, name="on_load_filament") def on_load_filament(self, status: bool): + """Handle load filament object updated""" if self.loadignore: self.loadignore = False return @@ -160,6 +163,7 @@ def on_load_filament(self, status: bool): @QtCore.pyqtSlot(bool, name="on_unload_filament") def on_unload_filament(self, status: bool): + """Handle unload filament object updated""" if self.unloadignore: self.unloadignore = False return @@ -177,6 +181,7 @@ def on_unload_filament(self, status: bool): @QtCore.pyqtSlot(int, int, name="load_filament") def load_filament(self, toolhead: int = 0, temp: int = 220) -> None: + """Handle load filament buttons clicked""" if not self.isVisible: return @@ -197,6 +202,7 @@ def load_filament(self, toolhead: int = 0, temp: int = 220) -> None: @QtCore.pyqtSlot(str, int, name="unload_filament") def unload_filament(self, toolhead: int = 0, temp: int = 220) -> None: + """Handle unload filament button clicked""" if not self.isVisible: return @@ -218,6 +224,7 @@ def unload_filament(self, toolhead: int = 0, temp: int = 220) -> None: self.run_gcode.emit(f"UNLOAD_FILAMENT TEMPERATURE={temp}") def handle_filament_state(self): + """Handle ui changes on filament states""" if self._filament_state == self.FilamentStates.LOADED: self.panel.filament_page_load_btn.setDisabled(True) self.panel.filament_page_load_btn.setDisabled(False) @@ -233,29 +240,22 @@ def filament_state(self): return self._filament_state def change_page(self, index): + """Issue a page change""" self.request_change_page.emit(1, index) def back_button(self): + """Go back a page""" self.request_back.emit() - def sizeHint(self) -> QtCore.QSize: - return super().sizeHint() - def paintEvent(self, a0: QtGui.QPaintEvent | None) -> None: + """Widget painting""" if self.panel.load_page.isVisible() and self.toolhead_count == 1: self.panel.load_header_page_title.setText("Load Toolhead") if a0 is not None: return super().paintEvent(a0) - def removeWidget(self, w: QtWidgets.QWidget | None) -> None: - if w is not None: - return super().removeWidget(w) - - def resizeEvent(self, a0: QtGui.QResizeEvent | None) -> None: - if a0 is not None: - return super().resizeEvent(a0) - def find_routine_objects(self): + """Check if printer has load/unload printer objects""" if not self.printer: return diff --git a/BlocksScreen/lib/panels/instructionsWindow.py b/BlocksScreen/lib/panels/instructionsWindow.py deleted file mode 100644 index cce27c46..00000000 --- a/BlocksScreen/lib/panels/instructionsWindow.py +++ /dev/null @@ -1,30 +0,0 @@ -from PyQt6.QtWidgets import QStackedWidget, QWidget -from PyQt6 import QtCore -import typing - -from lib.ui.instructionsWindow_ui import Ui_utilitiesStackedWidget - - -# TODO: Complete this panel - -class InstructionsWindow(QStackedWidget): - - def __init__(self, parent: typing.Optional[QWidget] = ...) -> None: - super().__init__(parent) - - self.panel = Ui_utilitiesStackedWidget() - self.panel.setupUi(self) - # self.show() - - self.index_stack = [] - - # Connecting the print_btn.clicked event to the change_page method - #self.panel.main_print_btn.clicked.connect(self.change_page) - #self.panel.files_back_folder_btn.clicked.connect(self.change_page) - - - def change_page(self, int): - self.setCurrentIndex(int) - self.index_stack.append(self.currentIndex()) - - \ No newline at end of file diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index f381febb..3ea87f52 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -16,6 +16,7 @@ from lib.panels.widgets.popupDialogWidget import Popup from lib.printer import Printer from lib.ui.mainWindow_ui import Ui_MainWindow # With header +from lib.panels.widgets.updatePage import UpdatePage # from lib.ui.mainWindow_v2_ui import Ui_MainWindow # No header from lib.ui.resources.background_resources_rc import * @@ -35,11 +36,12 @@ def api_handler(func): """Decorator for methods that handle api responses""" def wrapper(*args, **kwargs): + """Decorator for api_handler""" try: result = func(*args, **kwargs) return result except Exception as e: - _logger.error(f"Caught Exception in %s : %s ", func.__name__, e) + _logger.error("Caught Exception in %s : %s ", func.__name__, e) raise return wrapper @@ -57,6 +59,7 @@ class MainWindow(QtWidgets.QMainWindow): gcode_response = QtCore.pyqtSignal(list, name="gcode_response") handle_error_response = QtCore.pyqtSignal(list, name="handle_error_response") call_network_panel = QtCore.pyqtSignal(name="call-network-panel") + call_update_panel = QtCore.pyqtSignal(name="call-update-panel") on_update_message: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( dict, name="on-update-message" ) @@ -76,10 +79,14 @@ def __init__(self): self.index_stack = deque(maxlen=4) self.printer = Printer(self, self.ws) self.conn_window = ConnectionPage(self, self.ws) + self.up = UpdatePage(self) + self.up.hide() self.installEventFilter(self.conn_window) self.printPanel = PrintTab( self.ui.printTab, self.file_data, self.ws, self.printer ) + QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.CursorShape.BlankCursor) + self.filamentPanel = FilamentTab(self.ui.filamentTab, self.printer, self.ws) self.controlPanel = ControlTab(self.ui.controlTab, self.ws, self.printer) self.utilitiesPanel = UtilitiesTab(self.ui.utilitiesTab, self.ws, self.printer) @@ -149,32 +156,71 @@ def __init__(self): self.controlPanel.probe_helper_page.handle_error_response ) self.controlPanel.disable_popups.connect(self.popup_toggle) - self.on_update_message.connect(self.utilitiesPanel.on_update_message) + self.on_update_message.connect(self.up.handle_update_message) + self.up.request_full_update.connect(self.ws.api.full_update) + self.up.request_recover_repo[str].connect(self.ws.api.recover_corrupt_repo) + self.up.request_recover_repo[str, bool].connect( + self.ws.api.recover_corrupt_repo + ) + self.up.request_refresh_update.connect(self.ws.api.refresh_update_status) + self.up.request_refresh_update[str].connect(self.ws.api.refresh_update_status) + self.up.request_rollback_update.connect(self.ws.api.rollback_update) + self.up.request_update_client.connect(self.ws.api.update_client) + self.up.request_update_klipper.connect(self.ws.api.update_klipper) + self.up.request_update_moonraker.connect(self.ws.api.update_moonraker) + self.up.request_update_status.connect(self.ws.api.update_status) + self.up.request_update_system.connect(self.ws.api.update_system) + self.up.update_back_btn.clicked.connect(self.up.hide) + self.utilitiesPanel.show_update_page.connect(self.show_update_page) + self.conn_window.update_button_clicked.connect(self.show_update_page) self.ui.extruder_temp_display.display_format = "upper_downer" self.ui.bed_temp_display.display_format = "upper_downer" if self.config.has_section("server"): # @ Start websocket connection with moonraker self.bo_ws_startup.emit() self.reset_tab_indexes() + + @QtCore.pyqtSlot(bool, name="show-update-page") + def show_update_page(self, fullscreen: bool): + """Slot for displaying update Panel""" + if not fullscreen: + self.up.setParent(self.ui.main_content_widget) + current_index = self.ui.main_content_widget.currentIndex() + tab_rect = self.ui.main_content_widget.tabBar().tabRect(current_index) + width = tab_rect.width() + _parent_size = self.up.parent().size() + self.up.setGeometry( + width, 0, _parent_size.width() - width, _parent_size.height() + ) + else: + self.up.setParent(self) + self.up.setGeometry(0, 0, self.width(), self.height()) + + self.up.raise_() + self.up.updateGeometry() + self.up.repaint() + self.up.show() + @QtCore.pyqtSlot(name="on-cancel-print") def on_cancel_print(self): - self.enable_tab_bar() - self.ui.extruder_temp_display.clicked.disconnect() - self.ui.bed_temp_display.clicked.disconnect() - self.ui.filament_type_icon.setDisabled(False) - self.ui.nozzle_size_icon.setDisabled(False) - self.ui.extruder_temp_display.clicked.connect( - lambda: self.global_change_page( - self.ui.main_content_widget.indexOf(self.ui.controlTab), - self.controlPanel.indexOf(self.controlPanel.panel.temperature_page), - ) + """Slot for cancel print signal""" + self.enable_tab_bar() + self.ui.extruder_temp_display.clicked.disconnect() + self.ui.bed_temp_display.clicked.disconnect() + self.ui.filament_type_icon.setDisabled(False) + self.ui.nozzle_size_icon.setDisabled(False) + self.ui.extruder_temp_display.clicked.connect( + lambda: self.global_change_page( + self.ui.main_content_widget.indexOf(self.ui.controlTab), + self.controlPanel.indexOf(self.controlPanel.panel.temperature_page), ) - self.ui.bed_temp_display.clicked.connect( - lambda: self.global_change_page( - self.ui.main_content_widget.indexOf(self.ui.controlTab), - self.controlPanel.indexOf(self.controlPanel.panel.temperature_page), - ) + ) + self.ui.bed_temp_display.clicked.connect( + lambda: self.global_change_page( + self.ui.main_content_widget.indexOf(self.ui.controlTab), + self.controlPanel.indexOf(self.controlPanel.panel.temperature_page), ) + ) @QtCore.pyqtSlot(bool, name="update-available") def on_update_available(self, state: bool = False): @@ -262,6 +308,7 @@ def reset_tab_indexes(self): Used to grantee all tabs reset to their first page once the user leaves the tab """ + self.up.hide() self.printPanel.setCurrentIndex(0) self.filamentPanel.setCurrentIndex(0) self.controlPanel.setCurrentIndex(0) @@ -492,14 +539,7 @@ def _handle_notify_klippy_message(self, method, data, metadata) -> None: @api_handler def _handle_notify_filelist_changed_message(self, method, data, metadata) -> None: """Handle websocket file list messages""" - _file_change_list = data.get("params") - if _file_change_list: - fileaction = _file_change_list[0].get("action") - filepath = ( - _file_change_list[0].get("item").get("path") - ) # TODO : NOTIFY_FILELIST_CHANGED, I DON'T KNOW IF I REALLY WANT TO SEND NOTIFICATIONS ON FILE CHANGES. ... - # self.file_data.request_file_list.emit() @api_handler def _handle_notify_service_state_changed_message( @@ -508,15 +548,13 @@ def _handle_notify_service_state_changed_message( """Handle websocket service messages""" entry = data.get("params") if entry: - if not self._popup_toggle: + if self._popup_toggle: return service_entry: dict = entry[0] service_name, service_info = service_entry.popitem() self.popup.new_message( message_type=Popup.MessageType.INFO, - message=f"""{service_name} service changed state to - {service_info.get("sub_state")} - """, + message=f"{service_name} service changed state to \n{service_info.get('sub_state')}", ) @api_handler @@ -525,15 +563,18 @@ def _handle_notify_gcode_response_message(self, method, data, metadata) -> None: _gcode_response = data.get("params") self.gcode_response[list].emit(_gcode_response) if _gcode_response: - if not self._popup_toggle: + if self._popup_toggle: return _gcode_msg_type, _message = str(_gcode_response[0]).split(" ", maxsplit=1) - _msg_type = Popup.MessageType.UNKNOWN - if _gcode_msg_type == "!!": - _msg_type = Popup.MessageType.ERROR - elif _gcode_msg_type == "//": - _msg_type = Popup.MessageType.INFO - self.popup.new_message(message_type=_msg_type, message=str(_message)) + popupWhitelist = ["filament runout", "no filament"] + if _message.lower() not in popupWhitelist or _gcode_msg_type != "!!": + return + + self.popup.new_message( + message_type=Popup.MessageType.ERROR, + message=str(_message), + userInput=True, + ) @api_handler def _handle_error_message(self, method, data, metadata) -> None: @@ -542,17 +583,24 @@ def _handle_error_message(self, method, data, metadata) -> None: if "metadata" in data.get("message", "").lower(): # Quick fix, don't care about no metadata errors return - if not self._popup_toggle: + if self._popup_toggle: return + text = data + if isinstance(data, dict): + if "message" in data: + text = f"{data['message']}" + else: + text = data self.popup.new_message( message_type=Popup.MessageType.ERROR, - message=str(data), + message=str(text), + userInput=True, ) @api_handler def _handle_notify_cpu_throttled_message(self, method, data, metadata) -> None: """Handle websocket cpu throttled messages""" - if not self._popup_toggle: + if self._popup_toggle: return self.popup.new_message( message_type=Popup.MessageType.WARNING, @@ -622,7 +670,6 @@ def event(self, event: QtCore.QEvent) -> bool: self.messageReceivedEvent(event) return True return False - if event.type() == events.PrintStart.type(): self.disable_tab_bar() self.ui.extruder_temp_display.clicked.disconnect() @@ -643,10 +690,10 @@ def event(self, event: QtCore.QEvent) -> bool: ) return False - if event.type() == ( - events.PrintError.type() - or events.PrintComplete.type() - or events.PrintCancelled.type() + if event.type() in ( + events.PrintError.type(), + events.PrintComplete.type(), + events.PrintCancelled.type(), ): self.enable_tab_bar() self.ui.extruder_temp_display.clicked.disconnect() diff --git a/BlocksScreen/lib/panels/networkWindow.py b/BlocksScreen/lib/panels/networkWindow.py index 5df4b81d..f9636a82 100644 --- a/BlocksScreen/lib/panels/networkWindow.py +++ b/BlocksScreen/lib/panels/networkWindow.py @@ -1,17 +1,18 @@ import logging +import subprocess # nosec: B404 import typing -import subprocess from functools import partial from lib.network import SdbusNetworkManagerAsync +from lib.panels.widgets.keyboardPage import CustomQwertyKeyboard from lib.panels.widgets.popupDialogWidget import Popup from lib.ui.wifiConnectivityWindow_ui import Ui_wifi_stacked_page from lib.utils.list_button import ListCustomButton -from lib.panels.widgets.keyboardPage import CustomQwertyKeyboard from PyQt6 import QtCore, QtGui, QtWidgets logger = logging.getLogger("logs/BlocksScreen.log") + class BuildNetworkList(QtCore.QThread): """Retrieves information from sdbus interface about scanned networks""" @@ -222,7 +223,7 @@ def __init__(self, parent: typing.Optional[QtWidgets.QWidget], /) -> None: ) ) self.delete_network_signal.connect(self.delete_network) - self.panel.saved_connection_change_password_field.returnPressed.connect( + self.panel.snd_back.clicked.connect( lambda: self.update_network( ssid=self.panel.saved_connection_network_name.text(), password=self.panel.saved_connection_change_password_field.text(), @@ -233,7 +234,7 @@ def __init__(self, parent: typing.Optional[QtWidgets.QWidget], /) -> None: partial( self.panel.saved_connection_change_password_field.setEchoMode, QtWidgets.QLineEdit.EchoMode.Normal, - ) + ) ) self.panel.saved_connection_change_password_view.released.connect( partial( @@ -315,6 +316,16 @@ def __init__(self, parent: typing.Optional[QtWidgets.QWidget], /) -> None: QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg") ) + self.panel.network_details_btn.setPixmap( + QtGui.QPixmap(":/ui/media/btn_icons/printer_settings.svg") + ) + + self.panel.snd_back.clicked.connect( + lambda: self.setCurrentIndex(self.indexOf(self.panel.saved_connection_page)) + ) + self.panel.network_details_btn.clicked.connect( + lambda: self.setCurrentIndex(self.indexOf(self.panel.saved_details_page)) + ) self.panel.network_activate_btn.clicked.connect( lambda: self.saved_wifi_option_selected() @@ -356,30 +367,41 @@ def __init__(self, parent: typing.Optional[QtWidgets.QWidget], /) -> None: ) def saved_wifi_option_selected(self): + """Handle connect/delete network button clicks""" _sender = self.sender() - self.panel.wifi_button.toggle_button.state = self.panel.wifi_button.toggle_button.State.ON - self.panel.hotspot_button.toggle_button.state = self.panel.hotspot_button.toggle_button.State.OFF + self.panel.wifi_button.toggle_button.state = ( + self.panel.wifi_button.toggle_button.State.ON + ) + self.panel.hotspot_button.toggle_button.state = ( + self.panel.hotspot_button.toggle_button.State.OFF + ) if _sender == self.panel.network_delete_btn: - self.sdbus_network.delete_network(self.panel.saved_connection_network_name.text()) + self.sdbus_network.delete_network( + self.panel.saved_connection_network_name.text() + ) self.setCurrentIndex(self.indexOf(self.panel.main_network_page)) elif _sender == self.panel.network_activate_btn: self.setCurrentIndex(self.indexOf(self.panel.main_network_page)) - self.sdbus_network.connect_network(self.panel.saved_connection_network_name.text()) + self.sdbus_network.connect_network( + self.panel.saved_connection_network_name.text() + ) self.info_box_load(True) - def on_show_keyboard(self, panel: QtWidgets.QWidget, field: QtWidgets.QLineEdit): + """Handle keyboard show""" self.previousPanel = panel self.currentField = field self.qwerty.set_value(field.text()) self.setCurrentIndex(self.indexOf(self.qwerty)) def on_qwerty_go_back(self): + """Hide keyboard""" self.setCurrentIndex(self.indexOf(self.previousPanel)) def on_qwerty_value_selected(self, value: str): + """Handle keyboard value input""" self.setCurrentIndex(self.indexOf(self.previousPanel)) if hasattr(self, "currentField") and self.currentField: self.currentField.setText(value) @@ -390,16 +412,15 @@ def info_box_load(self, toggle: bool = False) -> None: Sets a 30-second timeout to handle loading failures. """ self._show_loadscreen(toggle) - + self.panel.wifi_button.setEnabled(not toggle) self.panel.hotspot_button.setEnabled(not toggle) - + if toggle: if self._load_timer.isActive(): self._load_timer.stop() self._load_timer.start(30000) - def _handle_load_timeout(self): """ Logic to execute if the loading screen is still visible after 30 seconds.< @@ -416,12 +437,10 @@ def _handle_load_timeout(self): else: message = "Loading timed out.\n Please check your connection \n and try again." - - self.panel.mn_info_box.setText(message) self._show_loadscreen(False) self._expand_infobox(True) - + hotspot_btn.setEnabled(True) wifi_btn.setEnabled(True) @@ -448,10 +467,11 @@ def _show_loadscreen(self, toggle: bool = False): @QtCore.pyqtSlot(object, name="stateChange") def on_toggle_state(self, new_state) -> None: + """Handle toggle button changes""" sender_button = self.sender() wifi_btn = self.panel.wifi_button.toggle_button hotspot_btn = self.panel.hotspot_button.toggle_button - is_sender_now_on = (new_state == sender_button.State.ON) + is_sender_now_on = new_state == sender_button.State.ON _old_hotspot = None saved_network = self.sdbus_network.get_saved_networks() @@ -463,8 +483,13 @@ def on_toggle_state(self, new_state) -> None: if saved_network: try: ssid = next( - (n["ssid"] for n in saved_network if "ap" not in n['mode']and n["signal"] != 0), - None) + ( + n["ssid"] + for n in saved_network + if "ap" not in n["mode"] and n["signal"] != 0 + ), + None, + ) self.sdbus_network.connect_network(str(ssid)) except Exception as e: @@ -532,7 +557,7 @@ def evaluate_network_state(self, nm_state: str = "") -> None: break if _old_hotspot: self.panel.hotspot_name_input_field.setText(_old_hotspot["ssid"]) - + connection = self.sdbus_network.check_connectivity() if connection == "FULL": self.panel.wifi_button.toggle_button.state = ( @@ -547,23 +572,22 @@ def evaluate_network_state(self, nm_state: str = "") -> None: ) self.panel.hotspot_button.toggle_button.state = ( self.panel.hotspot_button.toggle_button.State.ON - ) - + ) if not self.sdbus_network.check_wifi_interface(): return if hotspot_btn.state == hotspot_btn.State.ON: - ipv4_addr = self.get_hotspot_ip_via_shell("wlan0") + ipv4_addr = self.get_hotspot_ip_via_shell() self.panel.netlist_ssuid.setText(self.panel.hotspot_name_input_field.text()) - self.panel.netlist_ip.setText(f"IP: {ipv4_addr or 'No IP Address'}") + self.panel.netlist_ip.setText(f"IP: {ipv4_addr or 'No IP Address'}") self.panel.netlist_strength.setText("--") - + self.panel.netlist_security.setText("--") - + self.panel.mn_info_box.setText("Hotspot On") if wifi_btn.state == wifi_btn.State.ON: @@ -589,7 +613,6 @@ def evaluate_network_state(self, nm_state: str = "") -> None: self.panel.hotspot_button.setEnabled(True) self.repaint() - if ( wifi_btn.state == wifi_btn.State.OFF and hotspot_btn.state == hotspot_btn.State.OFF @@ -600,37 +623,47 @@ def evaluate_network_state(self, nm_state: str = "") -> None: "Network connection required.\n\nConnect to Wi-Fi\nor\nTurn on Hotspot" ) - def get_hotspot_ip_via_shell(self, interface: str): + def get_hotspot_ip_via_shell(self): """ Executes a shell command to retrieve the IPv4 address for a specified interface. - Args: - interface: The name of the hotspot interface (e.g., 'wlan0'). Returns: The IP address string (e.g., '10.42.0.1') or None if not found. """ - command = ( - f"ip a show {interface} | grep 'inet ' | awk '{{print $2}}' | cut -d/ -f1" - ) + command = ["ip", "-4", "addr", "show", "wlan0"] try: - result = subprocess.run( + result = subprocess.run( # nosec: B603 command, - shell=True, capture_output=True, text=True, check=True, timeout=5, ) - - ip_addr = result.stdout.strip() - if ip_addr and len(ip_addr.split(".")) == 4: - return ip_addr - except Exception as e: - print(f"An unexpected error occurred: {e}") - - return None + except subprocess.CalledProcessError as e: + logging.error( + "Caught exception (exit code %d) failed to run command: %s \nStderr: %s", + e.returncode, + command, + e.stderr.strip(), + ) + return "" + except FileNotFoundError: + logging.error("Command not found") + return "" + except subprocess.TimeoutExpired as e: + logging.error("Caught exception, failed to run command %s", e) + return "" + + for line in result.stdout.splitlines(): + line = line.strip() + if line.startswith("inet "): + ip_address = line.split()[1].split("/")[0] + return ip_address + logging.error("No IPv4 address found in output for wlan0") + return "" def close(self) -> bool: + """Close class, close network module""" self.sdbus_network.close() return super().close() @@ -656,14 +689,17 @@ def _expand_infobox(self, toggle: bool = False) -> None: @QtCore.pyqtSlot(str, name="delete-network") def delete_network(self, ssid: str) -> None: + """Delete network""" self.sdbus_network.delete_network(ssid=ssid) @QtCore.pyqtSlot(name="rescan-networks") def rescan_networks(self) -> None: + """Rescan for networks""" self.sdbus_network.rescan_networks() @QtCore.pyqtSlot(name="handle-hotspot-back") def handle_hotspot_back(self) -> None: + """Handle go back a page from hotspot page""" if ( self.panel.hotspot_password_input_field.text() != self.sdbus_network.hotspot_password @@ -709,7 +745,12 @@ def add_network(self) -> None: if not error_msg: # Assume it was a success QtCore.QTimer().singleShot(5000, self.network_list_worker.build) - QtCore.QTimer().singleShot(5000, lambda: self.sdbus_network.connect_network(self.panel.add_network_network_label.text())) + QtCore.QTimer().singleShot( + 5000, + lambda: self.sdbus_network.connect_network( + self.panel.add_network_network_label.text() + ), + ) self.info_box_load(True) self.setCurrentIndex(self.indexOf(self.panel.main_network_page)) self.panel.add_network_validation_button.setEnabled(True) @@ -731,7 +772,6 @@ def add_network(self) -> None: self.panel.add_network_validation_button.setEnabled(True) self.panel.add_network_validation_button.repaint() self.popup.new_message(message_type=Popup.MessageType.ERROR, message=message) - @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, name="ssid_item_clicked") def ssid_item_clicked(self, item: QtWidgets.QListWidgetItem) -> None: @@ -765,19 +805,27 @@ def update_network( password: typing.Union[str, None], new_ssid: typing.Union[str, None], ) -> None: + """Update network information""" if not self.sdbus_network.is_known(ssid): return + checked_btn = self.panel.priority_btn_group.checkedButton() + if checked_btn == self.panel.high_priority_btn: + priority = 90 + elif checked_btn == self.panel.low_priority_btn: + priority = 20 + else: + priority = 50 + self.sdbus_network.update_connection_settings( - ssid=ssid, - password=password, - new_ssid=new_ssid, + ssid=ssid, password=password, new_ssid=new_ssid, priority=priority ) QtCore.QTimer().singleShot(10000, lambda: self.network_list_worker.build()) self.setCurrentIndex(self.indexOf(self.panel.network_list_page)) @QtCore.pyqtSlot(list, name="finished-network-list-build") def handle_network_list(self, data: typing.List[typing.Tuple]) -> None: + """Handle available network list update""" scroll_bar_position = self.network_list_widget.verticalScrollBar().value() self.network_list_widget.blockSignals(True) self.network_list_widget.clear() @@ -804,14 +852,34 @@ def handle_network_list(self, data: typing.List[typing.Tuple]) -> None: def handle_button_click(self, ssid: str): """Handles pressing a network""" - if ssid in self.sdbus_network.get_saved_ssid_names(): + _saved_ssids = self.sdbus_network.get_saved_networks() + if any(item["ssid"] == ssid for item in _saved_ssids): self.setCurrentIndex(self.indexOf(self.panel.saved_connection_page)) self.panel.saved_connection_network_name.setText(str(ssid)) + self.panel.snd_name.setText(str(ssid)) + + # find the entry for this SSID + entry = next((item for item in _saved_ssids if item["ssid"] == ssid), None) + + logger.debug(_saved_ssids) + + if entry is not None: + priority = entry.get("priority") + + if priority == 90: + self.panel.high_priority_btn.setChecked(True) + elif priority == 20: + self.panel.low_priority_btn.setChecked(True) + else: + self.panel.med_priority_btn.setChecked(True) _curr_ssid = self.sdbus_network.get_current_ssid() if _curr_ssid != str(ssid): - self.panel.network_activate_btn.show() + self.panel.network_activate_btn.setDisabled(False) + self.panel.sn_info.setText("Saved Network") else: - self.panel.network_activate_btn.hide() + self.panel.network_activate_btn.setDisabled(True) + self.panel.sn_info.setText("Active Network") + self.panel.frame.repaint() else: diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py index d899c5e1..6331585a 100644 --- a/BlocksScreen/lib/panels/printTab.py +++ b/BlocksScreen/lib/panels/printTab.py @@ -1,24 +1,28 @@ +import logging import os import typing from functools import partial -from lib.panels.widgets.babystepPage import BabystepPage -from lib.panels.widgets.tunePage import TuneWidget +from configfile import BlocksScreenConfig, get_configparser from lib.files import Files from lib.moonrakerComm import MoonWebSocket +from lib.panels.widgets.babystepPage import BabystepPage +from lib.panels.widgets.basePopup import BasePopup from lib.panels.widgets.confirmPage import ConfirmWidget from lib.panels.widgets.filesPage import FilesPage from lib.panels.widgets.jobStatusPage import JobStatusWidget +from lib.panels.widgets.loadWidget import LoadingOverlayWidget +from lib.panels.widgets.numpadPage import CustomNumpad from lib.panels.widgets.sensorsPanel import SensorsWindow -from lib.printer import Printer from lib.panels.widgets.slider_selector_page import SliderPage +from lib.panels.widgets.tunePage import TuneWidget +from lib.printer import Printer from lib.utils.blocks_button import BlocksCustomButton -from lib.panels.widgets.numpadPage import CustomNumpad -from lib.panels.widgets.loadPage import LoadScreen -from lib.panels.widgets.dialogPage import DialogPage -from configfile import BlocksScreenConfig, get_configparser +from lib.utils.display_button import DisplayButton from PyQt6 import QtCore, QtGui, QtWidgets +logger = logging.getLogger(name="logs/BlocksScreen.log") + class PrintTab(QtWidgets.QStackedWidget): """QStackedWidget that contains the following widget panels: @@ -42,15 +46,15 @@ class PrintTab(QtWidgets.QStackedWidget): """ - request_query_print_stats: typing.ClassVar[QtCore.pyqtSignal] = ( - QtCore.pyqtSignal(dict, name="request_query_print_stats") + request_query_print_stats: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + dict, name="request_query_print_stats" ) request_back: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( name="request-back" ) - request_change_page: typing.ClassVar[QtCore.pyqtSignal] = ( - QtCore.pyqtSignal(int, int, name="request_change_page") + request_change_page: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + int, int, name="request_change_page" ) run_gcode_signal: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( @@ -61,6 +65,8 @@ class PrintTab(QtWidgets.QStackedWidget): ) _z_offset: float = 0.0 + _active_z_offset: float = 0.0 + _finish_print_handled: bool = False def __init__( self, @@ -79,22 +85,25 @@ def __init__( self.gcode_path = os.path.expanduser("~/printer_data/gcodes") self.setMouseTracking(True) - self.sliderPage = SliderPage(self) self.addWidget(self.sliderPage) self.sliderPage.request_back.connect(self.back_button) self.numpadPage = CustomNumpad(self) self.numpadPage.request_back.connect(self.back_button) self.addWidget(self.numpadPage) - self.loadscreen = LoadScreen(self, LoadScreen.AnimationGIF.DEFAULT) - self.addWidget(self.loadscreen) + + self.loadscreen = BasePopup(self, floating=False, dialog=False) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.loadscreen.add_widget(self.loadwidget) self.file_data: Files = file_data self.filesPage_widget = FilesPage(self) self.addWidget(self.filesPage_widget) - self.dialogPage = DialogPage(self) - + self.BasePopup = BasePopup(self) + self.BasePopup_z_offset = BasePopup(self, floating=True) self.confirmPage_widget = ConfirmWidget(self) self.addWidget(self.confirmPage_widget) @@ -124,13 +133,10 @@ def __init__( self.filesPage_widget.request_dir_info[str].connect( self.file_data.request_dir_info[str] ) - self.filesPage_widget.request_dir_info.connect( - self.file_data.request_dir_info - ) + self.filesPage_widget.request_dir_info.connect(self.file_data.request_dir_info) self.file_data.on_file_list.connect(self.filesPage_widget.on_file_list) self.jobStatusPage_widget = JobStatusWidget(self) self.addWidget(self.jobStatusPage_widget) - self.confirmPage_widget.on_accept.connect( self.jobStatusPage_widget.on_print_start ) @@ -138,7 +144,6 @@ def __init__( lambda: self.change_page(self.indexOf(self.jobStatusPage_widget)) ) self.jobStatusPage_widget.hide_request.connect( - # lambda: self.change_page(self.indexOf(self.panel.print_page)) lambda: self.change_page(self.indexOf(self.print_page)) ) self.jobStatusPage_widget.request_file_info.connect( @@ -146,13 +151,10 @@ def __init__( ) self.file_data.fileinfo.connect(self.jobStatusPage_widget.on_fileinfo) self.jobStatusPage_widget.print_start.connect(self.ws.api.start_print) - self.jobStatusPage_widget.print_resume.connect( - self.ws.api.resume_print - ) - self.jobStatusPage_widget.print_cancel.connect( - self.handle_cancel_print - ) + self.jobStatusPage_widget.print_resume.connect(self.ws.api.resume_print) + self.jobStatusPage_widget.print_cancel.connect(self.handle_cancel_print) self.jobStatusPage_widget.print_pause.connect(self.ws.api.pause_print) + self.jobStatusPage_widget.print_finish.connect(self.finish_print_signal) self.jobStatusPage_widget.request_query_print_stats.connect( self.ws.api.object_query ) @@ -175,25 +177,16 @@ def __init__( self.printer.print_stats_update[str, float].connect( self.jobStatusPage_widget.on_print_stats_update ) - - self.printer.print_stats_update[str, str].connect( - self.on_print_stats_update - ) - self.printer.print_stats_update[str, dict].connect( - self.on_print_stats_update - ) - self.printer.print_stats_update[str, float].connect( - self.on_print_stats_update - ) - + self.printer.print_stats_update[str, str].connect(self.on_print_stats_update) + self.printer.print_stats_update[str, dict].connect(self.on_print_stats_update) + self.printer.print_stats_update[str, float].connect(self.on_print_stats_update) self.printer.gcode_move_update[str, list].connect( self.jobStatusPage_widget.on_gcode_move_update ) - + self.printer.request_available_objects_signal.connect(self.klipper_ready_signal) self.babystepPage = BabystepPage(self) self.babystepPage.request_back.connect(self.back_button) self.addWidget(self.babystepPage) - self.tune_page = TuneWidget(self) self.addWidget(self.tune_page) self.jobStatusPage_widget.tune_clicked.connect( @@ -218,16 +211,17 @@ def __init__( self.printer.gcode_move_update[str, list].connect( self.babystepPage.on_gcode_move_update ) + self.printer.gcode_move_update[str, list].connect(self.activate_save_button) self.tune_page.run_gcode.connect(self.ws.api.run_gcode) self.tune_page.request_sliderPage[str, int, "PyQt_PyObject"].connect( self.on_slidePage_request ) - self.tune_page.request_sliderPage[ - str, int, "PyQt_PyObject", int, int - ].connect(self.on_slidePage_request) - self.tune_page.request_numpad[ - str, int, "PyQt_PyObject", int, int - ].connect(self.on_numpad_request) + self.tune_page.request_sliderPage[str, int, "PyQt_PyObject", int, int].connect( + self.on_slidePage_request + ) + self.tune_page.request_numpad[str, int, "PyQt_PyObject", int, int].connect( + self.on_numpad_request + ) self.tune_page.request_numpad[ str, int, @@ -239,10 +233,8 @@ def __init__( self.tune_page.request_sensorsPage.connect( lambda: self.change_page(self.indexOf(self.sensorsPanel)) ) - self.sensorsPanel = SensorsWindow(self) self.addWidget(self.sensorsPanel) - self.printer.request_object_subscription_signal.connect( self.sensorsPanel.handle_available_fil_sensors ) @@ -259,23 +251,16 @@ def __init__( partial(self.change_page, self.indexOf(self.filesPage_widget)) ) self.babystepPage.run_gcode.connect(self.ws.api.run_gcode) - self.run_gcode_signal.connect(self.ws.api.run_gcode) - - self.confirmPage_widget.on_delete.connect( - self.delete_file - ) - - self.change_page( - self.indexOf(self.print_page) - ) # force set the initial page + self.confirmPage_widget.on_delete.connect(self.delete_file) + self.change_page(self.indexOf(self.print_page)) # force set the initial page + self.save_config_btn.clicked.connect(self.save_config) + self.BasePopup_z_offset.accepted.connect(self.update_configuration_file) @QtCore.pyqtSlot(str, dict, name="on_print_stats_update") @QtCore.pyqtSlot(str, float, name="on_print_stats_update") @QtCore.pyqtSlot(str, str, name="on_print_stats_update") - def on_print_stats_update( - self, field: str, value: dict | float | str - ) -> None: + def on_print_stats_update(self, field: str, value: dict | float | str) -> None: """ unblocks tabs if on standby """ @@ -284,13 +269,8 @@ def on_print_stats_update( if value in ("standby"): self.on_cancel_print.emit() - - - @QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on_numpad_request") - @QtCore.pyqtSlot( - str, int, "PyQt_PyObject", int, int, name="on_numpad_request" - ) + @QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on_numpad_request") def on_numpad_request( self, name: str, @@ -299,6 +279,7 @@ def on_numpad_request( min_value: int = 0, max_value: int = 100, ) -> None: + """Handle numpad request""" self.numpadPage.value_selected.connect(callback) self.numpadPage.set_name(name) self.numpadPage.set_value(current_value) @@ -308,9 +289,7 @@ def on_numpad_request( self.change_page(self.indexOf(self.numpadPage)) @QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on_slidePage_request") - @QtCore.pyqtSlot( - str, int, "PyQt_PyObject", int, int, name="on_slidePage_request" - ) + @QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on_slidePage_request") def on_slidePage_request( self, name: str, @@ -319,6 +298,7 @@ def on_slidePage_request( min_value: int = 0, max_value: int = 100, ) -> None: + """Handle slider page request""" self.sliderPage.value_selected.connect(callback) self.sliderPage.set_name(name) self.sliderPage.set_slider_position(int(current_value)) @@ -326,38 +306,54 @@ def on_slidePage_request( self.sliderPage.set_slider_maximum(max_value) self.change_page(self.indexOf(self.sliderPage)) - def delete_file(self,direcotry:str,name:str): - self.directory:str = direcotry - self.filename:str = name - self.dialogPage.set_message("Are you sure you want to delete this file?") - self.dialogPage.button_clicked.connect(self.on_dialog_button_clicked) - self.dialogPage.show() - - def on_dialog_button_clicked(self, button_name: str) -> None: - print(button_name) - """Handle dialog button clicks""" - if button_name == "Confirm": - self.ws.api.delete_file(self.filename,self.directory) - self.dialogPage.hide() - else: - self.dialogPage.hide() - - - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: - """ - REFACTOR: Instead of using a background svg pixmap just draw the - background with with the correct styles and everything - """ - if self.babystepPage.isVisible(): - _button_name_str = f"nozzle_offset_{self._z_offset}" - if hasattr(self, _button_name_str): - _button_attr = getattr(self, _button_name_str) - if callable(_button_attr) and isinstance( - _button_attr, BlocksCustomButton - ): - _button_attr.setChecked(True) - - return super().paintEvent(a0) + @QtCore.pyqtSlot(str, str, name="delete_file") + @QtCore.pyqtSlot(str, name="delete_file") + def delete_file(self, filename: str, directory: str = "gcodes") -> None: + """Handle Delete file signal, shows confirmation dialog""" + self.BasePopup.set_message("Are you sure you want to delete this file?") + self.BasePopup.accepted.connect( + lambda: self._on_delete_file_confirmed(filename, directory) + ) + self.BasePopup.open() + + def save_config(self) -> None: + """Handle Save configuration behaviour, shows confirmation dialog""" + if self._finish_print_handled: + self.run_gcode_signal.emit("Z_OFFSET_APPLY_PROBE") + self._z_offset = self._active_z_offset + self.babystepPage.bbp_z_offset_title_label.setText( + f"Z: {self._z_offset:.3f}mm" + ) + self.BasePopup_z_offset.set_message( + f"The Z‑Offset is now {self._active_z_offset:.3f} mm.\n" + "Would you like to save this change permanently?\n" + "The machine will restart." + ) + self.BasePopup_z_offset.cancel_button_text("Later") + self.BasePopup_z_offset.open() + + def update_configuration_file(self): + """Runs the `SAVE_CONFIG` gcode""" + self.run_gcode_signal.emit("Z_OFFSET_APPLY_PROBE") + self.run_gcode_signal.emit("SAVE_CONFIG") + self.BasePopup_z_offset.disconnect() + + @QtCore.pyqtSlot(str, list, name="activate_save_button") + def activate_save_button(self, name: str, value: list) -> None: + """Sync the `Save config` popup with the save_config_pending state""" + if not value: + return + + if name == "homing_origin": + self._active_z_offset = value[2] + self.save_config_btn.setVisible(value[2] != 0) + + def _on_delete_file_confirmed(self, filename: str, directory: str) -> None: + """Handle confirmed file deletion after user accepted the dialog""" + self.file_data.on_request_delete_file(filename, directory) + self.request_back.emit() + self.filesPage_widget.reset_dir() + self.BasePopup.disconnect() def setProperty(self, name: str, value: typing.Any) -> bool: """Intercept the set property method @@ -372,13 +368,13 @@ def setProperty(self, name: str, value: typing.Any) -> bool: if name == "backgroundPixmap": self.background = value return super().setProperty(name, value) - + def handle_cancel_print(self) -> None: """Handles the print cancel action""" self.ws.api.cancel_print() - self.on_cancel_print.emit() self.loadscreen.show() - self.loadscreen.set_status_message("Cancelling print...\nPlease wait") + self.loadscreen.setModal(True) + self.loadwidget.set_status_message("Cancelling print...\nPlease wait") def change_page(self, index: int) -> None: """Requests a page change page to the global manager @@ -393,7 +389,23 @@ def back_button(self) -> None: """Goes back to the previous page""" self.request_back.emit() + @QtCore.pyqtSlot(name="klipper_ready_signal") + def klipper_ready_signal(self) -> None: + """React to klipper ready signal""" + self.babystepPage.baby_stepchange = False + self._finish_print_handled = False + + @QtCore.pyqtSlot(name="finish_print_signal") + def finish_print_signal(self) -> None: + """Behaviour when the print ends — but only once.""" + if self._finish_print_handled: + return + if self._active_z_offset != 0 and self.babystepPage.baby_stepchange: + self.save_config() + self._finish_print_handled = True + def setupMainPrintPage(self) -> None: + """Setup UI for print page""" self.setObjectName("printStackedWidget") self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) self.resize(710, 410) @@ -409,9 +421,7 @@ def setupMainPrintPage(self) -> None: self.setMaximumSize(QtCore.QSize(720, 420)) self.setProperty( "backgroundPixmap", - QtGui.QPixmap( - ":/background/media/graphics/scroll_list_window.svg" - ), + QtGui.QPixmap(":/background/media/graphics/scroll_list_window.svg"), ) self.print_page = QtWidgets.QWidget() sizePolicy = QtWidgets.QSizePolicy( @@ -420,9 +430,7 @@ def setupMainPrintPage(self) -> None: ) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth( - self.print_page.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.print_page.sizePolicy().hasHeightForWidth()) self.print_page.setSizePolicy(sizePolicy) self.print_page.setMinimumSize(QtCore.QSize(710, 400)) self.print_page.setMaximumSize(QtCore.QSize(720, 420)) @@ -452,9 +460,7 @@ def setupMainPrintPage(self) -> None: self.main_print_btn.setContextMenuPolicy( QtCore.Qt.ContextMenuPolicy.NoContextMenu ) - self.main_print_btn.setLayoutDirection( - QtCore.Qt.LayoutDirection.LeftToRight - ) + self.main_print_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) self.main_print_btn.setStyleSheet("") self.main_print_btn.setAutoDefault(False) self.main_print_btn.setFlat(True) @@ -462,6 +468,27 @@ def setupMainPrintPage(self) -> None: "icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/print.svg") ) self.main_print_btn.setObjectName("main_print_btn") + self.save_config_btn = DisplayButton(parent=self.print_page) + self.save_config_btn.setGeometry(QtCore.QRect(540, 20, 170, 50)) + font.setPointSize(8) + font.setFamily("Montserrat") + self.save_config_btn.setFont(font) + self.save_config_btn.setMouseTracking(False) + self.save_config_btn.setTabletTracking(True) + self.save_config_btn.setContextMenuPolicy( + QtCore.Qt.ContextMenuPolicy.NoContextMenu + ) + self.save_config_btn.setProperty( + "icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/save.svg") + ) + self.save_config_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.save_config_btn.setStyleSheet("") + self.save_config_btn.setAutoDefault(False) + self.save_config_btn.setFlat(True) + self.save_config_btn.setMinimumSize(QtCore.QSize(170, 50)) + self.save_config_btn.setMaximumSize(QtCore.QSize(170, 50)) + self.save_config_btn.setText("Save\nZ-Offset") + self.save_config_btn.hide() self.main_text_label = QtWidgets.QLabel(parent=self.print_page) self.main_text_label.setEnabled(True) self.main_text_label.setGeometry(QtCore.QRect(105, 180, 500, 200)) @@ -481,9 +508,7 @@ def setupMainPrintPage(self) -> None: font.setFamily("Montserrat") font.setPointSize(14) self.main_text_label.setFont(font) - self.main_text_label.setStyleSheet( - "background: transparent; color: white;" - ) + self.main_text_label.setStyleSheet("background: transparent; color: white;") self.main_text_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.main_text_label.setTextInteractionFlags( QtCore.Qt.TextInteractionFlag.NoTextInteraction @@ -497,6 +522,4 @@ def setupMainPrintPage(self) -> None: self.main_print_btn.setProperty( "class", _translate("printStackedWidget", "menu_btn") ) - self.main_text_label.setText( - _translate("printStackedWidget", "Printer ready") - ) + self.main_text_label.setText(_translate("printStackedWidget", "Printer ready")) diff --git a/BlocksScreen/lib/panels/userauthWindow.py b/BlocksScreen/lib/panels/userauthWindow.py deleted file mode 100644 index de1a7e0b..00000000 --- a/BlocksScreen/lib/panels/userauthWindow.py +++ /dev/null @@ -1,6 +0,0 @@ -import logging - - -# TODO: Create user authentication panel -# TODO: Create change user login -# TODO: Create admin mode diff --git a/BlocksScreen/lib/panels/utilitiesTab.py b/BlocksScreen/lib/panels/utilitiesTab.py index f67bc22d..37fa6f61 100644 --- a/BlocksScreen/lib/panels/utilitiesTab.py +++ b/BlocksScreen/lib/panels/utilitiesTab.py @@ -1,19 +1,23 @@ -import csv import typing from dataclasses import dataclass from enum import Enum, auto from functools import partial from lib.moonrakerComm import MoonWebSocket -from lib.panels.widgets.loadPage import LoadScreen from lib.panels.widgets.troubleshootPage import TroubleshootPage -from lib.panels.widgets.updatePage import UpdatePage from lib.printer import Printer from lib.ui.utilitiesStackedWidget_ui import Ui_utilitiesStackedWidget from lib.utils.blocks_button import BlocksCustomButton from lib.utils.toggleAnimatedButton import ToggleAnimatedButton from PyQt6 import QtCore, QtGui, QtWidgets +from lib.panels.widgets.optionCardWidget import OptionCard +from lib.panels.widgets.inputshaperPage import InputShaperPage +from lib.panels.widgets.basePopup import BasePopup +from lib.panels.widgets.loadWidget import LoadingOverlayWidget + +import re + @dataclass class LedState: @@ -82,6 +86,10 @@ class UtilitiesTab(QtWidgets.QStackedWidget): bool, name="update-available" ) + show_update_page: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + bool, name="show-update-page" + ) + def __init__( self, parent: QtWidgets.QWidget, ws: MoonWebSocket, printer: Printer ) -> None: @@ -111,31 +119,39 @@ def __init__( self.amount: int = 1 self.tb: bool = False self.cg = None + self.aut: bool = False # --- UI Setup --- self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.loadPage = LoadScreen(self) - self.addWidget(self.loadPage) + self.loadPage = BasePopup(self, dialog=False) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.loadPage.add_widget(self.loadwidget) + + self.panel.update_btn.clicked.connect( + lambda: self.show_update_page[bool].emit(False) + ) + + self.is_page = InputShaperPage(self) + self.addWidget(self.is_page) + + self.dialog_page = BasePopup(self, dialog=True, floating=True) + self.addWidget(self.dialog_page) - self.update_page = UpdatePage(self) - self.addWidget(self.update_page) - - self.panel.utilities_input_shaper_btn.hide() # --- Back Buttons --- for button in ( - self.panel.is_back_btn, self.panel.leds_back_btn, self.panel.info_back_btn, self.panel.leds_slider_back_btn, self.panel.input_shaper_back_btn, self.panel.routine_check_back_btn, - self.update_page.update_back_btn, + self.is_page.update_back_btn, ): button.clicked.connect(self.back_button) # --- Page Navigation --- self._connect_page_change(self.panel.utilities_axes_btn, self.panel.axes_page) - self._connect_page_change(self.panel.update_btn, self.update_page) self._connect_page_change( self.panel.utilities_input_shaper_btn, self.panel.input_shaper_page ) @@ -143,7 +159,6 @@ def __init__( self._connect_page_change( self.panel.utilities_routine_check_btn, self.panel.routines_page ) - self._connect_page_change(self.panel.is_confirm_btn, self.panel.utilities_page) self._connect_page_change(self.panel.am_cancel, self.panel.utilities_page) self._connect_page_change(self.panel.axes_back_btn, self.panel.utilities_page) @@ -166,20 +181,6 @@ def __init__( self.panel.axis_y_btn.clicked.connect(partial(self.axis_maintenance, "y")) self.panel.axis_z_btn.clicked.connect(partial(self.axis_maintenance, "z")) - # --- Input Shaper --- - self.panel.is_X_startis_btn.clicked.connect( - partial(self.run_resonance_test, "x") - ) - self.panel.is_Y_startis_btn.clicked.connect( - partial(self.run_resonance_test, "y") - ) - self.panel.am_confirm.clicked.connect(self.apply_input_shaper_selection) - self.panel.isc_btn_group.buttonClicked.connect( - lambda btn: setattr(self, "ammount", int(btn.text())) - ) - self._connect_numpad_request(self.panel.isui_fq, "frequency", "Frequency") - self._connect_numpad_request(self.panel.isui_sm, "smoothing", "Smoothing") - self.panel.toggle_led_button.state = ToggleAnimatedButton.State.ON # --- LEDs --- @@ -191,6 +192,7 @@ def __init__( # --- Websocket/Printer Signals --- self.run_gcode_signal.connect(self.ws.api.run_gcode) + self.is_page.run_gcode_signal.connect(self.ws.api.run_gcode) self.subscribe_config[str, "PyQt_PyObject"].connect( self.printer.on_subscribe_config ) @@ -202,38 +204,161 @@ def __init__( self.printer.printer_config.connect(self.on_printer_config_received) self.printer.gcode_move_update.connect(self.on_gcode_move_update) - # ---- Websocket connections ---- + self.panel.update_btn.setPixmap( + QtGui.QPixmap(":/system/media/btn_icons/update-software-icon.svg") + ) - self.on_update_message.connect(self.update_page.handle_update_message) - self.update_page.request_full_update.connect(self.ws.api.full_update) - self.update_page.request_recover_repo[str].connect( - self.ws.api.recover_corrupt_repo + # ---- Input Shaper ---- + self.automatic_is = OptionCard( + self, + "Automatic\nInput Shaper", + "Automatic Input Shaper", + QtGui.QPixmap(":/input_shaper/media/btn_icons/input_shaper_auto.svg"), + ) # type: ignore + self.automatic_is.setObjectName("Automatic_IS_Card") + self.panel.is_content_layout.addWidget( + self.automatic_is, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter ) - self.update_page.request_recover_repo[str, bool].connect( - self.ws.api.recover_corrupt_repo + self.automatic_is.continue_clicked.connect( + lambda: self.handle_is("SHAPER_CALIBRATE") ) - self.update_page.request_refresh_update.connect( - self.ws.api.refresh_update_status + + self.manual_is = OptionCard( + self, + "Manual\nInput Shaper", + "Manual Input Shaper", + QtGui.QPixmap(":/input_shaper/media/btn_icons/input_shaper_manual.svg"), + ) # type: ignore + self.manual_is.setObjectName("Manual_IS_Card") + self.panel.is_content_layout.addWidget( + self.manual_is, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter ) - self.update_page.request_refresh_update[str].connect( - self.ws.api.refresh_update_status + self.manual_is.continue_clicked.connect(lambda: self.handle_is("")) + + self.is_types: dict = {} + self.is_aut_types: dict = {} + self.dialog_page.accepted.connect( + lambda: self.handle_is("SHAPER_CALIBRATE AXIS=Y") ) - self.update_page.request_rollback_update.connect(self.ws.api.rollback_update) - self.update_page.request_update_client.connect(self.ws.api.update_client) - self.update_page.request_update_klipper.connect(self.ws.api.update_klipper) - self.update_page.request_update_moonraker.connect(self.ws.api.update_moonraker) - self.update_page.request_update_status.connect(self.ws.api.update_status) - self.update_page.request_update_system.connect(self.ws.api.update_system) - self.update_page.update_available.connect(self.update_available.emit) - self.update_page.update_available.connect( - self.panel.update_btn.setShowNotification + self.dialog_page.rejected.connect( + lambda: self.handle_is("SHAPER_CALIBRATE AXIS=X") ) - self.panel.update_btn.setPixmap( - QtGui.QPixmap(":/system/media/btn_icons/update-software-icon.svg") + + self.is_page.action_btn.clicked.connect( + lambda: self.change_page(self.indexOf(self.panel.input_shaper_page)) + ) + + def handle_gcode_response(self, data: list[str]) -> None: + """ + Parses a Klipper Input Shaper console message and updates self.is_types. + """ + + if not isinstance(data, list) or len(data) != 1 or not isinstance(data[0], str): + print( + f"WARNING: Invalid input format. Expected a list with one string. Received: {data}" + ) + return + + message = data[0] + + pattern_fitted = re.compile( + r"Fitted shaper '(?P\w+)' frequency = (?P[\d\.]+) Hz \(vibrations = (?P[\d\.]+)%" + ) + match_fitted = pattern_fitted.search(message) + + if match_fitted: + name = match_fitted.group("name") + freq = float(match_fitted.group("freq")) + vib = float(match_fitted.group("vib")) + current_data = self.is_types.get(name, {}) + current_data.update( + { + "frequency": freq, + "vibration": vib, + "max_accel": current_data.get("max_accel", 0.0), + } + ) + self.is_types[name] = current_data + + return + pattern_accel = re.compile( + r"To avoid too much smoothing with '(?P\w+)', suggested max_accel <= (?P[\d\.]+) mm/sec\^2" ) + match_accel = pattern_accel.search(message) + + if match_accel: + name = match_accel.group("name") + accel = float(match_accel.group("accel")) + + if name in self.is_types and isinstance(self.is_types[name], dict): + self.is_types[name]["max_accel"] = accel + else: + self.is_types[name] = self.is_types.get(name, {}) + self.is_types[name]["max_accel"] = accel + return + + pattern_recommended = re.compile( + r"Recommended shaper_type_(?P[xy]) = (?P\w+), shaper_freq_(?P=axis) = (?P[\d\.]+) Hz" + ) + match_recommended = pattern_recommended.search(message) + if match_recommended: + axis = match_recommended.group("axis") + recommended_type = match_recommended.group("type") + self.is_types["Axis"] = axis + if self.aut: + self.is_aut_types[axis] = recommended_type + if len(self.is_aut_types) == 2: + self.run_gcode_signal.emit("SAVE_CONFIG") + self.loadPage.hide() + self.aut = False + return + return + + reordered = {recommended_type: self.is_types[recommended_type]} + for key, value in self.is_types.items(): + if key not in ("suggested_type", recommended_type, "Axis"): + reordered[key] = value + + self.is_page.set_type_dictionary(self.is_types) + first_key = next(iter(reordered.keys()), None) + for key in reordered.keys(): + if key == first_key: + self.is_page.add_type_entry(key, "Recommended type") + else: + self.is_page.add_type_entry(key) + + self.is_page.build_model_list() + self.loadPage.hide() + return + + def handle_is(self, gcode: str) -> None: + if gcode == "SHAPER_CALIBRATE": + self.run_gcode_signal.emit("G28\nM400") + self.aut = True + self.run_gcode_signal.emit(gcode) + elif gcode == "": + self.dialog_page.confirm_background_color("#dfdfdf") + self.dialog_page.cancel_background_color("#dfdfdf") + self.dialog_page.cancel_font_color("#000000") + self.dialog_page.confirm_font_color("#000000") + self.dialog_page.cancel_button_text("X axis") + self.dialog_page.confirm_button_text("Y axis") + self.dialog_page.set_message( + "Select the axis you want to execute the input shaper on:" + ) + self.dialog_page.show() + return + else: + self.run_gcode_signal.emit("G28\nM400") + self.run_gcode_signal.emit(gcode) + self.change_page(self.indexOf(self.is_page)) + + self.loadwidget.set_status_message("Running Input Shaper...") + self.loadPage.show() @QtCore.pyqtSlot(list, name="on_object_list") def on_object_list(self, object_list: list) -> None: + """Handle receiving printer object list""" self.cg = object_list for obj in self.cg: base_name = obj.split()[0] @@ -246,6 +371,7 @@ def on_object_list(self, object_list: list) -> None: @QtCore.pyqtSlot(dict, name="on_object_config") @QtCore.pyqtSlot(list, name="on_object_config") def on_object_config(self, config: typing.Union[dict, list]) -> None: + """Handle receiving printer object configurations""" if not config: return config_items = [config] if isinstance(config, dict) else config @@ -269,6 +395,7 @@ def on_object_config(self, config: typing.Union[dict, list]) -> None: } def on_printer_config_received(self, config: dict) -> None: + """Handle printer configuration""" for axis in ("x", "y", "z"): self.subscribe_config[str, "PyQt_PyObject"].emit( f"stepper_{axis}", self.on_object_config @@ -276,26 +403,14 @@ def on_printer_config_received(self, config: dict) -> None: @QtCore.pyqtSlot(str, list, name="on_gcode_move_update") def on_gcode_move_update(self, name: str, value: list) -> None: + """Handle gcode move""" if not value: return if name == "gcode_position": ... - def _connect_numpad_request(self, button: QtWidgets.QWidget, name: str, title: str): - if isinstance(button, QtWidgets.QPushButton): - button.clicked.connect( - lambda: self.request_numpad_signal.emit( - 3, name, title, self.handle_numpad_change, self - ) - ) - - def handle_numpad_change(self, name: str, new_value: typing.Union[int, float]): - if name == "frequency": - self.panel.isui_fq.setText(f"Frequency: {new_value} Hz") - elif name == "smoothing": - self.panel.isui_sm.setText(f"Smoothing: {new_value}") - def run_routine(self, process: Process): + """Run check routine for available processes""" self.current_process = process routine_configs = { Process.FAN: ("fans", "fan is spinning"), @@ -325,8 +440,7 @@ def run_routine(self, process: Process): message = "Please check if the temperature reaches 60°C. \n you may need to wait a few moments." self.set_routine_check_page( - f"Running routine for: {self.current_object}", - message + f"Running routine for: {self.current_object}", message ) self.show_waiting_page( self.indexOf(self.panel.rc_page), @@ -358,6 +472,7 @@ def _advance_routine_object(self, obj_list: list) -> bool: return True def on_routine_answer(self) -> None: + """Handle routine ongoing process""" if self.current_process is None or self.current_object is None: return if self.sender() == self.panel.rc_yes: @@ -391,8 +506,10 @@ def _send_routine_gcode(self): if fan_name == "fan": self.run_gcode_signal.emit("M106 S255\nM400") else: - self.run_gcode_signal.emit(f"SET_FAN_SPEED FAN={fan_name} SPEED=0.8\nM400") - + self.run_gcode_signal.emit( + f"SET_FAN_SPEED FAN={fan_name} SPEED=0.8\nM400" + ) + return gcode_map = { @@ -412,18 +529,16 @@ def _send_routine_gcode(self): if gcode := gcode_map.get(key): self.run_gcode_signal.emit(f"{gcode}\nM400") - def set_routine_check_page(self, title: str, label: str): + """Set text on routine page""" self.panel.rc_tittle.setText(title) self.panel.rc_label.setText(label) def update_led_values(self) -> None: + """Update led state and color values""" if self.current_object not in self.objects["leds"]: return led_state: LedState = self.objects["leds"][self.current_object] - # led_state.red = self.panel.leds_r_slider.value() - # led_state.green = self.panel.leds_g_slider.value() - # led_state.blue = self.panel.leds_b_slider.value() led_state.white = int(self.panel.leds_w_slider.value() * 255 / 100) self.save_led_state() @@ -469,10 +584,12 @@ def _update_leds_from_config(self): partial(self.handle_led_button, led_names[0]) ) else: - self._connect_page_change(self.panel.utilities_leds_btn, self.panel.leds_page) - + self._connect_page_change( + self.panel.utilities_leds_btn, self.panel.leds_page + ) def toggle_led_state(self) -> None: + """Toggle leds""" if self.current_object not in self.objects["leds"]: return led_state: LedState = self.objects["leds"][self.current_object] @@ -485,96 +602,25 @@ def toggle_led_state(self) -> None: self.save_led_state() def handle_led_button(self, name: str) -> None: + """Handle led button clicked""" self.current_object = name led_state: LedState = self.objects["leds"].get(name) if not led_state: return is_rgb = led_state.led_type == "rgb" - # self.panel.leds_r_slider.setVisible(is_rgb) - # self.panel.leds_g_slider.setVisible(is_rgb) - # self.panel.leds_b_slider.setVisible(is_rgb) self.panel.leds_w_slider.setVisible(not is_rgb) - #self.panel.leds_slider_tittle_label.setText(name) - # self.panel.leds_r_slider.setValue(led_state.red) - # self.panel.leds_g_slider.setValue(led_state.green) - # self.panel.leds_b_slider.setValue(led_state.blue) self.panel.leds_w_slider.setValue(led_state.white) self.change_page(self.indexOf(self.panel.leds_slider_page)) def save_led_state(self): + """Save led state""" if self.current_object: if self.current_object in self.objects["leds"]: led_state: LedState = self.objects["leds"][self.current_object] self.run_gcode_signal.emit(led_state.get_gcode(self.current_object)) - # input shapper - def run_resonance_test(self, axis: str) -> None: - self.axis_in = axis - path_map = { - "x": "/tmp/resonances_x_axis_data.csv", - "y": "/tmp/resonances_y_axis_data.csv", - } - if not (csv_path := path_map.get(axis)): - return - self.run_gcode_signal.emit(f"SHAPER_CALIBRATE AXIS={axis.upper()}") - self.data = self._parse_shaper_csv(csv_path) - for entry in self.data: - shaper = entry["shaper"] - panel_attr = f"am_{shaper}" - if hasattr(self.panel, panel_attr): - text = ( - f"Shaper: {shaper}, Freq: {entry['frequency']}Hz, Vibrations: {entry['vibrations']}%\n" - f"Smoothing: {entry['smoothing']}, Max Accel: {entry['max_accel']}mm/sec" - ) - getattr(self.panel, panel_attr).setText(text) - self.x_inputshaper[panel_attr] = entry - self.change_page(self.indexOf(self.panel.is_page)) - - def _parse_shaper_csv(self, file_path: str) -> list: - results = [] - try: - with open(file_path, newline="") as csvfile: - reader = csv.DictReader(csvfile) - for row in reader: - if row.get("shaper") and row.get("freq"): - results.append( - { - k: row.get(v, "N/A") - for k, v in { - "shaper": "shaper", - "frequency": "freq", - "vibrations": "vibrations", - "smoothing": "smoothing", - "max_accel": "max_accel", - }.items() - } - ) - except FileNotFoundError: - ... - except csv.Error as e: - ... - return results - - def apply_input_shaper_selection(self) -> None: - if not (checked_button := self.panel.is_btn_group.checkedButton()): - return - selected_name = checked_button.objectName() - if selected_name == "am_user_input": - self.change_page( - self.indexOf(self.panel.input_shaper_page) - ) # TEST: CHANGED THIS FROM input_shaper_user_input - return - if not (shaper_data := self.x_inputshaper.get(selected_name)): - return - gcode = ( - f"SET_INPUT_SHAPER SHAPER_TYPE={shaper_data['shaper']} " - f"SHAPER_FREQ_{self.axis_in.upper()}={shaper_data['frequency']} " - f"SHAPER_DAMPING_{self.axis_in.upper()}={shaper_data['smoothing']}" - ) - self.run_gcode_signal.emit(gcode) - self.change_page(self.indexOf(self.panel.utilities_page)) - def axis_maintenance(self, axis: str) -> None: + """Routine, checks axis movement for printer debugging""" self.current_process = Process.AXIS_MAINTENANCE self.current_object = axis self.run_gcode_signal.emit(f"G28 {axis.upper()}\nM400") @@ -605,11 +651,12 @@ def _run_axis_maintenance_gcode(self, axis: str): self.change_page(self.indexOf(self.panel.axes_page)) def troubleshoot_request(self) -> None: - self.troubleshoot_page.geometry_calc() + """Show troubleshoot page""" self.troubleshoot_page.show() def show_waiting_page(self, page_to_go_to: int, label: str, time_ms: int): - self.loadPage.label.setText(label) + """Show placeholder page""" + self.loadwidget.set_status_message(label) self.loadPage.show() QtCore.QTimer.singleShot(time_ms, lambda: self.change_page(page_to_go_to)) @@ -618,6 +665,7 @@ def _connect_page_change(self, button: QtWidgets.QWidget, page: QtWidgets.QWidge button.clicked.connect(lambda: self.change_page(self.indexOf(page))) def change_page(self, index: int): + """Request change page by index""" self.loadPage.hide() self.troubleshoot_page.hide() if index < self.count(): @@ -625,4 +673,5 @@ def change_page(self, index: int): @QtCore.pyqtSlot(name="request-back") def back_button(self) -> None: + """Request back""" self.request_back.emit() diff --git a/BlocksScreen/lib/panels/widgets/babystepPage.py b/BlocksScreen/lib/panels/widgets/babystepPage.py index 4df7403e..273e8f9c 100644 --- a/BlocksScreen/lib/panels/widgets/babystepPage.py +++ b/BlocksScreen/lib/panels/widgets/babystepPage.py @@ -1,9 +1,8 @@ import typing -from lib.utils.blocks_button import BlocksCustomButton from lib.utils.blocks_label import BlocksLabel +from lib.utils.check_button import BlocksCustomCheckButton from lib.utils.icon_button import IconButton -from lib.utils.group_button import GroupButton from PyQt6 import QtCore, QtGui, QtWidgets @@ -33,24 +32,38 @@ def __init__(self, parent) -> None: self.bbp_nozzle_offset_025.toggled.connect(self.handle_z_offset_change) self.bbp_nozzle_offset_05.toggled.connect(self.handle_z_offset_change) self.bbp_nozzle_offset_1.toggled.connect(self.handle_z_offset_change) + self._baby_stepchange = False - self.savebutton.clicked.connect(self.savevalue) + @property + def baby_stepchange(self): + """Returns if the babystep was changed during print""" + return self._baby_stepchange + + @baby_stepchange.setter + def baby_stepchange(self, value: bool) -> None: + if not isinstance(value, bool): + raise ValueError("Value must be a bool") + self._baby_stepchange = value @QtCore.pyqtSlot(name="on_move_nozzle_close") def on_move_nozzle_close(self) -> None: - """Move the nozzle closer to the print plate by the amount set in **` self._z_offset`**""" + """Move the nozzle closer to the print plate + by the amount set in **` self._z_offset`** + """ self.run_gcode.emit( - f"SET_GCODE_OFFSET Z_ADJUST=-{self._z_offset}" # Z_ADJUST adds the value to the existing offset + f"SET_GCODE_OFFSET Z_ADJUST=-{self._z_offset} MOVE=1" # Z_ADJUST adds the value to the existing offset ) - self.savebutton.setVisible(True) + self._baby_stepchange = True @QtCore.pyqtSlot(name="on_move_nozzle_away") def on_move_nozzle_away(self) -> None: - """Slot for Babystep button to get far from the bed by **` self._z_offset`** amount""" + """Slot for Babystep button to get far from the + bed by **` self._z_offset`** amount + """ self.run_gcode.emit( - f"SET_GCODE_OFFSET Z_ADJUST=+{self._z_offset}" # Z_ADJUST adds the value to the existing offset + f"SET_GCODE_OFFSET Z_ADJUST=+{self._z_offset} MOVE=1" # Z_ADJUST adds the value to the existing offset ) - self.savebutton.setVisible(True) + self._baby_stepchange = True @QtCore.pyqtSlot(name="handle_z_offset_change") def handle_z_offset_change(self) -> None: @@ -63,36 +76,22 @@ def handle_z_offset_change(self) -> None: Possible values are: 0.01, 0.025, 0.05, 0.1 **mm** """ - _possible_z_values: typing.List = [0.01, 0.025, 0.05, 0.1] _sender: QtCore.QObject | None = self.sender() if self._z_offset == float(_sender.text()[:-3]): return self._z_offset = float(_sender.text()[:-3]) - def savevalue(self): - self.run_gcode.emit("Z_OFFSET_APPLY_PROBE") - self.savebutton.setVisible(False) - self.bbp_z_offset_title_label.setText( - self.bbp_z_offset_current_value.text() - ) - - return - def on_gcode_move_update(self, name: str, value: list) -> None: + """Handle gcode move updates""" if not value: return if name == "homing_origin": self._z_offset_text = value[2] - self.bbp_z_offset_current_value.setText( - f"Z: {self._z_offset_text:.3f}mm" - ) - if self.bbp_z_offset_title_label.text() == "smth": - self.bbp_z_offset_title_label.setText( - f"Z: {self._z_offset_text:.3f}mm" - ) + self.bbp_z_offset_current_value.setText(f"Z: {self._z_offset_text:.3f}mm") def setupUI(self): + """Setup babystep page ui""" self.bbp_offset_value_selector_group = QtWidgets.QButtonGroup(self) self.bbp_offset_value_selector_group.setExclusive(True) sizePolicy = QtWidgets.QSizePolicy( @@ -144,18 +143,6 @@ def setupUI(self): self.bbp_header_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.bbp_header_title.setObjectName("bbp_header_title") - self.savebutton = BlocksCustomButton(self) - self.savebutton.setGeometry(QtCore.QRect(460, 340, 200, 60)) - self.savebutton.setText("Save?") - self.savebutton.setObjectName("savebutton") - self.savebutton.setPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/save.svg") - ) - self.savebutton.setVisible(False) - font = QtGui.QFont() - font.setPointSize(15) - self.savebutton.setFont(font) - spacerItem = QtWidgets.QSpacerItem( 60, 20, @@ -178,16 +165,13 @@ def setupUI(self): self.babystep_back_btn.setMaximumSize(QtCore.QSize(60, 60)) self.babystep_back_btn.setText("") self.babystep_back_btn.setFlat(True) - self.babystep_back_btn.setPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/back.svg") - ) + self.babystep_back_btn.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) self.babystep_back_btn.setObjectName("babystep_back_btn") self.bbp_header_layout.addWidget( self.babystep_back_btn, 0, - QtCore.Qt.AlignmentFlag.AlignRight - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter, ) self.bbp_header_layout.setStretch(0, 1) self.verticalLayout.addLayout(self.bbp_header_layout) @@ -218,7 +202,7 @@ def setupUI(self): self.bbp_offset_steps_buttons.setObjectName("bbp_offset_steps_buttons") # 0.1mm button - self.bbp_nozzle_offset_1 = GroupButton( + self.bbp_nozzle_offset_1 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box ) self.bbp_nozzle_offset_1.setMinimumSize(QtCore.QSize(100, 70)) @@ -233,20 +217,41 @@ def setupUI(self): self.bbp_nozzle_offset_1.setFlat(True) self.bbp_nozzle_offset_1.setProperty("button_type", "") self.bbp_nozzle_offset_1.setObjectName("bbp_nozzle_offset_1") - self.bbp_offset_value_selector_group.addButton( - self.bbp_nozzle_offset_1 - ) + self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_1) self.bbp_offset_steps_buttons.addWidget( self.bbp_nozzle_offset_1, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, + ) + + # 0.05mm button + self.bbp_nozzle_offset_05 = BlocksCustomCheckButton( + parent=self.bbp_offset_steps_buttons_group_box + ) + self.bbp_nozzle_offset_05.setMinimumSize(QtCore.QSize(100, 70)) + self.bbp_nozzle_offset_05.setMaximumSize( + QtCore.QSize(100, 70) + ) # Increased max width by 5 pixels + self.bbp_nozzle_offset_05.setText("0.05 mm") + + font = QtGui.QFont() + font.setPointSize(14) + self.bbp_nozzle_offset_05.setFont(font) + self.bbp_nozzle_offset_05.setCheckable(True) + self.bbp_nozzle_offset_05.setFlat(True) + self.bbp_nozzle_offset_05.setProperty("button_type", "") + self.bbp_nozzle_offset_05.setObjectName("bbp_nozzle_offset_05") + self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_05) + self.bbp_offset_steps_buttons.addWidget( + self.bbp_nozzle_offset_05, + 0, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) # Line separator for 0.1mm - set size policy to expanding horizontally # 0.01mm button - self.bbp_nozzle_offset_01 = GroupButton( + self.bbp_nozzle_offset_01 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box ) self.bbp_nozzle_offset_01.setMinimumSize(QtCore.QSize(100, 70)) @@ -262,45 +267,15 @@ def setupUI(self): self.bbp_nozzle_offset_01.setFlat(True) self.bbp_nozzle_offset_01.setProperty("button_type", "") self.bbp_nozzle_offset_01.setObjectName("bbp_nozzle_offset_01") - self.bbp_offset_value_selector_group.addButton( - self.bbp_nozzle_offset_01 - ) + self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_01) self.bbp_offset_steps_buttons.addWidget( self.bbp_nozzle_offset_01, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, - ) - - # 0.05mm button - self.bbp_nozzle_offset_05 = GroupButton( - parent=self.bbp_offset_steps_buttons_group_box - ) - self.bbp_nozzle_offset_05.setMinimumSize(QtCore.QSize(100, 70)) - self.bbp_nozzle_offset_05.setMaximumSize( - QtCore.QSize(100, 70) - ) # Increased max width by 5 pixels - self.bbp_nozzle_offset_05.setText("0.05 mm") - - font = QtGui.QFont() - font.setPointSize(14) - self.bbp_nozzle_offset_05.setFont(font) - self.bbp_nozzle_offset_05.setCheckable(True) - self.bbp_nozzle_offset_05.setFlat(True) - self.bbp_nozzle_offset_05.setProperty("button_type", "") - self.bbp_nozzle_offset_05.setObjectName("bbp_nozzle_offset_05") - self.bbp_offset_value_selector_group.addButton( - self.bbp_nozzle_offset_05 - ) - self.bbp_offset_steps_buttons.addWidget( - self.bbp_nozzle_offset_05, - 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) # 0.025mm button - self.bbp_nozzle_offset_025 = GroupButton( + self.bbp_nozzle_offset_025 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box ) self.bbp_nozzle_offset_025.setMinimumSize(QtCore.QSize(100, 70)) @@ -316,22 +291,17 @@ def setupUI(self): self.bbp_nozzle_offset_025.setFlat(True) self.bbp_nozzle_offset_025.setProperty("button_type", "") self.bbp_nozzle_offset_025.setObjectName("bbp_nozzle_offset_025") - self.bbp_offset_value_selector_group.addButton( - self.bbp_nozzle_offset_025 - ) + self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_025) self.bbp_offset_steps_buttons.addWidget( self.bbp_nozzle_offset_025, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) # Line separator for 0.025mm - set size policy to expanding horizontally # Set the layout for the group box - self.bbp_offset_steps_buttons_group_box.setLayout( - self.bbp_offset_steps_buttons - ) + self.bbp_offset_steps_buttons_group_box.setLayout(self.bbp_offset_steps_buttons) # Add the group box to the main content horizontal layout FIRST for left placement self.main_content_horizontal_layout.addWidget( self.bbp_offset_steps_buttons_group_box @@ -339,9 +309,7 @@ def setupUI(self): # Graphic and Current Value Frame (This will now be in the MIDDLE) self.frame_2 = QtWidgets.QFrame(parent=self) - sizePolicy.setHeightForWidth( - self.frame_2.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth()) self.frame_2.setSizePolicy(sizePolicy) self.frame_2.setMinimumSize(QtCore.QSize(350, 160)) self.frame_2.setMaximumSize(QtCore.QSize(350, 160)) @@ -357,18 +325,14 @@ def setupUI(self): QtGui.QPixmap(":/graphics/media/graphics/babystep_graphic.png") ) self.bbp_babystep_graphic.setScaledContents(False) - self.bbp_babystep_graphic.setAlignment( - QtCore.Qt.AlignmentFlag.AlignCenter - ) + self.bbp_babystep_graphic.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.bbp_babystep_graphic.setObjectName("bbp_babystep_graphic") # === NEW LABEL ADDED HERE === # This is the title label that appears above the red value box. self.bbp_z_offset_title_label = QtWidgets.QLabel(parent=self) # Position it just above the red box. Red box is at y=70, so y=40 is appropriate. - self.bbp_z_offset_title_label.setGeometry( - QtCore.QRect(100, 40, 200, 30) - ) + self.bbp_z_offset_title_label.setGeometry(QtCore.QRect(100, 40, 200, 30)) font = QtGui.QFont() font.setPointSize(12) @@ -377,17 +341,14 @@ def setupUI(self): self.bbp_z_offset_title_label.setStyleSheet( "color: gray; background: transparent;" ) - self.bbp_z_offset_title_label.setText("Z-Offset") self.bbp_z_offset_title_label.setObjectName("bbp_z_offset_title_label") - self.bbp_z_offset_title_label.setText("smth") + self.bbp_z_offset_title_label.setText("Z: 0.000mm") self.bbp_z_offset_title_label.setGeometry(420, 270, 200, 30) # === END OF NEW LABEL === self.bbp_z_offset_current_value = BlocksLabel(parent=self.frame_2) - self.bbp_z_offset_current_value.setGeometry( - QtCore.QRect(100, 70, 200, 60) - ) + self.bbp_z_offset_current_value.setGeometry(QtCore.QRect(100, 70, 200, 60)) sizePolicy.setHeightForWidth( self.bbp_z_offset_current_value.sizePolicy().hasHeightForWidth() ) @@ -407,15 +368,12 @@ def setupUI(self): self.bbp_z_offset_current_value.setAlignment( QtCore.Qt.AlignmentFlag.AlignCenter ) - self.bbp_z_offset_current_value.setObjectName( - "bbp_z_offset_current_value" - ) + self.bbp_z_offset_current_value.setObjectName("bbp_z_offset_current_value") # Add graphic frame AFTER the offset buttons group box self.main_content_horizontal_layout.addWidget( self.frame_2, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) # Move Buttons Layout (This will now be on the RIGHT) @@ -423,16 +381,14 @@ def setupUI(self): self.bbp_buttons_layout.setContentsMargins(5, 5, 5, 5) self.bbp_buttons_layout.setObjectName("bbp_buttons_layout") self.bbp_mvup = IconButton(parent=self) - sizePolicy.setHeightForWidth( - self.bbp_mvup.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.bbp_mvup.sizePolicy().hasHeightForWidth()) self.bbp_mvup.setSizePolicy(sizePolicy) self.bbp_mvup.setMinimumSize(QtCore.QSize(80, 80)) self.bbp_mvup.setMaximumSize(QtCore.QSize(80, 80)) self.bbp_mvup.setText("") self.bbp_mvup.setFlat(True) self.bbp_mvup.setPixmap( - QtGui.QPixmap(":/arrow_icons/media/btn_icons/up_arrow.svg") + QtGui.QPixmap(":/baby_step/media/btn_icons/move_nozzle_close.svg") ) self.bbp_mvup.setObjectName("bbp_away_from_bed") self.bbp_option_button_group = QtWidgets.QButtonGroup(self) @@ -442,16 +398,14 @@ def setupUI(self): self.bbp_mvup, 0, QtCore.Qt.AlignmentFlag.AlignRight ) self.bbp_mvdown = IconButton(parent=self) - sizePolicy.setHeightForWidth( - self.bbp_mvdown.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.bbp_mvdown.sizePolicy().hasHeightForWidth()) self.bbp_mvdown.setSizePolicy(sizePolicy) self.bbp_mvdown.setMinimumSize(QtCore.QSize(80, 80)) self.bbp_mvdown.setMaximumSize(QtCore.QSize(80, 80)) self.bbp_mvdown.setText("") self.bbp_mvdown.setFlat(True) self.bbp_mvdown.setPixmap( - QtGui.QPixmap(":/arrow_icons/media/btn_icons/down_arrow.svg") + QtGui.QPixmap(":/baby_step/media/btn_icons/move_nozzle_away.svg") ) self.bbp_mvdown.setObjectName("bbp_close_to_bed") self.bbp_option_button_group.addButton(self.bbp_mvdown) diff --git a/BlocksScreen/lib/panels/widgets/basePopup.py b/BlocksScreen/lib/panels/widgets/basePopup.py new file mode 100644 index 00000000..a9a4d188 --- /dev/null +++ b/BlocksScreen/lib/panels/widgets/basePopup.py @@ -0,0 +1,255 @@ +import typing + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class BasePopup(QtWidgets.QDialog): + """Simple popup with custom message and Confirm/Back buttons + + To assert if the user accepted or rejected the dialog connect to the **accepted()** or **rejected()** signals. + + The `finished()` signal can also be used to get the result of the dialog. This is emitted after + the accepted and rejected signals. + + + """ + + x_offset: float = 0.7 + y_offset: float = 0.7 + border_radius: int = 20 + border_margin: int = 5 + + def __init__( + self, + parent: QtWidgets.QWidget, # Make parent optional for easier testing + floating: bool = False, + dialog: bool = True, + ) -> None: + super().__init__(parent) + self.setWindowFlags( + QtCore.Qt.WindowType.Dialog + | QtCore.Qt.WindowType.FramelessWindowHint + | QtCore.Qt.WindowType.CustomizeWindowHint + ) + self.floating = floating + self.dialog = dialog + # Color Variables + self.btns_text_color = "#ffffff" + self.cancel_bk_color = "#F44336" + self.confirm_bk_color = "#4CAF50" + self.confirm_ft_color = "#ffffff" + self.cancel_ft_color = "#ffffff" + + self.setupUI() + self.update() + + if floating: + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True) + self.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) + else: + self.setStyleSheet( + """ + #MyParent { + background-image: url(:/background/media/1st_background.png); + } + """ + ) + + def _update_button_style(self) -> None: + """Applies the current color variables and adds the central border to the stylesheets.""" + if not self.dialog: + return + + if not self.floating: + self.confirm_button.setStyleSheet( + f""" + background-color: {self.confirm_bk_color}; + color: {self.confirm_ft_color}; + border: none; + padding: 10px; + """ + ) + + self.cancel_button.setStyleSheet( + f""" + background-color: {self.cancel_bk_color}; + color: {self.cancel_ft_color}; + border: none; + padding: 10px; + """ + ) + else: + self.confirm_button.setStyleSheet( + f""" + background-color: {self.confirm_bk_color}; + color: {self.confirm_ft_color}; + border-top: none; + border-left: 2px solid #80807e;; + border-bottom: 2px solid #80807e; + border-right: 1px solid #80807e; + border-bottom-left-radius: 16px; + padding: 10px; + """ + ) + + self.cancel_button.setStyleSheet( + f""" + background-color: {self.cancel_bk_color}; + color: {self.cancel_ft_color}; + border-left: 1px solid #80807e;; + border-bottom: 2px solid #80807e; + border-right: 2px solid #80807e; + border-bottom-right-radius: 16px; + padding: 10px; + """ + ) + + def set_message(self, message: str) -> None: + self.label.setText(message) + + def cancel_button_text(self, text: str) -> None: + if not self.dialog: + return + self.cancel_button.setText(text) + + def confirm_button_text(self, text: str) -> None: + if not self.dialog: + return + self.confirm_button.setText(text) + + def cancel_background_color(self, color: str) -> None: + if not self.dialog: + return + self.cancel_bk_color = color + self._update_button_style() + + def confirm_background_color(self, color: str) -> None: + if not self.dialog: + return + self.confirm_bk_color = color + self._update_button_style() + + def cancel_font_color(self, color: str) -> None: + if not self.dialog: + return + self.cancel_ft_color = color + self._update_button_style() + + def confirm_font_color(self, color: str) -> None: + if not self.dialog: + return + self.confirm_ft_color = color + self._update_button_style() + + def add_widget(self, widget: QtWidgets.QWidget) -> None: + """Replace the label with a custom widget in the layout""" + + layout = self.vlayout + index = layout.indexOf(self.label) + self.label.setParent(None) + self.label.hide() + layout.insertWidget(index, widget) + widget.show() + + def _get_mainWindow_widget(self) -> typing.Optional[QtWidgets.QMainWindow]: + """Get the main application window""" + app_instance = QtWidgets.QApplication.instance() + if not app_instance: + return None + main_window = app_instance.activeWindow() + if main_window is None: + for widget in app_instance.allWidgets(): + if isinstance(widget, QtWidgets.QMainWindow): + main_window = widget + break + return main_window if isinstance(main_window, QtWidgets.QMainWindow) else None + + def _geometry_calc(self) -> None: + """Calculate dialog widget position relative to the window""" + main_window = self._get_mainWindow_widget() + if main_window is None: + return + + if self.floating: + width = int(main_window.width() * self.x_offset) + height = int(main_window.height() * self.y_offset) + x = int(main_window.geometry().x() + (main_window.width() - width) / 2) + y = int(main_window.geometry().y() + (main_window.height() - height) / 2) + else: + x = main_window.geometry().x() + y = main_window.geometry().y() + width = main_window.width() + height = main_window.height() + + self.setGeometry(x, y, width, height) + + def sizeHint(self) -> QtCore.QSize: + """Re-implemented method, widget size hint""" + popup_width = int(self.geometry().width()) + popup_height = int(self.geometry().height()) + popup_x = self.x() + popup_y = self.y() + (self.height() - popup_height) // 2 + self.move(popup_x, popup_y) + self.setFixedSize(popup_width, popup_height) + self.setMinimumSize(popup_width, popup_height) + return super().sizeHint() + + def open(self): + """Re-implemented method, open widget""" + self._geometry_calc() + return super().open() + + def show(self) -> None: + self._geometry_calc() + return super().show() + + def paintEvent(self, a0: QtGui.QPaintEvent | None) -> None: + """Re-implemented method, paint widget""" + if not self.floating: + return + + self._geometry_calc() + painter = QtGui.QPainter(self) + painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) + rect = self.rect() + painter.setBrush(QtGui.QBrush(QtGui.QColor(63, 63, 63))) + border_color = QtGui.QColor(128, 128, 128) + pen = QtGui.QPen() + pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap) + painter.setPen(QtGui.QPen(border_color, self.border_margin)) + painter.drawRoundedRect(rect, self.border_radius, self.border_radius) + painter.end() + + def setupUI(self) -> None: + self.vlayout = QtWidgets.QVBoxLayout(self) + self.setObjectName("MyParent") + self.label = QtWidgets.QLabel("Test Message", self) + font = QtGui.QFont() + font.setPointSize(25) + self.label.setFont(font) + self.label.setStyleSheet("color: #ffffff; background: transparent;") + self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.label.setWordWrap(True) + self.vlayout.addWidget(self.label) + if self.dialog: + self.hlauyout = QtWidgets.QHBoxLayout() + self.hlauyout.setContentsMargins(0, 0, 0, 0) + self.hlauyout.setSpacing(0) + self.vlayout.addLayout(self.hlauyout) + self.vlayout.setContentsMargins(0, 0, 0, 0) + self.confirm_button = QtWidgets.QPushButton("Confirm", self) + self.cancel_button = QtWidgets.QPushButton("Back", self) + + button_font = QtGui.QFont() + button_font.setPointSize(14) + self.confirm_button.setFont(button_font) + self.confirm_button.setMinimumHeight(60) + self.cancel_button.setFont(button_font) + self.cancel_button.setMinimumHeight(60) + self.confirm_button.setStyleSheet("background: transparent;") + self.cancel_button.setStyleSheet("background: transparent;") + self.hlauyout.addWidget(self.confirm_button) + self.hlauyout.addWidget(self.cancel_button) + self.confirm_button.clicked.connect(self.accept) + self.cancel_button.clicked.connect(self.reject) + self._update_button_style() diff --git a/BlocksScreen/lib/panels/widgets/confirmPage.py b/BlocksScreen/lib/panels/widgets/confirmPage.py index 525b0bbe..12432449 100644 --- a/BlocksScreen/lib/panels/widgets/confirmPage.py +++ b/BlocksScreen/lib/panels/widgets/confirmPage.py @@ -1,48 +1,47 @@ +import os import typing +import helper_methods from lib.utils.blocks_button import BlocksCustomButton +from lib.utils.blocks_frame import BlocksCustomFrame from lib.utils.blocks_label import BlocksLabel from lib.utils.icon_button import IconButton -from lib.utils.blocks_frame import BlocksCustomFrame from PyQt6 import QtCore, QtGui, QtWidgets -import helper_methods - - -import os - class ConfirmWidget(QtWidgets.QWidget): on_accept: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( - str, list, name="on_accept" + str, name="on_accept" + ) + request_back: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + name="request-back" ) - on_reject: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(name="on_reject") - on_delete: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( - str, str, name="on_delete" + str, str, name="delete_file" ) def __init__(self, parent) -> None: super().__init__(parent) - self.setupUI() + self._setupUI() self.setMouseTracking(True) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) self.thumbnail: QtGui.QImage = QtGui.QImage() self._thumbnails: typing.List = [] - self.directory = "" + self.directory = "gcodes" self.filename = "" self.confirm_button.clicked.connect( lambda: self.on_accept.emit( - str(os.path.join(self.directory, self.filename)), self._thumbnails + str(os.path.join(self.directory, self.filename)) ) ) - self.back_btn.clicked.connect(self.on_reject.emit) - self.reject_button.clicked.connect( - lambda: self.on_delete.emit(self.directory, self.filename) + self.back_btn.clicked.connect(self.request_back.emit) + self.delete_file_button.clicked.connect( + lambda: self.on_delete.emit(self.filename, self.directory) ) @QtCore.pyqtSlot(str, dict, name="on_show_widget") def on_show_widget(self, text: str, filedata: dict | None = None) -> None: + """Handle widget show""" directory = os.path.dirname(text) filename = os.path.basename(text) self.directory = directory @@ -101,11 +100,13 @@ def estimate_print_time(self, seconds: int) -> list: return [days, hours, minutes, seconds] def hide(self): + """Hide widget""" self.directory = "" self.filename = "" return super().hide() def paintEvent(self, event: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" if not self.isVisible(): self.directory = "" self.filename = "" @@ -144,14 +145,13 @@ def paintEvent(self, event: QtGui.QPaintEvent) -> None: self._scene.setSceneRect(graphics_rect) def showEvent(self, a0: QtGui.QShowEvent) -> None: + """Re-implemented method, Handle widget show event""" if not self.thumbnail: self.cf_thumbnail.close() return super().showEvent(a0) - def hideEvent(self, a0: QtGui.QHideEvent) -> None: - return super().hideEvent(a0) - - def setupUI(self) -> None: + def _setupUI(self) -> None: + """Setup widget ui""" sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding, @@ -261,18 +261,18 @@ def setupUI(self) -> None: self.confirm_button, 0, QtCore.Qt.AlignmentFlag.AlignCenter ) - self.reject_button = BlocksCustomButton(parent=self.info_frame) - self.reject_button.setMinimumSize(QtCore.QSize(250, 70)) - self.reject_button.setMaximumSize(QtCore.QSize(250, 70)) - self.reject_button.setFont(font) - self.reject_button.setFlat(True) - self.reject_button.setProperty( + self.delete_file_button = BlocksCustomButton(parent=self.info_frame) + self.delete_file_button.setMinimumSize(QtCore.QSize(250, 70)) + self.delete_file_button.setMaximumSize(QtCore.QSize(250, 70)) + self.delete_file_button.setFont(font) + self.delete_file_button.setFlat(True) + self.delete_file_button.setProperty( "icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/garbage-icon.svg") ) - self.reject_button.setText("Delete") + self.delete_file_button.setText("Delete") # 2. Align buttons to the right self.cf_confirm_layout.addWidget( - self.reject_button, 0, QtCore.Qt.AlignmentFlag.AlignCenter + self.delete_file_button, 0, QtCore.Qt.AlignmentFlag.AlignCenter ) self.info_layout.addLayout(self.cf_confirm_layout) diff --git a/BlocksScreen/lib/panels/widgets/connectionPage.py b/BlocksScreen/lib/panels/widgets/connectionPage.py index 7760cca5..1e9c32d1 100644 --- a/BlocksScreen/lib/panels/widgets/connectionPage.py +++ b/BlocksScreen/lib/panels/widgets/connectionPage.py @@ -3,25 +3,28 @@ from events import KlippyDisconnected, KlippyReady, KlippyShutdown from lib.moonrakerComm import MoonWebSocket from lib.ui.connectionWindow_ui import Ui_ConnectivityForm -from PyQt6 import QtCore, QtWidgets, QtGui +from PyQt6 import QtCore, QtWidgets class ConnectionPage(QtWidgets.QFrame): text_updated = QtCore.pyqtSignal(int, name="connection_text_updated") - retry_connection_clicked = QtCore.pyqtSignal( - name="retry_connection_clicked" - ) + retry_connection_clicked = QtCore.pyqtSignal(name="retry_connection_clicked") wifi_button_clicked = QtCore.pyqtSignal(name="call_network_page") reboot_clicked = QtCore.pyqtSignal(name="reboot_clicked") restart_klipper_clicked = QtCore.pyqtSignal(name="restart_klipper_clicked") - firmware_restart_clicked = QtCore.pyqtSignal( - name="firmware_restart_clicked" - ) + firmware_restart_clicked = QtCore.pyqtSignal(name="firmware_restart_clicked") + update_button_clicked = QtCore.pyqtSignal(bool, name="show-update-page") def __init__(self, parent: QtWidgets.QWidget, ws: MoonWebSocket, /): super().__init__(parent) + self.setMinimumSize(QtCore.QSize(800, 480)) self.panel = Ui_ConnectivityForm() self.panel.setupUi(self) + + self.panel.updatepageButton.clicked.connect( + lambda: self.update_button_clicked[bool].emit(True) + ) + self.ws = ws self._moonraker_status: str = "disconnected" self._klippy_state: str = "closed" @@ -31,7 +34,7 @@ def __init__(self, parent: QtWidgets.QWidget, ws: MoonWebSocket, /): self.message = None self.dot_timer = QtCore.QTimer(self) self.dot_timer.setInterval(1000) - self.dot_timer.timeout.connect(self.add_dot) + self.dot_timer.timeout.connect(self._add_dot) self.installEventFilter(self.parent()) @@ -47,10 +50,11 @@ def __init__(self, parent: QtWidgets.QWidget, ws: MoonWebSocket, /): self.restart_klipper_clicked.emit ) self.ws.connection_lost.connect(slot=self.show) - self.ws.klippy_connected_signal.connect(self.on_klippy_connection) + self.ws.klippy_connected_signal.connect(self.on_klippy_connected) self.ws.klippy_state_signal.connect(self.on_klippy_state) def show_panel(self, reason: str | None = None): + """Show widget""" self.show() if reason is not None: self.text_update(reason) @@ -58,12 +62,28 @@ def show_panel(self, reason: str | None = None): self.text_update() return False - @QtCore.pyqtSlot(bool, name="klippy_connection") - def on_klippy_connection(self, state: bool): - pass + def showEvent(self, a0: QtCore.QEvent | None): + """Handle show event""" + self.ws.api.refresh_update_status() + return super().showEvent(a0) + + @QtCore.pyqtSlot(bool, name="on_klippy_connected") + def on_klippy_connection(self, connected: bool): + """Handle klippy connection state""" + self.dot_timer.stop() + + self._klippy_connection = connected + if not connected: + self.panel.connectionTextBox.setText("Klipper Disconnected") + if not self.isVisible(): + self.show() + else: + self.panel.connectionTextBox.setText("Klipper Connected") @QtCore.pyqtSlot(str, name="on_klippy_state") def on_klippy_state(self, state: str): + """Handle klippy state changes""" + self.dot_timer.stop() if state == "error": self.panel.connectionTextBox.setText("Klipper Connection Error") if not self.isVisible(): @@ -82,30 +102,32 @@ def on_klippy_state(self, state: str): self.panel.connectionTextBox.setText("Klipper Startup") elif state == "ready": self.panel.connectionTextBox.setText("Klipper Ready") - self.hide() @QtCore.pyqtSlot(int, name="on_websocket_connecting") @QtCore.pyqtSlot(str, name="on_websocket_connecting") def on_websocket_connecting(self, attempt: int): + """Handle websocket connecting state""" self.text_update(attempt) @QtCore.pyqtSlot(name="on_websocket_connection_achieved") def on_websocket_connection_achieved(self): - self.panel.connectionTextBox.setText( - "Moonraker Connected\n Klippy not ready" - ) - self.hide() + """Handle websocket connected state""" + self.dot_timer.stop() + self.panel.connectionTextBox.setText("Moonraker Connected\n Klippy not ready") - @QtCore.pyqtSlot(name="on_websocket_connection_lost") + @QtCore.pyqtSlot(name="on_websocket_connection_lzost") def on_websocket_connection_lost(self): + """Handle websocket connection lost state""" if not self.isVisible(): self.show() + self.dot_timer.stop() self.text_update(text="Websocket lost") def text_update(self, text: int | str | None = None): + """Update widget text""" if self.state == "shutdown" and self.message is not None: return False - + self.dot_timer.stop() logging.debug(f"[ConnectionWindowPanel] text_update: {text}") if text == "wb lost": self.panel.connectionTextBox.setText("Moonraker connection lost") @@ -118,62 +140,58 @@ def text_update(self, text: int | str | None = None): return True if isinstance(text, str): self.panel.connectionTextBox.setText( - f""" - Connection to Moonraker unavailable\nTry again by reconnecting or \nrestarting klipper\n{text} - """ + f"""Connection to Moonraker unavailable\nTry again by reconnecting or \nrestarting klipper\n{text}""" ) return True if isinstance(text, int): # * Websocket connection messages + + self.base_text = f"Attempting to reconnect to Moonraker.\n\nConnection try number: {text}" + if text == 0: - self.dot_timer.stop() self.panel.connectionTextBox.setText( - "Unable to Connect to Moonraker.\n\nTry again" + "Connection to Moonraker timeout \n \n please retry" ) - return False + return + self.dot_count = 0 - if text == 1: - if self.dot_timer.isActive(): - self.dot_timer.stop() - return - self.dot_timer.start() - - self.text2 = f"Attempting to reconnect to Moonraker.\n\nConnection try number: {text}" + self.dot_timer.start() + self._add_dot() return False - def add_dot(self): - if self.state == "shutdown" and self.message is not None: + def _add_dot(self): + """Add one dot per second (max 3).""" + self.dot_count += 1 + if self.dot_count > 3: self.dot_timer.stop() - return False - - if self.dot_count > 2: - self.dot_count = 1 - else: - self.dot_count += 1 + return dots = "." * self.dot_count + " " * (3 - self.dot_count) - self.panel.connectionTextBox.setText(f"{self.text2}{dots}") + self.panel.connectionTextBox.setText(f"{self.base_text}{dots}") @QtCore.pyqtSlot(str, str, name="webhooks_update") def webhook_update(self, state: str, message: str): + """Handle websocket webhook updates""" self.state = state self.message = message self.text_update() - def eventFilter( - self, object: QtCore.QObject, event: QtCore.QEvent - ) -> bool: + def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool: + """Re-implemented method, filter events""" if event.type() == KlippyDisconnected.type(): + self.dot_timer.stop() if not self.isVisible(): self.panel.connectionTextBox.setText("Klippy Disconnected") self.show() elif event.type() == KlippyReady.type(): + self.dot_timer.stop() self.panel.connectionTextBox.setText("Klippy Ready") self.hide() return False elif event.type() == KlippyShutdown.type(): + self.dot_timer.stop() if not self.isVisible(): self.panel.connectionTextBox.setText(f"{self.message}") self.show() diff --git a/BlocksScreen/lib/panels/widgets/dialogPage.py b/BlocksScreen/lib/panels/widgets/dialogPage.py deleted file mode 100644 index 7ed3d42c..00000000 --- a/BlocksScreen/lib/panels/widgets/dialogPage.py +++ /dev/null @@ -1,183 +0,0 @@ -from PyQt6 import QtCore, QtGui, QtWidgets - - -class DialogPage(QtWidgets.QDialog): - button_clicked = QtCore.pyqtSignal( - str - ) # Signal to emit which button was clicked - - def __init__( - self, - parent: QtWidgets.QWidget, - ) -> None: - super().__init__(parent) - - self.setWindowFlags( - QtCore.Qt.WindowType.Popup - | QtCore.Qt.WindowType.FramelessWindowHint - ) - self.setAttribute( - QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True - ) # Make background transparent - - self.setupUI() - self.repaint() - - def set_message(self, message: str) -> None: - self.label.setText(message) - - def geometry_calc(self) -> None: - app_instance = QtWidgets.QApplication.instance() - main_window = app_instance.activeWindow() if app_instance else None - if main_window is None and app_instance: - for widget in app_instance.allWidgets(): - if isinstance(widget, QtWidgets.QMainWindow): - main_window = widget - - x_offset = 0.7 - y_offset = 0.7 - - width = int(main_window.width() * x_offset) - height = int(main_window.height() * y_offset) - self.testwidth = width - self.testheight = height - x = int(main_window.geometry().x() + (main_window.width() - width) / 2) - y = int( - main_window.geometry().y() + (main_window.height() - height) / 2 - ) - - self.setGeometry(x, y, width, height) - - def paintEvent(self, event: QtGui.QPaintEvent) -> None: - self.geometry_calc() - painter = QtGui.QPainter(self) - painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) - - rect = self.rect() - radius = 20 # Adjust the radius for rounded corners - - # Set background color - painter.setBrush( - QtGui.QBrush(QtGui.QColor(63, 63, 63)) - ) # Semi-transparent dark gray - - # Set border color and width - border_color = QtGui.QColor(128, 128, 128) # Gray color - border_width = 5 # Reduced border thickness - - pen = QtGui.QPen() - pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap) - painter.setPen(QtGui.QPen(border_color, border_width)) - - painter.drawRoundedRect(rect, radius, radius) - - painter.end() - - def sizeHint(self) -> QtCore.QSize: - popup_width = int(self.geometry().width()) - popup_height = int(self.geometry().height()) - # Centering logic - - popup_x = self.x() - popup_y = self.y() + (self.height() - popup_height) // 2 - self.move(popup_x, popup_y) - self.setFixedSize(popup_width, popup_height) - self.setMinimumSize(popup_width, popup_height) - return super().sizeHint() - - def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: - return - - def resizeEvent(self, event: QtGui.QResizeEvent) -> None: - super().resizeEvent(event) - - label_width = self.testwidth - label_height = self.testheight - label_x = (self.width() - label_width) // 2 - label_y = ( - int(label_height / 4) - 20 - ) # Move the label to the top (adjust as needed) - - self.label.setGeometry(label_x, -label_y, label_width, label_height) - - # Adjust button positions on resize - self.confirm_button.setGeometry( - int(0), self.height() - 70, int(self.width() / 2), 70 - ) - self.cancel_button.setGeometry( - int(self.width() / 2), - self.height() - 70, - int(self.width() / 2), - 70, - ) - - def show(self) -> None: - self.geometry_calc() - return super().show() - - def setupUI(self) -> None: - self.label = QtWidgets.QLabel("Test", self) - font = QtGui.QFont() - font.setPointSize(25) - self.label.setFont(font) - self.label.setStyleSheet("color: #ffffff; background: transparent;") - self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.label.setWordWrap(True) - - # Create Confirm and Cancel buttons - self.confirm_button = QtWidgets.QPushButton("Confirm", self) - self.cancel_button = QtWidgets.QPushButton("Back", self) - - # Set button styles - button_font = QtGui.QFont() - button_font.setPointSize(14) - self.confirm_button.setFont(button_font) - self.cancel_button.setFont(button_font) - - # Apply styles for rounded corners - self.confirm_button.setStyleSheet( - """ - background-color: #4CAF50; - color: white; - border: none; - border-bottom-left-radius: 20px; - padding: 10px; - """ - ) - self.cancel_button.setStyleSheet( - """ - background-color: #F44336; - color: white; - border: none; - border-bottom-right-radius: 20px; - padding: 10px; - """ - ) - - # Position buttons - self.confirm_button.setGeometry( - int(0), self.height() - 70, int(self.width() / 2), 70 - ) - self.cancel_button.setGeometry( - int(self.width() / 2), - self.height() - 70, - int(self.width() / 2), - 70, - ) - - # Connect button signals - self.confirm_button.clicked.connect( - lambda: self.on_button_clicked("Confirm") - ) - self.cancel_button.clicked.connect( - lambda: self.on_button_clicked("Cancel") - ) - - def on_button_clicked(self, button_name: str) -> None: - self.button_clicked.emit( - button_name - ) # Emit the signal with the button name - if button_name == "Confirm": - self.accept() # Close the dialog with an accepted state - elif button_name == "Cancel": - self.reject() # Close the dialog with a rejected state diff --git a/BlocksScreen/lib/panels/widgets/fansPage.py b/BlocksScreen/lib/panels/widgets/fansPage.py index 5dffba42..925c0230 100644 --- a/BlocksScreen/lib/panels/widgets/fansPage.py +++ b/BlocksScreen/lib/panels/widgets/fansPage.py @@ -1,13 +1,15 @@ from PyQt6 import QtCore, QtWidgets -import typing +import typing -class FansPage(QtWidgets.QWidget): +class FansPage(QtWidgets.QWidget): def __init__( - self, parent: typing.Optional["QtWidgets.QWidget"], flags: typing.Optional["QtCore.Qt.WindowType"] + self, + parent: typing.Optional["QtWidgets.QWidget"], + flags: typing.Optional["QtCore.Qt.WindowType"], ) -> None: - if parent is not None and flags is not None: + if parent is not None and flags is not None: super(FansPage, self).__init__(parent, flags) - else : - super(FansPage, self).__init__() \ No newline at end of file + else: + super(FansPage, self).__init__() diff --git a/BlocksScreen/lib/panels/widgets/filesPage.py b/BlocksScreen/lib/panels/widgets/filesPage.py index 238c5eed..f8fa490f 100644 --- a/BlocksScreen/lib/panels/widgets/filesPage.py +++ b/BlocksScreen/lib/panels/widgets/filesPage.py @@ -5,9 +5,10 @@ import helper_methods from lib.utils.blocks_Scrollbar import CustomScrollBar from lib.utils.icon_button import IconButton -from lib.utils.list_button import ListCustomButton from PyQt6 import QtCore, QtGui, QtWidgets +from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem + logger = logging.getLogger("logs/BlocksScreen.log") @@ -27,15 +28,17 @@ class FilesPage(QtWidgets.QWidget): request_file_list: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( [], [str], name="api-get-files-list" ) - request_file_metadata: typing.ClassVar[QtCore.pyqtSignal] = ( - QtCore.pyqtSignal(str, name="api-get-gcode-metadata") + request_file_metadata: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + str, name="api-get-gcode-metadata" ) file_list: list = [] files_data: dict = {} directories: list = [] def __init__(self, parent) -> None: - super().__init__(parent) + super().__init__() + self.model = EntryListModel() + self.entry_delegate = EntryDelegate() self._setupUI() self.setMouseTracking(True) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) @@ -43,42 +46,67 @@ def __init__(self, parent) -> None: self.ReloadButton.clicked.connect( lambda: self.request_dir_info[str].emit(self.curr_dir) ) - self.listWidget.verticalScrollBar().valueChanged.connect( - self._handle_scrollbar - ) + self.listWidget.verticalScrollBar().valueChanged.connect(self._handle_scrollbar) self.scrollbar.valueChanged.connect(self._handle_scrollbar) self.scrollbar.valueChanged.connect( lambda value: self.listWidget.verticalScrollBar().setValue(value) ) self.back_btn.clicked.connect(self.reset_dir) + self.entry_delegate.item_selected.connect(self._on_item_selected) + self._refresh_one_and_half_sec_timer = QtCore.QTimer() + self._refresh_one_and_half_sec_timer.timeout.connect( + lambda: self.request_dir_info[str].emit(self.curr_dir) + ) + self._refresh_one_and_half_sec_timer.start(1500) + + @QtCore.pyqtSlot(ListItem, name="on-item-selected") + def _on_item_selected(self, item: ListItem) -> None: + """Slot called when a list item is selected in the UI. + This method is connected to the `item_selected` signal of the entry delegate. + It handles the selection of a `ListItem` and process it accoding it with its type + Args: + item : ListItem The item that was selected by the user. + """ + if not item.left_icon: + filename = self.curr_dir + "/" + item.text + ".gcode" + self._fileItemClicked(filename) + else: + if item.text == "Go Back": + go_back_path = os.path.dirname(self.curr_dir) + if go_back_path == "/": + go_back_path = "" + self._on_goback_dir(go_back_path) + else: + self._dirItemClicked("/" + item.text) + @QtCore.pyqtSlot(name="reset-dir") def reset_dir(self) -> None: + """Reset current directory""" self.curr_dir = "" self.request_dir_info[str].emit(self.curr_dir) def showEvent(self, a0: QtGui.QShowEvent) -> None: + """Re-implemented method, handle widget show event""" self._build_file_list() return super().showEvent(a0) @QtCore.pyqtSlot(list, name="on-file-list") def on_file_list(self, file_list: list) -> None: + """Handle receiving files list from websocket""" self.files_data.clear() self.file_list = file_list - # if self.isVisible(): # Only build the list when directories come - # self._build_file_list() @QtCore.pyqtSlot(list, name="on-dirs") def on_directories(self, directories_data: list) -> None: + """Handle receiving available directories from websocket""" self.directories = directories_data if self.isVisible(): self._build_file_list() - @QtCore.pyqtSlot(str, name="on-delete-file") - def on_delete_file(self, filename: str) -> None: ... - @QtCore.pyqtSlot(dict, name="on-fileinfo") def on_fileinfo(self, filedata: dict) -> None: + """Method called per file to contruct file entry to the list""" if not filedata or not self.isVisible(): return filename = filedata.get("filename", "") @@ -86,11 +114,7 @@ def on_fileinfo(self, filedata: dict) -> None: return self.files_data.update({f"{filename}": filedata}) estimated_time = filedata.get("estimated_time", 0) - seconds = ( - int(estimated_time) - if isinstance(estimated_time, (int, float)) - else 0 - ) + seconds = int(estimated_time) if isinstance(estimated_time, (int, float)) else 0 filament_type = ( filedata.get("filament_type", "Unknown filament") if filedata.get("filament_type", "Unknown filament") != -1.0 @@ -110,61 +134,53 @@ def on_fileinfo(self, filedata: dict) -> None: else: time_str = f"{minutes}m" - list_items = [ - self.listWidget.item(i) for i in range(self.listWidget.count()) - ] - if not list_items: - return - for list_item in list_items: - item_widget = self.listWidget.itemWidget(list_item) - if item_widget.text() in filename: - item_widget.setRightText(f"{filament_type} - {time_str}") + name = helper_methods.get_file_name(filename) + item = ListItem( + text=name[:-6], + right_text=f"{filament_type} - {time_str}", + right_icon=self.path.get("right_arrow"), + left_icon=None, + callback=None, + selected=False, + allow_check=False, + _lfontsize=17, + _rfontsize=12, + height=80, + notificate=False, + ) + + self.model.add_item(item) - @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, name="file-item-clicked") - def _fileItemClicked(self, item: QtWidgets.QListWidgetItem) -> None: + @QtCore.pyqtSlot(str, name="file-item-clicked") + def _fileItemClicked(self, filename: str) -> None: """Slot for List Item clicked Args: - item (QListWidgetItem): Clicked item + filename (str): Clicked item path """ - if item: - widget = self.listWidget.itemWidget(item) - for file in self.file_list: - path = ( - file.get("path") - if "path" in file.keys() - else file.get("filename") - ) - if not path: - return - if widget.text() in path: - file_path = ( - path - if not self.curr_dir - else str(self.curr_dir + "/" + path) - ) - self.file_selected.emit( - str(file_path.removeprefix("/")), - self.files_data.get( - file_path.removeprefix("/") - ), # Defaults to Nothing - ) - - @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, str, name="dir-item-clicked") - def _dirItemClicked( - self, item: QtWidgets.QListWidgetItem, directory: str - ) -> None: + self.file_selected.emit( + str(filename.removeprefix("/")), + self.files_data.get(filename.removeprefix("/")), + ) + + def _dirItemClicked(self, directory: str) -> None: + """Method that changes the current view in the list""" self.curr_dir = self.curr_dir + directory self.request_dir_info[str].emit(self.curr_dir) def _build_file_list(self) -> None: """Inserts the currently available gcode files on the QListWidget""" self.listWidget.blockSignals(True) - self.listWidget.clear() - if not self.file_list and not self.directories: + self.model.clear() + self.entry_delegate.clear() + if ( + not self.file_list + and not self.directories + and os.path.islink(self.curr_dir) + ): self._add_placeholder() return - self.listWidget.setSpacing(35) + if self.directories or self.curr_dir != "": if self.curr_dir != "" and self.curr_dir != "/": self._add_back_folder_entry() @@ -172,62 +188,61 @@ def _build_file_list(self) -> None: if dir_data.get("dirname").startswith("."): continue self._add_directory_list_item(dir_data) - sorted_list = sorted( - self.file_list, key=lambda x: x["modified"], reverse=True - ) + sorted_list = sorted(self.file_list, key=lambda x: x["modified"], reverse=True) for item in sorted_list: self._add_file_list_item(item) - self._add_spacer() + self._setup_scrollbar() self.listWidget.blockSignals(False) - self.repaint() + self.listWidget.update() def _add_directory_list_item(self, dir_data: dict) -> None: + """Method that adds directories to the list""" dir_name = dir_data.get("dirname", "") if not dir_name: return - button = ListCustomButton() - button.setText(str(dir_data.get("dirname"))) - button.setSecondPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/folderIcon.svg") - ) - button.setMinimumSize(600, 80) - button.setMaximumSize(700, 80) - button.setLeftFontSize(17) - button.setRightFontSize(12) - list_item = QtWidgets.QListWidgetItem() - list_item.setSizeHint(button.sizeHint()) - self.listWidget.addItem(list_item) - self.listWidget.setItemWidget(list_item, button) - button.clicked.connect( - lambda: self._dirItemClicked(list_item, "/" + dir_name) - ) + item = ListItem( + text=str(dir_name), + left_icon=self.path.get("folderIcon"), + right_text="", + selected=False, + callback=None, + allow_check=False, + _lfontsize=17, + _rfontsize=12, + height=80, + ) + self.model.add_item(item) def _add_back_folder_entry(self) -> None: - button = ListCustomButton() - button.setText("Go Back") - button.setSecondPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/back_folder.svg") - ) - button.setMinimumSize(600, 80) - button.setMaximumSize(700, 80) - button.setLeftFontSize(17) - button.setRightFontSize(12) - list_item = QtWidgets.QListWidgetItem() - list_item.setSizeHint(button.sizeHint()) - self.listWidget.addItem(list_item) - self.listWidget.setItemWidget(list_item, button) + """Method to insert in the list the "Go back" item""" go_back_path = os.path.dirname(self.curr_dir) if go_back_path == "/": go_back_path = "" - button.clicked.connect(lambda: (self._on_goback_dir(go_back_path))) + + item = ListItem( + text="Go Back", + right_text="", + right_icon=None, + left_icon=self.path.get("back_folder"), + callback=None, + selected=False, + allow_check=False, + _lfontsize=17, + _rfontsize=12, + height=80, + notificate=False, + ) + self.model.add_item(item) @QtCore.pyqtSlot(str, str, name="on-goback-dir") def _on_goback_dir(self, directory) -> None: + """Go back behaviour""" self.request_dir_info[str].emit(directory) self.curr_dir = directory def _add_file_list_item(self, file_data_item) -> None: + """Request file information and metadata to create filelist""" if not file_data_item: return @@ -237,73 +252,31 @@ def _add_file_list_item(self, file_data_item) -> None: else file_data_item["filename"] ) if not name.endswith(".gcode"): - # Only list .gcode files, all else ignore return - button = ListCustomButton() - button.setText(name[:-6]) - button.setPixmap( - QtGui.QPixmap(":/arrow_icons/media/btn_icons/right_arrow.svg") - ) - button.setMinimumSize(600, 80) - button.setMaximumSize(700, 80) - button.setLeftFontSize(17) - button.setRightFontSize(12) - list_item = QtWidgets.QListWidgetItem() - list_item.setSizeHint(button.sizeHint()) - self.listWidget.addItem(list_item) - self.listWidget.setItemWidget(list_item, button) - button.clicked.connect(lambda: self._fileItemClicked(list_item)) file_path = ( name if not self.curr_dir else str(self.curr_dir + "/" + name) ).removeprefix("/") + self.request_file_metadata.emit(file_path.removeprefix("/")) self.request_file_info.emit(file_path.removeprefix("/")) - def _add_spacer(self) -> None: - spacer_item = QtWidgets.QListWidgetItem() - spacer_widget = QtWidgets.QWidget() - spacer_widget.setFixedHeight(10) - spacer_item.setSizeHint(spacer_widget.sizeHint()) - self.listWidget.addItem(spacer_item) - def _add_placeholder(self) -> None: - self.listWidget.setSpacing(-1) - placeholder_label = QtWidgets.QLabel("No Files found") - font = QtGui.QFont() - font.setPointSize(25) - placeholder_label.setFont(font) - placeholder_label.setStyleSheet("color: gray;") - placeholder_label.setAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter - ) - placeholder_label.setMinimumSize( - QtCore.QSize(self.listWidget.width(), self.listWidget.height()) - ) + """Shows placeholder when no items exist""" self.scrollbar.hide() - placeholder_item = QtWidgets.QListWidgetItem() - placeholder_item.setSizeHint( - QtCore.QSize(self.listWidget.width(), self.listWidget.height()) - ) - self.listWidget.addItem(placeholder_item) - self.listWidget.setItemWidget(placeholder_item, placeholder_label) + self.listWidget.hide() + self.label.show() def _handle_scrollbar(self, value): - # Block signals to avoid recursion + """Updates scrollbar value""" self.scrollbar.blockSignals(True) self.scrollbar.setValue(value) self.scrollbar.blockSignals(False) def _setup_scrollbar(self) -> None: - self.scrollbar.setMinimum( - self.listWidget.verticalScrollBar().minimum() - ) - self.scrollbar.setMaximum( - self.listWidget.verticalScrollBar().maximum() - ) - self.scrollbar.setPageStep( - self.listWidget.verticalScrollBar().pageStep() - ) + """Syncs the scrollbar with the list size""" + self.scrollbar.setMinimum(self.listWidget.verticalScrollBar().minimum()) + self.scrollbar.setMaximum(self.listWidget.verticalScrollBar().maximum()) + self.scrollbar.setPageStep(self.listWidget.verticalScrollBar().pageStep()) self.scrollbar.show() def _setupUI(self): @@ -321,9 +294,7 @@ def _setupUI(self): self.setFont(font) self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) self.setAutoFillBackground(False) - self.setStyleSheet( - "#file_page{\n background-color: transparent;\n}" - ) + self.setStyleSheet("#file_page{\n background-color: transparent;\n}") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self) self.verticalLayout_5.setObjectName("verticalLayout_5") self.fp_header_layout = QtWidgets.QHBoxLayout() @@ -360,7 +331,10 @@ def _setupUI(self): self.fp_content_layout = QtWidgets.QHBoxLayout() self.fp_content_layout.setContentsMargins(0, 0, 0, 0) self.fp_content_layout.setObjectName("fp_content_layout") - self.listWidget = QtWidgets.QListWidget(parent=self) + self.listWidget = QtWidgets.QListView(parent=self) + self.listWidget.setModel(self.model) + self.listWidget.setItemDelegate(self.entry_delegate) + self.listWidget.setSpacing(5) self.listWidget.setProperty("showDropIndicator", False) self.listWidget.setProperty("selectionMode", "NoSelection") self.listWidget.setStyleSheet("background: transparent;") @@ -368,11 +342,10 @@ def _setupUI(self): self.listWidget.setUniformItemSizes(True) self.listWidget.setObjectName("listWidget") self.listWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.listWidget.setDefaultDropAction(QtCore.Qt.DropAction.IgnoreAction) self.listWidget.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems ) - self.listWidget.setHorizontalScrollBarPolicy( # No horizontal scroll + self.listWidget.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff ) self.listWidget.setVerticalScrollMode( @@ -397,26 +370,42 @@ def _setupUI(self): scroller_props = scroller_instance.scrollerProperties() scroller_props.setScrollMetric( QtWidgets.QScrollerProperties.ScrollMetric.DragVelocitySmoothingFactor, - 0.05, # Lower = more responsive + 0.05, ) scroller_props.setScrollMetric( QtWidgets.QScrollerProperties.ScrollMetric.DecelerationFactor, - 0.4, # higher = less inertia + 0.4, ) QtWidgets.QScroller.scroller(self.listWidget).setScrollerProperties( scroller_props ) + font = QtGui.QFont() font.setPointSize(25) - placeholder_item = QtWidgets.QListWidgetItem() - placeholder_item.setSizeHint( + self.label = QtWidgets.QLabel("No Files found") + self.label.setFont(font) + self.label.setStyleSheet("color: gray;") + self.label.setMinimumSize( QtCore.QSize(self.listWidget.width(), self.listWidget.height()) ) - self.fp_content_layout.addWidget(self.listWidget) + self.scrollbar = CustomScrollBar() + + self.fp_content_layout.addWidget( + self.label, + alignment=QtCore.Qt.AlignmentFlag.AlignHCenter + | QtCore.Qt.AlignmentFlag.AlignVCenter, + ) + self.fp_content_layout.addWidget(self.listWidget) self.fp_content_layout.addWidget(self.scrollbar) self.verticalLayout_5.addLayout(self.fp_content_layout) - self.scrollbar.setAttribute( - QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents, True - ) - self.scroller = QtWidgets.QScroller.scroller(self.listWidget) + self.scrollbar.show() + self.label.hide() + + self.path = { + "back_folder": QtGui.QPixmap(":/ui/media/btn_icons/back_folder.svg"), + "folderIcon": QtGui.QPixmap(":/ui/media/btn_icons/folderIcon.svg"), + "right_arrow": QtGui.QPixmap( + ":/arrow_icons/media/btn_icons/right_arrow.svg" + ), + } diff --git a/BlocksScreen/lib/panels/widgets/inputshaperPage.py b/BlocksScreen/lib/panels/widgets/inputshaperPage.py new file mode 100644 index 00000000..539dcaf4 --- /dev/null +++ b/BlocksScreen/lib/panels/widgets/inputshaperPage.py @@ -0,0 +1,459 @@ +from lib.panels.widgets.loadWidget import LoadingOverlayWidget +from lib.panels.widgets.basePopup import BasePopup +from lib.utils.blocks_button import BlocksCustomButton +from lib.utils.blocks_frame import BlocksCustomFrame +from lib.utils.icon_button import IconButton +from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem +from PyQt6 import QtCore, QtGui, QtWidgets + +import typing + + +class InputShaperPage(QtWidgets.QWidget): + """Update GUI Page, + retrieves from moonraker available clients and adds functionality + for updating or recovering them + """ + + run_gcode_signal: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + str, name="run-gcode" + ) + + def __init__(self, parent=None) -> None: + if parent: + super().__init__(parent) + else: + super().__init__() + self._setupUI() + self.selected_item: ListItem | None = None + self.ongoing_update: bool = False + self.type_dict: dict = {} + + self.loadscreen = BasePopup(self, floating=False, dialog=False) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.loadscreen.add_widget(self.loadwidget) + self.repeated_request_status = QtCore.QTimer() + self.repeated_request_status.setInterval(2000) # every 2 seconds + self.model = EntryListModel() + self.model.setParent(self.update_buttons_list_widget) + self.entry_delegate = EntryDelegate() + self.update_buttons_list_widget.setModel(self.model) + self.update_buttons_list_widget.setItemDelegate(self.entry_delegate) + self.entry_delegate.item_selected.connect(self.on_item_clicked) + self.update_back_btn.clicked.connect(self.reset_view_model) + + self.action_btn.clicked.connect(self.handle_ism_confirm) + + def handle_update_end(self) -> None: + """Handles update end signal + (closes loading page, returns to normal operation) + """ + if self.load_popup.isVisible(): + self.load_popup.close() + self.repeated_request_status.stop() + self.build_model_list() + + def handle_ongoing_update(self) -> None: + """Handled ongoing update signal, + calls loading page (blocks user interaction) + """ + self.loadwidget.set_status_message("Updating...") + self.load_popup.show() + self.repeated_request_status.start(2000) + + def reset_view_model(self) -> None: + """Clears items from ListView + (Resets `QAbstractListModel` by clearing entries) + """ + self.model.clear() + self.entry_delegate.clear() + + def deleteLater(self) -> None: + """Schedule the object for deletion, resets the list model first""" + self.reset_view_model() + return super().deleteLater() + + def showEvent(self, a0: QtGui.QShowEvent | None) -> None: + """Re-add clients to update list""" + return super().showEvent(a0) + + def build_model_list(self) -> None: + """Builds the model list (`self.model`) containing updatable clients""" + self.update_buttons_list_widget.blockSignals(True) + self.model.setData(self.model.index(0), True, EntryListModel.EnableRole) + self.on_item_clicked( + self.model.data(self.model.index(0), QtCore.Qt.ItemDataRole.UserRole) + ) + self.update_buttons_list_widget.blockSignals(False) + + def set_type_dictionary(self, dict) -> None: + """Receives the dictionary of input shaper types from the utilities tab""" + self.type_dict = dict + return + + @QtCore.pyqtSlot(ListItem, name="on-item-clicked") + def on_item_clicked(self, item: ListItem) -> None: + """Setup information for the currently clicked list item on the info box. + Keeps track of the list item + """ + self.currentItem: ListItem = item + if not item: + return + current_info = self.type_dict.get(self.currentItem.text, {}) + if not current_info: + return + + self.vib_label.setText(str("%.0f" % current_info.get("vibration", "N/A")) + "%") + self.sug_accel_label.setText( + str("%.0f" % current_info.get("max_accel", "N/A")) + "mm/s²" + ) + + self.action_btn.show() + + def handle_ism_confirm(self) -> None: + current_info = self.type_dict.get(self.currentItem.text, {}) + frequency = current_info.get("frequency", "N/A") + if self.type_dict["Axis"] == "x": + self.run_gcode_signal.emit( + f"SET_INPUT_SHAPER SHAPER_TYPE_X={self.currentItem.text} SHAPER_FREQ_X={frequency}" + ) + elif self.type_dict["Axis"] == "y": + self.run_gcode_signal.emit( + f"SET_INPUT_SHAPER SHAPER_TYPE_Y={self.currentItem.text} SHAPER_FREQ_Y={frequency}" + ) + + self.run_gcode_signal.emit("SAVE_CONFIG") + self.reset_view_model() + + def add_type_entry(self, cli_name: str, recommended: str = "") -> None: + """Adds a new item to the list model""" + item = ListItem( + text=cli_name, + right_text=recommended, + right_icon=QtGui.QPixmap(":/arrow_icons/media/btn_icons/right_arrow.svg"), + selected=False, + _lfontsize=17, + _rfontsize=9, + height=60, + allow_check=True, + notificate=False, + ) + self.model.add_item(item) + + def _setupUI(self) -> None: + """Setup UI for updatePage""" + font_id = QtGui.QFontDatabase.addApplicationFont( + ":/font/media/fonts for text/Momcake-Bold.ttf" + ) + font_family = QtGui.QFontDatabase.applicationFontFamilies(font_id)[0] + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.MinimumExpanding, + QtWidgets.QSizePolicy.Policy.MinimumExpanding, + ) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(1) + self.setSizePolicy(sizePolicy) + self.setMinimumSize(QtCore.QSize(710, 400)) + self.setMaximumSize(QtCore.QSize(720, 420)) + self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.update_page_content_layout = QtWidgets.QVBoxLayout() + self.update_page_content_layout.setContentsMargins(15, 15, 2, 2) + + self.header_content_layout = QtWidgets.QHBoxLayout() + self.header_content_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) + + self.spacer_left = QtWidgets.QLabel(self) + self.spacer_left.setMinimumSize(QtCore.QSize(60, 60)) + self.spacer_left.setMaximumSize(QtCore.QSize(60, 60)) + self.header_content_layout.addWidget(self.spacer_left, 0) + self.header_title = QtWidgets.QLabel(self) + self.header_title.setMinimumSize(QtCore.QSize(100, 60)) + self.header_title.setMaximumSize(QtCore.QSize(16777215, 60)) + font = QtGui.QFont() + font.setFamily(font_family) + font.setPointSize(24) + palette = self.header_title.palette() + palette.setColor(palette.ColorRole.WindowText, QtGui.QColor("#FFFFFF")) + self.header_title.setFont(font) + self.header_title.setPalette(palette) + self.header_title.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) + self.header_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.header_title.setObjectName("header-title") + self.header_title.setText("Input Shaper") + self.header_content_layout.addWidget(self.header_title, 0) + self.update_back_btn = IconButton(self) + self.update_back_btn.setMinimumSize(QtCore.QSize(60, 60)) + self.update_back_btn.setMaximumSize(QtCore.QSize(60, 60)) + self.update_back_btn.setFlat(True) + self.update_back_btn.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) + self.header_content_layout.addWidget(self.update_back_btn, 0) + self.update_page_content_layout.addLayout(self.header_content_layout, 0) + + self.main_content_layout = QtWidgets.QHBoxLayout() + self.main_content_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + + self.update_buttons_frame = BlocksCustomFrame(self) + + self.update_buttons_frame.setMinimumSize(QtCore.QSize(300, 300)) + self.update_buttons_frame.setMaximumSize(QtCore.QSize(350, 500)) + + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Button, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Base, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Window, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 120, 215, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Highlight, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Link, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Button, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Base, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Window, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 120, 215, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Highlight, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Link, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Button, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Base, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Window, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 120, 215, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Highlight, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Link, + brush, + ) + self.update_buttons_list_widget = QtWidgets.QListView(self.update_buttons_frame) + self.update_buttons_list_widget.setMouseTracking(True) + self.update_buttons_list_widget.setTabletTracking(True) + + self.update_buttons_list_widget.setPalette(palette) + self.update_buttons_list_widget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.update_buttons_list_widget.setStyleSheet("background-color:transparent") + self.update_buttons_list_widget.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.update_buttons_list_widget.setMinimumSize(self.update_buttons_frame.size()) + self.update_buttons_list_widget.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) + self.update_buttons_list_widget.setVerticalScrollBarPolicy( + QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + self.update_buttons_list_widget.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + self.update_buttons_list_widget.setSizeAdjustPolicy( + QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents + ) + self.update_buttons_list_widget.setAutoScroll(False) + self.update_buttons_list_widget.setProperty("showDropIndicator", False) + self.update_buttons_list_widget.setDefaultDropAction( + QtCore.Qt.DropAction.IgnoreAction + ) + self.update_buttons_list_widget.setAlternatingRowColors(False) + self.update_buttons_list_widget.setSelectionMode( + QtWidgets.QAbstractItemView.SelectionMode.NoSelection + ) + self.update_buttons_list_widget.setSelectionBehavior( + QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems + ) + self.update_buttons_list_widget.setVerticalScrollMode( + QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel + ) + self.update_buttons_list_widget.setHorizontalScrollMode( + QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel + ) + QtWidgets.QScroller.grabGesture( + self.update_buttons_list_widget, + QtWidgets.QScroller.ScrollerGestureType.TouchGesture, + ) + QtWidgets.QScroller.grabGesture( + self.update_buttons_list_widget, + QtWidgets.QScroller.ScrollerGestureType.LeftMouseButtonGesture, + ) + self.update_buttons_layout = QtWidgets.QVBoxLayout() + self.update_buttons_layout.setContentsMargins(15, 20, 20, 5) + self.update_buttons_layout.addWidget(self.update_buttons_list_widget, 0) + self.update_buttons_frame.setLayout(self.update_buttons_layout) + + self.main_content_layout.addWidget(self.update_buttons_frame, 0) + + self.infobox_frame = BlocksCustomFrame() + self.infobox_frame.setMinimumSize(QtCore.QSize(250, 300)) + + self.info_box_layout = QtWidgets.QVBoxLayout() + self.info_box_layout.setContentsMargins(10, 10, 10, 10) + + font = QtGui.QFont() + font.setFamily(font_family) + font.setPointSize(20) + self.info_box = QtWidgets.QGridLayout() + self.info_box.setContentsMargins(0, 0, 0, 0) + + self.vib_title_label = QtWidgets.QLabel(self) + self.vib_title_label.setText("Vibrations: ") + self.vib_title_label.setMinimumSize(QtCore.QSize(60, 60)) + self.vib_title_label.setMaximumSize( + QtCore.QSize(int(self.infobox_frame.size().width() * 0.40), 9999) + ) + palette = self.vib_title_label.palette() + palette.setColor(palette.ColorRole.WindowText, QtGui.QColor("#FFFFFF")) + self.vib_title_label.setAlignment( + QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter + ) + self.vib_title_label.setFont(font) + self.vib_title_label.setPalette(palette) + self.vib_title_label.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) + self.vib_label = QtWidgets.QLabel(self) + self.vib_label.setMinimumSize(QtCore.QSize(100, 60)) + self.vib_label.setMaximumSize(QtCore.QSize(16777215, 9999)) + palette = self.vib_label.palette() + palette.setColor(palette.ColorRole.WindowText, QtGui.QColor("#FFFFFF")) + self.vib_label.setFont(font) + self.vib_label.setPalette(palette) + self.vib_label.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) + self.vib_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.vib_label.setObjectName("version-tracking") + + self.info_box.addWidget(self.vib_title_label, 0, 0) + self.info_box.addWidget(self.vib_label, 0, 1) + + self.sug_accel_title_label = QtWidgets.QLabel(self) + self.sug_accel_title_label.setText("Sugested Max\nAcceleration:") + self.sug_accel_title_label.setMinimumSize(QtCore.QSize(60, 60)) + self.sug_accel_title_label.setMaximumSize( + QtCore.QSize(int(self.infobox_frame.size().width() * 0.40), 9999) + ) + palette = self.sug_accel_title_label.palette() + palette.setColor(palette.ColorRole.WindowText, QtGui.QColor("#FFFFFF")) + self.sug_accel_title_label.setAlignment( + QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter + ) + self.sug_accel_title_label.setFont(font) + self.sug_accel_title_label.setPalette(palette) + self.sug_accel_title_label.setLayoutDirection( + QtCore.Qt.LayoutDirection.RightToLeft + ) + + self.sug_accel_label = QtWidgets.QLabel(self) + self.sug_accel_label.setMinimumSize(QtCore.QSize(100, 60)) + self.sug_accel_label.setMaximumSize( + QtCore.QSize(int(self.infobox_frame.size().width() * 0.60), 9999) + ) + palette = self.sug_accel_label.palette() + palette.setColor(palette.ColorRole.WindowText, QtGui.QColor("#FFFFFF")) + self.sug_accel_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.sug_accel_label.setFont(font) + self.sug_accel_label.setPalette(palette) + + self.info_box.addWidget(self.sug_accel_title_label, 1, 0) + self.info_box.addWidget(self.sug_accel_label, 1, 1) + + self.info_box_layout.addLayout(self.info_box, 1) + + self.button_box = QtWidgets.QVBoxLayout() + self.button_box.setContentsMargins(0, 0, 0, 0) + self.button_box.addSpacing(-1) + + self.action_btn = BlocksCustomButton() + self.action_btn.setMinimumSize(QtCore.QSize(200, 60)) + self.action_btn.setMaximumSize(QtCore.QSize(250, 60)) + font.setPointSize(20) + self.action_btn.setFont(font) + self.action_btn.setPalette(palette) + self.action_btn.setSizePolicy(sizePolicy) + self.action_btn.setText("Confirm") + self.action_btn.setPixmap(QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg")) + self.button_box.addWidget( + self.action_btn, + 0, + QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignBottom, + ) + + self.info_box_layout.addLayout( + self.button_box, + 0, + ) + self.infobox_frame.setLayout(self.info_box_layout) + self.main_content_layout.addWidget(self.infobox_frame, 1) + self.update_page_content_layout.addLayout(self.main_content_layout, 1) + self.setLayout(self.update_page_content_layout) diff --git a/BlocksScreen/lib/panels/widgets/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py index e632dc2f..bd5bbb8c 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -3,25 +3,26 @@ import events from helper_methods import calculate_current_layer, estimate_print_time -from lib.panels.widgets import dialogPage +from lib.panels.widgets.basePopup import BasePopup from lib.utils.blocks_button import BlocksCustomButton from lib.utils.blocks_label import BlocksLabel from lib.utils.blocks_progressbar import CustomProgressBar from lib.utils.display_button import DisplayButton from PyQt6 import QtCore, QtGui, QtWidgets +logger = logging.getLogger("logs/BlocksScreen.log") -class ClickableGraphicsView(QtWidgets.QGraphicsView): - clicked = QtCore.pyqtSignal() +class JobStatusWidget(QtWidgets.QWidget): + """Job status widget page, page shown when there is a active print job. -def mousePressEvent(self, event: QtGui.QMouseEvent) -> None: - if event.button() == QtCore.Qt.MouseButton.LeftButton: - self.clicked.emit() - super(ClickableGraphicsView, self).mousePressEvent(event) + Enables mid print printer tuning and inspection of print progress. -class JobStatusWidget(QtWidgets.QWidget): + Args: + QtWidgets (QtWidgets.QWidget): Parent widget + """ + print_start: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( str, name="print_start" ) @@ -34,6 +35,9 @@ class JobStatusWidget(QtWidgets.QWidget): print_cancel: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( name="print_cancel" ) + print_finish: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + name="print_finish" + ) tune_clicked: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( name="tune_clicked" ) @@ -57,93 +61,114 @@ class JobStatusWidget(QtWidgets.QWidget): def __init__(self, parent) -> None: super().__init__(parent) - - self.canceldialog = dialogPage.DialogPage(self) - self.setupUI() + self.thumbnail_graphics = [] + self.layer_fallback = False + self._setupUI() + self.cancel_print_dialog = BasePopup(self, floating=True) self.tune_menu_btn.clicked.connect(self.tune_clicked.emit) self.pause_printing_btn.clicked.connect(self.pause_resume_print) self.stop_printing_btn.clicked.connect(self.handleCancel) - self.CBVSmallThumbnail.clicked.connect(self.showthumbnail) - self.CBVBigThumbnail.clicked.connect(self.hidethumbnail) + @QtCore.pyqtSlot(name="toggle-thumbnail-expansion") + def toggle_thumbnail_expansion(self) -> None: + """Toggle thumbnail expansion""" + if not self.thumbnail_view.scene(): + return + if not self.thumbnail_view.isVisible(): + self.thumbnail_view.show() + self.progressWidget.hide() + self.contentWidget.hide() + self.printing_progress_bar.hide() + self.btnWidget.hide() + self.headerWidget.hide() + return + self.thumbnail_view.hide() + self.progressWidget.show() + self.contentWidget.show() + self.printing_progress_bar.show() + self.btnWidget.show() + self.headerWidget.show() + self.show() - self.smalthumbnail = QtGui.QImage( - "BlocksScreen/lib/ui/resources/media/smalltest.png" - ) - self.bigthumbnail = QtGui.QImage( - "BlocksScreen/lib/ui/resources/media/thumbnailmissing.png" - ) - self.CBVSmallThumbnail.installEventFilter(self) - self.CBVBigThumbnail.installEventFilter(self) + def showEvent(self, a0) -> None: + """Reimplemented method, handle `show` Event""" + if self._current_file_name: + self.request_file_info.emit(self._current_file_name) - def eventFilter(self, source, event): - if ( - source == self.CBVSmallThumbnail - and event.type() == QtCore.QEvent.Type.MouseButtonPress - ): - if event.button() == QtCore.Qt.MouseButton.LeftButton: - self.showthumbnail() + def eventFilter(self, sender_obj: QtCore.QObject, event: events.QEvent) -> bool: + """Filter events, + currently only filters events from `self.thumbnail_view` QGraphicsView widget + """ if ( - source == self.CBVBigThumbnail + sender_obj == self.thumbnail_view and event.type() == QtCore.QEvent.Type.MouseButtonPress ): - if event.button() == QtCore.Qt.MouseButton.LeftButton: - self.hidethumbnail() - - return super().eventFilter(source, event) - - @QtCore.pyqtSlot(name="show-thumbnail") - def showthumbnail(self): - self.contentWidget.hide() - self.progressWidget.hide() - self.headerWidget.hide() - self.btnWidget.hide() - self.smallthumb_widget.hide() - self.bigthumb_widget.show() - - @QtCore.pyqtSlot(name="hide-thumbnail") - def hidethumbnail(self): - self.contentWidget.show() - self.progressWidget.show() - self.headerWidget.show() - self.btnWidget.show() - self.smallthumb_widget.show() - self.bigthumb_widget.hide() + self.toggle_thumbnail_expansion() + return True + return super().eventFilter(sender_obj, event) + + def _load_thumbnails(self, *thumbnails) -> None: + """Pre-load available thumbnails for the current print object""" + self.thumbnail_graphics = list( + filter( + lambda thumb: not thumb.isNull(), + [QtGui.QPixmap(thumb) for thumb in thumbnails], + ) + ) + if not self.thumbnail_graphics: + logger.debug("Unable to load thumbnails, no thumbnails provided") + return + self.create_thumbnail_widget() + self.thumbnail_view.installEventFilter(self) + scene = QtWidgets.QGraphicsScene() + _biggest_thumb = self.thumbnail_graphics[-1] + self.thumbnail_view.setSceneRect( + QtCore.QRectF( + self.rect().x(), + self.rect().y(), + _biggest_thumb.width(), + _biggest_thumb.height(), + ) + ) + scaled = QtGui.QPixmap(_biggest_thumb).scaled( + _biggest_thumb.width(), + _biggest_thumb.height(), + QtCore.Qt.AspectRatioMode.KeepAspectRatio, + QtCore.Qt.TransformationMode.SmoothTransformation, + ) + item = QtWidgets.QGraphicsPixmapItem(scaled) + scene.addItem(item) + self.thumbnail_view.setFrameRect( + QtCore.QRect( + 0, 0, self.contentsRect().width(), self.contentsRect().height() + ) + ) + self.thumbnail_view.setScene(scene) + self.printing_progress_bar.set_inner_pixmap(self.thumbnail_graphics[-1]) + self.printing_progress_bar.thumbnail_clicked.connect( + self.toggle_thumbnail_expansion + ) @QtCore.pyqtSlot(name="handle-cancel") def handleCancel(self) -> None: - """Handle the cancel print job dialog""" - self.canceldialog.set_message( - "Are you sure you \n want to cancel \n this print job?" + """Handle cancel print job dialog""" + self.cancel_print_dialog.set_message( + "Are you sure you \n want to cancel \n the current print job?" ) - self.canceldialog.button_clicked.connect(self.on_dialog_button_clicked) - self.canceldialog.show() + self.cancel_print_dialog.accepted.connect(self.print_cancel) + self.cancel_print_dialog.open() - def on_dialog_button_clicked(self, button_name: str) -> None: - """Handle dialog button clicks""" - if button_name == "Confirm": - self.print_cancel.emit() # Emit the print_cancel signal - elif button_name == "Cancel": - pass - - @QtCore.pyqtSlot(str, list, name="on_print_start") - def on_print_start(self, file: str, thumbnails: list) -> None: + @QtCore.pyqtSlot(str, name="on_print_start") + def on_print_start(self, file: str) -> None: """Start a print job, show job status page""" self._current_file_name = file self.js_file_name_label.setText(self._current_file_name) self.layer_display_button.setText("?") self.print_time_display_button.setText("?") - if thumbnails: - self.smalthumbnail = thumbnails[0] - self.bigthumbnail = thumbnails[1] - self.printing_progress_bar.reset() self._internal_print_status = "printing" - self.request_file_info.emit( - file - ) # Request file metadata (or file info whatever) - + self.request_file_info.emit(file) self.print_start.emit(file) print_start_event = events.PrintStart( self._current_file_name, self.file_metadata @@ -155,43 +180,76 @@ def on_print_start(self, file: str, thumbnails: list) -> None: else: raise TypeError("QApplication.instance expected non None value") except Exception as e: - logging.debug(f"Unexpected error while posting print job start event: {e}") + logger.debug("Unexpected error while posting print job start event: %s", e) @QtCore.pyqtSlot(dict, name="on_fileinfo") def on_fileinfo(self, fileinfo: dict) -> None: - self.total_layers = str(fileinfo.get("layer_count", "?")) - self.layer_display_button.setText("?") - if ( - fileinfo.get("thumbnail_images", []) - and len(fileinfo.get("thumbnail_images", [])) > 0 - ): - self.smalthumbnail = fileinfo["thumbnail_images"][1] - self.bigthumbnail = fileinfo["thumbnail_images"][ - -1 - ] # Last 'biggest' element - + """Handle received file information/metadata""" + if not self.isVisible(): + return + self.total_layers = str(fileinfo.get("layer_count", "---")) + self.layer_display_button.setText("---") self.layer_display_button.secondary_text = str(self.total_layers) self.file_metadata = fileinfo + self._load_thumbnails(*fileinfo.get("thumbnail_images", [])) @QtCore.pyqtSlot(name="pause_resume_print") def pause_resume_print(self) -> None: - if not getattr(self, "_pause_locked", False): - self._pause_locked = True - self.pause_printing_btn.setEnabled(False) - - if self._internal_print_status == "printing": - self.print_pause.emit() - self._internal_print_status = "paused" - - elif self._internal_print_status == "paused": - self.print_resume.emit() - self._internal_print_status = "printing" - - QtCore.QTimer.singleShot(5000, self._unlock_pause_button) - - def _unlock_pause_button(self): - self._pause_locked = False - self.pause_printing_btn.setEnabled(True) + """Handle pause/resume print job button clicked""" + self.pause_printing_btn.setEnabled(False) + if self._internal_print_status == "printing": + self._internal_print_status = "paused" + self.print_pause.emit() + elif self._internal_print_status == "paused": + self._internal_print_status = "printing" + self.print_resume.emit() + + def _handle_print_state(self, state: str) -> None: + """Handle print state change received from + printer_status object updated + """ + valid_states = {"printing", "paused"} + invalid_states = {"cancelled", "complete", "error", "standby"} + lstate = state.lower() + if lstate in valid_states: + self._internal_print_status = lstate + if lstate == "paused": + self.pause_printing_btn.setText(" Resume") + self.pause_printing_btn.setPixmap( + QtGui.QPixmap(":/ui/media/btn_icons/play.svg") + ) + elif lstate == "printing": + self.pause_printing_btn.setText("Pause") + self.pause_printing_btn.setPixmap( + QtGui.QPixmap(":/ui/media/btn_icons/pause.svg") + ) + self.pause_printing_btn.setEnabled(True) + self.request_query_print_stats.emit({"print_stats": ["filename"]}) + self.show_request.emit() + lstate = "start" + elif lstate in invalid_states: + if lstate != "standby": + self.print_finish.emit() + self._current_file_name = "" + self._internal_print_status = "" + self.total_layers = "?" + self.file_metadata.clear() + self.hide_request.emit() + if hasattr(self, "thumbnail_view"): + getattr(self, "thumbnail_view").deleteLater() + # Send Event on Print state + if hasattr(events, str("Print" + lstate.capitalize())): + event_obj = getattr(events, str("Print" + lstate.capitalize())) + event = event_obj(self._current_file_name, self.file_metadata) + instance = QtWidgets.QApplication.instance() + if instance: + instance.postEvent(self.window(), event) + return + logger.error( + "QApplication.instance expected non None value,\ + Unable to post event %s", + str("Print" + lstate.capitalize()), + ) @QtCore.pyqtSlot(str, dict, name="on_print_stats_update") @QtCore.pyqtSlot(str, float, name="on_print_stats_update") @@ -205,182 +263,92 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None: value (dict | float | str): The value for the field. """ if isinstance(value, str): + if "state" in field: + self._handle_print_state(value) if "filename" in field: self._current_file_name = value if self.js_file_name_label.text().lower() != value.lower(): self.js_file_name_label.setText(self._current_file_name) - self.request_file_info.emit(value) # Request file metadata - if "state" in field: - if value.lower() == "printing" or value == "paused": - self._internal_print_status = value - if value == "paused": - self.pause_printing_btn.setText(" Resume") - self.pause_printing_btn.setPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/play.svg") - ) - elif value == "printing": - self.pause_printing_btn.setText("Pause") - self.pause_printing_btn.setPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/pause.svg") - ) - self.request_query_print_stats.emit({"print_stats": ["filename"]}) - self.show_request.emit() - value = "start" # This is for event compatibility - elif value in ("cancelled", "complete", "error", "standby"): - self._current_file_name = "" - self._internal_print_status = "" - self.total_layers = "?" - self.file_metadata.clear() - self.hide_request.emit() - - - if hasattr(events, str("Print" + value.capitalize())): - event_obj = getattr(events, str("Print" + value.capitalize())) - event = event_obj(self._current_file_name, self.file_metadata) - try: - instance = QtWidgets.QApplication.instance() - if instance: - instance.postEvent(self.window(), event) - else: - raise TypeError( - "QApplication.instance expected non None value" - ) - except Exception as e: - logging.info( - f"Unexpected error while posting print job start event: {e}" - ) - + if self.isVisible(): + self.request_file_info.emit(value) if not self.file_metadata: return + if not self.isVisible(): + return if isinstance(value, dict): + self.layer_fallback = False if "total_layer" in value.keys(): self.total_layers = value["total_layer"] - self.layer_display_button.secondary_text = str(self.total_layers) + if value["total_layer"] is not None: + self.layer_display_button.secondary_text = str(self.total_layers) + + else: + self.total_layers = "---" + self.layer_fallback = True + if "current_layer" in value.keys(): if value["current_layer"] is not None: _current_layer = value["current_layer"] - if _current_layer is not None: - self.layer_display_button.setText(f"{int(_current_layer)}") + self.layer_display_button.setText(f"{int(_current_layer)}") + else: + self.layer_display_button.setText("---") + self.layer_fallback = True elif isinstance(value, float): if "total_duration" in field: - self.print_total_duration = value - _time = estimate_print_time(int(self.print_total_duration)) + _time = estimate_print_time(int(value)) _print_time_string = ( f"{_time[0]}Day {_time[1]}H {_time[2]}min {_time[3]} s" if _time[0] != 0 else f"{_time[1]}H {_time[2]}min {_time[3]}s" ) self.print_time_display_button.setText(_print_time_string) - elif "print_duration" in field: - self.current_print_duration_seconds = value - elif "filament_used" in field: - self.filament_used_mm = value @QtCore.pyqtSlot(str, list, name="on_gcode_move_update") def on_gcode_move_update(self, field: str, value: list) -> None: - # """Processes the information that comes from the printer object "gcode_move" - - # Args: - # field (str): Name of the updated field - # value (list): New value for the field - # """ - - if isinstance(value, list): - if "gcode_position" in field: # Without offsets - if self._internal_print_status == "printing": + """Handle gcode move""" + if not self.isVisible(): + return + if "gcode_position" in field: + if self._internal_print_status == "printing": + if self.layer_fallback: + object_height = float(self.file_metadata.get("object_height", -1.0)) + layer_height = float(self.file_metadata.get("layer_height", -1.0)) + first_layer_height = float( + self.file_metadata.get("first_layer_height", -1.0) + ) _current_layer = calculate_current_layer( z_position=value[2], - object_height=float( - self.file_metadata.get("object_height", -1.0) - ), - layer_height=float( - self.file_metadata.get("layer_height", -1.0) - ), - first_layer_height=float( - self.file_metadata.get("first_layer_height", -1.0) - ), + object_height=object_height, + layer_height=layer_height, + first_layer_height=first_layer_height, + ) + + total_layer = ( + (object_height) / layer_height if layer_height > 0 else -1 + ) + self.layer_display_button.secondary_text = ( + f"{int(total_layer)}" if total_layer != -1 else "---" ) self.layer_display_button.setText( - f"{int(_current_layer)}" if _current_layer != -1 else "?" + f"{int(_current_layer)}" if _current_layer != -1 else "---" ) @QtCore.pyqtSlot(str, float, name="virtual_sdcard_update") @QtCore.pyqtSlot(str, bool, name="virtual_sdcard_update") def virtual_sdcard_update(self, field: str, value: float | bool) -> None: - """Slot for incoming printer object virtual_sdcard information update + """Handle virtual sdcard Args: field (str): Name of the updated field on the virtual_sdcard object value (float | bool): The updated information for the corresponding field """ - if isinstance(value, bool): - self.sdcard_read = value - elif isinstance(value, float): - if "progress" == field: - self.print_progress = value - self.printing_progress_bar.setValue(self.print_progress) - - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: - _scene = QtWidgets.QGraphicsScene() - if not self.smalthumbnail.isNull(): - _graphics_rect = self.CBVSmallThumbnail.rect().toRectF() - _image_rect = self.smalthumbnail.rect() - - scaled_width = _image_rect.width() - scaled_height = _image_rect.height() - adjusted_x = (_graphics_rect.width() - scaled_width) // 2.0 - adjusted_y = (_graphics_rect.height() - scaled_height) // 2.0 - - adjusted_rect = QtCore.QRectF( - _image_rect.x() + adjusted_x, - _image_rect.y() + adjusted_y, - scaled_width, - scaled_height, - ) - _scene.setSceneRect(adjusted_rect) - _item_scaled = QtWidgets.QGraphicsPixmapItem( - QtGui.QPixmap.fromImage(self.smalthumbnail).scaled( - int(scaled_width), - int(scaled_height), - QtCore.Qt.AspectRatioMode.KeepAspectRatio, - QtCore.Qt.TransformationMode.SmoothTransformation, - ) - ) - _scene.addItem(_item_scaled) - self.CBVSmallThumbnail.setScene(_scene) - - else: - self.request_file_info.emit(self.js_file_name_label.text()) - _scene = QtWidgets.QGraphicsScene() - - if not self.bigthumbnail.isNull(): - _graphics_rect = self.CBVBigThumbnail.rect().toRectF() - _image_rect = self.bigthumbnail.rect() - - scaled_width = _image_rect.width() - scaled_height = _image_rect.height() - adjusted_x = (_graphics_rect.width() - scaled_width) // 2.0 - adjusted_y = (_graphics_rect.height() - scaled_height) // 2.0 - - adjusted_rect = QtCore.QRectF( - _image_rect.x() + adjusted_x, - _image_rect.y() + adjusted_y, - scaled_width, - scaled_height, - ) - _scene.setSceneRect(adjusted_rect) - _item_scaled = QtWidgets.QGraphicsPixmapItem( - QtGui.QPixmap.fromImage(self.bigthumbnail).scaled( - int(scaled_width), - int(scaled_height), - QtCore.Qt.AspectRatioMode.KeepAspectRatio, - QtCore.Qt.TransformationMode.SmoothTransformation, - ) - ) - _scene.addItem(_item_scaled) - self.CBVBigThumbnail.setScene(_scene) + if not self.isVisible(): + return + if "progress" == field: + self.printing_progress_bar.setValue(value) - def setupUI(self) -> None: + def _setupUI(self) -> None: + """Setup widget ui""" sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding, @@ -388,79 +356,44 @@ def setupUI(self) -> None: sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) - # ----------------------------------size policy - self.setSizePolicy(sizePolicy) self.setMinimumSize(QtCore.QSize(710, 420)) self.setMaximumSize(QtCore.QSize(720, 420)) self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - - # ---------------------------------Widgets - self.bigthumb_widget = QtWidgets.QWidget(self) - self.bigthumb_widget.setGeometry( - QtCore.QRect(0, 0, self.width(), self.height()) - ) - self.bigthumb_widget.setObjectName("bigthumb_widget") - self.headerWidget = QtWidgets.QWidget(self) self.headerWidget.setGeometry(QtCore.QRect(11, 11, 691, 62)) self.headerWidget.setObjectName("headerWidget") - self.btnWidget = QtWidgets.QWidget(self) self.btnWidget.setGeometry(QtCore.QRect(10, 80, 691, 90)) self.btnWidget.setObjectName("btnWidget") - self.progressWidget = QtWidgets.QWidget(self) self.progressWidget.setGeometry(QtCore.QRect(10, 170, 471, 241)) self.progressWidget.setObjectName("progressWidget") - self.contentWidget = QtWidgets.QWidget(self) self.contentWidget.setGeometry(QtCore.QRect(480, 170, 221, 241)) self.contentWidget.setObjectName("contentWidget") - - self.smallthumb_widget = QtWidgets.QLabel(self) - self.smallthumb_widget.setGeometry(QtCore.QRect(10, 170, 471, 241)) - self.smallthumb_widget.setObjectName("smallthumb_widget") - - # ---------------------------------layout - - self.smalllayout = QtWidgets.QHBoxLayout(self.smallthumb_widget) - - self.biglayout = QtWidgets.QHBoxLayout(self.bigthumb_widget) - self.job_status_header_layout = QtWidgets.QHBoxLayout(self.headerWidget) self.job_status_header_layout.setSpacing(20) self.job_status_header_layout.setObjectName("job_status_header_layout") - self.job_status_progress_layout = QtWidgets.QVBoxLayout(self.progressWidget) self.job_status_progress_layout.setSizeConstraint( QtWidgets.QLayout.SizeConstraint.SetMinimumSize ) - self.job_status_btn_layout = QtWidgets.QHBoxLayout(self.btnWidget) self.job_status_btn_layout.setSizeConstraint( QtWidgets.QLayout.SizeConstraint.SetMinimumSize ) - self.job_content_layout = QtWidgets.QVBoxLayout(self.contentWidget) self.job_content_layout.setObjectName("job_content_layout") - self.job_status_btn_layout.setContentsMargins(5, 5, 5, 5) self.job_status_btn_layout.setSpacing(5) self.job_status_btn_layout.setObjectName("job_status_btn_layout") - self.job_stats_display_layout = QtWidgets.QVBoxLayout() self.job_stats_display_layout.setObjectName("job_stats_display_layout") - - # -----------------------------Fonts font = QtGui.QFont() font.setFamily("Montserrat") font.setPointSize(14) - - # ------------------------------Header - self.js_file_name_icon = BlocksLabel(parent=self) - self.js_file_name_icon.setSizePolicy(sizePolicy) self.js_file_name_icon.setMinimumSize(QtCore.QSize(60, 60)) self.js_file_name_icon.setMaximumSize(QtCore.QSize(60, 60)) @@ -473,25 +406,18 @@ def setupUI(self) -> None: QtGui.QPixmap(":/files/media/btn_icons/file_icon.svg"), ) self.js_file_name_icon.setObjectName("js_file_name_icon") - self.js_file_name_label = BlocksLabel(parent=self) self.js_file_name_label.setEnabled(True) self.js_file_name_label.setSizePolicy(sizePolicy) self.js_file_name_label.setMinimumSize(QtCore.QSize(200, 80)) self.js_file_name_label.setMaximumSize(QtCore.QSize(16777215, 60)) - self.js_file_name_label.setFont(font) self.js_file_name_label.setStyleSheet("background: transparent; color: white;") self.js_file_name_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.js_file_name_label.setObjectName("js_file_name_label") - self.job_status_header_layout.addWidget(self.js_file_name_icon) self.job_status_header_layout.addWidget(self.js_file_name_label) - - # -----------------------------buttons - font.setPointSize(18) - self.pause_printing_btn = BlocksCustomButton(self) self.pause_printing_btn.setSizePolicy(sizePolicy) self.pause_printing_btn.setMinimumSize(QtCore.QSize(200, 80)) @@ -501,143 +427,96 @@ def setupUI(self) -> None: "icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/pause.svg") ) self.pause_printing_btn.setObjectName("pause_printing_btn") - self.stop_printing_btn = BlocksCustomButton(self) self.stop_printing_btn.setSizePolicy(sizePolicy) self.stop_printing_btn.setMinimumSize(QtCore.QSize(200, 80)) self.stop_printing_btn.setMaximumSize(QtCore.QSize(200, 80)) - self.stop_printing_btn.setFont(font) self.stop_printing_btn.setProperty( "icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/stop.svg") ) self.stop_printing_btn.setObjectName("stop_printing_btn") - self.tune_menu_btn = BlocksCustomButton(self) self.tune_menu_btn.setSizePolicy(sizePolicy) - self.tune_menu_btn.setMinimumSize(QtCore.QSize(200, 60)) self.tune_menu_btn.setMaximumSize(QtCore.QSize(200, 80)) - self.tune_menu_btn.setFont(font) self.tune_menu_btn.setProperty( "icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/tune.svg") ) self.tune_menu_btn.setObjectName("tune_menu_btn") - self.job_status_btn_layout.addWidget(self.pause_printing_btn) self.job_status_btn_layout.addWidget(self.stop_printing_btn) self.job_status_btn_layout.addWidget(self.tune_menu_btn) - self.tune_menu_btn.setText("Tune") self.stop_printing_btn.setText("Cancel") self.pause_printing_btn.setText("Pause") - - # -----------------------------Progress bar - - self.printing_progress_bar = CustomProgressBar() + self.printing_progress_bar = CustomProgressBar(self) self.printing_progress_bar.setMinimumHeight(150) - self.printing_progress_bar.setObjectName("printing_progress_bar") self.printing_progress_bar.setSizePolicy(sizePolicy) - self.job_status_progress_layout.addWidget(self.printing_progress_bar) - - # -----------------------------SMALL-THUMBNAIL - - self.CBVSmallThumbnail = ClickableGraphicsView(self.smallthumb_widget) - self.CBVSmallThumbnail.setSizePolicy(sizePolicy) - self.CBVSmallThumbnail.setMaximumSize(QtCore.QSize(48, 48)) - self.CBVSmallThumbnail.setStyleSheet( - "QGraphicsView{\nbackground-color:transparent;\n}" - ) - self.CBVSmallThumbnail.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) - self.CBVSmallThumbnail.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) - self.CBVSmallThumbnail.setSizeAdjustPolicy( - QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustIgnored - ) - brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) - brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) - self.CBVSmallThumbnail.setBackgroundBrush(brush) - self.CBVSmallThumbnail.setRenderHints( - QtGui.QPainter.RenderHint.Antialiasing - | QtGui.QPainter.RenderHint.SmoothPixmapTransform - | QtGui.QPainter.RenderHint.TextAntialiasing - ) - self.CBVSmallThumbnail.setObjectName("CBVSmallThumbnail") - - self.smalllayout.addWidget(self.CBVSmallThumbnail) - - # -----------------------------Big-Thumbnail - self.CBVBigThumbnail = ClickableGraphicsView() - self.CBVBigThumbnail.setSizePolicy(sizePolicy) - self.CBVBigThumbnail.setMaximumSize(QtCore.QSize(300, 300)) - self.CBVBigThumbnail.setStyleSheet( - "QGraphicsView{\nbackground-color:transparent;\n}" - ) - # "QGraphicsView{\nbackground-color:grey;border-radius:10px;\n}" grey background - self.CBVBigThumbnail.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) - self.CBVBigThumbnail.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) - self.CBVBigThumbnail.setVerticalScrollBarPolicy( - QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff - ) - self.CBVBigThumbnail.setHorizontalScrollBarPolicy( - QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff - ) - self.CBVBigThumbnail.setSizeAdjustPolicy( - QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustIgnored - ) - brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) - brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) - self.CBVBigThumbnail.setBackgroundBrush(brush) - self.CBVBigThumbnail.setRenderHints( - QtGui.QPainter.RenderHint.Antialiasing - | QtGui.QPainter.RenderHint.SmoothPixmapTransform - | QtGui.QPainter.RenderHint.TextAntialiasing - ) - self.CBVBigThumbnail.setViewportUpdateMode( - QtWidgets.QGraphicsView.ViewportUpdateMode.SmartViewportUpdate - ) - - self.CBVBigThumbnail.setObjectName("CBVBigThumbnail") - self.biglayout.addWidget(self.CBVBigThumbnail) - self.bigthumb_widget.hide() - - # -----------------------------display buttons - self.layer_display_button = DisplayButton(self) self.layer_display_button.button_type = "display_secondary" self.layer_display_button.setEnabled(False) self.layer_display_button.setSizePolicy(sizePolicy) - self.layer_display_button.setMinimumSize(QtCore.QSize(200, 80)) - self.layer_display_button.setProperty( "icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/layers.svg") ) self.layer_display_button.setObjectName("layer_display_button") - self.print_time_display_button = DisplayButton(self) self.print_time_display_button.button_type = "normal" self.print_time_display_button.setEnabled(False) self.print_time_display_button.setSizePolicy(sizePolicy) - self.print_time_display_button.setMinimumSize(QtCore.QSize(200, 80)) - self.print_time_display_button.setProperty( "icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/time.svg") ) self.print_time_display_button.setObjectName("print_time_display_button") - self.job_stats_display_layout.addWidget( self.layer_display_button, 0, QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) - self.job_stats_display_layout.addWidget( self.print_time_display_button, 0, QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) self.job_content_layout.addLayout(self.job_stats_display_layout) + + def create_thumbnail_widget(self) -> None: + """Create thumbnail graphics view widget""" + self.thumbnail_view = QtWidgets.QGraphicsView() + self.thumbnail_view.setMinimumSize(QtCore.QSize(48, 48)) + self.thumbnail_view.setAttribute( + QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True + ) + self.thumbnail_view.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) + self.thumbnail_view.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) + self.thumbnail_view.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint) + self.thumbnail_view.setObjectName("thumbnail_scene") + _thumbnail_palette = QtGui.QPalette() + _thumbnail_palette.setColor( + QtGui.QPalette.ColorRole.Window, QtGui.QColor(0, 0, 0, 0) + ) + _thumbnail_palette.setColor( + QtGui.QPalette.ColorRole.Base, QtGui.QColor(0, 0, 0, 0) + ) + self.thumbnail_view.setPalette(_thumbnail_palette) + _thumbnail_brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + _thumbnail_brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) + self.thumbnail_view.setBackgroundBrush(_thumbnail_brush) + self.thumbnail_view.setRenderHints( + QtGui.QPainter.RenderHint.Antialiasing + | QtGui.QPainter.RenderHint.SmoothPixmapTransform + | QtGui.QPainter.RenderHint.LosslessImageRendering + ) + self.thumbnail_view.setViewportUpdateMode( + QtWidgets.QGraphicsView.ViewportUpdateMode.SmartViewportUpdate + ) + self.thumbnail_view.setObjectName("thumbnail_scene") + self.thumbnail_view_layout = QtWidgets.QHBoxLayout(self) + self.thumbnail_view_layout.addWidget(self.thumbnail_view) + self.thumbnail_view.hide() diff --git a/BlocksScreen/lib/panels/widgets/keyboardPage.py b/BlocksScreen/lib/panels/widgets/keyboardPage.py index 07c9b338..4f1d13d8 100644 --- a/BlocksScreen/lib/panels/widgets/keyboardPage.py +++ b/BlocksScreen/lib/panels/widgets/keyboardPage.py @@ -4,7 +4,7 @@ class CustomQwertyKeyboard(QtWidgets.QWidget): - """A custom numpad for inserting integer values""" + """A custom keyboard for inserting integer values""" value_selected = QtCore.pyqtSignal(str, name="value_selected") request_back = QtCore.pyqtSignal(name="request_back") @@ -14,7 +14,7 @@ def __init__( parent, ) -> None: super().__init__(parent) - self.setupUi() + self._setupUi() self.current_value: str = "" self.symbolsrun = False self.setCursor( @@ -54,8 +54,8 @@ def __init__( self.inserted_value.setText("") - self.K_keychange.clicked.connect(self.handle_checkbuttons) - self.K_shift.clicked.connect(self.handle_checkbuttons) + self.K_keychange.clicked.connect(self.handle_keyboard_layout) + self.K_shift.clicked.connect(self.handle_keyboard_layout) self.numpad_back_btn.clicked.connect(lambda: self.request_back.emit()) @@ -75,9 +75,10 @@ def __init__( color: white; } """) - self.handle_checkbuttons() + self.handle_keyboard_layout() - def handle_checkbuttons(self): + def handle_keyboard_layout(self): + """Verifies if shift is toggled, changes layout accordingly""" shift = self.K_shift.isChecked() keychange = self.K_keychange.isChecked() @@ -207,7 +208,7 @@ def handle_checkbuttons(self): self.K_shift.setText("Shift") def value_inserted(self, value: str) -> None: - """Handle number insertion on the numpad + """Handle value insertion on the keyboard Args: value (int | str): value @@ -235,10 +236,11 @@ def value_inserted(self, value: str) -> None: self.inserted_value.setText(str(self.current_value)) def set_value(self, value: str) -> None: + """Set keyboard value""" self.current_value = value self.inserted_value.setText(value) - def setupUi(self): + def _setupUi(self): self.setObjectName("self") self.setEnabled(True) self.resize(800, 480) diff --git a/BlocksScreen/lib/panels/widgets/loadPage.py b/BlocksScreen/lib/panels/widgets/loadPage.py deleted file mode 100644 index d4ddcbcf..00000000 --- a/BlocksScreen/lib/panels/widgets/loadPage.py +++ /dev/null @@ -1,188 +0,0 @@ -import enum -from configfile import BlocksScreenConfig, get_configparser -from PyQt6 import QtCore, QtGui, QtWidgets - - -class LoadScreen(QtWidgets.QDialog): - class AnimationGIF(enum.Enum): - # [x]: WATHERE ARE NO GIFS IN LOADSCREEN PLEASE REMEMBER THIS IM WARNING - - # TODO : add more types into LoadScreen - DEFAULT = None - PLACEHOLDER = "" - - def __init__( - self, - parent: QtWidgets.QWidget, - anim_type: AnimationGIF = AnimationGIF.DEFAULT, - ) -> None: - super().__init__(parent) - - self.anim_type = anim_type - self._angle = 0 - self._span_angle = 90.0 - self._is_span_growing = True - self.min_length = 5.0 - self.max_length = 150.0 - self.length_step = 2.5 - - self.setStyleSheet( - "background-image: url(:/background/media/1st_background.png);" - ) - - self.setWindowFlags( - QtCore.Qt.WindowType.Popup - | QtCore.Qt.WindowType.FramelessWindowHint - ) - self.setupUI() - config: BlocksScreenConfig = get_configparser() - try: - if config: - loading_config = config["loading"] - animation = loading_config.get( - str(self.anim_type.name), - default=LoadScreen.AnimationGIF.DEFAULT, - ) - except Exception: - self.anim_type = LoadScreen.AnimationGIF.DEFAULT - - self.timer = QtCore.QTimer(self) - self.timer.timeout.connect(self._update_animation) - - if self.anim_type == LoadScreen.AnimationGIF.PLACEHOLDER: - self.movie = QtGui.QMovie(animation) # Create QMovie object - self.gifshow.setMovie(self.movie) # Set QMovie to QLabel - self.movie.start() # Start the QMovie - - # Only start the animation timer if no GIF is provided - if self.anim_type == LoadScreen.AnimationGIF.DEFAULT: - self.timer.start(16) - - self.repaint() - - def set_status_message(self, message: str) -> None: - self.label.setText(message) - - def geometry_calc(self) -> None: - # REFACTOR: find another way to get mainwindow geometry , this version consumes too much ram - app_instance = QtWidgets.QApplication.instance() - main_window = app_instance.activeWindow() if app_instance else None - if main_window is None and app_instance: - for widget in app_instance.allWidgets(): - if isinstance(widget, QtWidgets.QMainWindow): - main_window = widget - x = main_window.geometry().x() - y = main_window.geometry().y() - width = main_window.width() - height = main_window.height() - - self.setGeometry(x, y, width, height) - - def close(self) -> bool: - self.timer.stop() - self.label.setText("Loading...") - self._angle = 0 - # Stop the GIF animation if it was started - if self.anim_type != LoadScreen.AnimationGIF.DEFAULT: - self.gifshow.movie().stop() - return super().close() - - def _update_animation(self) -> None: - self._angle = (self._angle + 5) % 360 - if self._is_span_growing: - self._span_angle += self.length_step - if self._span_angle >= self.max_length: - self._span_angle = self.max_length - self._is_span_growing = False - else: - self._span_angle -= self.length_step - if self._span_angle <= self.min_length: - self._span_angle = self.min_length - self._is_span_growing = True - self.update() - - def sizeHint(self) -> QtCore.QSize: - popup_width = int(self.geometry().width()) - popup_height = int(self.geometry().height()) - # Centering logic - - popup_x = self.x() - popup_y = self.y() + (self.height() - popup_height) // 2 - self.move(popup_x, popup_y) - self.setFixedSize(popup_width, popup_height) - self.setMinimumSize(popup_width, popup_height) - return super().sizeHint() - - def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: - return - - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: - painter = QtGui.QPainter(self) - # loading circle draw - if self.anim_type == LoadScreen.AnimationGIF.DEFAULT: - painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) - painter.setRenderHint( - QtGui.QPainter.RenderHint.LosslessImageRendering, True - ) - painter.setRenderHint( - QtGui.QPainter.RenderHint.SmoothPixmapTransform, True - ) - painter.setRenderHint( - QtGui.QPainter.RenderHint.TextAntialiasing, True - ) - pen = QtGui.QPen() - pen.setWidth(8) - pen.setColor(QtGui.QColor("#ffffff")) - pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap) - painter.setPen(pen) - - center_x = self.width() // 2 - center_y = int(self.height() * 0.4) - arc_size = 150 - - painter.translate(center_x, center_y) - painter.rotate(self._angle) - - arc_rect = QtCore.QRectF( - -arc_size / 2, -arc_size / 2, arc_size, arc_size - ) - span_angle = int(self._span_angle * 16) - painter.drawArc(arc_rect, 0, span_angle) - - def resizeEvent(self, event: QtGui.QResizeEvent) -> None: - super().resizeEvent(event) - - label_width = self.width() - label_height = 100 - label_x = (self.width() - label_width) // 2 - label_y = int(self.height() * 0.65) - - margin = 20 - # Center the GIF - gifshow_width = self.width() - margin * 2 - gifshow_height = self.height() - (self.height() - label_y) - margin - - self.gifshow.setGeometry(margin, margin, gifshow_width, gifshow_height) - - self.label.setGeometry(label_x, label_y, label_width, label_height) - - def show(self) -> None: - self.geometry_calc() - # Start the animation timer only if no GIF is present - if self.anim_type == LoadScreen.AnimationGIF.DEFAULT: - self.timer.start() - self.repaint() - return super().show() - - def setupUI(self) -> None: - self.gifshow = QtWidgets.QLabel("", self) - self.gifshow.setObjectName("gifshow") - self.gifshow.setStyleSheet("background: transparent;") - self.gifshow.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - - self.label = QtWidgets.QLabel("Test", self) - font = QtGui.QFont() - font.setPointSize(20) - self.label.setFont(font) - self.label.setStyleSheet("color: #ffffff; background: transparent;") - self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) diff --git a/BlocksScreen/lib/panels/widgets/loadWidget.py b/BlocksScreen/lib/panels/widgets/loadWidget.py index 4001d900..1119bd14 100644 --- a/BlocksScreen/lib/panels/widgets/loadWidget.py +++ b/BlocksScreen/lib/panels/widgets/loadWidget.py @@ -1,11 +1,24 @@ - from PyQt6 import QtCore, QtGui, QtWidgets +import enum +import os +from configfile import BlocksScreenConfig, get_configparser + + +class LoadingOverlayWidget(QtWidgets.QLabel): + """ + A full-overlay widget to display a loading animation (GIF or spinning arc). + """ + class AnimationGIF(enum.Enum): + """Animation type""" + + DEFAULT = None + PLACEHOLDER = "placeholder" -class LoadingOverlayWidget(QtWidgets.QLabel): def __init__( self, parent: QtWidgets.QWidget, + initial_anim_type: AnimationGIF = AnimationGIF.DEFAULT, ) -> None: super().__init__(parent) @@ -16,22 +29,83 @@ def __init__( self.max_length = 150.0 self.length_step = 2.5 - self.setupUI() + self._setupUI() + + config: BlocksScreenConfig = get_configparser() + animation_path = None + + if initial_anim_type == LoadingOverlayWidget.AnimationGIF.PLACEHOLDER: + animation_path = ( + "~/BlocksScreen/BlocksScreen/lib/ui/resources/intro_blocks.gif" + ) + self.anim_type = initial_anim_type + + else: + try: + loading_config = config.loading + animation_path = loading_config.get( + str(initial_anim_type.name), + ) + self.anim_type = initial_anim_type + except Exception: + self.anim_type = LoadingOverlayWidget.AnimationGIF.DEFAULT + + if ( + self.anim_type != LoadingOverlayWidget.AnimationGIF.DEFAULT + and animation_path + ): + abs_animation_path = os.path.expanduser(animation_path) + + self.movie = QtGui.QMovie(abs_animation_path) + + if self.movie.isValid(): + self.gifshow.setMovie(self.movie) + self.gifshow.setScaledContents(True) + self.movie.start() + self.gifshow.show() + else: + self.anim_type = LoadingOverlayWidget.AnimationGIF.DEFAULT + self.gifshow.hide() self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self._update_animation) - self.timer.start(16) + + if self.anim_type == LoadingOverlayWidget.AnimationGIF.DEFAULT: + self.timer.start(16) + self.gifshow.hide() + self.label.setText("Loading...") self.repaint() + def set_animation_path(self, path: str) -> None: + """Set widget animation path""" + abs_animation_path = os.path.expanduser(path) + if os.path.isfile(abs_animation_path): + self.movie = QtGui.QMovie(abs_animation_path) + if self.movie.isValid(): + self.gifshow.setMovie(self.movie) + self.gifshow.setScaledContents(True) + self.movie.start() + self.gifshow.show() + self.anim_type = LoadingOverlayWidget.AnimationGIF.PLACEHOLDER + if self.timer.isActive(): + self.timer.stop() + def set_status_message(self, message: str) -> None: + """Set widget message""" self.label.setText(message) - def close(self) -> bool: + """Re-implemented method, close widget""" self.timer.stop() self.label.setText("Loading...") self._angle = 0 + if ( + self.anim_type != LoadingOverlayWidget.AnimationGIF.DEFAULT + and hasattr(self, "movie") + and self.movie.isValid() + ): + self.movie.stop() return super().close() def _update_animation(self) -> None: @@ -48,69 +122,68 @@ def _update_animation(self) -> None: self._is_span_growing = True self.update() - - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + def paintEvent(self, a0: QtGui.QPaintEvent | None) -> None: + """Re-implemented method, paint widget""" painter = QtGui.QPainter(self) - painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) - painter.setRenderHint( - QtGui.QPainter.RenderHint.LosslessImageRendering, True - ) - painter.setRenderHint( - QtGui.QPainter.RenderHint.SmoothPixmapTransform, True - ) - painter.setRenderHint( - QtGui.QPainter.RenderHint.TextAntialiasing, True - ) - pen = QtGui.QPen() - pen.setWidth(8) - pen.setColor(QtGui.QColor("#ffffff")) - pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap) - painter.setPen(pen) - - center_x = self.width() // 2 - center_y = int(self.height() * 0.4) - arc_size = 150 - - painter.translate(center_x, center_y) - painter.rotate(self._angle) - - arc_rect = QtCore.QRectF( - -arc_size / 2, -arc_size / 2, arc_size, arc_size - ) - span_angle = int(self._span_angle * 16) - painter.drawArc(arc_rect, 0, span_angle) - - def resizeEvent(self, event: QtGui.QResizeEvent) -> None: - super().resizeEvent(event) - + if self.anim_type == LoadingOverlayWidget.AnimationGIF.DEFAULT: + painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) + painter.setRenderHint( + QtGui.QPainter.RenderHint.LosslessImageRendering, True + ) + painter.setRenderHint(QtGui.QPainter.RenderHint.SmoothPixmapTransform, True) + painter.setRenderHint(QtGui.QPainter.RenderHint.TextAntialiasing, True) + pen = QtGui.QPen() + pen.setWidth(8) + pen.setColor(QtGui.QColor("#ffffff")) + pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap) + painter.setPen(pen) + + center_x = self.width() // 2 + center_y = int(self.height() * 0.4) + arc_size = 150 + + painter.translate(center_x, center_y) + painter.rotate(self._angle) + + arc_rect = QtCore.QRectF(-arc_size / 2, -arc_size / 2, arc_size, arc_size) + span_angle = int(self._span_angle * 16) + painter.drawArc(arc_rect, 0, span_angle) + + super().paintEvent(a0) + + def resizeEvent(self, a0: QtGui.QResizeEvent | None) -> None: + """Re-implemented method, handle widget resize event""" + super().resizeEvent(a0) label_width = self.width() label_height = 100 label_x = (self.width() - label_width) // 2 label_y = int(self.height() * 0.65) - margin = 20 - # Center the GIF - gifshow_width = self.width() - margin * 2 - gifshow_height = self.height() - (self.height() - label_y) - margin + self.label.setGeometry(label_x, label_y, label_width, label_height) + gifshow_max_height = label_y - margin + size = min(self.width() - margin * 2, gifshow_max_height) - self.gifshow.setGeometry(margin, margin, gifshow_width, gifshow_height) + gifshow_x = (self.width() - size) // 2 + gifshow_y = (gifshow_max_height - size) // 2 - self.label.setGeometry(label_x, label_y, label_width, label_height) + self.gifshow.setGeometry(gifshow_x, gifshow_y, size, size) def show(self) -> None: - self.timer.start() + """Re-implemented method, show widget""" self.repaint() return super().show() - def setupUI(self) -> None: + def _setupUI(self) -> None: + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True) self.gifshow = QtWidgets.QLabel("", self) self.gifshow.setObjectName("gifshow") self.gifshow.setStyleSheet("background: transparent;") self.gifshow.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.gifshow.hide() self.label = QtWidgets.QLabel(self) font = QtGui.QFont() font.setPointSize(20) self.label.setFont(font) self.label.setStyleSheet("color: #ffffff; background: transparent;") - self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) \ No newline at end of file + self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) diff --git a/BlocksScreen/lib/panels/widgets/numpadPage.py b/BlocksScreen/lib/panels/widgets/numpadPage.py index dcfe6b5b..9674084d 100644 --- a/BlocksScreen/lib/panels/widgets/numpadPage.py +++ b/BlocksScreen/lib/panels/widgets/numpadPage.py @@ -17,7 +17,7 @@ def __init__( parent, ) -> None: super().__init__(parent) - self.setupUI() + self._setupUI() self.current_value: str = "0" self.name: str = "" self.min_value: int = 0 @@ -37,9 +37,7 @@ def __init__( self.numpad_enter.clicked.connect(lambda: self.value_inserted("enter")) self.numpad_clear.clicked.connect(lambda: self.value_inserted("clear")) self.numpad_back_btn.clicked.connect(self.back_button) - self.start_glow_animation.connect( - self.inserted_value.start_glow_animation - ) + self.start_glow_animation.connect(self.inserted_value.start_glow_animation) def value_inserted(self, value: str) -> None: """Handle number insertion on the numpad @@ -59,14 +57,8 @@ def value_inserted(self, value: str) -> None: if "enter" in value and self.current_value.isnumeric(): if len(self.current_value) == 0: self.current_value = "0" - if ( - self.min_value - <= int(self.current_value) - <= self.max_value - ): - self.value_selected.emit( - self.name, int(self.current_value) - ) + if self.min_value <= int(self.current_value) <= self.max_value: + self.value_selected.emit(self.name, int(self.current_value)) self.request_back.emit() elif "clear" in value: @@ -81,8 +73,9 @@ def value_inserted(self, value: str) -> None: self.inserted_value.glow_animation.stop() self.inserted_value.setText(str(self.current_value)) - + def back_button(self): + """Request back page""" self.request_back.emit() def set_name(self, name: str) -> None: @@ -93,24 +86,25 @@ def set_name(self, name: str) -> None: self.update() def set_value(self, value: int) -> None: + """Set numpad value""" self.current_value = str(value) self.inserted_value.setText(str(value)) def set_min_value(self, min_value: int) -> None: + """Set minimum allowed value""" self.min_value = min_value self.update_min_max_label() def set_max_value(self, max_value: int) -> None: + """Set maximum allowed value""" self.max_value = max_value self.update_min_max_label() def update_min_max_label(self) -> None: """Updates the text of the min/max label.""" - self.min_max_label.setText( - f"Range: {self.min_value} - {self.max_value}" - ) - - def setupUI(self) -> None: + self.min_max_label.setText(f"Range: {self.min_value} - {self.max_value}") + + def _setupUI(self) -> None: self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.BlankCursor)) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) @@ -151,8 +145,7 @@ def setupUI(self) -> None: self.header_layout.addWidget( self.numpad_title, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) self.numpad_back_btn = IconButton(self) @@ -168,9 +161,7 @@ def setupUI(self) -> None: self.numpad_back_btn.setSizePolicy(sizePolicy) self.numpad_back_btn.setMinimumSize(QtCore.QSize(60, 60)) self.numpad_back_btn.setMaximumSize(QtCore.QSize(60, 60)) - self.numpad_back_btn.setPixmap( - QtGui.QPixmap(":ui/media/btn_icons/back.svg") - ) + self.numpad_back_btn.setPixmap(QtGui.QPixmap(":ui/media/btn_icons/back.svg")) self.numpad_back_btn.setObjectName("numpad_back_btn") self.header_layout.addWidget( self.numpad_back_btn, @@ -230,11 +221,9 @@ def setupUI(self) -> None: self.value_and_range_layout.addWidget( self.inserted_value, 0, QtCore.Qt.AlignmentFlag.AlignCenter ) - - self.main_content_layout.addLayout( - self.value_and_range_layout, 1 - ) - + + self.main_content_layout.addLayout(self.value_and_range_layout, 1) + self.inserted_value.setBackgroundRole(QtGui.QPalette.ColorRole.Window) self.setBackgroundRole(QtGui.QPalette.ColorRole.Window) self.line = QtWidgets.QFrame(self) @@ -258,9 +247,7 @@ def setupUI(self) -> None: font.setPointSize(28) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault) self.numpad_9 = NumpadButton(self) - sizePolicy.setHeightForWidth( - self.numpad_9.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_9.sizePolicy().hasHeightForWidth()) self.numpad_9.setSizePolicy(sizePolicy) self.numpad_9.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_9.setFont(font) @@ -271,9 +258,7 @@ def setupUI(self) -> None: self.numpad_9, 0, 2, 1, 1, QtCore.Qt.AlignmentFlag.AlignLeft ) self.numpad_8 = NumpadButton(parent=self) - sizePolicy.setHeightForWidth( - self.numpad_8.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_8.sizePolicy().hasHeightForWidth()) self.numpad_8.setSizePolicy(sizePolicy) self.numpad_8.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_8.setFont(font) @@ -284,9 +269,7 @@ def setupUI(self) -> None: self.numpad_8, 0, 1, 1, 1, QtCore.Qt.AlignmentFlag.AlignHCenter ) self.numpad_7 = NumpadButton(self) - sizePolicy.setHeightForWidth( - self.numpad_7.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_7.sizePolicy().hasHeightForWidth()) self.numpad_7.setSizePolicy(sizePolicy) self.numpad_7.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_7.setFont(font) @@ -297,9 +280,7 @@ def setupUI(self) -> None: self.numpad_7, 0, 0, 1, 1, QtCore.Qt.AlignmentFlag.AlignLeft ) self.numpad_6 = NumpadButton(self) - sizePolicy.setHeightForWidth( - self.numpad_6.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_6.sizePolicy().hasHeightForWidth()) self.numpad_6.setSizePolicy(sizePolicy) self.numpad_6.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_6.setFont(font) @@ -311,9 +292,7 @@ def setupUI(self) -> None: self.numpad_6, 1, 2, 1, 1, QtCore.Qt.AlignmentFlag.AlignRight ) self.numpad_5 = NumpadButton(self) - sizePolicy.setHeightForWidth( - self.numpad_5.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_5.sizePolicy().hasHeightForWidth()) self.numpad_5.setSizePolicy(sizePolicy) self.numpad_5.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_5.setFont(font) @@ -323,9 +302,7 @@ def setupUI(self) -> None: self.numpad_5, 1, 1, 1, 1, QtCore.Qt.AlignmentFlag.AlignHCenter ) self.numpad_4 = NumpadButton(self) - sizePolicy.setHeightForWidth( - self.numpad_4.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_4.sizePolicy().hasHeightForWidth()) self.numpad_4.setSizePolicy(sizePolicy) self.numpad_4.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_4.setFont(font) @@ -336,9 +313,7 @@ def setupUI(self) -> None: self.numpad_4, 1, 0, 1, 1, QtCore.Qt.AlignmentFlag.AlignLeft ) self.numpad_3 = NumpadButton(parent=self) - sizePolicy.setHeightForWidth( - self.numpad_3.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_3.sizePolicy().hasHeightForWidth()) self.numpad_3.setSizePolicy(sizePolicy) self.numpad_3.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_3.setFont(font) @@ -349,9 +324,7 @@ def setupUI(self) -> None: self.numpad_3, 2, 2, 1, 1, QtCore.Qt.AlignmentFlag.AlignRight ) self.numpad_2 = NumpadButton(self) - sizePolicy.setHeightForWidth( - self.numpad_2.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_2.sizePolicy().hasHeightForWidth()) self.numpad_2.setSizePolicy(sizePolicy) self.numpad_2.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_2.setFont(font) @@ -362,9 +335,7 @@ def setupUI(self) -> None: self.numpad_2, 2, 1, 1, 1, QtCore.Qt.AlignmentFlag.AlignCenter ) self.numpad_1 = NumpadButton(parent=self) - sizePolicy.setHeightForWidth( - self.numpad_1.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_1.sizePolicy().hasHeightForWidth()) self.numpad_1.setSizePolicy(sizePolicy) self.numpad_1.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_1.setFont(font) @@ -375,9 +346,7 @@ def setupUI(self) -> None: self.numpad_1, 2, 0, 1, 1, QtCore.Qt.AlignmentFlag.AlignLeft ) self.numpad_0 = NumpadButton(parent=self) - sizePolicy.setHeightForWidth( - self.numpad_0.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_0.sizePolicy().hasHeightForWidth()) self.numpad_0.setSizePolicy(sizePolicy) self.numpad_0.setMinimumSize(QtCore.QSize(150, 60)) self.numpad_0.setFont(font) @@ -389,89 +358,57 @@ def setupUI(self) -> None: ) self.numpad_enter = IconButton(parent=self) self.numpad_enter.setEnabled(True) - sizePolicy.setHeightForWidth( - self.numpad_enter.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_enter.sizePolicy().hasHeightForWidth()) self.numpad_enter.setSizePolicy(sizePolicy) self.numpad_enter.setMinimumSize(QtCore.QSize(60, 60)) self.numpad_enter.setFlat(True) - self.numpad_enter.setPixmap( - QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg") - ) + self.numpad_enter.setPixmap(QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg")) self.numpad_enter.setObjectName("numpad_enter") self.button_grid_layout.addWidget( self.numpad_enter, 3, 0, 1, 1, QtCore.Qt.AlignmentFlag.AlignCenter ) self.numpad_clear = IconButton(parent=self) - sizePolicy.setHeightForWidth( - self.numpad_clear.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.numpad_clear.sizePolicy().hasHeightForWidth()) self.numpad_clear.setSizePolicy(sizePolicy) self.numpad_clear.setMinimumSize(QtCore.QSize(60, 60)) self.numpad_clear.setFlat(True) - self.numpad_clear.setPixmap( - QtGui.QPixmap(":/dialog/media/btn_icons/no.svg") - ) + self.numpad_clear.setPixmap(QtGui.QPixmap(":/dialog/media/btn_icons/no.svg")) self.numpad_clear.setObjectName("numpad_clear") self.button_grid_layout.addWidget( self.numpad_clear, 3, 2, 1, 1, QtCore.Qt.AlignmentFlag.AlignCenter ) - self.button_grid_layout.setAlignment( - QtCore.Qt.AlignmentFlag.AlignCenter - ) + self.button_grid_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.main_content_layout.addLayout(self.button_grid_layout) - self.main_content_layout.setAlignment( - QtCore.Qt.AlignmentFlag.AlignCenter - ) + self.main_content_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.setLayout(self.main_content_layout) - self.retranslateUI() + self._retranslateUI() QtCore.QMetaObject.connectSlotsByName(self) - def retranslateUI(self) -> None: + def _retranslateUI(self) -> None: _translate = QtCore.QCoreApplication.translate self.setWindowTitle(_translate("customNumpad", "Form")) - self.numpad_title.setText( - _translate("customNumpad", "Target Temperature") - ) + self.numpad_title.setText(_translate("customNumpad", "Target Temperature")) self.numpad_back_btn.setProperty( "button_type", _translate("customNumpad", "icon") ) self.numpad_6.setText(_translate("customNumpad", "6")) - self.numpad_6.setProperty( - "position", _translate("customNumpad", "right") - ) + self.numpad_6.setProperty("position", _translate("customNumpad", "right")) self.numpad_9.setText(_translate("customNumpad", "9")) - self.numpad_9.setProperty( - "position", _translate("customNumpad", "right") - ) + self.numpad_9.setProperty("position", _translate("customNumpad", "right")) self.numpad_8.setText(_translate("customNumpad", "8")) self.numpad_2.setText(_translate("customNumpad", "2")) self.numpad_0.setText(_translate("customNumpad", "0")) - self.numpad_0.setProperty( - "position", _translate("customNumpad", "down") - ) + self.numpad_0.setProperty("position", _translate("customNumpad", "down")) self.numpad_3.setText(_translate("customNumpad", "3")) - self.numpad_3.setProperty( - "position", _translate("customNumpad", "right") - ) + self.numpad_3.setProperty("position", _translate("customNumpad", "right")) self.numpad_4.setText(_translate("customNumpad", "4")) - self.numpad_4.setProperty( - "position", _translate("customNumpad", "left") - ) + self.numpad_4.setProperty("position", _translate("customNumpad", "left")) self.numpad_5.setText(_translate("customNumpad", "5")) self.numpad_1.setText(_translate("customNumpad", "1")) - self.numpad_1.setProperty( - "position", _translate("customNumpad", "left") - ) - self.numpad_enter.setProperty( - "button_type", _translate("customNumpad", "icon") - ) + self.numpad_1.setProperty("position", _translate("customNumpad", "left")) + self.numpad_enter.setProperty("button_type", _translate("customNumpad", "icon")) self.numpad_7.setText(_translate("customNumpad", "7")) - self.numpad_7.setProperty( - "position", _translate("customNumpad", "left") - ) - self.numpad_clear.setProperty( - "button_type", _translate("customNumpad", "icon") - ) \ No newline at end of file + self.numpad_7.setProperty("position", _translate("customNumpad", "left")) + self.numpad_clear.setProperty("button_type", _translate("customNumpad", "icon")) diff --git a/BlocksScreen/lib/panels/widgets/optionCardWidget.py b/BlocksScreen/lib/panels/widgets/optionCardWidget.py index 81cb3bdb..6fbfe20d 100644 --- a/BlocksScreen/lib/panels/widgets/optionCardWidget.py +++ b/BlocksScreen/lib/panels/widgets/optionCardWidget.py @@ -1,12 +1,11 @@ import typing from PyQt6 import QtCore, QtGui, QtWidgets -from lib.utils.blocks_label import BlocksLabel from lib.utils.icon_button import IconButton -class OptionCard(QtWidgets.QFrame): - continue_clicked: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( +class OptionCard(QtWidgets.QAbstractButton): + clicked: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( "PyQt_PyObject", name="continue_clicked" ) @@ -25,30 +24,56 @@ def __init__( self.icon_background_color = QtGui.QColor(150, 150, 130, 80) self.name = name self.card_text = text - self.setupUi(self) - self.continue_button.clicked.connect( - lambda: self.continue_clicked.emit(self) + self.doubleT: bool = False + self._setupUi(self) + self.option_icon.setAttribute( + QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents ) + self.option_text.setAttribute( + QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents + ) + self.secondtext.setAttribute( + QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents + ) + self.line_separator.setAttribute( + QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents + ) + self.continue_button.setAttribute( + QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents + ) + + self.setMode(False) self.set_card_icon(icon) self.set_card_text(text) def disable_button(self) -> None: + """Disable widget button""" self.continue_button.setDisabled(True) self.repaint() def enable_button(self) -> None: + """Enable widget button""" self.continue_button.setEnabled(True) self.repaint() def set_card_icon(self, pixmap: QtGui.QPixmap) -> None: - self.option_icon.setPixmap(pixmap) + """Set widget icon""" + scaled = pixmap.scaled( + 300, + 300, + QtCore.Qt.AspectRatioMode.IgnoreAspectRatio, + QtCore.Qt.TransformationMode.SmoothTransformation, + ) + self.option_icon.setPixmap(scaled) self.repaint() def set_card_text(self, text: str) -> None: + """Set widget text""" self.option_text.setText(text) self.repaint() def set_card_text_color(self, color: QtGui.QColor) -> None: + """Set widget text color""" self.text_color = color _palette = self.option_text.palette() _palette.setColor(QtGui.QPalette.ColorRole.WindowText, color) @@ -56,35 +81,74 @@ def set_card_text_color(self, color: QtGui.QColor) -> None: self.repaint() def set_background_color(self, color: QtGui.QColor) -> None: + """Set widget background color""" self.color = color self.repaint() - def sizeHint(self) -> QtCore.QSize: - return super().sizeHint() - - def underMouse(self) -> bool: - return super().underMouse() - def enterEvent(self, event: QtGui.QEnterEvent) -> None: + """Re-implemented method, highlight widget edges""" # Illuminate the edges to a lighter blue # To achieve this just Force update the widget self.update() return super().enterEvent(event) def leaveEvent(self, a0: QtCore.QEvent) -> None: + """Re-implemented method, disable widget edges highlight""" # Reset the color # Just as before force update the widget self.update() return super().leaveEvent(a0) def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: + """Re-implemented method, handle mouse press event""" + self.clicked.emit(self) self.update() return super().mousePressEvent(a0) + def setMode(self, double_mode: bool = False): + """Set the mode of the layout: single or double text.""" + self.doubleT = double_mode + + # Clear existing widgets from layout before adding new ones + while self.verticalLayout.count(): + item = self.verticalLayout.takeAt(0) + widget = item.widget() + if widget is not None: + widget.setParent(None) + + if self.doubleT: + self.verticalLayout.addWidget( + self.option_icon, + 0, + QtCore.Qt.AlignmentFlag.AlignHCenter + | QtCore.Qt.AlignmentFlag.AlignBottom, + ) + self.verticalLayout.addWidget( + self.secondtext, 0, QtCore.Qt.AlignmentFlag.AlignHCenter + ) + self.verticalLayout.addWidget( + self.line_separator, 0, QtCore.Qt.AlignmentFlag.AlignCenter + ) + self.verticalLayout.addWidget(self.option_text) + self.verticalLayout.addItem(self.spacer) + self.secondtext.show() + else: + self.verticalLayout.addWidget( + self.option_icon, 0, QtCore.Qt.AlignmentFlag.AlignCenter + ) + self.verticalLayout.addWidget( + self.line_separator, 0, QtCore.Qt.AlignmentFlag.AlignCenter + ) + self.verticalLayout.addWidget(self.option_text) + self.verticalLayout.addWidget(self.continue_button) + + self.update() + def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" # Rounded background edges - self.background_path = QtGui.QPainterPath() - self.background_path.addRoundedRect( + background_path = QtGui.QPainterPath() + background_path.addRoundedRect( self.rect().toRectF(), 20.0, 20.0, QtCore.Qt.SizeMode.AbsoluteSize ) @@ -106,7 +170,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.setRenderHint(painter.RenderHint.Antialiasing) painter.setRenderHint(painter.RenderHint.SmoothPixmapTransform) painter.setRenderHint(painter.RenderHint.LosslessImageRendering) - painter.fillPath(self.background_path, bg_color) + painter.fillPath(background_path, bg_color) if self.underMouse(): _pen = QtGui.QPen() _pen.setStyle(QtCore.Qt.PenStyle.SolidLine) @@ -132,11 +196,11 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: _gradient.setColorAt(1, _color3) painter.setBrush(_gradient) painter.setPen(QtCore.Qt.PenStyle.NoPen) - painter.fillPath(self.background_path, painter.brush()) + painter.fillPath(background_path, painter.brush()) painter.end() - def setupUi(self, option_card): + def _setupUi(self, option_card): option_card.setObjectName("option_card") option_card.resize(200, 300) sizePolicy = QtWidgets.QSizePolicy( @@ -145,21 +209,27 @@ def setupUi(self, option_card): ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth( - option_card.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(option_card.sizePolicy().hasHeightForWidth()) option_card.setSizePolicy(sizePolicy) option_card.setMinimumSize(QtCore.QSize(200, 300)) option_card.setMaximumSize(QtCore.QSize(200, 300)) self.verticalLayout = QtWidgets.QVBoxLayout(option_card) self.verticalLayout.setContentsMargins(0, 0, -1, -1) self.verticalLayout.setObjectName("verticalLayout") - self.option_icon = BlocksLabel(parent=option_card) + self.option_icon = IconButton(parent=option_card) self.option_icon.setMinimumSize(QtCore.QSize(200, 150)) self.option_icon.setObjectName("option_icon") - self.verticalLayout.addWidget( - self.option_icon, 0, QtCore.Qt.AlignmentFlag.AlignHCenter - ) + _button_font = QtGui.QFont() + _button_font.setBold(True) + _button_font.setPointSize(20) + self.secondtext = QtWidgets.QLabel(parent=option_card) + self.secondtext.setText("%") + self.secondtext.setStyleSheet("color:white") + self.secondtext.setFont(_button_font) + self.secondtext.setObjectName("option_text") + self.secondtext.setWordWrap(True) + self.secondtext.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.secondtext.hide() self.line_separator = QtWidgets.QFrame(parent=option_card) self.line_separator.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.line_separator.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) @@ -169,28 +239,20 @@ def setupUi(self, option_card): self.verticalLayout.addWidget( self.line_separator, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) self.option_text = QtWidgets.QLabel(parent=option_card) self.option_text.setMinimumSize(QtCore.QSize(200, 50)) self.option_text.setObjectName("option_text") - self.verticalLayout.addWidget( - self.option_text, - ) - self.continue_button = IconButton(parent=option_card) - self.option_text.setAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter - ) self.option_text.setWordWrap(True) - _button_font = QtGui.QFont() - _button_font.setBold(True) + self.option_text.setStyleSheet("color:white") + self.option_text.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) _palette = self.option_text.palette() _palette.setColor(QtGui.QPalette.ColorRole.WindowText, self.text_color) self.option_text.setPalette(_palette) - _button_font.setPointSize(15) + self.option_text.setFont(_button_font) + self.continue_button = IconButton(parent=option_card) sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding, @@ -209,12 +271,17 @@ def setupUi(self, option_card): QtGui.QPixmap(":/arrow_icons/media/btn_icons/right_arrow.svg"), ) self.continue_button.setObjectName("continue_button") - self.verticalLayout.addWidget(self.continue_button) - self.retranslateUi(option_card) + self.spacer = QtWidgets.QSpacerItem( + 20, + 40, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Expanding, + ) + self._retranslateUi(option_card) QtCore.QMetaObject.connectSlotsByName(option_card) - def retranslateUi(self, option_card): + def _retranslateUi(self, option_card): _translate = QtCore.QCoreApplication.translate option_card.setWindowTitle(_translate("option_card", "Frame")) self.option_text.setText(_translate("option_card", "TextLabel")) diff --git a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py index 112d660a..3696c90e 100644 --- a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py +++ b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py @@ -2,38 +2,37 @@ from collections import deque from typing import Deque from PyQt6 import QtCore, QtGui, QtWidgets - - -BASE_POPUP_TIMEOUT = 6000 +from lib.utils.icon_button import IconButton class Popup(QtWidgets.QDialog): class MessageType(enum.Enum): + """Popup Message type (level)""" + INFO = enum.auto() WARNING = enum.auto() ERROR = enum.auto() UNKNOWN = enum.auto() class ColorCode(enum.Enum): + """Popup message-color code""" + INFO = QtGui.QColor("#446CDB") WARNING = QtGui.QColor("#E7E147") ERROR = QtGui.QColor("#CA4949") def __init__(self, parent) -> None: super().__init__(parent) - - # Instance variables - self.popup_timeout = BASE_POPUP_TIMEOUT self.timeout_timer = QtCore.QTimer(self) + self.timeout_timer.setSingleShot(True) self.messages: Deque = deque() + self.isShown = False self.persistent_notifications: Deque = deque() - self.message_type: Popup.MessageType = Popup.MessageType.INFO self.default_background_color = QtGui.QColor(164, 164, 164) self.info_icon = QtGui.QPixmap(":ui/media/btn_icons/info.svg") self.warning_icon = QtGui.QPixmap(":ui/media/btn_icons/warning.svg") self.error_icon = QtGui.QPixmap(":ui/media/btn_icons/error.svg") - self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True) self.setMouseTracking(True) self.setWindowFlags( @@ -41,32 +40,39 @@ def __init__(self, parent) -> None: | QtCore.Qt.WindowType.FramelessWindowHint | QtCore.Qt.WindowType.X11BypassWindowManagerHint ) - - self.setupUI() - - + self._setupUI() self.slide_in_animation = QtCore.QPropertyAnimation(self, b"geometry") self.slide_in_animation.setDuration(1000) self.slide_in_animation.setEasingCurve(QtCore.QEasingCurve.Type.OutCubic) - - self.slide_out_animation = QtCore.QPropertyAnimation(self, b"geometry") self.slide_out_animation.setDuration(200) self.slide_out_animation.setEasingCurve(QtCore.QEasingCurve.Type.InCubic) + self.SingleTime = QtCore.QTimer(self) + self.SingleTime.setInterval(5000) + self.SingleTime.setSingleShot(True) + self.SingleTime.timeout.connect(self._add_popup) - self.slide_in_animation.finished.connect(self.on_slide_in_finished) self.slide_out_animation.finished.connect(self.on_slide_out_finished) - self.timeout_timer.timeout.connect(self.slide_out_animation.start) + self.slide_in_animation.finished.connect(self.on_slide_in_finished) + self.timeout_timer.timeout.connect(lambda: self.slide_out_animation.start()) + self.actionbtn.clicked.connect(self.slide_out_animation.start) def on_slide_in_finished(self): + """Handle slide in animation finished""" + if self.userInput: + return self.timeout_timer.start() def on_slide_out_finished(self): - self.close() - self.add_popup() - + """Handle slide out animation finished""" + self.hide() + self.isShown = False + self.timeout_timer.stop() + self._add_popup() + def _calculate_target_geometry(self) -> QtCore.QRect: + """Calculate on end posisition rect for popup""" app_instance = QtWidgets.QApplication.instance() main_window = app_instance.activeWindow() if app_instance else None if main_window is None and app_instance: @@ -74,55 +80,99 @@ def _calculate_target_geometry(self) -> QtCore.QRect: if isinstance(widget, QtWidgets.QMainWindow): main_window = widget break - + parent_rect = main_window.geometry() width = int(parent_rect.width() * 0.85) - height = min(self.text_label.rect().height(), self.icon_label.rect().height()) - - x = parent_rect.x() + (parent_rect.width() - width) // 2 + height = ( + max( + self.text_label.height(), + self.icon_label.height(), + ) + + 10 + ) + + x = parent_rect.x() + (parent_rect.width() - width) // 2 y = parent_rect.y() + 20 - + return QtCore.QRect(x, y, width, height) def updateMask(self) -> None: + """Update widget mask properties""" path = QtGui.QPainterPath() path.addRoundedRect(self.rect().toRectF(), 10, 10) region = QtGui.QRegion(path.toFillPolygon(QtGui.QTransform()).toPolygon()) self.setMask(region) def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: + """Re-implemented method, handle mouse press events""" + if self.userInput: + return self.timeout_timer.stop() + self.slide_out_animation.setStartValue(self.slide_in_animation.currentValue()) + self.slide_in_animation.stop() self.slide_out_animation.start() - def set_timeout(self, value: int) -> None: - if not isinstance(value, int): - raise ValueError("Expected type int ") - self.popup_timeout = value - def new_message( self, message_type: MessageType = MessageType.INFO, message: str = "", - persistent: bool = False, - timeout: int = 0, + timeout: int = 6000, + userInput: bool = False, ): + """Create new popup message + + Args: + message_type (MessageType, optional): Message Level, See `MessageType` Types. Defaults to MessageType.INFO. + message (str, optional): The message. Defaults to "". + timeout (int, optional): How long the message stays for, in milliseconds. Defaults to 0. + userInput (bool,optional): If the user is required to click to make the popup disappear. Defaults to False. + + Returns: + _type_: _description_ + """ + if len(self.messages) == 4: + return + self.messages.append( - {"message": message, "type": message_type, "timeout": timeout} + { + "message": message, + "type": message_type, + "timeout": timeout, + "userInput": userInput, + } ) - return self.add_popup() + return self._add_popup() + + def _add_popup(self) -> None: + """Add popup to queue""" + if self.isShown: + if self.SingleTime.isActive(): + return + self.SingleTime.start() + return - def add_popup(self) -> None: if ( self.messages - and self.slide_in_animation.state() == QtCore.QPropertyAnimation.State.Stopped - and self.slide_out_animation.state() == QtCore.QPropertyAnimation.State.Stopped + and self.slide_in_animation.state() + == QtCore.QPropertyAnimation.State.Stopped + and self.slide_out_animation.state() + == QtCore.QPropertyAnimation.State.Stopped ): message_entry = self.messages.popleft() self.message_type = message_entry.get("type") message = message_entry.get("message") - self.text_label.setText(message) - + timeout = message_entry.get("timeout") + self.timeout_timer.setInterval(timeout) + if message == self.text_label.text(): + self.messages = deque( + m for m in self.messages if m.get("message") != message + ) + return + self.userInput = message_entry.get("userInput") + self.text_label.setFixedHeight(60) + self.text_label.setFixedWidth(500) + match self.message_type: case Popup.MessageType.INFO: self.icon_label.setPixmap(self.info_icon) @@ -131,35 +181,42 @@ def add_popup(self) -> None: case Popup.MessageType.ERROR: self.icon_label.setPixmap(self.error_icon) - self.timeout_timer.setInterval( - self.popup_timeout - ) - end_rect = self._calculate_target_geometry() - - - start_rect = end_rect.translated(0, -end_rect.height()) + start_rect = end_rect.translated(0, -end_rect.height() * 2) self.slide_in_animation.setStartValue(start_rect) self.slide_in_animation.setEndValue(end_rect) self.slide_out_animation.setStartValue(end_rect) self.slide_out_animation.setEndValue(start_rect) - self.setGeometry(start_rect) - - self.open() + if not self.userInput: + self.actionbtn.clearPixmap() + else: + self.actionbtn.setPixmap( + QtGui.QPixmap(":/arrow_icons/media/btn_icons/right_arrow.svg") + ) + self.setGeometry(end_rect) + self.text_label.setText(message) + self.text_label.setFixedHeight( + int(self.text_label.sizeHint().height() * 1.2) + ) + self.show() def showEvent(self, a0: QtGui.QShowEvent) -> None: + """Re-implementation, widget show""" self.slide_in_animation.start() + self.isShown = True super().showEvent(a0) def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: + """Re-implementation, handle resize event""" self.updateMask() super().resizeEvent(a0) def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) - + _base_color = self.default_background_color if self.message_type == Popup.MessageType.INFO: _base_color = Popup.ColorCode.INFO.value @@ -168,43 +225,42 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: elif self.message_type == Popup.MessageType.WARNING: _base_color = Popup.ColorCode.WARNING.value - center_point = QtCore.QPointF(self.rect().center()) gradient = QtGui.QRadialGradient(center_point, self.rect().width() / 2.0) - + gradient.setColorAt(0, _base_color) gradient.setColorAt(1.0, _base_color.darker(160)) painter.setBrush(gradient) painter.setPen(QtCore.Qt.PenStyle.NoPen) painter.drawRoundedRect(self.rect(), 10, 10) - - def setupUI(self) -> None: - self.vertical_layout = QtWidgets.QVBoxLayout(self) - self.horizontal_layout = QtWidgets.QHBoxLayout() + + def _setupUI(self) -> None: + self.horizontal_layout = QtWidgets.QHBoxLayout(self) self.horizontal_layout.setContentsMargins(5, 5, 5, 5) self.icon_label = QtWidgets.QLabel(self) self.icon_label.setFixedSize(QtCore.QSize(60, 60)) + self.icon_label.setMaximumSize(QtCore.QSize(60, 60)) self.icon_label.setScaledContents(True) - self.horizontal_layout.addWidget(self.icon_label) - self.text_label = QtWidgets.QLabel(self) + self.text_label.setStyleSheet("background: transparent; color:white") + self.text_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.text_label.setWordWrap(True) - self.text_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignVCenter | QtCore.Qt.AlignmentFlag.AlignHCenter) - font = self.text_label.font() font.setPixelSize(18) font.setFamily("sans-serif") palette = self.text_label.palette() - palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtCore.Qt.GlobalColor.white) + palette.setColor( + QtGui.QPalette.ColorRole.WindowText, QtCore.Qt.GlobalColor.white + ) self.text_label.setPalette(palette) self.text_label.setFont(font) - self.spacer = QtWidgets.QSpacerItem(60, 60) - - self.horizontal_layout.addWidget(self.text_label, 1) - self.horizontal_layout.addItem(self.spacer) + self.actionbtn = IconButton(self) + self.actionbtn.setMaximumSize(QtCore.QSize(60, 60)) - self.vertical_layout.addLayout(self.horizontal_layout) \ No newline at end of file + self.horizontal_layout.addWidget(self.icon_label) + self.horizontal_layout.addWidget(self.text_label) + self.horizontal_layout.addWidget(self.actionbtn) diff --git a/BlocksScreen/lib/panels/widgets/printcorePage.py b/BlocksScreen/lib/panels/widgets/printcorePage.py index 6da6814f..c2683cd1 100644 --- a/BlocksScreen/lib/panels/widgets/printcorePage.py +++ b/BlocksScreen/lib/panels/widgets/printcorePage.py @@ -1,35 +1,33 @@ from lib.utils.blocks_button import BlocksCustomButton from PyQt6 import QtCore, QtGui, QtWidgets -class SwapPrintcorePage(QtWidgets.QDialog): - - +class SwapPrintcorePage(QtWidgets.QDialog): def __init__( - self, parent: QtWidgets.QWidget, + self, + parent: QtWidgets.QWidget, ) -> None: super().__init__(parent) self.setStyleSheet( "background-image: url(:/background/media/1st_background.png);" ) self.setWindowFlags( - QtCore.Qt.WindowType.Popup - | QtCore.Qt.WindowType.FramelessWindowHint + QtCore.Qt.WindowType.Popup | QtCore.Qt.WindowType.FramelessWindowHint ) - self.setupUI() + self._setupUI() self.repaint() def setText(self, text: str) -> None: + """Set widget text""" self.label.setText(text) self.repaint() - - def Text(self) -> str: - return self.label.text() - - + def text(self) -> str: + """Return current widget text""" + return self.label.text() - def geometry_calc(self) -> None: + def _geometry_calc(self) -> None: + """Calculate widget position relative to the screen""" app_instance = QtWidgets.QApplication.instance() main_window = app_instance.activeWindow() if app_instance else None if main_window is None and app_instance: @@ -44,6 +42,7 @@ def geometry_calc(self) -> None: self.setGeometry(x, y, width, height) def sizeHint(self) -> QtCore.QSize: + """Re-implemented method, handle widget size""" popup_width = int(self.geometry().width()) popup_height = int(self.geometry().height()) # Centering logic @@ -53,39 +52,36 @@ def sizeHint(self) -> QtCore.QSize: self.move(popup_x, popup_y) self.setFixedSize(popup_width, popup_height) self.setMinimumSize(popup_width, popup_height) - + return super().sizeHint() def resizeEvent(self, event: QtGui.QResizeEvent) -> None: + """Re-implemented method, handle widget resize event""" super().resizeEvent(event) - self.tittle.setGeometry(0, 0, self.width(), 60) - - # Calculate label geometry first label_margin = 20 label_height = int(self.height() * 0.65) - label_margin - self.label.setGeometry(label_margin, 60, self.width() - 2 * label_margin, label_height) - # Calculate button geometry based on the window's dimensions + self.label.setGeometry( + label_margin, 60, self.width() - 2 * label_margin, label_height + ) button_width = 250 button_height = 80 spacing = 100 total_button_width = 2 * button_width + spacing - # Center the buttons horizontally start_x = (self.width() - total_button_width) // 2 - button_y = self.height() - button_height -45 + button_y = self.height() - button_height - 45 self.pc_accept.setGeometry(start_x, button_y, button_width, button_height) - self.pc_cancel.setGeometry(start_x + button_width+100 , button_y, button_width, button_height) + self.pc_cancel.setGeometry( + start_x + button_width + 100, button_y, button_width, button_height + ) def show(self) -> None: - self.geometry_calc() + """Re-implemented method, widget show""" + self._geometry_calc() self.repaint() return super().show() - - - - - def setupUI(self) -> None: + def _setupUI(self) -> None: font = QtGui.QFont() font.setPointSize(20) @@ -104,7 +100,9 @@ def setupUI(self) -> None: self.pc_cancel = BlocksCustomButton(parent=self) self.pc_cancel.setMinimumSize(QtCore.QSize(250, 80)) self.pc_cancel.setMaximumSize(QtCore.QSize(250, 80)) - self.pc_cancel.setProperty("icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/no.svg")) + self.pc_cancel.setProperty( + "icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/no.svg") + ) self.pc_cancel.setObjectName("pc_cancel") self.pc_cancel.setFont(font) self.pc_cancel.setText("Cancel") @@ -112,7 +110,9 @@ def setupUI(self) -> None: self.pc_accept = BlocksCustomButton(parent=self) self.pc_accept.setMinimumSize(QtCore.QSize(250, 80)) self.pc_accept.setMaximumSize(QtCore.QSize(250, 80)) - self.pc_accept.setProperty("icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg")) + self.pc_accept.setProperty( + "icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg") + ) self.pc_accept.setObjectName("pc_accept") self.pc_accept.setFont(font) - self.pc_accept.setText("Continue?") \ No newline at end of file + self.pc_accept.setText("Continue?") diff --git a/BlocksScreen/lib/panels/widgets/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/probeHelperPage.py index e546afdd..77e17d11 100644 --- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py +++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py @@ -4,10 +4,11 @@ from PyQt6 import QtCore, QtGui, QtWidgets from lib.utils.blocks_label import BlocksLabel from lib.utils.icon_button import IconButton -from lib.utils.group_button import GroupButton +from lib.utils.check_button import BlocksCustomCheckButton from lib.utils.blocks_button import BlocksCustomButton -from lib.panels.widgets.loadPage import LoadScreen +from lib.panels.widgets.loadWidget import LoadingOverlayWidget +from lib.panels.widgets.basePopup import BasePopup class ProbeHelper(QtWidgets.QWidget): @@ -18,8 +19,8 @@ class ProbeHelper(QtWidgets.QWidget): str, name="run_gcode" ) - query_printer_object: typing.ClassVar[QtCore.pyqtSignal] = ( - QtCore.pyqtSignal(dict, name="query_object") + query_printer_object: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + dict, name="query_object" ) subscribe_config: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( [ @@ -46,71 +47,52 @@ class ProbeHelper(QtWidgets.QWidget): z_offset_config_method: tuple = () z_offset_calibration_speed: int = 100 - - def __init__(self, parent: QtWidgets.QWidget) -> None: super().__init__(parent) - self.Loadscreen = LoadScreen(self) - + self.Loadscreen = BasePopup(self, dialog=False) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.Loadscreen.add_widget(self.loadwidget) self.setObjectName("probe_offset_page") - self.setupUi() - + self._setupUi() self.inductive_icon = QtGui.QPixmap( ":/z_levelling/media/btn_icons/inductive.svg" ) - self.bltouch_icon = QtGui.QPixmap( - ":/z_levelling/media/btn_icons/bltouch.svg" - ) + self.bltouch_icon = QtGui.QPixmap(":/z_levelling/media/btn_icons/bltouch.svg") self.endstop_icon = QtGui.QPixmap( ":/extruder_related/media/btn_icons/switch_zoom.svg" ) - self.eddy_icon = QtGui.QPixmap( - ":/z_levelling/media/btn_icons/eddy_mech.svg" - ) - + self.eddy_icon = QtGui.QPixmap(":/z_levelling/media/btn_icons/eddy_mech.svg") self._toggle_tool_buttons(False) self._setup_move_option_buttons() self.move_option_1.toggled.connect( - lambda: self.handle_zhopHeight_change( - new_value=float(self.distances[0]) - ) + lambda: self.handle_zhopHeight_change(new_value=float(self.distances[0])) ) self.move_option_2.toggled.connect( - lambda: self.handle_zhopHeight_change( - new_value=float(self.distances[1]) - ) + lambda: self.handle_zhopHeight_change(new_value=float(self.distances[1])) ) self.move_option_3.toggled.connect( - lambda: self.handle_zhopHeight_change( - new_value=float(self.distances[2]) - ) + lambda: self.handle_zhopHeight_change(new_value=float(self.distances[2])) ) self.move_option_4.toggled.connect( - lambda: self.handle_zhopHeight_change( - new_value=float(self.distances[3]) - ) + lambda: self.handle_zhopHeight_change(new_value=float(self.distances[3])) ) self.move_option_5.toggled.connect( - lambda: self.handle_zhopHeight_change( - new_value=float(self.distances[4]) - ) - ) - self.mb_raise_nozzle.clicked.connect( - lambda:self.handle_nozzle_move("raise") - ) - self.mb_lower_nozzle.clicked.connect( - lambda:self.handle_nozzle_move("lower") + lambda: self.handle_zhopHeight_change(new_value=float(self.distances[4])) ) + self.mb_raise_nozzle.clicked.connect(lambda: self.handle_nozzle_move("raise")) + self.mb_lower_nozzle.clicked.connect(lambda: self.handle_nozzle_move("lower")) self.po_back_button.clicked.connect(self.request_back) self.accept_button.clicked.connect(self.handle_accept) self.abort_button.clicked.connect(self.handle_abort) self.update() - self.block_z = False self.block_list = False def on_klippy_status(self, state: str): + """Handle Klippy status event change""" if state.lower() == "standby": self.block_z = False self.block_list = False @@ -138,9 +120,9 @@ def on_klippy_status(self, state: str): if child_widget is not None: child_widget.setParent(None) child_widget.deleteLater() - return def handle_nozzle_move(self, direction: str): + """Handle move z buttons click""" if direction == "raise": self._pending_gcode = f"TESTZ Z={self._zhop_height}" elif direction == "lower": @@ -177,7 +159,9 @@ def _configure_option_cards(self, probes_list: list[str]) -> None: _card = OptionCard(self, _card_text, str(probe), _icon) # type: ignore _card.setObjectName(str(probe)) self.card_options.update({str(probe): _card}) - self.main_content_horizontal_layout.addWidget(_card, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter) + self.main_content_horizontal_layout.addWidget( + _card, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter + ) if not hasattr(self.card_options.get(probe), "continue_clicked"): del _card self.card_options.pop(probe) @@ -194,22 +178,20 @@ def _hide_option_cards(self) -> None: def _show_option_cards(self) -> None: list(map(lambda x: x[1].show(), self.card_options.items())) - def init_probe_config(self) -> None: + def _init_probe_config(self) -> None: + """Initialize internal probe tracking""" if not self.z_offset_config_method: return - if self.z_offset_config_type != "endstop": self.z_offsets = tuple( map( - lambda axis: self.z_offset_config_method[1].get( - f"{axis}_offset" - ), + lambda axis: self.z_offset_config_method[1].get(f"{axis}_offset"), ["x", "y", "z"], ) ) - self.z_offset_calibration_speed = self.z_offset_config_method[ - 1 - ].get("speed") + self.z_offset_calibration_speed = self.z_offset_config_method[1].get( + "speed" + ) @QtCore.pyqtSlot(list, name="on_object_config") @QtCore.pyqtSlot(dict, name="on_object_config") @@ -224,29 +206,30 @@ def on_object_config(self, config: dict | list) -> None: return # BUG: If i don't add if not self.probe_config i'll just receive the configuration a bunch of times - if isinstance(config, list):... - # if self.block_list: - # return - # else: - # self.block_list = True - - # _keys = [] - # if not isinstance(config, list): - # return - - # list(map(lambda item: _keys.extend(item.keys()), config)) - - # probe, *_ = config[0].items() - # self.z_offset_method_type = probe[0] # The one found first - # self.z_offset_method_config = ( - # probe[1], - # "PROBE_CALIBRATE", - # "Z_OFFSET_APPLY_PROBE", - # ) - # self.init_probe_config() - # if not _keys: - # return - # self._configure_option_cards(_keys) + if isinstance(config, list): + ... + # if self.block_list: + # return + # else: + # self.block_list = True + + # _keys = [] + # if not isinstance(config, list): + # return + + # list(map(lambda item: _keys.extend(item.keys()), config)) + + # probe, *_ = config[0].items() + # self.z_offset_method_type = probe[0] # The one found first + # self.z_offset_method_config = ( + # probe[1], + # "PROBE_CALIBRATE", + # "Z_OFFSET_APPLY_PROBE", + # ) + # self.init_probe_config() + # if not _keys: + # return + # self._configure_option_cards(_keys) elif isinstance(config, dict): if config.get("stepper_z"): @@ -254,14 +237,12 @@ def on_object_config(self, config: dict | list) -> None: return else: self.block_z = True - + _virtual_endstop = "probe:z_virtual_endstop" _config = config.get("stepper_z") if not _config: return - if ( - _config.get("endstop_pin") == _virtual_endstop - ): # home with probe + if _config.get("endstop_pin") == _virtual_endstop: # home with probe return self.z_offset_config_type = "endstop" self.z_offset_config_method = ( @@ -304,6 +285,7 @@ def on_object_config(self, config: dict | list) -> None: @QtCore.pyqtSlot(dict, name="on_printer_config") def on_printer_config(self, config: dict) -> None: + """Handle received printer config""" _probe_types = [ "probe", "bltouch", @@ -326,6 +308,7 @@ def on_printer_config(self, config: dict) -> None: @QtCore.pyqtSlot(dict, name="on_available_gcode_cmds") def on_available_gcode_cmds(self, gcode_cmds: dict) -> None: + """Setup available probe calibration commands""" _available_commands = gcode_cmds.keys() if "PROBE_CALIBRATE" in _available_commands: self._calibration_commands.append("PROBE_CALIBRATE") @@ -411,18 +394,16 @@ def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None: """ if not sender: return - + for i in self.card_options.values(): i.setDisabled(True) self.Loadscreen.show() - self.Loadscreen.set_status_message("Homing Axes...") + self.loadwidget.set_status_message("Homing Axes...") if self.z_offset_safe_xy: self.run_gcode_signal.emit("G28\nM400") - self._move_to_pos( - self.z_offset_safe_xy[0], self.z_offset_safe_xy[1], 100 - ) + self._move_to_pos(self.z_offset_safe_xy[0], self.z_offset_safe_xy[1], 100) self.helper_initialize = True _timer = QtCore.QTimer() _timer.setSingleShot(True) @@ -462,7 +443,7 @@ def handle_abort(self) -> None: @QtCore.pyqtSlot(str, list, name="on_gcode_move_update") def on_gcode_move_update(self, name: str, value: list) -> None: - # TODO: catch the z distances and update the values on the window + """Handle gcode move update""" if not value: return @@ -477,6 +458,7 @@ def on_gcode_move_update(self, name: str, value: list) -> None: @QtCore.pyqtSlot(dict, name="on_manual_probe_update") def on_manual_probe_update(self, update: dict) -> None: + """Handle manual probe update""" if not update: return @@ -492,13 +474,9 @@ def on_manual_probe_update(self, update: dict) -> None: self._toggle_tool_buttons(True) if update.get("z_position_upper"): - self.old_offset_info.setText( - f"{update.get('z_position_upper'):.4f} mm" - ) + self.old_offset_info.setText(f"{update.get('z_position_upper'):.4f} mm") if update.get("z_position"): - self.current_offset_info.setText( - f"{update.get('z_position'):.4f} mm" - ) + self.current_offset_info.setText(f"{update.get('z_position'):.4f} mm") @QtCore.pyqtSlot(list, name="handle_gcode_response") def handle_gcode_response(self, data: list) -> None: @@ -508,13 +486,9 @@ def handle_gcode_response(self, data: list) -> None: data (list): A list containing the gcode that originated the response and the response """ - # TODO: Only check for messages if we are in the tool otherwise ignore them if self.isVisible(): if data[0].startswith("!!"): # An error occurred - if ( - "already in a manual z probe" - in data[0].strip("!! ").lower() - ): + if "already in a manual z probe" in data[0].strip("!! ").lower(): self._hide_option_cards() self.helper_start = True self._toggle_tool_buttons(True) @@ -527,6 +501,7 @@ def handle_gcode_response(self, data: list) -> None: @QtCore.pyqtSlot(list, name="handle_error_response") def handle_error_response(self, data: list) -> None: + """Handle received error response""" ... # _data, _metadata, *extra = data + [None] * max(0, 2 - len(data)) @@ -535,12 +510,6 @@ def _move_to_pos(self, x, y, speed) -> None: self.run_gcode_signal.emit(f"G90\nG1 X{x} Y{y} F{speed * 60}\nM400") return - ############################################################################### - ################################# UI RELATED ################################## - ############################################################################### - def show(self) -> None: - return super().show() - def _setup_move_option_buttons(self) -> None: """Change move_option_x buttons text for configured zhop values in stored in the class variable `distances` @@ -549,7 +518,6 @@ def _setup_move_option_buttons(self) -> None: """ if self.distances: return - self.move_option_1.setText(str(self.distances[0])) self.move_option_2.setText(str(self.distances[1])) self.move_option_3.setText(str(self.distances[2])) @@ -579,7 +547,12 @@ def _toggle_tool_buttons(self, state: bool) -> None: self.mb_raise_nozzle.show() self.mb_lower_nozzle.show() self.frame_2.show() - self.spacerItem.changeSize(40,20,QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.spacerItem.changeSize( + 40, + 20, + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Minimum, + ) else: self.po_back_button.setEnabled(True) @@ -594,13 +567,17 @@ def _toggle_tool_buttons(self, state: bool) -> None: self.mb_raise_nozzle.hide() self.mb_lower_nozzle.hide() self.frame_2.hide() - self.spacerItem.changeSize(0,0,QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - + self.spacerItem.changeSize( + 0, + 0, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Minimum, + ) + self.update() return - - def setupUi(self) -> None: + def _setupUi(self) -> None: self.bbp_offset_value_selector_group = QtWidgets.QButtonGroup(self) self.bbp_offset_value_selector_group.setExclusive(True) sizePolicy = QtWidgets.QSizePolicy( @@ -656,9 +633,7 @@ def setupUi(self) -> None: self.accept_button.setGeometry(QtCore.QRect(480, 340, 170, 60)) self.accept_button.setText("Accept") self.accept_button.setObjectName("accept_button") - self.accept_button.setPixmap( - QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg") - ) + self.accept_button.setPixmap(QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg")) self.accept_button.setVisible(False) font = QtGui.QFont() font.setPointSize(15) @@ -668,9 +643,7 @@ def setupUi(self) -> None: self.abort_button.setGeometry(QtCore.QRect(300, 340, 170, 60)) self.abort_button.setText("Abort") self.abort_button.setObjectName("accept_button") - self.abort_button.setPixmap( - QtGui.QPixmap(":/dialog/media/btn_icons/no.svg") - ) + self.abort_button.setPixmap(QtGui.QPixmap(":/dialog/media/btn_icons/no.svg")) self.abort_button.setVisible(False) font = QtGui.QFont() font.setPointSize(15) @@ -698,16 +671,13 @@ def setupUi(self) -> None: self.po_back_button.setMaximumSize(QtCore.QSize(60, 60)) self.po_back_button.setText("") self.po_back_button.setFlat(True) - self.po_back_button.setPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/back.svg") - ) + self.po_back_button.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) self.po_back_button.setObjectName("po_back_button") self.bbp_header_layout.addWidget( self.po_back_button, 0, - QtCore.Qt.AlignmentFlag.AlignRight - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter, ) self.bbp_header_layout.setStretch(0, 1) self.verticalLayout.addLayout(self.bbp_header_layout) @@ -724,9 +694,6 @@ def setupUi(self) -> None: self.separator_line.setObjectName("separator_line") self.verticalLayout.addWidget(self.separator_line) - - - # Offset Steps Buttons Group Box (LEFT side of main_content_horizontal_layout) self.bbp_offset_steps_buttons_group_box = QtWidgets.QGroupBox(self) font = QtGui.QFont() @@ -748,7 +715,7 @@ def setupUi(self) -> None: self.bbp_offset_steps_buttons.setObjectName("bbp_offset_steps_buttons") # 0.1mm button - self.move_option_1 = GroupButton( + self.move_option_1 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box ) self.move_option_1.setMinimumSize(QtCore.QSize(100, 60)) @@ -763,20 +730,15 @@ def setupUi(self) -> None: self.move_option_1.setFlat(True) self.move_option_1.setProperty("button_type", "") self.move_option_1.setObjectName("move_option_1") - self.bbp_offset_value_selector_group.addButton( - self.move_option_1 - ) + self.bbp_offset_value_selector_group.addButton(self.move_option_1) self.bbp_offset_steps_buttons.addWidget( self.move_option_1, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) - - # 0.01mm button - self.move_option_2 = GroupButton( + self.move_option_2 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box ) self.move_option_2.setMinimumSize(QtCore.QSize(100, 60)) @@ -792,18 +754,15 @@ def setupUi(self) -> None: self.move_option_2.setFlat(True) self.move_option_2.setProperty("button_type", "") self.move_option_2.setObjectName("move_option_2") - self.bbp_offset_value_selector_group.addButton( - self.move_option_2 - ) + self.bbp_offset_value_selector_group.addButton(self.move_option_2) self.bbp_offset_steps_buttons.addWidget( self.move_option_2, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) # 0.05mm button - self.move_option_3 = GroupButton( + self.move_option_3 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box ) self.move_option_3.setMinimumSize(QtCore.QSize(100, 60)) @@ -819,18 +778,15 @@ def setupUi(self) -> None: self.move_option_3.setFlat(True) self.move_option_3.setProperty("button_type", "") self.move_option_3.setObjectName("move_option_3") - self.bbp_offset_value_selector_group.addButton( - self.move_option_3 - ) + self.bbp_offset_value_selector_group.addButton(self.move_option_3) self.bbp_offset_steps_buttons.addWidget( self.move_option_3, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) # 0.025mm button - self.move_option_4 = GroupButton( + self.move_option_4 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box ) self.move_option_4.setMinimumSize(QtCore.QSize(100, 60)) @@ -846,18 +802,15 @@ def setupUi(self) -> None: self.move_option_4.setFlat(True) self.move_option_4.setProperty("button_type", "") self.move_option_4.setObjectName("move_option_4") - self.bbp_offset_value_selector_group.addButton( - self.move_option_4 - ) + self.bbp_offset_value_selector_group.addButton(self.move_option_4) self.bbp_offset_steps_buttons.addWidget( self.move_option_4, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) - # 0.01mm button - self.move_option_5 = GroupButton( + # 0.01mm button + self.move_option_5 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box ) self.move_option_5.setMinimumSize(QtCore.QSize(100, 60)) @@ -873,22 +826,17 @@ def setupUi(self) -> None: self.move_option_5.setFlat(True) self.move_option_5.setProperty("button_type", "") self.move_option_5.setObjectName("move_option_4") - self.bbp_offset_value_selector_group.addButton( - self.move_option_5 - ) + self.bbp_offset_value_selector_group.addButton(self.move_option_5) self.bbp_offset_steps_buttons.addWidget( self.move_option_5, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) # Line separator for 0.025mm - set size policy to expanding horizontally # Set the layout for the group box - self.bbp_offset_steps_buttons_group_box.setLayout( - self.bbp_offset_steps_buttons - ) + self.bbp_offset_steps_buttons_group_box.setLayout(self.bbp_offset_steps_buttons) # Add the group box to the main content horizontal layout FIRST for left placement self.main_content_horizontal_layout.addWidget( self.bbp_offset_steps_buttons_group_box @@ -896,9 +844,7 @@ def setupUi(self) -> None: # Graphic and Current Value Frame (This will now be in the MIDDLE) self.frame_2 = QtWidgets.QFrame(parent=self) - sizePolicy.setHeightForWidth( - self.frame_2.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth()) self.frame_2.setSizePolicy(sizePolicy) self.frame_2.setMinimumSize(QtCore.QSize(350, 160)) self.frame_2.setMaximumSize(QtCore.QSize(350, 160)) @@ -907,33 +853,25 @@ def setupUi(self) -> None: self.frame_2.setObjectName("frame_2") self.tool_image = QtWidgets.QLabel(parent=self.frame_2) self.tool_image.setGeometry(QtCore.QRect(0, 30, 371, 121)) - self.tool_image.setLayoutDirection( - QtCore.Qt.LayoutDirection.RightToLeft - ) + self.tool_image.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) self.tool_image.setPixmap( QtGui.QPixmap(":/graphics/media/graphics/babystep_graphic.png") ) self.tool_image.setScaledContents(False) - self.tool_image.setAlignment( - QtCore.Qt.AlignmentFlag.AlignCenter - ) + self.tool_image.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.tool_image.setObjectName("tool_image") # === NEW LABEL ADDED HERE === # This is the title label that appears above the red value box. self.old_offset_info = QtWidgets.QLabel(parent=self.frame_2) # Position it just above the red box. Red box is at y=70, so y=40 is appropriate. - self.old_offset_info.setGeometry( - QtCore.QRect(240, 95, 200, 60) - ) + self.old_offset_info.setGeometry(QtCore.QRect(240, 95, 200, 60)) font = QtGui.QFont() font.setPointSize(12) self.old_offset_info.setFont(font) # Set color to white to be visible on the dark background - self.old_offset_info.setStyleSheet( - "color: gray; background: transparent;" - ) + self.old_offset_info.setStyleSheet("color: gray; background: transparent;") self.old_offset_info.setText("Z-Offset") self.old_offset_info.setObjectName("old_offset_info") self.old_offset_info.setText("0 mm") @@ -941,9 +879,7 @@ def setupUi(self) -> None: # === END OF NEW LABEL === self.current_offset_info = BlocksLabel(parent=self.frame_2) - self.current_offset_info.setGeometry( - QtCore.QRect(100, 70, 200, 60) - ) + self.current_offset_info.setGeometry(QtCore.QRect(100, 70, 200, 60)) sizePolicy.setHeightForWidth( self.current_offset_info.sizePolicy().hasHeightForWidth() ) @@ -953,25 +889,18 @@ def setupUi(self) -> None: font = QtGui.QFont() font.setPointSize(14) self.current_offset_info.setFont(font) - self.current_offset_info.setStyleSheet( - "background: transparent; color: white;" - ) + self.current_offset_info.setStyleSheet("background: transparent; color: white;") self.current_offset_info.setText("Z:0mm") self.current_offset_info.setPixmap( QtGui.QPixmap(":/graphics/media/btn_icons/z_offset_adjust.svg") ) - self.current_offset_info.setAlignment( - QtCore.Qt.AlignmentFlag.AlignCenter - ) - self.current_offset_info.setObjectName( - "current_offset_info" - ) + self.current_offset_info.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.current_offset_info.setObjectName("current_offset_info") # Add graphic frame AFTER the offset buttons group box self.main_content_horizontal_layout.addWidget( self.frame_2, 0, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) # Move Buttons Layout (This will now be on the RIGHT) @@ -1025,7 +954,6 @@ def setupUi(self) -> None: # Add move buttons layout LAST for right placement self.main_content_horizontal_layout.addLayout(self.bbp_buttons_layout) - self.main_content_horizontal_layout.addItem(self.spacerItem) # Set stretch factors for main content horizontal layout diff --git a/BlocksScreen/lib/panels/widgets/sensorWidget.py b/BlocksScreen/lib/panels/widgets/sensorWidget.py index 109792fa..e0ed9955 100644 --- a/BlocksScreen/lib/panels/widgets/sensorWidget.py +++ b/BlocksScreen/lib/panels/widgets/sensorWidget.py @@ -1,4 +1,5 @@ import enum +import typing from lib.utils.blocks_label import BlocksLabel from lib.utils.toggleAnimatedButton import ToggleAnimatedButton @@ -7,23 +8,35 @@ class SensorWidget(QtWidgets.QWidget): class SensorType(enum.Enum): + """Filament sensor type""" + SWITCH = enum.auto() MOTION = enum.auto() class SensorFlags(enum.Flag): + """Filament sensor flags""" + CLICKABLE = enum.auto() DISPLAY = enum.auto() class FilamentState(enum.Enum): + """Current filament state, sensor has or does not have filament""" + MISSING = 0 PRESENT = 1 class SensorState(enum.IntEnum): + """Current sensor filament state, if it's turned on or not""" + OFF = False ON = True + run_gcode_signal: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + str, name="run_gcode" + ) + def __init__(self, parent, sensor_name: str): - super(SensorWidget, self).__init__(parent) + super().__init__(parent) self.name = str(sensor_name).split(" ")[1] self.sensor_type: SensorWidget.SensorType = ( self.SensorType.SWITCH @@ -32,22 +45,18 @@ def __init__(self, parent, sensor_name: str): ) self.setObjectName(self.name) - self.setMinimumSize(parent.contentsRect().width(), 60) + self.setMinimumSize(250, 250) self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) self._sensor_type: SensorWidget.SensorType = self.SensorType.SWITCH self._flags: SensorWidget.SensorFlags = self.SensorFlags.CLICKABLE self.filament_state: SensorWidget.FilamentState = ( - SensorWidget.FilamentState.MISSING - ) - self.sensor_state: SensorWidget.SensorState = ( - SensorWidget.SensorState.OFF + SensorWidget.FilamentState.PRESENT ) + self.sensor_state: SensorWidget.SensorState = SensorWidget.SensorState.ON self._icon_label = None self._text_label = None - self._text: str = ( - str(self.sensor_type.name) + " Sensor: " + str(self.name) - ) + self._text = self.name self._item_rect: QtCore.QRect = QtCore.QRect() self.icon_pixmap_fp: QtGui.QPixmap = QtGui.QPixmap( ":/filament_related/media/btn_icons/filament_sensor_turn_on.svg" @@ -55,10 +64,12 @@ def __init__(self, parent, sensor_name: str): self.icon_pixmap_fnp: QtGui.QPixmap = QtGui.QPixmap( ":/filament_related/media/btn_icons/filament_sensor_off.svg" ) - self.setupUI() + self._setupUI() + self.toggle_button.stateChange.connect(self.toggle_sensor_state) @property def type(self) -> SensorType: + """Sensor type""" return self._sensor_type @type.setter @@ -67,6 +78,7 @@ def type(self, type: SensorType): @property def flags(self) -> SensorFlags: + """Current filament sensor flags""" return self._flags @flags.setter @@ -75,6 +87,7 @@ def flags(self, flags: SensorFlags) -> None: @property def text(self) -> str: + """Filament sensor text""" return self._text @text.setter @@ -83,113 +96,170 @@ def text(self, new_text) -> None: self._text_label.setText(f"{new_text}") self._text = new_text - @QtCore.pyqtSlot(bool, name="change_fil_sensor_state") + @QtCore.pyqtSlot(FilamentState, name="change_fil_sensor_state") def change_fil_sensor_state(self, state: FilamentState): - if isinstance(state, SensorWidget.FilamentState): - self.filament_state = state + """Invert the filament state in response to a Klipper update""" + if not isinstance(state, SensorWidget.FilamentState): + return + self.filament_state = SensorWidget.FilamentState(not state.value) + self.update() + + def toggle_button_state(self, state: ToggleAnimatedButton.State) -> None: + """Called when the Klipper firmware reports an update to the filament sensor state""" + self.toggle_button.setDisabled(False) + if state.value != self.sensor_state.value: + self.sensor_state = self.SensorState(state.value) + self.toggle_button.state = ToggleAnimatedButton.State( + self.sensor_state.value + ) + self.update() + + @QtCore.pyqtSlot(ToggleAnimatedButton.State, name="state-change") + def toggle_sensor_state(self, state: ToggleAnimatedButton.State) -> None: + """Emit the appropriate G-Code command to change the filament sensor state.""" + if state.value != self.sensor_state.value: + self.toggle_button.setDisabled(True) + self.run_gcode_signal.emit( + f"SET_FILAMENT_SENSOR SENSOR={self.text} ENABLE={int(state.value)}" + ) + self.update() def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: + """Handle widget resize events.""" return super().resizeEvent(a0) def paintEvent(self, a0: QtGui.QPaintEvent) -> None: - # if ( - # self._scaled_select_on_pixmap is not None - # and self._scaled_select_off_pixmap is not None - # ): # Update the toggle button pixmap which indicates the sensor state - # self._button_icon_label.setPixmap( - # self._scaled_select_on_pixmap - # if self.sensor_state == SensorWidget.SensorState.ON - # else self._scaled_select_off_pixmap - # ) - + """Re-implemented method, paint widget""" style_painter = QtWidgets.QStylePainter(self) - style_painter.setRenderHint( - style_painter.RenderHint.Antialiasing, True - ) + style_painter.setRenderHint(style_painter.RenderHint.Antialiasing, True) style_painter.setRenderHint( style_painter.RenderHint.SmoothPixmapTransform, True ) style_painter.setRenderHint( style_painter.RenderHint.LosslessImageRendering, True ) - - if self.filament_state == SensorWidget.FilamentState.PRESENT: - _color = QtGui.QColor(2, 204, 59, 100) - else: - _color = QtGui.QColor(204, 50, 50, 100) - _brush = QtGui.QBrush() - _brush.setColor(_color) - - _brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) - pen = style_painter.pen() - pen.setStyle(QtCore.Qt.PenStyle.NoPen) if self._icon_label: self._icon_label.setPixmap( self.icon_pixmap_fp if self.filament_state == self.FilamentState.PRESENT else self.icon_pixmap_fnp ) - background_rect = QtGui.QPainterPath() - background_rect.addRoundedRect( - self.contentsRect().toRectF(), - 15, - 15, - QtCore.Qt.SizeMode.AbsoluteSize, - ) - style_painter.setBrush(_brush) - style_painter.fillPath(background_rect, _brush) - style_painter.end() + _font = QtGui.QFont() + _font.setPointSize(20) + style_painter.setFont(_font) - @property - def toggle_sensor_gcode_command(self) -> str: - self.sensor_state = ( - SensorWidget.SensorState.ON - if self.sensor_state == SensorWidget.SensorState.OFF - else SensorWidget.SensorState.OFF + label_name = self._text_label_name_ + label_detected = self._text_label_detected + label_state = self._text_label_state + + palette = label_name.palette() + palette.setColor(palette.ColorRole.WindowText, QtGui.QColorConstants.White) + style_painter.drawItemText( + label_name.geometry(), + label_name.alignment(), + palette, + True, + label_name.text(), + QtGui.QPalette.ColorRole.WindowText, ) - return str( - f"SET_FILAMENT_SENSOR SENSOR={self.text} ENABLE={not self.sensor_state.value}" + + _font.setPointSize(16) + style_painter.setFont(_font) + filament_text = self.filament_state.name.capitalize() + tab_spacer = 12 * "\t" + style_painter.drawItemText( + label_state.geometry(), + label_state.alignment(), + palette, + True, + f"Filament: {tab_spacer}{filament_text}", + QtGui.QPalette.ColorRole.WindowText, + ) + + sensor_state_text = self.sensor_state.name.capitalize() + tab_spacer += 3 * "\t" + style_painter.drawItemText( + label_detected.geometry(), + label_detected.alignment(), + palette, + True, + f"Enable: {tab_spacer}{sensor_state_text}", + QtGui.QPalette.ColorRole.WindowText, ) + style_painter.end() - def setupUI(self): + def _setupUI(self): _policy = QtWidgets.QSizePolicy.Policy.MinimumExpanding size_policy = QtWidgets.QSizePolicy(_policy, _policy) size_policy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(size_policy) - self.sensor_horizontal_layout = QtWidgets.QHBoxLayout() - self.sensor_horizontal_layout.setGeometry(QtCore.QRect(0, 0, 640, 60)) - self.sensor_horizontal_layout.setObjectName("sensorHorizontalLayout") + self.sensor_vertical_layout = QtWidgets.QVBoxLayout() + self.sensor_vertical_layout.setObjectName("sensorVerticalLayout") self._icon_label = BlocksLabel(self) - size_policy.setHeightForWidth( - self._icon_label.sizePolicy().hasHeightForWidth() - ) + size_policy.setHeightForWidth(self._icon_label.sizePolicy().hasHeightForWidth()) + parent_width = self.parentWidget().width() self._icon_label.setSizePolicy(size_policy) - self._icon_label.setMinimumSize(60, 60) - self._icon_label.setMaximumSize(60, 60) + self._icon_label.setMinimumSize(120, 100) + self._icon_label.setPixmap( self.icon_pixmap_fp if self.filament_state == self.FilamentState.PRESENT else self.icon_pixmap_fnp ) - self.sensor_horizontal_layout.addWidget(self._icon_label) - self._text_label = QtWidgets.QLabel(parent=self) + self._text_label_name_ = QtWidgets.QLabel(parent=self) size_policy.setHeightForWidth( - self._text_label.sizePolicy().hasHeightForWidth() + self._text_label_name_.sizePolicy().hasHeightForWidth() ) - self._text_label.setMinimumSize(100, 60) - self._text_label.setMaximumSize(500, 60) - _font = QtGui.QFont() - _font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - _font.setPointSize(18) - palette = self._text_label.palette() + self._text_label_name_.setMinimumSize(self.rect().width(), 40) + self._text_label_name_.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + palette = self._text_label_name_.palette() palette.setColor( - palette.ColorRole.WindowText, QtGui.QColorConstants.White + palette.ColorRole.WindowText, QtGui.QColorConstants.Transparent + ) + self._text_label_name_.setPalette(palette) + self._text_label_name_.setText(str(self._text)) + self._icon_label.setSizePolicy(size_policy) + + self._text_label_detected = QtWidgets.QLabel(parent=self) + size_policy.setHeightForWidth( + self._text_label_detected.sizePolicy().hasHeightForWidth() ) - self._text_label.setPalette(palette) - self._text_label.setFont(_font) - self._text_label.setText(str(self._text)) - self.sensor_horizontal_layout.addWidget(self._text_label) + self._text_label_detected.setMinimumSize(parent_width, 20) + + self._text_label_detected.setPalette(palette) + self._text_label_detected.setText(f"Filament: {self.filament_state}") + + self._text_label_state = QtWidgets.QLabel(parent=self) + size_policy.setHeightForWidth( + self._text_label_state.sizePolicy().hasHeightForWidth() + ) + self._text_label_state.setMinimumSize(parent_width, 20) + + self._text_label_state.setPalette(palette) + self._text_label_state.setText(f"Enable: {self.sensor_state.name}") + + self._icon_label.setSizePolicy(size_policy) self.toggle_button = ToggleAnimatedButton(self) - self.toggle_button.setMaximumWidth(100) - self.sensor_horizontal_layout.addWidget(self.toggle_button) - self.setLayout(self.sensor_horizontal_layout) + self.toggle_button.setMinimumSize(100, 50) + self.toggle_button.state = ToggleAnimatedButton.State.ON + + self.sensor_vertical_layout.addWidget( + self._icon_label, alignment=QtCore.Qt.AlignmentFlag.AlignCenter + ) + self.sensor_vertical_layout.addWidget( + self._text_label_name_, alignment=QtCore.Qt.AlignmentFlag.AlignCenter + ) + self.sensor_vertical_layout.addStretch() + self.sensor_vertical_layout.addWidget( + self._text_label_state, alignment=QtCore.Qt.AlignmentFlag.AlignLeft + ) + self.sensor_vertical_layout.addStretch() + self.sensor_vertical_layout.addWidget( + self._text_label_detected, alignment=QtCore.Qt.AlignmentFlag.AlignLeft + ) + self.sensor_vertical_layout.addStretch() + self.sensor_vertical_layout.addWidget( + self.toggle_button, alignment=QtCore.Qt.AlignmentFlag.AlignCenter + ) + + self.setLayout(self.sensor_vertical_layout) diff --git a/BlocksScreen/lib/panels/widgets/sensorsPanel.py b/BlocksScreen/lib/panels/widgets/sensorsPanel.py index a40247b8..29eea5e7 100644 --- a/BlocksScreen/lib/panels/widgets/sensorsPanel.py +++ b/BlocksScreen/lib/panels/widgets/sensorsPanel.py @@ -1,7 +1,9 @@ import typing -from lib.panels.widgets.sensorWidget import SensorWidget +from lib.utils.blocks_frame import BlocksCustomFrame from lib.utils.icon_button import IconButton +from lib.panels.widgets.sensorWidget import SensorWidget +from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem from PyQt6 import QtCore, QtGui, QtWidgets @@ -9,92 +11,91 @@ class SensorsWindow(QtWidgets.QWidget): run_gcode_signal: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( str, name="run_gcode" ) - change_fil_sensor_state: typing.ClassVar[QtCore.pyqtSignal] = ( - QtCore.pyqtSignal( - SensorWidget.FilamentState, name="change_fil_sensor_state" - ) + change_fil_sensor_state: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + SensorWidget.FilamentState, name="change_fil_sensor_state" ) request_back: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( name="request_back" ) - sensor_list: list[SensorWidget] = [] def __init__(self, parent): super(SensorsWindow, self).__init__(parent) - self.setupUi() - self.setAttribute( - QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True - ) + self.model = EntryListModel() + self.entry_delegate = EntryDelegate() + self.sensor_tracking_widget = {} + self.current_widget = None + self.sensor_list: list[SensorWidget] = [] + self._setupUi() + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) self.setTabletTracking(True) - self.fs_sensors_list.itemClicked.connect(self.handle_sensor_clicked) - self.fs_sensors_list.itemClicked self.fs_back_button.clicked.connect(self.request_back) + def reset_view_model(self) -> None: + """Clears items from ListView + (Resets `QAbstractListModel` by clearing entries) + """ + self.model.clear() + self.entry_delegate.clear() + @QtCore.pyqtSlot(dict, name="handle_available_fil_sensors") def handle_available_fil_sensors(self, sensors: dict) -> None: + """Handle available filament sensors, create `SensorWidget` for each detected sensor""" if not isinstance(sensors, dict): return - + self.reset_view_model() filtered_sensors = list( - filter( - lambda printer_obj: str(printer_obj).startswith("filament_switch_sensor") - or str(printer_obj).startswith("filament_motion_sensor"), - sensors.keys(), + filter( + lambda printer_obj: str(printer_obj).startswith( + "filament_switch_sensor" + ) + or str(printer_obj).startswith("filament_motion_sensor") + or str(printer_obj).startswith("cutter_sensor"), + sensors.keys(), ) ) - if filtered_sensors: - self.fs_sensors_list.setRowHidden(self.fs_sensors_list.row(self.item), True) self.sensor_list = [ self.create_sensor_widget(name=sensor) for sensor in filtered_sensors ] + self.model.setData(self.model.index(0), True, EntryListModel.EnableRole) else: - self.fs_sensors_list.setRowHidden(self.fs_sensors_list.row(self.item), False) - - + self.no_update_placeholder.show() @QtCore.pyqtSlot(str, str, bool, name="handle_fil_state_change") def handle_fil_state_change( self, sensor_name: str, parameter: str, value: bool ) -> None: - if sensor_name in self.sensor_list: - state = SensorWidget.FilamentState(value) - _split = sensor_name.split(" ") - _item = self.fs_sensors_list.findChild( - SensorWidget, - name=_split[1], - options=QtCore.Qt.FindChildOption.FindChildrenRecursively, - ) + """Handle Klipper signals for filament sensor changes""" + _item = self.sensor_tracking_widget.get(sensor_name) + if _item: if parameter == "filament_detected": - if isinstance(_item, SensorWidget) and hasattr( - _item, "change_fil_sensor_state" - ): - _item.change_fil_sensor_state( - SensorWidget.FilamentState.PRESENT - ) - _item.repaint() - elif parameter == "filament_missing": - if isinstance(_item, SensorWidget) and hasattr( - _item, "change_fil_sensor_state" - ): - _item.change_fil_sensor_state( - SensorWidget.FilamentState.MISSING - ) - _item.repaint() + state = SensorWidget.FilamentState(not value) + _item.change_fil_sensor_state(state) elif parameter == "enabled": - if _item and isinstance(_item, SensorWidget): - self.run_gcode_signal.emit( - _item.toggle_sensor_gcode_command - ) - - @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, name="handle_sensor_clicked") - def handle_sensor_clicked(self, sensor: QtWidgets.QListWidgetItem) -> None: - _item = self.fs_sensors_list.itemWidget(sensor) - # FIXME: This is just not working - _item.toggle_button.state = ~_item.toggle_button.state - if _item and isinstance(_item, SensorWidget): - self.run_gcode_signal.emit(_item.toggle_sensor_gcode_command) + _item.toggle_button_state(SensorWidget.SensorState(value)) + + def showEvent(self, event: QtGui.QShowEvent | None) -> None: + """Re-add clients to update list""" + return super().showEvent(event) + + @QtCore.pyqtSlot(ListItem, name="on-item-clicked") + def on_item_clicked(self, item: ListItem) -> None: + """Setup information for the currently clicked list item on the info box. + Keeps track of the list item + """ + if not item: + return + + if self.current_widget: + self.current_widget.hide() + + name_id = item.text + current_widget = self.sensor_tracking_widget.get(name_id) + if current_widget is None: + return + self.current_widget = current_widget + self.current_widget.show() def create_sensor_widget(self, name: str) -> SensorWidget: """Creates a sensor row to be added to the QListWidget @@ -102,148 +103,271 @@ def create_sensor_widget(self, name: str) -> SensorWidget: Args: name (str): The name of the filament sensor object """ - _item_widget = SensorWidget(self.fs_sensors_list, name) - _list_item = QtWidgets.QListWidgetItem() - _list_item.setFlags(~QtCore.Qt.ItemFlag.ItemIsEditable) - _list_item.setSizeHint( - QtCore.QSize(self.fs_sensors_list.contentsRect().width(), 80) - ) - _item_widget.toggle_button.stateChange.connect( - lambda: self.fs_sensors_list.itemClicked.emit(_item_widget) - ) + _item_widget = SensorWidget(self.infobox_frame, name) + self.info_box_layout.addWidget(_item_widget) - self.fs_sensors_list.setItemWidget(_list_item, _item_widget) + if self.current_widget: + _item_widget.hide() + else: + _item_widget.show() + self.current_widget = _item_widget + name_id = str(name).split(" ")[1] + item = ListItem( + text=name_id, + right_text="", + right_icon=self.pixmap, + left_icon=None, + callback=None, + selected=False, + allow_check=False, + _lfontsize=17, + _rfontsize=12, + height=80, + notificate=False, + ) + _item_widget.run_gcode_signal.connect(self.run_gcode_signal) + self.sensor_tracking_widget[name_id] = _item_widget + self.model.add_item(item) return _item_widget - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: ... - - def setupUi(self): - self.setObjectName("filament_sensors_page") + def _setupUi(self) -> None: + """Setup UI for updatePage""" + font_id = QtGui.QFontDatabase.addApplicationFont( + ":/font/media/fonts for text/Momcake-Bold.ttf" + ) + font_family = QtGui.QFontDatabase.applicationFontFamilies(font_id)[0] sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding, ) - self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(1) self.setSizePolicy(sizePolicy) - self.setMinimumSize(QtCore.QSize(710, 410)) + self.setMinimumSize(QtCore.QSize(710, 400)) self.setMaximumSize(QtCore.QSize(720, 420)) - self.content_vertical_layout = QtWidgets.QVBoxLayout() - self.content_vertical_layout.setObjectName("contentVerticalLayout") - self.fs_header_layout = QtWidgets.QHBoxLayout() - self.fs_header_layout.setContentsMargins(0, 0, 0, 0) - self.fs_header_layout.setObjectName("fs_header_layout") - self.fs_header_layout.setGeometry(QtCore.QRect(10, 10, 691, 71)) - self.fs_page_title = QtWidgets.QLabel(parent=self) - sizePolicy.setHeightForWidth( - self.fs_page_title.sizePolicy().hasHeightForWidth() - ) - self.fs_page_title.setSizePolicy(sizePolicy) - self.fs_page_title.setMinimumSize(QtCore.QSize(300, 71)) - self.fs_page_title.setMaximumSize(QtCore.QSize(16777215, 71)) + self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.update_page_content_layout = QtWidgets.QVBoxLayout() + self.update_page_content_layout.setContentsMargins(15, 15, 2, 2) + + self.header_content_layout = QtWidgets.QHBoxLayout() + self.header_content_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) + self.fs_page_title = QtWidgets.QLabel(self) + self.fs_page_title.setMinimumSize(QtCore.QSize(100, 60)) + self.fs_page_title.setMaximumSize(QtCore.QSize(16777215, 60)) font = QtGui.QFont() - font.setPointSize(22) - palette = QtGui.QPalette() - palette.setColor( - palette.ColorRole.WindowText, QtGui.QColorConstants.White - ) - self.fs_page_title.setPalette(palette) + font.setFamily(font_family) + font.setPointSize(24) + palette = self.fs_page_title.palette() + palette.setColor(palette.ColorRole.WindowText, QtGui.QColor("#FFFFFF")) self.fs_page_title.setFont(font) + self.fs_page_title.setPalette(palette) + self.fs_page_title.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) + self.fs_page_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.fs_page_title.setObjectName("fs_page_title") - self.fs_header_layout.addWidget(self.fs_page_title, 0) + self.fs_page_title.setText("Filament Sensors") + self.header_content_layout.addWidget(self.fs_page_title, 0) self.fs_back_button = IconButton(self) - sizePolicy.setHeightForWidth( - self.fs_back_button.sizePolicy().hasHeightForWidth() - ) - self.fs_back_button.setSizePolicy(sizePolicy) self.fs_back_button.setMinimumSize(QtCore.QSize(60, 60)) self.fs_back_button.setMaximumSize(QtCore.QSize(60, 60)) self.fs_back_button.setFlat(True) - self.fs_back_button.setPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/back.svg") + self.fs_back_button.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) + self.header_content_layout.addWidget(self.fs_back_button, 0) + self.update_page_content_layout.addLayout(self.header_content_layout, 0) + + self.main_content_layout = QtWidgets.QHBoxLayout() + self.main_content_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + + self.sensor_buttons_frame = BlocksCustomFrame(self) + + self.sensor_buttons_frame.setMinimumSize(QtCore.QSize(320, 300)) + self.sensor_buttons_frame.setMaximumSize(QtCore.QSize(450, 500)) + + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Button, + brush, ) - self.fs_back_button.setObjectName("fs_back_button") - self.fs_header_layout.addWidget( - self.fs_back_button, - 0, + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Base, + brush, ) - self.content_vertical_layout.addLayout(self.fs_header_layout) - self.fs_sensors_list = QtWidgets.QListWidget(self) - sizePolicy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Policy.MinimumExpanding, - QtWidgets.QSizePolicy.Policy.MinimumExpanding, + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Window, + brush, ) - sizePolicy.setHorizontalStretch(1) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth( - self.fs_sensors_list.sizePolicy().hasHeightForWidth() + brush = QtGui.QBrush(QtGui.QColor(0, 120, 215, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Highlight, + brush, ) - self.fs_sensors_list.setSizePolicy(sizePolicy) - self.fs_sensors_list.setMinimumSize(QtCore.QSize(650, 300)) - self.fs_sensors_list.setMaximumSize(QtCore.QSize(700, 300)) - self.fs_sensors_list.setLayoutDirection( - QtCore.Qt.LayoutDirection.LeftToRight + brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Link, + brush, ) - self.fs_sensors_list.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.fs_sensors_list.setObjectName("fs_sensors_list") - self.fs_sensors_list.setViewMode( - self.fs_sensors_list.ViewMode.ListMode + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Button, + brush, ) - self.fs_sensors_list.setItemAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Base, + brush, ) - self.fs_sensors_list.setFlow(self.fs_sensors_list.Flow.TopToBottom) - self.fs_sensors_list.setFrameStyle(0) - palette = self.fs_sensors_list.palette() - palette.setColor( - palette.ColorRole.Base, QtGui.QColorConstants.Transparent + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Window, + brush, ) + brush = QtGui.QBrush(QtGui.QColor(0, 120, 215, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Highlight, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Inactive, + QtGui.QPalette.ColorRole.Link, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Button, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Base, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Window, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 120, 215, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Highlight, + brush, + ) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 0)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush( + QtGui.QPalette.ColorGroup.Disabled, + QtGui.QPalette.ColorRole.Link, + brush, + ) + self.fs_sensors_list = QtWidgets.QListView(self.sensor_buttons_frame) + self.fs_sensors_list.setModel(self.model) + self.fs_sensors_list.setItemDelegate(self.entry_delegate) + self.entry_delegate.item_selected.connect(self.on_item_clicked) + self.fs_sensors_list.setMouseTracking(True) + self.fs_sensors_list.setTabletTracking(True) + self.fs_sensors_list.setSpacing(7) self.fs_sensors_list.setPalette(palette) - self.fs_sensors_list.setDropIndicatorShown(False) - self.fs_sensors_list.setAcceptDrops(False) + self.fs_sensors_list.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.fs_sensors_list.setStyleSheet("background-color:transparent") + self.fs_sensors_list.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.fs_sensors_list.setMinimumSize(self.sensor_buttons_frame.size()) + self.fs_sensors_list.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) + self.fs_sensors_list.setVerticalScrollBarPolicy( + QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + self.fs_sensors_list.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + self.fs_sensors_list.setSizeAdjustPolicy( + QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents + ) + self.fs_sensors_list.setAutoScroll(False) self.fs_sensors_list.setProperty("showDropIndicator", False) - self.content_vertical_layout.setStretch(0, 0) - self.content_vertical_layout.setStretch(1, 1) - self.content_vertical_layout.addWidget( + self.fs_sensors_list.setDefaultDropAction(QtCore.Qt.DropAction.IgnoreAction) + self.fs_sensors_list.setAlternatingRowColors(False) + self.fs_sensors_list.setSelectionMode( + QtWidgets.QAbstractItemView.SelectionMode.NoSelection + ) + self.fs_sensors_list.setSelectionBehavior( + QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems + ) + self.fs_sensors_list.setVerticalScrollMode( + QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel + ) + self.fs_sensors_list.setHorizontalScrollMode( + QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel + ) + QtWidgets.QScroller.grabGesture( self.fs_sensors_list, - 1, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtWidgets.QScroller.ScrollerGestureType.TouchGesture, ) + QtWidgets.QScroller.grabGesture( + self.fs_sensors_list, + QtWidgets.QScroller.ScrollerGestureType.LeftMouseButtonGesture, + ) + self.sensor_buttons_layout = QtWidgets.QVBoxLayout() + self.sensor_buttons_layout.setContentsMargins(15, 20, 20, 5) + self.sensor_buttons_layout.addWidget(self.fs_sensors_list, 0) + self.sensor_buttons_frame.setLayout(self.sensor_buttons_layout) - font = QtGui.QFont() - font.setPointSize(25) - - - self.item = QtWidgets.QListWidgetItem() - self.item.setSizeHint(QtCore.QSize(self.fs_sensors_list.width(),self.fs_sensors_list.height())) - - - self.label = QtWidgets.QLabel("No sensors found") - self.label.setFont(font) - self.label.setStyleSheet("color: gray;") - self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.label.hide() - - self.fs_sensors_list.addItem(self.item) - self.fs_sensors_list.setItemWidget(self.item,self.label) - + self.main_content_layout.addWidget(self.sensor_buttons_frame, 0) + self.infobox_frame = BlocksCustomFrame() + self.infobox_frame.setMinimumSize(QtCore.QSize(250, 300)) + self.infobox_frame.setMaximumSize(QtCore.QSize(450, 500)) - self.content_vertical_layout.addSpacing(5) - self.setLayout(self.content_vertical_layout) - self.retranslateUi() + self.info_box_layout = QtWidgets.QVBoxLayout() + self.info_box_layout.setContentsMargins(0, 0, 0, 0) - def retranslateUi(self): - _translate = QtCore.QCoreApplication.translate - self.setWindowTitle(_translate("filament_sensors_page", "Form")) - self.fs_page_title.setText( - _translate("filament_sensors_page", "Filament Sensors") - ) - self.fs_back_button.setProperty( - "button_type", _translate("filament_sensors_page", "icon") + font = QtGui.QFont() + font.setFamily(font_family) + font.setPointSize(20) + self.version_box = QtWidgets.QHBoxLayout() + self.no_update_placeholder = QtWidgets.QLabel(self) + self.no_update_placeholder.setMinimumSize(QtCore.QSize(200, 60)) + self.no_update_placeholder.setMaximumSize(QtCore.QSize(300, 60)) + self.no_update_placeholder.setFont(font) + self.no_update_placeholder.setPalette(palette) + self.no_update_placeholder.setSizePolicy(sizePolicy) + self.no_update_placeholder.setText("No Sensors Available") + self.no_update_placeholder.setWordWrap(True) + self.no_update_placeholder.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.info_box_layout.addWidget( + self.no_update_placeholder, 0, QtCore.Qt.AlignmentFlag.AlignBottom ) + self.pixmap = QtGui.QPixmap(":/ui/media/btn_icons/info.svg") + self.no_update_placeholder.hide() + self.infobox_frame.setLayout(self.info_box_layout) + self.main_content_layout.addWidget(self.infobox_frame, 1) + self.update_page_content_layout.addLayout(self.main_content_layout, 1) + self.setLayout(self.update_page_content_layout) diff --git a/BlocksScreen/lib/panels/widgets/slider_selector_page.py b/BlocksScreen/lib/panels/widgets/slider_selector_page.py index 02f9006a..2f6c89f7 100644 --- a/BlocksScreen/lib/panels/widgets/slider_selector_page.py +++ b/BlocksScreen/lib/panels/widgets/slider_selector_page.py @@ -27,33 +27,34 @@ def __init__(self, parent) -> None: self.decrease_button_icon = QtGui.QPixmap( ":/arrow_icons/media/btn_icons/left_arrow.svg" ) - self.background = QtGui.QPixmap( - ":/ui/background/media/1st_background.png" - ) + self.background = QtGui.QPixmap(":/ui/background/media/1st_background.png") self.setStyleSheet( "#SliderPage{background-image: url(:/background/media/1st_background.png);}\n" ) self.setObjectName("SliderPage") - self.setupUI() + self._setupUI() self.back_button.clicked.connect(self.request_back.emit) self.back_button.clicked.connect(self.value_selected.disconnect) - self.slider.valueChanged.connect(self.on_slider_value_change) + self.slider.sliderReleased.connect(self.on_slider_value_change) self.increase_button.pressed.connect( - lambda: ( - self.slider.setSliderPosition(self.slider.sliderPosition() + 5) - ) + lambda: { + (self.slider.setSliderPosition(self.slider.sliderPosition() + 5)), + self.on_slider_value_change(), + } ) self.decrease_button.pressed.connect( - lambda: ( - self.slider.setSliderPosition(self.slider.sliderPosition() - 5) - ) + lambda: { + ( + self.slider.setSliderPosition(self.slider.sliderPosition() - 5), + self.on_slider_value_change(), + ) + } ) self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - @QtCore.pyqtSlot(int, name="valueChanged") - def on_slider_value_change(self, value) -> None: + def on_slider_value_change(self) -> None: """Handles slider position changes""" - self.value_selected.emit(self.name, value) + self.value_selected.emit(self.name, self.slider.value()) def set_name(self, name: str) -> None: """Sets the header name for the page""" @@ -64,9 +65,11 @@ def set_slider_position(self, value: int) -> None: self.slider.setSliderPosition(int(value)) def set_slider_minimum(self, value: int) -> None: + """Set slider minimum value""" self.slider.setMinimum(value) def set_slider_maximum(self, value: int) -> None: + """Set slider maximum value""" self.slider.setMaximum(value) def paintEvent(self, a0: QtGui.QPaintEvent) -> None: @@ -78,21 +81,9 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.drawPixmap(self.rect(), self.background, self.rect()) self.current_value_label.setText(str(self.slider.value()) + " " + "%") self.object_name_label.setText(str(self.name)) - # if "speed" in self.name.lower(): - # # REFACTOR: Change this, so it's not hardcoded to be with objects named "speed. " - # # Range should increase however if a flag is set, if the maximum is above 100 - # # then increase the range of the slider after it is set to the maximum - # if ( - # self.slider.maximum() <= self.max_value - # and self.slider.sliderPosition() + 10 >= self.slider.maximum() - # ): - # self.slider.setMaximum(int(int(self.slider.maximum()) + 100)) - # elif self.slider.maximum() <= 100: - # self.slider.setMaximum(100) - painter.end() - def setupUI(self) -> None: + def _setupUI(self) -> None: """Setup the components for the widget""" self.setMinimumSize(QtCore.QSize(700, 410)) self.setMaximumSize(QtCore.QSize(720, 420)) @@ -130,19 +121,14 @@ def setupUI(self) -> None: self.object_name_label = QtWidgets.QLabel(self) self.object_name_label.setFont(font) self.object_name_label.setPalette(palette) - self.object_name_label.setMinimumSize(QtCore.QSize(self.width(), 60)) - self.object_name_label.setMaximumSize( - QtCore.QSize(self.width() - 60, 60) - ) + self.object_name_label.setMinimumSize(QtCore.QSize(self.width(), 80)) + self.object_name_label.setMaximumSize(QtCore.QSize(self.width() - 60, 80)) self.object_name_label.setAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter ) self.back_button = IconButton(self) - self.back_button.setPixmap( - QtGui.QPixmap(":ui/media/btn_icons/back.svg") - ) + self.back_button.setPixmap(QtGui.QPixmap(":ui/media/btn_icons/back.svg")) self.back_button.has_text = False self.back_button.setMinimumSize(QtCore.QSize(60, 60)) self.back_button.setMaximumSize(QtCore.QSize(60, 60)) @@ -160,12 +146,9 @@ def setupUI(self) -> None: self.current_value_label.setFont(font) self.current_value_label.setPalette(palette) self.current_value_label.setMinimumSize(QtCore.QSize(self.width(), 80)) - self.current_value_label.setMaximumSize( - QtCore.QSize(self.width(), 300) - ) + self.current_value_label.setMaximumSize(QtCore.QSize(self.width(), 300)) self.current_value_label.setAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter ) self.middle_content_layout.addWidget( self.current_value_label, @@ -189,8 +172,7 @@ def setupUI(self) -> None: self.slider_layout.addWidget( self.slider, 0, - QtCore.Qt.AlignmentFlag.AlignVCenter - | QtCore.Qt.AlignmentFlag.AlignHCenter, + QtCore.Qt.AlignmentFlag.AlignVCenter | QtCore.Qt.AlignmentFlag.AlignHCenter, ) self.increase_button = IconButton(self) self.increase_button.setProperty( diff --git a/BlocksScreen/lib/panels/widgets/troubleshootPage.py b/BlocksScreen/lib/panels/widgets/troubleshootPage.py index 6a9b4886..0c327ac7 100644 --- a/BlocksScreen/lib/panels/widgets/troubleshootPage.py +++ b/BlocksScreen/lib/panels/widgets/troubleshootPage.py @@ -2,9 +2,11 @@ from lib.utils.icon_button import IconButton + class TroubleshootPage(QtWidgets.QDialog): def __init__( - self, parent: QtWidgets.QWidget, + self, + parent: QtWidgets.QWidget, ) -> None: super().__init__(parent) self.setStyleSheet( @@ -16,23 +18,23 @@ def __init__( """ ) self.setWindowFlags( - QtCore.Qt.WindowType.Popup - | QtCore.Qt.WindowType.FramelessWindowHint + QtCore.Qt.WindowType.Popup | QtCore.Qt.WindowType.FramelessWindowHint + ) + self._setupUI() + self.label_4.setText( + "For more information check our website \n www.blockstec.com \n or \nsupport@blockstec.com" ) - self.setupUI() - self.label_4.setText("For more information check our website \n www.blockstec.com \n or \nsupport@blockstec.com") - - self.repaint() - def geometry_calc(self) -> None: + def _geometry_calc(self) -> None: + """Calculate widget position relative to the screen""" app_instance = QtWidgets.QApplication.instance() main_window = app_instance.activeWindow() if app_instance else None if main_window is None and app_instance: for widget in app_instance.allWidgets(): if isinstance(widget, QtWidgets.QMainWindow): main_window = widget - if main_window: + if main_window: x = main_window.geometry().x() y = main_window.geometry().y() width = main_window.width() @@ -40,21 +42,32 @@ def geometry_calc(self) -> None: self.setGeometry(x, y, width, height) def show(self) -> None: - self.geometry_calc() + """Re-implemented method, widget show""" + self._geometry_calc() self.repaint() return super().show() - def setupUI(self) -> None: + def _setupUI(self) -> None: self.setObjectName("troubleshoot_page") self.verticalLayout = QtWidgets.QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.leds_slider_header_layout_2 = QtWidgets.QHBoxLayout() self.leds_slider_header_layout_2.setObjectName("leds_slider_header_layout_2") - spacerItem18 = QtWidgets.QSpacerItem(60, 60, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + spacerItem18 = QtWidgets.QSpacerItem( + 60, + 60, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Minimum, + ) self.leds_slider_header_layout_2.addItem(spacerItem18) - spacerItem19 = QtWidgets.QSpacerItem(181, 60, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + spacerItem19 = QtWidgets.QSpacerItem( + 181, + 60, + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Minimum, + ) self.leds_slider_header_layout_2.addItem(spacerItem19) - self.tb_tittle_label = QtWidgets.QLabel("Troubleshoot",parent=self) + self.tb_tittle_label = QtWidgets.QLabel("Troubleshoot", parent=self) self.tb_tittle_label.setMinimumSize(QtCore.QSize(0, 60)) self.tb_tittle_label.setMaximumSize(QtCore.QSize(16777215, 60)) font = QtGui.QFont() @@ -65,10 +78,18 @@ def setupUI(self) -> None: self.tb_tittle_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.tb_tittle_label.setObjectName("tb_tittle_label") self.leds_slider_header_layout_2.addWidget(self.tb_tittle_label) - spacerItem20 = QtWidgets.QSpacerItem(0, 60, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + spacerItem20 = QtWidgets.QSpacerItem( + 0, + 60, + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Minimum, + ) self.leds_slider_header_layout_2.addItem(spacerItem20) self.tb_back_btn = IconButton(parent=self) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.MinimumExpanding, + QtWidgets.QSizePolicy.Policy.MinimumExpanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tb_back_btn.sizePolicy().hasHeightForWidth()) @@ -88,7 +109,9 @@ def setupUI(self) -> None: self.tb_back_btn.setStyleSheet("") self.tb_back_btn.setAutoDefault(False) self.tb_back_btn.setFlat(True) - self.tb_back_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) + self.tb_back_btn.setProperty( + "icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/back.svg") + ) self.tb_back_btn.setObjectName("tb_back_btn") self.leds_slider_header_layout_2.addWidget(self.tb_back_btn) self.verticalLayout.addLayout(self.leds_slider_header_layout_2) @@ -96,8 +119,11 @@ def setupUI(self) -> None: self.horizontalLayout.setObjectName("horizontalLayout") self.verticalLayout_10 = QtWidgets.QVBoxLayout() self.verticalLayout_10.setObjectName("verticalLayout_10") - self.label_4 = QtWidgets.QLabel("idk whar to type this",parent=self) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) + self.label_4 = QtWidgets.QLabel("idk whar to type this", parent=self) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Expanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) @@ -110,11 +136,4 @@ def setupUI(self) -> None: self.label_4.setObjectName("label_4") self.verticalLayout_10.addWidget(self.label_4) self.horizontalLayout.addLayout(self.verticalLayout_10) - # self.widget = QtWidgets.QWidget(parent=self) - # self.widget.setMinimumSize(QtCore.QSize(300, 300)) - # self.widget.setMaximumSize(QtCore.QSize(300, 300)) - # self.widget.setAutoFillBackground(True) - # self.widget.setStyleSheet("color:white") - # self.widget.setObjectName("widget") - # self.horizontalLayout.addWidget(self.widget) - self.verticalLayout.addLayout(self.horizontalLayout) \ No newline at end of file + self.verticalLayout.addLayout(self.horizontalLayout) diff --git a/BlocksScreen/lib/panels/widgets/tunePage.py b/BlocksScreen/lib/panels/widgets/tunePage.py index ece1e770..09b5868d 100644 --- a/BlocksScreen/lib/panels/widgets/tunePage.py +++ b/BlocksScreen/lib/panels/widgets/tunePage.py @@ -1,10 +1,11 @@ +import re import typing +from helper_methods import normalize from lib.utils.blocks_button import BlocksCustomButton from lib.utils.display_button import DisplayButton from lib.utils.icon_button import IconButton from PyQt6 import QtCore, QtGui, QtWidgets -from helper_methods import normalize class TuneWidget(QtWidgets.QWidget): @@ -12,8 +13,8 @@ class TuneWidget(QtWidgets.QWidget): name="request_back_page" ) - request_sensorsPage: typing.ClassVar[QtCore.pyqtSignal] = ( - QtCore.pyqtSignal(name="request_sensorsPage") + request_sensorsPage: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + name="request_sensorsPage" ) request_bbpPage: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( name="request_bbpPage" @@ -40,14 +41,12 @@ class TuneWidget(QtWidgets.QWidget): def __init__(self, parent) -> None: super().__init__(parent) self.setObjectName("tune_page") - self.setupUI() + self._setupUI() self.sensors_menu_btn.clicked.connect(self.request_sensorsPage.emit) self.tune_babystep_menu_btn.clicked.connect(self.request_bbpPage.emit) self.tune_back_btn.clicked.connect(self.request_back) self.bed_display.clicked.connect( - lambda: self.request_numpad[ - str, int, "PyQt_PyObject", int, int - ].emit( + lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit( "Bed", int(round(self.bed_target)), self.on_numpad_change, @@ -56,9 +55,7 @@ def __init__(self, parent) -> None: ) ) self.extruder_display.clicked.connect( - lambda: self.request_numpad[ - str, int, "PyQt_PyObject", int, int - ].emit( + lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit( "Extruder", int(round(self.extruder_target)), self.on_numpad_change, @@ -67,9 +64,7 @@ def __init__(self, parent) -> None: ) ) self.speed_display.clicked.connect( - lambda: self.request_sliderPage[ - str, int, "PyQt_PyObject", int, int - ].emit( + lambda: self.request_sliderPage[str, int, "PyQt_PyObject", int, int].emit( "Speed", int(self.speed_factor_override * 100), self.on_slider_change, @@ -80,16 +75,16 @@ def __init__(self, parent) -> None: @QtCore.pyqtSlot(str, int, name="on_numpad_change") def on_numpad_change(self, name: str, new_value: int) -> None: + """Handle numpad value inserted""" if "bed" in name.lower(): name = "heater_bed" elif "extruder" in name.lower(): name = "extruder" - self.run_gcode.emit( - f"SET_HEATER_TEMPERATURE HEATER={name} TARGET={new_value}" - ) + self.run_gcode.emit(f"SET_HEATER_TEMPERATURE HEATER={name} TARGET={new_value}") @QtCore.pyqtSlot(str, int, name="on_slider_change") def on_slider_change(self, name: str, new_value: int) -> None: + """Handle slider page value inserted""" if "speed" in name.lower(): self.speed_factor_override = new_value / 100 self.run_gcode.emit(f"M220 S{new_value}") @@ -100,6 +95,7 @@ def on_slider_change(self, name: str, new_value: int) -> None: f"M106 S{int(round((normalize(float(new_value / 100), 0.0, 1.0, 0, 255))))}" ) # [0, 255] Range else: + name = name.replace(" ", "_") self.run_gcode.emit( f"SET_FAN_SPEED FAN={name} SPEED={float(new_value / 100.00)}" ) # [0.0, 1.0] Range @@ -116,18 +112,28 @@ def on_fan_object_update( field (str): field name new_value (int | float): New value for field name """ + fields = name.split() + first_field = fields[0] + second_field = fields[1] if len(fields) > 1 else None if "speed" in field: - if not self.tune_display_buttons.get(name, None): + if not self.tune_display_buttons.get(name, None) and first_field in ( + "fan", + "fan_generic", + ): + pattern_blower = r"(?:^|_)(?:blower|auxiliary)(?:_|$)" + pattern_exhaust = r"(?:^|_)exhaust(?:_|$)" + _new_display_button = self.create_display_button(name) _new_display_button.setParent(self) - if "blower" in name: - _new_display_button.icon_pixmap = QtGui.QPixmap( - ":/temperature_related/media/btn_icons/blower.svg" - ) - else: - _new_display_button.icon_pixmap = QtGui.QPixmap( - ":/temperature_related/media/btn_icons/fan.svg" - ) + _new_display_button.icon_pixmap = self.path.get("fan") + if second_field: + name = second_field.replace("_", " ") + second_field = second_field.lower() + if re.search(pattern_blower, second_field): + _new_display_button.icon_pixmap = self.path.get("blower") + elif re.search(pattern_exhaust, second_field): + _new_display_button.icon_pixmap = self.path.get("fan_cage") + self.tune_display_buttons.update( { name: { @@ -136,36 +142,23 @@ def on_fan_object_update( } } ) - if name in ("fan", "fan_generic"): - _new_display_button.clicked.connect( - lambda: self.request_sliderPage[ - str, int, "PyQt_PyObject", int, int - ].emit( - str(name), - int( - round( - self.tune_display_buttons.get(name).get( # type:ignore - "speed", 0 - ) - ) - ), - self.on_slider_change, - 0, - 100, - ) + _new_display_button.clicked.connect( + lambda: self.request_sliderPage[ + str, int, "PyQt_PyObject", int, int + ].emit( + str(name), + int(round(self.tune_display_buttons.get(name).get("speed", 0))), + self.on_slider_change, + 0, + 100, ) - else: - _new_display_button.setDisabled(True) - self.tune_display_vertical_child_layout_2.addWidget( - _new_display_button ) + self.tune_display_vertical_child_layout_2.addWidget(_new_display_button) _display_button = self.tune_display_buttons.get(name) if not _display_button: return _display_button.update({"speed": int(round(new_value * 100))}) - _display_button.get("display_button").setText( - f"{new_value * 100:.0f}%" - ) + _display_button.get("display_button").setText(f"{new_value * 100:.0f}%") def create_display_button(self, name: str) -> DisplayButton: """Create and return a DisplayButton @@ -187,11 +180,10 @@ def create_display_button(self, name: str) -> DisplayButton: @QtCore.pyqtSlot(str, float, name="on_gcode_move_update") def on_gcode_move_update(self, field: str, value: float) -> None: + """Handle gcode move update""" if "speed_factor" in field: self.speed_factor_override = value - self.speed_display.setText( - str(f"{int(self.speed_factor_override * 100)}%") - ) + self.speed_display.setText(str(f"{int(self.speed_factor_override * 100)}%")) @QtCore.pyqtSlot(str, str, float, name="on_extruder_update") def on_extruder_temperature_change( @@ -226,12 +218,11 @@ def on_heater_bed_temperature_change( self.bed_target = int(new_value) def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" if self.isVisible(): - self.speed_display.setText( - str(f"{int(self.speed_factor_override * 100)}%") - ) + self.speed_display.setText(str(f"{int(self.speed_factor_override * 100)}%")) - def setupUI(self) -> None: + def _setupUI(self) -> None: sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding, @@ -259,9 +250,7 @@ def setupUI(self) -> None: self.tune_title_label.setFont(font) self.tune_title_label.setPalette(palette) - self.tune_title_label.setLayoutDirection( - QtCore.Qt.LayoutDirection.RightToLeft - ) + self.tune_title_label.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) self.tune_title_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.tune_title_label.setObjectName("tune_title_label") self.tune_header.addWidget( @@ -273,9 +262,7 @@ def setupUI(self) -> None: self.tune_back_btn.setMinimumSize(QtCore.QSize(60, 60)) self.tune_back_btn.setMaximumSize(QtCore.QSize(60, 60)) self.tune_back_btn.setFlat(True) - self.tune_back_btn.setPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/back.svg") - ) + self.tune_back_btn.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) self.tune_header.addWidget( self.tune_back_btn, 0, @@ -332,9 +319,7 @@ def setupUI(self) -> None: self.tune_change_filament_btn.setAutoDefault(False) self.tune_change_filament_btn.setFlat(True) self.tune_change_filament_btn.setPixmap( - QtGui.QPixmap( - ":/filament_related/media/btn_icons/change_filament.svg" - ) + QtGui.QPixmap(":/filament_related/media/btn_icons/change_filament.svg") ) self.tune_change_filament_btn.setObjectName("tune_change_filament_btn") self.tune_menu_buttons.addWidget( @@ -355,46 +340,34 @@ def setupUI(self) -> None: self.sensors_menu_btn.setContextMenuPolicy( QtCore.Qt.ContextMenuPolicy.NoContextMenu ) - self.sensors_menu_btn.setLayoutDirection( - QtCore.Qt.LayoutDirection.LeftToRight - ) + self.sensors_menu_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) self.sensors_menu_btn.setAutoDefault(False) self.sensors_menu_btn.setFlat(True) self.sensors_menu_btn.setPixmap( - QtGui.QPixmap( - ":/filament_related/media/btn_icons/filament_sensor.svg" - ) + QtGui.QPixmap(":/filament_related/media/btn_icons/filament_sensor.svg") ) self.sensors_menu_btn.setObjectName("sensors_menu_btn") self.tune_menu_buttons.addWidget(self.sensors_menu_btn) self.tune_content.addLayout(self.tune_menu_buttons, 0) self.tune_display_horizontal_parent_layout = QtWidgets.QHBoxLayout() - self.tune_display_horizontal_parent_layout.setContentsMargins( - 2, 0, 2, 2 - ) + self.tune_display_horizontal_parent_layout.setContentsMargins(2, 0, 2, 2) self.tune_display_horizontal_parent_layout.setObjectName( "tune_display_horizontal_parent_layout" ) self.tune_display_vertical_child_layout_1 = QtWidgets.QVBoxLayout() - self.tune_display_vertical_child_layout_1.setContentsMargins( - 2, 0, 2, 2 - ) + self.tune_display_vertical_child_layout_1.setContentsMargins(2, 0, 2, 2) self.tune_display_vertical_child_layout_1.setSpacing(5) self.tune_display_vertical_child_layout_1.setObjectName( "tune_display_vertical_parent_layout" ) self.tune_display_vertical_child_layout_2 = QtWidgets.QVBoxLayout() - self.tune_display_vertical_child_layout_2.setContentsMargins( - 2, 0, 2, 2 - ) + self.tune_display_vertical_child_layout_2.setContentsMargins(2, 0, 2, 2) self.tune_display_vertical_child_layout_2.setSpacing(5) self.tune_display_vertical_child_layout_2.setObjectName( "tune_display_vertical_parent_layout_2" ) self.tune_display_vertical_child_layout_3 = QtWidgets.QVBoxLayout() - self.tune_display_vertical_child_layout_3.setContentsMargins( - 2, 0, 2, 2 - ) + self.tune_display_vertical_child_layout_3.setContentsMargins(2, 0, 2, 2) self.tune_display_vertical_child_layout_3.setSpacing(5) self.tune_display_vertical_child_layout_3.setObjectName( "tune_display_vertical_parent_layout_3" @@ -409,15 +382,11 @@ def setupUI(self) -> None: self.tune_display_vertical_child_layout_3 ) self.tune_display_horizontal_parent_layout.setSpacing(0) - self.tune_display_horizontal_parent_layout.setContentsMargins( - 0, 0, 0, 0 - ) + self.tune_display_horizontal_parent_layout.setContentsMargins(0, 0, 0, 0) self.bed_display = DisplayButton(parent=self) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth( - self.bed_display.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.bed_display.sizePolicy().hasHeightForWidth()) self.bed_display.setSizePolicy(sizePolicy) self.bed_display.setMinimumSize(QtCore.QSize(150, 60)) self.bed_display.setMaximumSize(QtCore.QSize(150, 60)) @@ -426,9 +395,7 @@ def setupUI(self) -> None: self.bed_display.setText("") self.bed_display.setFlat(True) self.bed_display.setPixmap( - QtGui.QPixmap( - ":/temperature_related/media/btn_icons/temperature_plate.svg" - ) + QtGui.QPixmap(":/temperature_related/media/btn_icons/temperature_plate.svg") ) self.bed_display.setObjectName("bed_display") self.tune_display_vertical_child_layout_1.addWidget(self.bed_display) @@ -443,14 +410,10 @@ def setupUI(self) -> None: self.extruder_display.setText("") self.extruder_display.setFlat(True) self.extruder_display.setPixmap( - QtGui.QPixmap( - ":/temperature_related/media/btn_icons/temperature.svg" - ) + QtGui.QPixmap(":/temperature_related/media/btn_icons/temperature.svg") ) self.extruder_display.setObjectName("extruder_display") - self.tune_display_vertical_child_layout_1.addWidget( - self.extruder_display - ) + self.tune_display_vertical_child_layout_1.addWidget(self.extruder_display) self.speed_display = DisplayButton(parent=self) self.speed_display.setFont(font) sizePolicy.setHeightForWidth( @@ -466,9 +429,7 @@ def setupUI(self) -> None: ) self.speed_display.setObjectName("speed_display") self.tune_display_vertical_child_layout_3.addWidget(self.speed_display) - self.tune_content.addLayout( - self.tune_display_horizontal_parent_layout, 1 - ) + self.tune_content.addLayout(self.tune_display_horizontal_parent_layout, 1) self.tune_display_horizontal_parent_layout.setStretch(0, 0) self.tune_display_horizontal_parent_layout.setStretch(1, 0) self.tune_display_horizontal_parent_layout.setStretch(2, 1) @@ -478,9 +439,15 @@ def setupUI(self) -> None: self.tune_content.setContentsMargins(2, 0, 2, 0) self.setLayout(self.tune_content) self.setContentsMargins(2, 2, 2, 2) - self.retranslateUI() - def retranslateUI(self): + self.path = { + "fan_cage": QtGui.QPixmap(":/fan_related/media/btn_icons/fan_cage.svg"), + "blower": QtGui.QPixmap(":/fan_related/media/btn_icons/blower.svg"), + "fan": QtGui.QPixmap(":/fan_related/media/btn_icons/fan.svg"), + } + self._retranslateUI() + + def _retranslateUI(self): _translate = QtCore.QCoreApplication.translate self.setWindowTitle(_translate("printStackedWidget", "StackedWidget")) self.tune_title_label.setText(_translate("printStackedWidget", "Tune")) diff --git a/BlocksScreen/lib/panels/widgets/updatePage.py b/BlocksScreen/lib/panels/widgets/updatePage.py index 7b725514..4857f92e 100644 --- a/BlocksScreen/lib/panels/widgets/updatePage.py +++ b/BlocksScreen/lib/panels/widgets/updatePage.py @@ -1,7 +1,8 @@ import copy import typing -from lib.panels.widgets.loadPage import LoadScreen +from lib.panels.widgets.basePopup import BasePopup +from lib.panels.widgets.loadWidget import LoadingOverlayWidget from lib.utils.blocks_button import BlocksCustomButton from lib.utils.blocks_frame import BlocksCustomFrame from lib.utils.icon_button import IconButton @@ -61,8 +62,11 @@ def __init__(self, parent=None) -> None: self.cli_tracking = {} self.selected_item: ListItem | None = None self.ongoing_update: bool = False - - self.load_popup: LoadScreen = LoadScreen(self) + self.load_popup = BasePopup(self, floating=False, dialog=False) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.load_popup.add_widget(self.loadwidget) self.repeated_request_status = QtCore.QTimer() self.repeated_request_status.setInterval(2000) # every 2 seconds self.model = EntryListModel() @@ -78,6 +82,9 @@ def __init__(self, parent=None) -> None: self.repeated_request_status.timeout.connect( lambda: self.request_update_status.emit(False) ) + self.reload_btn.clicked.connect(self.on_request_reload) + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_StyledBackground, True) + self.show_loading(True) def handle_update_end(self) -> None: """Handles update end signal @@ -86,17 +93,25 @@ def handle_update_end(self) -> None: if self.load_popup.isVisible(): self.load_popup.close() self.repeated_request_status.stop() - self.request_refresh_update.emit() + self.on_request_reload() self.build_model_list() def handle_ongoing_update(self) -> None: """Handled ongoing update signal, calls loading page (blocks user interaction) """ - self.load_popup.set_status_message("Updating...") + self.loadwidget.set_status_message("Updating...") self.load_popup.show() self.repeated_request_status.start(2000) + def on_request_reload(self, service: str | None = None) -> None: + """Handles reload button click, requests update status refresh""" + self.show_loading(True) + if service: + self.request_refresh_update.emit([service]) + else: + self.request_refresh_update.emit() + def reset_view_model(self) -> None: """Clears items from ListView (Resets `QAbstractListModel` by clearing entries) @@ -112,6 +127,7 @@ def deleteLater(self) -> None: def showEvent(self, event: QtGui.QShowEvent | None) -> None: """Re-add clients to update list""" self.build_model_list() + return super().showEvent(event) def build_model_list(self) -> None: @@ -151,10 +167,10 @@ def on_update_clicked(self) -> None: else: self.request_update_client.emit(cli_name) - self.load_popup.set_status_message(f"Updating {cli_name}") + self.loadwidget.set_status_message(f"Updating {cli_name}") else: self.request_recover_repo[str, bool].emit(cli_name, True) - self.load_popup.set_status_message(f"Recovering {cli_name}") + self.loadwidget.set_status_message(f"Recovering {cli_name}") self.load_popup.show() self.request_update_status.emit(False) @@ -165,13 +181,15 @@ def on_item_clicked(self, item: ListItem) -> None: """ if not item: return + self.show_loading(False) cli_data = self.cli_tracking.get(item.text, {}) if not cli_data: self.version_tracking_info.setText("Missing, Cannot Update") self.selected_item = copy.copy(item) if item.text == "system": - self.remote_version_title.hide() - self.remote_version_tracking.hide() + self.no_update_placeholder.hide() + self.remote_version_title.setText("") + self.remote_version_tracking.setText("") updatable_packages = cli_data.get("package_count", 0) if updatable_packages == 0: self.version_title.hide() @@ -190,6 +208,7 @@ def on_item_clicked(self, item: ListItem) -> None: self.remote_version_tracking.hide() self.remote_version_title.show() self.remote_version_tracking.show() + self.remote_version_title.setText("Remote Version: ") self.remote_version_tracking.setText(_remote_version) _curr_version = cli_data.get("version", None) if not _curr_version: @@ -224,6 +243,17 @@ def on_item_clicked(self, item: ListItem) -> None: self.no_update_placeholder.hide() self.action_btn.show() + def show_loading(self, loading: bool = False) -> None: + """Show or hide loading overlay""" + self.loadwidget2.setVisible(loading) + self.update_buttons_list_widget.setVisible(not loading) + self.remote_version_title.setVisible(not loading) + self.remote_version_tracking.setVisible(not loading) + self.version_tracking_info.setVisible(not loading) + self.version_title.setVisible(not loading) + self.action_btn.setVisible(not loading) + self.no_update_placeholder.setVisible(not loading) + @QtCore.pyqtSlot(dict, name="handle-update-message") def handle_update_message(self, message: dict) -> None: """Handle receiving current state of each item update. @@ -239,11 +269,12 @@ def handle_update_message(self, message: dict) -> None: elif self.ongoing_update or complete: self.ongoing_update = False self.update_end.emit() - + cli_version_info = message.get("version_info", None) if not cli_version_info: return self.cli_tracking = cli_version_info + self.build_model_list() # Signal that updates exist (Used to render red dots) _update_avail = any( value @@ -281,14 +312,27 @@ def _setupUI(self) -> None: sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) self.setSizePolicy(sizePolicy) - self.setMinimumSize(QtCore.QSize(710, 400)) - self.setMaximumSize(QtCore.QSize(720, 420)) + self.setObjectName("updatePage") + self.setStyleSheet( + """#updatePage { + background-image: url(:/background/media/1st_background.png); + }""" + ) self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) self.update_page_content_layout = QtWidgets.QVBoxLayout() - self.update_page_content_layout.setContentsMargins(15, 15, 2, 2) + self.update_page_content_layout.setContentsMargins(15, 15, 15, 15) self.header_content_layout = QtWidgets.QHBoxLayout() self.header_content_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) + self.reload_btn = IconButton(self) + self.reload_btn.setMinimumSize(QtCore.QSize(60, 60)) + self.reload_btn.setMaximumSize(QtCore.QSize(60, 60)) + self.reload_btn.setFlat(True) + self.reload_btn.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/refresh.svg")) + self.header_content_layout.addWidget( + self.reload_btn + ) # alignment=QtCore.Qt.AlignmentFlag.AlignCenter) + self.header_title = QtWidgets.QLabel(self) self.header_title.setMinimumSize(QtCore.QSize(100, 60)) self.header_title.setMaximumSize(QtCore.QSize(16777215, 60)) @@ -300,16 +344,24 @@ def _setupUI(self) -> None: self.header_title.setFont(font) self.header_title.setPalette(palette) self.header_title.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) - self.header_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.header_title.setObjectName("header-title") self.header_title.setText("Update Manager") - self.header_content_layout.addWidget(self.header_title, 0) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Expanding, + ) + self.header_title.setSizePolicy(sizePolicy) + self.header_content_layout.addWidget( + self.header_title, alignment=QtCore.Qt.AlignmentFlag.AlignCenter + ) self.update_back_btn = IconButton(self) self.update_back_btn.setMinimumSize(QtCore.QSize(60, 60)) self.update_back_btn.setMaximumSize(QtCore.QSize(60, 60)) self.update_back_btn.setFlat(True) self.update_back_btn.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) - self.header_content_layout.addWidget(self.update_back_btn, 0) + self.header_content_layout.addWidget( + self.update_back_btn + ) # alignment=QtCore.Qt.AlignmentFlag.AlignCenter) self.update_page_content_layout.addLayout(self.header_content_layout, 0) self.main_content_layout = QtWidgets.QHBoxLayout() @@ -472,17 +524,22 @@ def _setupUI(self) -> None: QtWidgets.QScroller.ScrollerGestureType.LeftMouseButtonGesture, ) self.update_buttons_layout = QtWidgets.QVBoxLayout() - self.update_buttons_layout.setContentsMargins(15, 20, 20, 5) + self.update_buttons_layout.setContentsMargins(10, 10, 10, 10) self.update_buttons_layout.addWidget(self.update_buttons_list_widget, 0) + self.update_buttons_list_widget.hide() + self.loadwidget2 = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.loadwidget2.setMinimumSize(self.update_buttons_frame.size()) + self.update_buttons_layout.addWidget(self.loadwidget2, 1) self.update_buttons_frame.setLayout(self.update_buttons_layout) self.main_content_layout.addWidget(self.update_buttons_frame, 0) self.infobox_frame = BlocksCustomFrame() - self.infobox_frame.setMinimumSize(QtCore.QSize(250, 300)) - self.info_box_layout = QtWidgets.QVBoxLayout() - self.info_box_layout.setContentsMargins(10, 0, 10, 0) + self.info_box_layout.setContentsMargins(10, 10, 10, 10) + self.infobox_frame.setLayout(self.info_box_layout) font = QtGui.QFont() font.setFamily(font_family) @@ -556,7 +613,7 @@ def _setupUI(self) -> None: self.action_btn = BlocksCustomButton() self.action_btn.setMinimumSize(QtCore.QSize(200, 60)) - self.action_btn.setMaximumSize(QtCore.QSize(250, 60)) + self.action_btn.setMaximumSize(QtCore.QSize(300, 60)) font.setPointSize(20) self.action_btn.setFont(font) self.action_btn.setPalette(palette) @@ -566,7 +623,7 @@ def _setupUI(self) -> None: QtGui.QPixmap(":/system/media/btn_icons/update-software-icon.svg") ) self.button_box.addWidget( - self.action_btn, 0, QtCore.Qt.AlignmentFlag.AlignHCenter + self.action_btn, 0, QtCore.Qt.AlignmentFlag.AlignCenter ) self.no_update_placeholder = QtWidgets.QLabel(self) self.no_update_placeholder.setMinimumSize(QtCore.QSize(200, 60)) @@ -579,16 +636,13 @@ def _setupUI(self) -> None: self.no_update_placeholder.setWordWrap(True) self.no_update_placeholder.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.info_box_layout.addWidget( - self.no_update_placeholder, 0, QtCore.Qt.AlignmentFlag.AlignBottom + self.no_update_placeholder, 0, QtCore.Qt.AlignmentFlag.AlignCenter ) - self.no_update_placeholder.hide() - self.info_box_layout.addLayout( self.button_box, 0, ) - self.infobox_frame.setLayout(self.info_box_layout) self.main_content_layout.addWidget(self.infobox_frame, 1) self.update_page_content_layout.addLayout(self.main_content_layout, 1) self.setLayout(self.update_page_content_layout) diff --git a/BlocksScreen/lib/printer.py b/BlocksScreen/lib/printer.py index c3df0d8a..52706fd5 100644 --- a/BlocksScreen/lib/printer.py +++ b/BlocksScreen/lib/printer.py @@ -464,7 +464,7 @@ def _heater_fan_object_updated(self, value: dict, fan_name: str = "") -> None: # Associated with a heater, on when heater is active # Parameters same as a normal fan _names = ["heater_fan", fan_name] - object_name = " ".join(_names) + # object_name = " ".join(_names) def _idle_timeout_object_updated( self, value: dict, name: str = "idle_timeout" diff --git a/BlocksScreen/lib/qrcode_gen.py b/BlocksScreen/lib/qrcode_gen.py index 3720f261..1901cef1 100644 --- a/BlocksScreen/lib/qrcode_gen.py +++ b/BlocksScreen/lib/qrcode_gen.py @@ -4,9 +4,7 @@ BLOCKS_URL = "https://blockstec.com" RF50_MANUAL_PAGE = "https://blockstec.com/RF50" RF50_PRODUCT_PAGE = "https://blockstec.com/rf-50" -RF50_DATASHEET_PAGE = ( - "https://www.blockstec.com/assets/downloads/rf50_datasheet.pdf" -) +RF50_DATASHEET_PAGE = "https://www.blockstec.com/assets/downloads/rf50_datasheet.pdf" RF50_DATASHEET_PAGE = "https://blockstec.com/assets/files/rf50_user_manual.pdf" @@ -28,5 +26,7 @@ def make_qrcode(data) -> ImageQt.ImageQt: def generate_wifi_qrcode( ssid: str, password: str, auth_type: str, hidden: bool = False ) -> ImageQt.ImageQt: - wifi_data = f"WIFI:T:{auth_type};S:{ssid};P:{password};{'H:true;' if hidden else ''};" + wifi_data = ( + f"WIFI:T:{auth_type};S:{ssid};P:{password};{'H:true;' if hidden else ''};" + ) return make_qrcode(wifi_data) diff --git a/BlocksScreen/lib/ui/connectionWindow.ui b/BlocksScreen/lib/ui/connectionWindow.ui index 84558abd..f2bd4899 100644 --- a/BlocksScreen/lib/ui/connectionWindow.ui +++ b/BlocksScreen/lib/ui/connectionWindow.ui @@ -42,12 +42,8 @@ #ConnectivityForm{ - background-image: url(:/background/media/1st_background.png); -} - - - - +background-image: url(:/background/media/1st_background.png); +} @@ -115,551 +111,613 @@ 0 - - - - 623 - 10 - 154 - 80 - - - - - 0 - 0 - - - - - 154 - 80 - - - - - 154 - 80 - - - - - 80 - 80 - - - - - - - - - - - - 13 - - - - true - - - Qt::ClickFocus - - - false - - - - - - Retry - - - - :/system_icons/media/btn_icons/retry_connection.svg:/system_icons/media/btn_icons/retry_connection.svg - - - - 16 - 16 - - - - false - - + + 0 - + 0 - - false - - - false - - - true - - - bottom - - - :/system/media/btn_icons/restart_printer.svg - - - - 255 - 255 - 255 - - - - true - - - - - - 475 - 11 - 154 - 80 - - - - - 0 - 0 - - - - - 154 - 80 - - - - - 154 - 80 - - - - - 80 - 80 - - - - true - - - Qt::NoFocus - - - Qt::NoContextMenu - - - false - - - Wifi Settings - - - - :/system_icons/media/btn_icons/retry_connection.svg:/system_icons/media/btn_icons/retry_connection.svg - - - false - - - false - - - true - - - system_control_btn - - - :/network/media/btn_icons/wifi_config.svg - - - true - - - bottom - - - - - - 315 - 10 - 154 - 80 - - - - - 0 - 0 - - - - - 154 - 80 - - - - - 154 - 80 - - - - - 160 - 80 - - - - BlankCursor - - - true - - - Qt::NoFocus - - - Qt::NoContextMenu - - - false - - - Firmware Restart - - - - :/system_icons/media/btn_icons/firmware_restart.svg:/system_icons/media/btn_icons/firmware_restart.svg - - - false - - - false - - - true + + 5 - - :/system/media/btn_icons/restart_firmware.svg - - - true - - - bottom - - - - 255 - 255 - 255 - - - - - - - 157 - 10 - 154 - 80 - - - - - 0 - 0 - - - - - 154 - 80 - - - - - 154 - 80 - - - - - 80 - 80 - - - - BlankCursor - - - true - - - Qt::NoFocus - - - Qt::NoContextMenu - - - false - - - Reboot - - - - :/system_icons/media/btn_icons/firmware_restart.svg:/system_icons/media/btn_icons/firmware_restart.svg - - - false - - - false - - - true - - - :/system/media/btn_icons/reboot.svg - - - bottom - - - - 255 - 255 - 255 - - - - true - - - - - - 4 - 10 - 154 - 80 - - - - - 0 - 0 - - - - - 154 - 80 - - - - - 154 - 80 - - - - - 160 - 80 - - - - - - - - - 66 - 66 - 66 - - - - - - - 66 - 66 - 66 - - - - - - - 66 - 66 - 66 - - - - - - - - - 66 - 66 - 66 - - - - - - - 66 - 66 - 66 - - - - - - - 66 - 66 - 66 - - - - - - - - - 66 - 66 - 66 - - - - - - - 66 - 66 - 66 - - - - - - - 66 - 66 - 66 - - - - - - - - - false - PreferAntialias - false - - - - BlankCursor - - - true - - - Qt::NoFocus - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - false - - - - - - Restart Klipper - - - - :/system_icons/media/btn_icons/restart_klipper.svg - - - - - 46 - 42 - - - - false - - - false - - - false - - + 0 - + 0 - - false - - - true - - - :/system/media/btn_icons/restart_klipper.svg - - - true - - - bottom - - - - 255 - 255 - 255 - - - + + + + + 0 + 0 + + + + + 100 + 80 + + + + + 100 + 80 + + + + + 160 + 80 + + + + + + + + + 66 + 66 + 66 + + + + + + + 66 + 66 + 66 + + + + + + + 66 + 66 + 66 + + + + + + + + + 66 + 66 + 66 + + + + + + + 66 + 66 + 66 + + + + + + + 66 + 66 + 66 + + + + + + + + + 66 + 66 + 66 + + + + + + + 66 + 66 + 66 + + + + + + + 66 + 66 + 66 + + + + + + + + + false + PreferAntialias + false + + + + BlankCursor + + + true + + + Qt::NoFocus + + + Qt::NoContextMenu + + + Qt::LeftToRight + + + false + + + + + + Restart Klipper + + + + :/system_icons/media/btn_icons/restart_klipper.svg + + + + + 46 + 42 + + + + false + + + false + + + false + + + 0 + + + 0 + + + false + + + true + + + :/system/media/btn_icons/restart_klipper.svg + + + true + + + bottom + + + + 255 + 255 + 255 + + + + + + + + + 0 + 0 + + + + + 100 + 80 + + + + + 100 + 80 + + + + + 80 + 80 + + + + BlankCursor + + + true + + + Qt::NoFocus + + + Qt::NoContextMenu + + + false + + + Reboot + + + + :/system_icons/media/btn_icons/firmware_restart.svg:/system_icons/media/btn_icons/firmware_restart.svg + + + false + + + false + + + true + + + :/system/media/btn_icons/reboot.svg + + + bottom + + + + 255 + 255 + 255 + + + + true + + + + + + + + 0 + 0 + + + + + 100 + 80 + + + + + 100 + 80 + + + + + 160 + 80 + + + + BlankCursor + + + true + + + Qt::NoFocus + + + Qt::NoContextMenu + + + false + + + Firmware Restart + + + + :/system_icons/media/btn_icons/firmware_restart.svg:/system_icons/media/btn_icons/firmware_restart.svg + + + false + + + false + + + true + + + :/system/media/btn_icons/restart_firmware.svg + + + true + + + bottom + + + + 255 + 255 + 255 + + + + + + + + + 0 + 0 + + + + + 100 + 80 + + + + + 100 + 80 + + + + + 80 + 80 + + + + + + + + + + + + 13 + + + + true + + + Qt::ClickFocus + + + false + + + + + + Retry + + + + :/system_icons/media/btn_icons/retry_connection.svg:/system_icons/media/btn_icons/retry_connection.svg + + + + 16 + 16 + + + + false + + + 0 + + + 0 + + + false + + + false + + + true + + + bottom + + + :/system/media/btn_icons/restart_printer.svg + + + + 255 + 255 + 255 + + + + true + + + + + + + + 0 + 0 + + + + + 100 + 80 + + + + + 100 + 80 + + + + + 80 + 80 + + + + BlankCursor + + + true + + + Qt::NoFocus + + + Qt::NoContextMenu + + + false + + + Update page + + + + :/system_icons/media/btn_icons/firmware_restart.svg:/system_icons/media/btn_icons/firmware_restart.svg + + + false + + + false + + + true + + + :/system/media/btn_icons/update-software-icon.svg + + + bottom + + + + 255 + 255 + 255 + + + + true + + + + + + + + 0 + 0 + + + + + 100 + 80 + + + + + 100 + 80 + + + + + 80 + 80 + + + + true + + + Qt::NoFocus + + + Qt::NoContextMenu + + + false + + + Wifi Settings + + + + :/system_icons/media/btn_icons/retry_connection.svg:/system_icons/media/btn_icons/retry_connection.svg + + + false + + + false + + + true + + + system_control_btn + + + :/network/media/btn_icons/wifi_config.svg + + + true + + + bottom + + + + @@ -691,6 +749,9 @@ false + + + QFrame::NoFrame diff --git a/BlocksScreen/lib/ui/connectionWindow_ui.py b/BlocksScreen/lib/ui/connectionWindow_ui.py index 58c5945b..772dc227 100644 --- a/BlocksScreen/lib/ui/connectionWindow_ui.py +++ b/BlocksScreen/lib/ui/connectionWindow_ui.py @@ -1,4 +1,4 @@ -# Form implementation generated from reading ui file 'main/BlocksScreen/BlocksScreen/lib/ui/connectionWindow.ui' +# Form implementation generated from reading ui file '/home/levi/BlocksScreen/BlocksScreen/lib/ui/connectionWindow.ui' # # Created by: PyQt6 UI code generator 6.7.1 # @@ -24,12 +24,8 @@ def setupUi(self, ConnectivityForm): ConnectivityForm.setWindowOpacity(1.0) ConnectivityForm.setAutoFillBackground(False) ConnectivityForm.setStyleSheet("#ConnectivityForm{\n" -" background-image: url(:/background/media/1st_background.png);\n" -"}\n" -"\n" -"\n" -"\n" -"") +"background-image: url(:/background/media/1st_background.png);\n" +"}") ConnectivityForm.setProperty("class", "") self.cw_buttonFrame = BlocksCustomFrame(parent=ConnectivityForm) self.cw_buttonFrame.setGeometry(QtCore.QRect(10, 380, 780, 124)) @@ -53,117 +49,18 @@ def setupUi(self, ConnectivityForm): self.cw_buttonFrame.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) self.cw_buttonFrame.setLineWidth(0) self.cw_buttonFrame.setObjectName("cw_buttonFrame") - self.RetryConnectionButton = IconButton(parent=self.cw_buttonFrame) - self.RetryConnectionButton.setGeometry(QtCore.QRect(623, 10, 154, 80)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.RetryConnectionButton.sizePolicy().hasHeightForWidth()) - self.RetryConnectionButton.setSizePolicy(sizePolicy) - self.RetryConnectionButton.setMinimumSize(QtCore.QSize(154, 80)) - self.RetryConnectionButton.setMaximumSize(QtCore.QSize(154, 80)) - self.RetryConnectionButton.setBaseSize(QtCore.QSize(80, 80)) - palette = QtGui.QPalette() - self.RetryConnectionButton.setPalette(palette) - font = QtGui.QFont() - font.setPointSize(13) - self.RetryConnectionButton.setFont(font) - self.RetryConnectionButton.setTabletTracking(True) - self.RetryConnectionButton.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus) - self.RetryConnectionButton.setAutoFillBackground(False) - self.RetryConnectionButton.setStyleSheet("") - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(":/system_icons/media/btn_icons/retry_connection.svg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) - self.RetryConnectionButton.setIcon(icon) - self.RetryConnectionButton.setIconSize(QtCore.QSize(16, 16)) - self.RetryConnectionButton.setCheckable(False) - self.RetryConnectionButton.setAutoRepeatDelay(0) - self.RetryConnectionButton.setAutoRepeatInterval(0) - self.RetryConnectionButton.setAutoDefault(False) - self.RetryConnectionButton.setDefault(False) - self.RetryConnectionButton.setFlat(True) - self.RetryConnectionButton.setProperty("icon_pixmap", QtGui.QPixmap(":/system/media/btn_icons/restart_printer.svg")) - self.RetryConnectionButton.setProperty("text_color", QtGui.QColor(255, 255, 255)) - self.RetryConnectionButton.setProperty("has_text", True) - self.RetryConnectionButton.setObjectName("RetryConnectionButton") - self.wifi_button = IconButton(parent=self.cw_buttonFrame) - self.wifi_button.setGeometry(QtCore.QRect(475, 11, 154, 80)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.wifi_button.sizePolicy().hasHeightForWidth()) - self.wifi_button.setSizePolicy(sizePolicy) - self.wifi_button.setMinimumSize(QtCore.QSize(154, 80)) - self.wifi_button.setMaximumSize(QtCore.QSize(154, 80)) - self.wifi_button.setBaseSize(QtCore.QSize(80, 80)) - self.wifi_button.setTabletTracking(True) - self.wifi_button.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.wifi_button.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.wifi_button.setAutoFillBackground(False) - self.wifi_button.setIcon(icon) - self.wifi_button.setAutoDefault(False) - self.wifi_button.setDefault(False) - self.wifi_button.setFlat(True) - self.wifi_button.setProperty("icon_pixmap", QtGui.QPixmap(":/network/media/btn_icons/wifi_config.svg")) - self.wifi_button.setProperty("has_text", True) - self.wifi_button.setObjectName("wifi_button") - self.FirmwareRestartButton = IconButton(parent=self.cw_buttonFrame) - self.FirmwareRestartButton.setGeometry(QtCore.QRect(315, 10, 154, 80)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.FirmwareRestartButton.sizePolicy().hasHeightForWidth()) - self.FirmwareRestartButton.setSizePolicy(sizePolicy) - self.FirmwareRestartButton.setMinimumSize(QtCore.QSize(154, 80)) - self.FirmwareRestartButton.setMaximumSize(QtCore.QSize(154, 80)) - self.FirmwareRestartButton.setBaseSize(QtCore.QSize(160, 80)) - self.FirmwareRestartButton.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.BlankCursor)) - self.FirmwareRestartButton.setTabletTracking(True) - self.FirmwareRestartButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.FirmwareRestartButton.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.FirmwareRestartButton.setAutoFillBackground(False) - icon1 = QtGui.QIcon() - icon1.addPixmap(QtGui.QPixmap(":/system_icons/media/btn_icons/firmware_restart.svg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) - self.FirmwareRestartButton.setIcon(icon1) - self.FirmwareRestartButton.setAutoDefault(False) - self.FirmwareRestartButton.setDefault(False) - self.FirmwareRestartButton.setFlat(True) - self.FirmwareRestartButton.setProperty("icon_pixmap", QtGui.QPixmap(":/system/media/btn_icons/restart_firmware.svg")) - self.FirmwareRestartButton.setProperty("has_text", True) - self.FirmwareRestartButton.setProperty("text_color", QtGui.QColor(255, 255, 255)) - self.FirmwareRestartButton.setObjectName("FirmwareRestartButton") - self.RebootSystemButton = IconButton(parent=self.cw_buttonFrame) - self.RebootSystemButton.setGeometry(QtCore.QRect(157, 10, 154, 80)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.RebootSystemButton.sizePolicy().hasHeightForWidth()) - self.RebootSystemButton.setSizePolicy(sizePolicy) - self.RebootSystemButton.setMinimumSize(QtCore.QSize(154, 80)) - self.RebootSystemButton.setMaximumSize(QtCore.QSize(154, 80)) - self.RebootSystemButton.setBaseSize(QtCore.QSize(80, 80)) - self.RebootSystemButton.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.BlankCursor)) - self.RebootSystemButton.setTabletTracking(True) - self.RebootSystemButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.RebootSystemButton.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.RebootSystemButton.setAutoFillBackground(False) - self.RebootSystemButton.setIcon(icon1) - self.RebootSystemButton.setAutoDefault(False) - self.RebootSystemButton.setDefault(False) - self.RebootSystemButton.setFlat(True) - self.RebootSystemButton.setProperty("icon_pixmap", QtGui.QPixmap(":/system/media/btn_icons/reboot.svg")) - self.RebootSystemButton.setProperty("text_color", QtGui.QColor(255, 255, 255)) - self.RebootSystemButton.setProperty("has_text", True) - self.RebootSystemButton.setObjectName("RebootSystemButton") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.cw_buttonFrame) + self.horizontalLayout.setContentsMargins(0, 5, 0, 0) + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") self.RestartKlipperButton = IconButton(parent=self.cw_buttonFrame) - self.RestartKlipperButton.setGeometry(QtCore.QRect(4, 10, 154, 80)) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.RestartKlipperButton.sizePolicy().hasHeightForWidth()) self.RestartKlipperButton.setSizePolicy(sizePolicy) - self.RestartKlipperButton.setMinimumSize(QtCore.QSize(154, 80)) - self.RestartKlipperButton.setMaximumSize(QtCore.QSize(154, 80)) + self.RestartKlipperButton.setMinimumSize(QtCore.QSize(100, 80)) + self.RestartKlipperButton.setMaximumSize(QtCore.QSize(100, 80)) self.RestartKlipperButton.setBaseSize(QtCore.QSize(160, 80)) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(66, 66, 66)) @@ -206,9 +103,9 @@ def setupUi(self, ConnectivityForm): self.RestartKlipperButton.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) self.RestartKlipperButton.setAutoFillBackground(False) self.RestartKlipperButton.setStyleSheet("") - icon2 = QtGui.QIcon() - icon2.addPixmap(QtGui.QPixmap(":/system_icons/media/btn_icons/restart_klipper.svg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.On) - self.RestartKlipperButton.setIcon(icon2) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(":/system_icons/media/btn_icons/restart_klipper.svg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.On) + self.RestartKlipperButton.setIcon(icon) self.RestartKlipperButton.setIconSize(QtCore.QSize(46, 42)) self.RestartKlipperButton.setCheckable(False) self.RestartKlipperButton.setAutoRepeat(False) @@ -221,6 +118,132 @@ def setupUi(self, ConnectivityForm): self.RestartKlipperButton.setProperty("has_text", True) self.RestartKlipperButton.setProperty("text_color", QtGui.QColor(255, 255, 255)) self.RestartKlipperButton.setObjectName("RestartKlipperButton") + self.horizontalLayout.addWidget(self.RestartKlipperButton, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) + self.RebootSystemButton = IconButton(parent=self.cw_buttonFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.RebootSystemButton.sizePolicy().hasHeightForWidth()) + self.RebootSystemButton.setSizePolicy(sizePolicy) + self.RebootSystemButton.setMinimumSize(QtCore.QSize(100, 80)) + self.RebootSystemButton.setMaximumSize(QtCore.QSize(100, 80)) + self.RebootSystemButton.setBaseSize(QtCore.QSize(80, 80)) + self.RebootSystemButton.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.BlankCursor)) + self.RebootSystemButton.setTabletTracking(True) + self.RebootSystemButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.RebootSystemButton.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.RebootSystemButton.setAutoFillBackground(False) + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap(":/system_icons/media/btn_icons/firmware_restart.svg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) + self.RebootSystemButton.setIcon(icon1) + self.RebootSystemButton.setAutoDefault(False) + self.RebootSystemButton.setDefault(False) + self.RebootSystemButton.setFlat(True) + self.RebootSystemButton.setProperty("icon_pixmap", QtGui.QPixmap(":/system/media/btn_icons/reboot.svg")) + self.RebootSystemButton.setProperty("text_color", QtGui.QColor(255, 255, 255)) + self.RebootSystemButton.setProperty("has_text", True) + self.RebootSystemButton.setObjectName("RebootSystemButton") + self.horizontalLayout.addWidget(self.RebootSystemButton, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) + self.FirmwareRestartButton = IconButton(parent=self.cw_buttonFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.FirmwareRestartButton.sizePolicy().hasHeightForWidth()) + self.FirmwareRestartButton.setSizePolicy(sizePolicy) + self.FirmwareRestartButton.setMinimumSize(QtCore.QSize(100, 80)) + self.FirmwareRestartButton.setMaximumSize(QtCore.QSize(100, 80)) + self.FirmwareRestartButton.setBaseSize(QtCore.QSize(160, 80)) + self.FirmwareRestartButton.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.BlankCursor)) + self.FirmwareRestartButton.setTabletTracking(True) + self.FirmwareRestartButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.FirmwareRestartButton.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.FirmwareRestartButton.setAutoFillBackground(False) + self.FirmwareRestartButton.setIcon(icon1) + self.FirmwareRestartButton.setAutoDefault(False) + self.FirmwareRestartButton.setDefault(False) + self.FirmwareRestartButton.setFlat(True) + self.FirmwareRestartButton.setProperty("icon_pixmap", QtGui.QPixmap(":/system/media/btn_icons/restart_firmware.svg")) + self.FirmwareRestartButton.setProperty("has_text", True) + self.FirmwareRestartButton.setProperty("text_color", QtGui.QColor(255, 255, 255)) + self.FirmwareRestartButton.setObjectName("FirmwareRestartButton") + self.horizontalLayout.addWidget(self.FirmwareRestartButton, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) + self.RetryConnectionButton = IconButton(parent=self.cw_buttonFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.RetryConnectionButton.sizePolicy().hasHeightForWidth()) + self.RetryConnectionButton.setSizePolicy(sizePolicy) + self.RetryConnectionButton.setMinimumSize(QtCore.QSize(100, 80)) + self.RetryConnectionButton.setMaximumSize(QtCore.QSize(100, 80)) + self.RetryConnectionButton.setBaseSize(QtCore.QSize(80, 80)) + palette = QtGui.QPalette() + self.RetryConnectionButton.setPalette(palette) + font = QtGui.QFont() + font.setPointSize(13) + self.RetryConnectionButton.setFont(font) + self.RetryConnectionButton.setTabletTracking(True) + self.RetryConnectionButton.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus) + self.RetryConnectionButton.setAutoFillBackground(False) + self.RetryConnectionButton.setStyleSheet("") + icon2 = QtGui.QIcon() + icon2.addPixmap(QtGui.QPixmap(":/system_icons/media/btn_icons/retry_connection.svg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) + self.RetryConnectionButton.setIcon(icon2) + self.RetryConnectionButton.setIconSize(QtCore.QSize(16, 16)) + self.RetryConnectionButton.setCheckable(False) + self.RetryConnectionButton.setAutoRepeatDelay(0) + self.RetryConnectionButton.setAutoRepeatInterval(0) + self.RetryConnectionButton.setAutoDefault(False) + self.RetryConnectionButton.setDefault(False) + self.RetryConnectionButton.setFlat(True) + self.RetryConnectionButton.setProperty("icon_pixmap", QtGui.QPixmap(":/system/media/btn_icons/restart_printer.svg")) + self.RetryConnectionButton.setProperty("text_color", QtGui.QColor(255, 255, 255)) + self.RetryConnectionButton.setProperty("has_text", True) + self.RetryConnectionButton.setObjectName("RetryConnectionButton") + self.horizontalLayout.addWidget(self.RetryConnectionButton, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) + self.updatepageButton = IconButton(parent=self.cw_buttonFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.updatepageButton.sizePolicy().hasHeightForWidth()) + self.updatepageButton.setSizePolicy(sizePolicy) + self.updatepageButton.setMinimumSize(QtCore.QSize(100, 80)) + self.updatepageButton.setMaximumSize(QtCore.QSize(100, 80)) + self.updatepageButton.setBaseSize(QtCore.QSize(80, 80)) + self.updatepageButton.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.BlankCursor)) + self.updatepageButton.setTabletTracking(True) + self.updatepageButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.updatepageButton.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.updatepageButton.setAutoFillBackground(False) + self.updatepageButton.setIcon(icon1) + self.updatepageButton.setAutoDefault(False) + self.updatepageButton.setDefault(False) + self.updatepageButton.setFlat(True) + self.updatepageButton.setProperty("icon_pixmap", QtGui.QPixmap(":/system/media/btn_icons/update-software-icon.svg")) + self.updatepageButton.setProperty("text_color", QtGui.QColor(255, 255, 255)) + self.updatepageButton.setProperty("has_text", True) + self.updatepageButton.setObjectName("updatepageButton") + self.horizontalLayout.addWidget(self.updatepageButton, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) + self.wifi_button = IconButton(parent=self.cw_buttonFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.wifi_button.sizePolicy().hasHeightForWidth()) + self.wifi_button.setSizePolicy(sizePolicy) + self.wifi_button.setMinimumSize(QtCore.QSize(100, 80)) + self.wifi_button.setMaximumSize(QtCore.QSize(100, 80)) + self.wifi_button.setBaseSize(QtCore.QSize(80, 80)) + self.wifi_button.setTabletTracking(True) + self.wifi_button.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.wifi_button.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.wifi_button.setAutoFillBackground(False) + self.wifi_button.setIcon(icon2) + self.wifi_button.setAutoDefault(False) + self.wifi_button.setDefault(False) + self.wifi_button.setFlat(True) + self.wifi_button.setProperty("icon_pixmap", QtGui.QPixmap(":/network/media/btn_icons/wifi_config.svg")) + self.wifi_button.setProperty("has_text", True) + self.wifi_button.setObjectName("wifi_button") + self.horizontalLayout.addWidget(self.wifi_button, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) self.cw_Frame = QtWidgets.QFrame(parent=ConnectivityForm) self.cw_Frame.setGeometry(QtCore.QRect(0, 0, 800, 380)) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) @@ -231,6 +254,7 @@ def setupUi(self, ConnectivityForm): self.cw_Frame.setMinimumSize(QtCore.QSize(800, 380)) self.cw_Frame.setMaximumSize(QtCore.QSize(800, 380)) self.cw_Frame.setAutoFillBackground(False) + self.cw_Frame.setStyleSheet("") self.cw_Frame.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) self.cw_Frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) self.cw_Frame.setObjectName("cw_Frame") @@ -297,16 +321,18 @@ def setupUi(self, ConnectivityForm): def retranslateUi(self, ConnectivityForm): _translate = QtCore.QCoreApplication.translate ConnectivityForm.setWindowTitle(_translate("ConnectivityForm", "Form")) + self.RestartKlipperButton.setText(_translate("ConnectivityForm", "Restart Klipper")) + self.RestartKlipperButton.setProperty("text_formatting", _translate("ConnectivityForm", "bottom")) + self.RebootSystemButton.setText(_translate("ConnectivityForm", "Reboot")) + self.RebootSystemButton.setProperty("text_formatting", _translate("ConnectivityForm", "bottom")) + self.FirmwareRestartButton.setText(_translate("ConnectivityForm", "Firmware Restart")) + self.FirmwareRestartButton.setProperty("text_formatting", _translate("ConnectivityForm", "bottom")) self.RetryConnectionButton.setText(_translate("ConnectivityForm", "Retry ")) self.RetryConnectionButton.setProperty("text_formatting", _translate("ConnectivityForm", "bottom")) + self.updatepageButton.setText(_translate("ConnectivityForm", "Update page")) + self.updatepageButton.setProperty("text_formatting", _translate("ConnectivityForm", "bottom")) self.wifi_button.setText(_translate("ConnectivityForm", "Wifi Settings")) self.wifi_button.setProperty("class", _translate("ConnectivityForm", "system_control_btn")) self.wifi_button.setProperty("text_formatting", _translate("ConnectivityForm", "bottom")) - self.FirmwareRestartButton.setText(_translate("ConnectivityForm", "Firmware Restart")) - self.FirmwareRestartButton.setProperty("text_formatting", _translate("ConnectivityForm", "bottom")) - self.RebootSystemButton.setText(_translate("ConnectivityForm", "Reboot")) - self.RebootSystemButton.setProperty("text_formatting", _translate("ConnectivityForm", "bottom")) - self.RestartKlipperButton.setText(_translate("ConnectivityForm", "Restart Klipper")) - self.RestartKlipperButton.setProperty("text_formatting", _translate("ConnectivityForm", "bottom")) from lib.utils.blocks_frame import BlocksCustomFrame from lib.utils.icon_button import IconButton diff --git a/BlocksScreen/lib/ui/controlStackedWidget.ui b/BlocksScreen/lib/ui/controlStackedWidget.ui index 949bf57e..92475345 100644 --- a/BlocksScreen/lib/ui/controlStackedWidget.ui +++ b/BlocksScreen/lib/ui/controlStackedWidget.ui @@ -32,7 +32,7 @@ StackedWidget - 0 + 7 @@ -102,8 +102,8 @@ - - + + 0 @@ -146,8 +146,7 @@ - Motion -Control + Z-Tilt false @@ -159,12 +158,12 @@ Control menu_btn - :/motion/media/btn_icons/axis_maintenance.svg + :/z_levelling/media/btn_icons/bed_levelling.svg - - + + 0 @@ -207,8 +206,8 @@ Control - Nozzle -Calibration + Temp. +Control false @@ -220,12 +219,12 @@ Calibration menu_btn - :/z_levelling/media/btn_icons/bed_levelling.svg + :/temperature_related/media/btn_icons/temperature.svg - - + + 0 @@ -268,8 +267,8 @@ Calibration - Temp. -Control + Nozzle +Calibration false @@ -281,12 +280,12 @@ Control menu_btn - :/temperature_related/media/btn_icons/temperature.svg + :/z_levelling/media/btn_icons/bed_levelling.svg - - + + 0 @@ -329,7 +328,8 @@ Control - Z-Tilt + Motion +Control false @@ -341,12 +341,12 @@ Control menu_btn - :/z_levelling/media/btn_icons/bed_levelling.svg + :/motion/media/btn_icons/axis_maintenance.svg - + 0 @@ -389,8 +389,7 @@ Control - Swap -Print Core + Fans false @@ -402,12 +401,12 @@ Print Core menu_btn - :/extruder_related/media/btn_icons/switch_print_core.svg + :/temperature_related/media/btn_icons/fan.svg - + 0 @@ -426,6 +425,45 @@ Print Core 80 + + + Momcake + 19 + false + PreferAntialias + + + + false + + + true + + + Qt::NoContextMenu + + + Qt::LeftToRight + + + + + + Swap +Print Core + + + false + + + true + + + menu_btn + + + :/extruder_related/media/btn_icons/switch_print_core.svg + @@ -5545,6 +5583,166 @@ Home + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 24 + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 60 + 20 + + + + + + + + + 0 + 0 + + + + + Momcake + 24 + + + + background: transparent; color: white; + + + Fans + + + Qt::AlignCenter + + + title_text + + + + + + + + 0 + 0 + + + + + 60 + 60 + + + + + 60 + 60 + + + + + Momcake + 20 + false + PreferAntialias + + + + false + + + true + + + Qt::NoContextMenu + + + Qt::LeftToRight + + + + + + Back + + + false + + + true + + + menu_btn + + + icon + + + :/ui/media/btn_icons/back.svg + + + + + + + + + Qt::Vertical + + + + 20 + 111 + + + + + + + + + + + Qt::Vertical + + + + 20 + 111 + + + + + + @@ -5557,6 +5755,11 @@ Home QPushButton
lib.utils.icon_button
+ + GroupButton + QPushButton +
lib.utils.group_button
+
DisplayButton QPushButton @@ -5567,11 +5770,6 @@ Home QLabel
lib.utils.blocks_label
- - GroupButton - QPushButton -
lib.utils.group_button
-
@@ -5583,9 +5781,9 @@ Home + + - - diff --git a/BlocksScreen/lib/ui/controlStackedWidget_ui.py b/BlocksScreen/lib/ui/controlStackedWidget_ui.py index c4d67b04..d76bb627 100644 --- a/BlocksScreen/lib/ui/controlStackedWidget_ui.py +++ b/BlocksScreen/lib/ui/controlStackedWidget_ui.py @@ -1,4 +1,4 @@ -# Form implementation generated from reading ui file '/home/levi/main/BlocksScreen/BlocksScreen/lib/ui/controlStackedWidget.ui' +# Form implementation generated from reading ui file '/home/levi/BlocksScreen/BlocksScreen/lib/ui/controlStackedWidget.ui' # # Created by: PyQt6 UI code generator 6.7.1 # @@ -54,30 +54,54 @@ def setupUi(self, controlStackedWidget): self.verticalLayout.addLayout(self.cp_header_layout) self.cp_content_layout = QtWidgets.QGridLayout() self.cp_content_layout.setObjectName("cp_content_layout") - self.cp_motion_btn = BlocksCustomButton(parent=self.control_page) + self.cp_z_tilt_btn = BlocksCustomButton(parent=self.control_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cp_motion_btn.sizePolicy().hasHeightForWidth()) - self.cp_motion_btn.setSizePolicy(sizePolicy) - self.cp_motion_btn.setMinimumSize(QtCore.QSize(10, 80)) - self.cp_motion_btn.setMaximumSize(QtCore.QSize(250, 80)) + sizePolicy.setHeightForWidth(self.cp_z_tilt_btn.sizePolicy().hasHeightForWidth()) + self.cp_z_tilt_btn.setSizePolicy(sizePolicy) + self.cp_z_tilt_btn.setMinimumSize(QtCore.QSize(10, 80)) + self.cp_z_tilt_btn.setMaximumSize(QtCore.QSize(250, 80)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(19) font.setItalic(False) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.cp_motion_btn.setFont(font) - self.cp_motion_btn.setMouseTracking(False) - self.cp_motion_btn.setTabletTracking(True) - self.cp_motion_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.cp_motion_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.cp_motion_btn.setStyleSheet("") - self.cp_motion_btn.setAutoDefault(False) - self.cp_motion_btn.setFlat(True) - self.cp_motion_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/motion/media/btn_icons/axis_maintenance.svg")) - self.cp_motion_btn.setObjectName("cp_motion_btn") - self.cp_content_layout.addWidget(self.cp_motion_btn, 0, 0, 1, 1) + self.cp_z_tilt_btn.setFont(font) + self.cp_z_tilt_btn.setMouseTracking(False) + self.cp_z_tilt_btn.setTabletTracking(True) + self.cp_z_tilt_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.cp_z_tilt_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.cp_z_tilt_btn.setStyleSheet("") + self.cp_z_tilt_btn.setAutoDefault(False) + self.cp_z_tilt_btn.setFlat(True) + self.cp_z_tilt_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/z_levelling/media/btn_icons/bed_levelling.svg")) + self.cp_z_tilt_btn.setObjectName("cp_z_tilt_btn") + self.cp_content_layout.addWidget(self.cp_z_tilt_btn, 1, 1, 1, 1) + self.cp_temperature_btn = BlocksCustomButton(parent=self.control_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cp_temperature_btn.sizePolicy().hasHeightForWidth()) + self.cp_temperature_btn.setSizePolicy(sizePolicy) + self.cp_temperature_btn.setMinimumSize(QtCore.QSize(10, 80)) + self.cp_temperature_btn.setMaximumSize(QtCore.QSize(250, 80)) + font = QtGui.QFont() + font.setFamily("Momcake") + font.setPointSize(19) + font.setItalic(False) + font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) + self.cp_temperature_btn.setFont(font) + self.cp_temperature_btn.setMouseTracking(False) + self.cp_temperature_btn.setTabletTracking(True) + self.cp_temperature_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.cp_temperature_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.cp_temperature_btn.setStyleSheet("") + self.cp_temperature_btn.setAutoDefault(False) + self.cp_temperature_btn.setFlat(True) + self.cp_temperature_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/temperature_related/media/btn_icons/temperature.svg")) + self.cp_temperature_btn.setObjectName("cp_temperature_btn") + self.cp_content_layout.addWidget(self.cp_temperature_btn, 0, 1, 1, 1) self.cp_nozzles_calibration_btn = BlocksCustomButton(parent=self.control_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -102,54 +126,54 @@ def setupUi(self, controlStackedWidget): self.cp_nozzles_calibration_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/z_levelling/media/btn_icons/bed_levelling.svg")) self.cp_nozzles_calibration_btn.setObjectName("cp_nozzles_calibration_btn") self.cp_content_layout.addWidget(self.cp_nozzles_calibration_btn, 1, 0, 1, 1) - self.cp_temperature_btn = BlocksCustomButton(parent=self.control_page) + self.cp_motion_btn = BlocksCustomButton(parent=self.control_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cp_temperature_btn.sizePolicy().hasHeightForWidth()) - self.cp_temperature_btn.setSizePolicy(sizePolicy) - self.cp_temperature_btn.setMinimumSize(QtCore.QSize(10, 80)) - self.cp_temperature_btn.setMaximumSize(QtCore.QSize(250, 80)) + sizePolicy.setHeightForWidth(self.cp_motion_btn.sizePolicy().hasHeightForWidth()) + self.cp_motion_btn.setSizePolicy(sizePolicy) + self.cp_motion_btn.setMinimumSize(QtCore.QSize(10, 80)) + self.cp_motion_btn.setMaximumSize(QtCore.QSize(250, 80)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(19) font.setItalic(False) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.cp_temperature_btn.setFont(font) - self.cp_temperature_btn.setMouseTracking(False) - self.cp_temperature_btn.setTabletTracking(True) - self.cp_temperature_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.cp_temperature_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.cp_temperature_btn.setStyleSheet("") - self.cp_temperature_btn.setAutoDefault(False) - self.cp_temperature_btn.setFlat(True) - self.cp_temperature_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/temperature_related/media/btn_icons/temperature.svg")) - self.cp_temperature_btn.setObjectName("cp_temperature_btn") - self.cp_content_layout.addWidget(self.cp_temperature_btn, 0, 1, 1, 1) - self.cp_z_tilt_btn = BlocksCustomButton(parent=self.control_page) + self.cp_motion_btn.setFont(font) + self.cp_motion_btn.setMouseTracking(False) + self.cp_motion_btn.setTabletTracking(True) + self.cp_motion_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.cp_motion_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.cp_motion_btn.setStyleSheet("") + self.cp_motion_btn.setAutoDefault(False) + self.cp_motion_btn.setFlat(True) + self.cp_motion_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/motion/media/btn_icons/axis_maintenance.svg")) + self.cp_motion_btn.setObjectName("cp_motion_btn") + self.cp_content_layout.addWidget(self.cp_motion_btn, 0, 0, 1, 1) + self.cp_fans_btn = BlocksCustomButton(parent=self.control_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cp_z_tilt_btn.sizePolicy().hasHeightForWidth()) - self.cp_z_tilt_btn.setSizePolicy(sizePolicy) - self.cp_z_tilt_btn.setMinimumSize(QtCore.QSize(10, 80)) - self.cp_z_tilt_btn.setMaximumSize(QtCore.QSize(250, 80)) + sizePolicy.setHeightForWidth(self.cp_fans_btn.sizePolicy().hasHeightForWidth()) + self.cp_fans_btn.setSizePolicy(sizePolicy) + self.cp_fans_btn.setMinimumSize(QtCore.QSize(10, 80)) + self.cp_fans_btn.setMaximumSize(QtCore.QSize(250, 80)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(19) font.setItalic(False) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.cp_z_tilt_btn.setFont(font) - self.cp_z_tilt_btn.setMouseTracking(False) - self.cp_z_tilt_btn.setTabletTracking(True) - self.cp_z_tilt_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.cp_z_tilt_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.cp_z_tilt_btn.setStyleSheet("") - self.cp_z_tilt_btn.setAutoDefault(False) - self.cp_z_tilt_btn.setFlat(True) - self.cp_z_tilt_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/z_levelling/media/btn_icons/bed_levelling.svg")) - self.cp_z_tilt_btn.setObjectName("cp_z_tilt_btn") - self.cp_content_layout.addWidget(self.cp_z_tilt_btn, 1, 1, 1, 1) + self.cp_fans_btn.setFont(font) + self.cp_fans_btn.setMouseTracking(False) + self.cp_fans_btn.setTabletTracking(True) + self.cp_fans_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.cp_fans_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.cp_fans_btn.setStyleSheet("") + self.cp_fans_btn.setAutoDefault(False) + self.cp_fans_btn.setFlat(True) + self.cp_fans_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/temperature_related/media/btn_icons/fan.svg")) + self.cp_fans_btn.setObjectName("cp_fans_btn") + self.cp_content_layout.addWidget(self.cp_fans_btn, 2, 0, 1, 1) self.cp_switch_print_core_btn = BlocksCustomButton(parent=self.control_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -173,17 +197,7 @@ def setupUi(self, controlStackedWidget): self.cp_switch_print_core_btn.setFlat(True) self.cp_switch_print_core_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/extruder_related/media/btn_icons/switch_print_core.svg")) self.cp_switch_print_core_btn.setObjectName("cp_switch_print_core_btn") - self.cp_content_layout.addWidget(self.cp_switch_print_core_btn, 2, 0, 1, 1) - self.blank_2 = QtWidgets.QWidget(parent=self.control_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.blank_2.sizePolicy().hasHeightForWidth()) - self.blank_2.setSizePolicy(sizePolicy) - self.blank_2.setMinimumSize(QtCore.QSize(10, 80)) - self.blank_2.setMaximumSize(QtCore.QSize(250, 80)) - self.blank_2.setObjectName("blank_2") - self.cp_content_layout.addWidget(self.blank_2, 2, 1, 1, 1) + self.cp_content_layout.addWidget(self.cp_switch_print_core_btn, 2, 1, 1, 1) self.verticalLayout.addLayout(self.cp_content_layout) controlStackedWidget.addWidget(self.control_page) self.motion_page = QtWidgets.QWidget() @@ -439,7 +453,7 @@ def setupUi(self, controlStackedWidget): self.exp_length_content_layout.setContentsMargins(5, 5, 5, 5) self.exp_length_content_layout.setSpacing(5) self.exp_length_content_layout.setObjectName("exp_length_content_layout") - self.extrude_select_length_10_btn = GroupButton(parent=self.layoutWidget) + self.extrude_select_length_10_btn = BlocksCustomCheckButton(parent=self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -507,7 +521,7 @@ def setupUi(self, controlStackedWidget): self.extrude_select_length_group.setObjectName("extrude_select_length_group") self.extrude_select_length_group.addButton(self.extrude_select_length_10_btn) self.exp_length_content_layout.addWidget(self.extrude_select_length_10_btn) - self.extrude_select_length_50_btn = GroupButton(parent=self.layoutWidget) + self.extrude_select_length_50_btn = BlocksCustomCheckButton(parent=self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -572,7 +586,7 @@ def setupUi(self, controlStackedWidget): self.extrude_select_length_50_btn.setObjectName("extrude_select_length_50_btn") self.extrude_select_length_group.addButton(self.extrude_select_length_50_btn) self.exp_length_content_layout.addWidget(self.extrude_select_length_50_btn) - self.extrude_select_length_100_btn = GroupButton(parent=self.layoutWidget) + self.extrude_select_length_100_btn = BlocksCustomCheckButton(parent=self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -663,7 +677,7 @@ def setupUi(self, controlStackedWidget): self.exp_feedrate_content_layout.setContentsMargins(5, 5, 5, 5) self.exp_feedrate_content_layout.setSpacing(5) self.exp_feedrate_content_layout.setObjectName("exp_feedrate_content_layout") - self.extrude_select_feedrate_2_btn = GroupButton(parent=self.layoutWidget1) + self.extrude_select_feedrate_2_btn = BlocksCustomCheckButton(parent=self.layoutWidget1) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -731,7 +745,7 @@ def setupUi(self, controlStackedWidget): self.extrude_select_feedrate_group.setObjectName("extrude_select_feedrate_group") self.extrude_select_feedrate_group.addButton(self.extrude_select_feedrate_2_btn) self.exp_feedrate_content_layout.addWidget(self.extrude_select_feedrate_2_btn) - self.extrude_select_feedrate_5_btn = GroupButton(parent=self.layoutWidget1) + self.extrude_select_feedrate_5_btn = BlocksCustomCheckButton(parent=self.layoutWidget1) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -796,7 +810,7 @@ def setupUi(self, controlStackedWidget): self.extrude_select_feedrate_5_btn.setObjectName("extrude_select_feedrate_5_btn") self.extrude_select_feedrate_group.addButton(self.extrude_select_feedrate_5_btn) self.exp_feedrate_content_layout.addWidget(self.extrude_select_feedrate_5_btn) - self.extrude_select_feedrate_10_btn = GroupButton(parent=self.layoutWidget1) + self.extrude_select_feedrate_10_btn = BlocksCustomCheckButton(parent=self.layoutWidget1) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -1182,7 +1196,7 @@ def setupUi(self, controlStackedWidget): self.mva_home_all_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/motion/media/btn_icons/home_all.svg")) self.mva_home_all_btn.setObjectName("mva_home_all_btn") self.mva_home_axis_layout.addWidget(self.mva_home_all_btn, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.mva_select_speed_25_btn = GroupButton(parent=self.move_axis_page) + self.mva_select_speed_25_btn = BlocksCustomCheckButton(parent=self.move_axis_page) self.mva_select_speed_25_btn.setGeometry(QtCore.QRect(96, 240, 100, 100)) self.mva_select_speed_25_btn.setMinimumSize(QtCore.QSize(60, 60)) self.mva_select_speed_25_btn.setMaximumSize(QtCore.QSize(100, 100)) @@ -1225,7 +1239,7 @@ def setupUi(self, controlStackedWidget): self.axis_select_speed_group = QtWidgets.QButtonGroup(controlStackedWidget) self.axis_select_speed_group.setObjectName("axis_select_speed_group") self.axis_select_speed_group.addButton(self.mva_select_speed_25_btn) - self.mva_select_speed_50_btn = GroupButton(parent=self.move_axis_page) + self.mva_select_speed_50_btn = BlocksCustomCheckButton(parent=self.move_axis_page) self.mva_select_speed_50_btn.setGeometry(QtCore.QRect(205, 240, 100, 100)) self.mva_select_speed_50_btn.setMinimumSize(QtCore.QSize(60, 60)) self.mva_select_speed_50_btn.setMaximumSize(QtCore.QSize(100, 100)) @@ -1265,7 +1279,7 @@ def setupUi(self, controlStackedWidget): self.mva_select_speed_50_btn.setFlat(False) self.mva_select_speed_50_btn.setObjectName("mva_select_speed_50_btn") self.axis_select_speed_group.addButton(self.mva_select_speed_50_btn) - self.mva_select_speed_100_btn = GroupButton(parent=self.move_axis_page) + self.mva_select_speed_100_btn = BlocksCustomCheckButton(parent=self.move_axis_page) self.mva_select_speed_100_btn.setGeometry(QtCore.QRect(315, 240, 100, 100)) self.mva_select_speed_100_btn.setMinimumSize(QtCore.QSize(60, 60)) self.mva_select_speed_100_btn.setMaximumSize(QtCore.QSize(100, 100)) @@ -1312,7 +1326,7 @@ def setupUi(self, controlStackedWidget): self.label.setFont(font) self.label.setStyleSheet("color:white") self.label.setObjectName("label") - self.mva_select_length_1_btn = GroupButton(parent=self.move_axis_page) + self.mva_select_length_1_btn = BlocksCustomCheckButton(parent=self.move_axis_page) self.mva_select_length_1_btn.setGeometry(QtCore.QRect(96, 110, 100, 100)) self.mva_select_length_1_btn.setMinimumSize(QtCore.QSize(60, 60)) self.mva_select_length_1_btn.setMaximumSize(QtCore.QSize(100, 100)) @@ -1355,7 +1369,7 @@ def setupUi(self, controlStackedWidget): self.axis_select_length_group = QtWidgets.QButtonGroup(controlStackedWidget) self.axis_select_length_group.setObjectName("axis_select_length_group") self.axis_select_length_group.addButton(self.mva_select_length_1_btn) - self.mva_select_length_10_btn = GroupButton(parent=self.move_axis_page) + self.mva_select_length_10_btn = BlocksCustomCheckButton(parent=self.move_axis_page) self.mva_select_length_10_btn.setGeometry(QtCore.QRect(204, 110, 100, 100)) self.mva_select_length_10_btn.setMinimumSize(QtCore.QSize(60, 60)) self.mva_select_length_10_btn.setMaximumSize(QtCore.QSize(100, 100)) @@ -1395,7 +1409,7 @@ def setupUi(self, controlStackedWidget): self.mva_select_length_10_btn.setFlat(False) self.mva_select_length_10_btn.setObjectName("mva_select_length_10_btn") self.axis_select_length_group.addButton(self.mva_select_length_10_btn) - self.mva_select_length_100_btn = GroupButton(parent=self.move_axis_page) + self.mva_select_length_100_btn = BlocksCustomCheckButton(parent=self.move_axis_page) self.mva_select_length_100_btn.setGeometry(QtCore.QRect(315, 110, 100, 100)) self.mva_select_length_100_btn.setMinimumSize(QtCore.QSize(60, 60)) self.mva_select_length_100_btn.setMaximumSize(QtCore.QSize(100, 100)) @@ -1981,9 +1995,66 @@ def setupUi(self, controlStackedWidget): self.printer_setting_content_layout.setContentsMargins(0, 0, 0, 0) self.printer_setting_content_layout.setObjectName("printer_setting_content_layout") controlStackedWidget.addWidget(self.printer_settings_page) + self.fans_page = QtWidgets.QWidget() + self.fans_page.setObjectName("fans_page") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.fans_page) + self.verticalLayout_5.setObjectName("verticalLayout_5") + spacerItem9 = QtWidgets.QSpacerItem(20, 24, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.verticalLayout_5.addItem(spacerItem9) + self.fans_header_layout = QtWidgets.QHBoxLayout() + self.fans_header_layout.setObjectName("fans_header_layout") + spacerItem10 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.fans_header_layout.addItem(spacerItem10) + self.fans_title_label = QtWidgets.QLabel(parent=self.fans_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fans_title_label.sizePolicy().hasHeightForWidth()) + self.fans_title_label.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Momcake") + font.setPointSize(24) + self.fans_title_label.setFont(font) + self.fans_title_label.setStyleSheet("background: transparent; color: white;") + self.fans_title_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.fans_title_label.setObjectName("fans_title_label") + self.fans_header_layout.addWidget(self.fans_title_label) + self.fans_back_btn = IconButton(parent=self.fans_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fans_back_btn.sizePolicy().hasHeightForWidth()) + self.fans_back_btn.setSizePolicy(sizePolicy) + self.fans_back_btn.setMinimumSize(QtCore.QSize(60, 60)) + self.fans_back_btn.setMaximumSize(QtCore.QSize(60, 60)) + font = QtGui.QFont() + font.setFamily("Momcake") + font.setPointSize(20) + font.setItalic(False) + font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) + self.fans_back_btn.setFont(font) + self.fans_back_btn.setMouseTracking(False) + self.fans_back_btn.setTabletTracking(True) + self.fans_back_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.fans_back_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.fans_back_btn.setStyleSheet("") + self.fans_back_btn.setAutoDefault(False) + self.fans_back_btn.setFlat(True) + self.fans_back_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) + self.fans_back_btn.setObjectName("fans_back_btn") + self.fans_header_layout.addWidget(self.fans_back_btn) + self.verticalLayout_5.addLayout(self.fans_header_layout) + spacerItem11 = QtWidgets.QSpacerItem(20, 111, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + self.verticalLayout_5.addItem(spacerItem11) + self.fans_content_layout = QtWidgets.QHBoxLayout() + self.fans_content_layout.setObjectName("fans_content_layout") + self.verticalLayout_5.addLayout(self.fans_content_layout) + spacerItem12 = QtWidgets.QSpacerItem(20, 111, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + self.verticalLayout_5.addItem(spacerItem12) + controlStackedWidget.addWidget(self.fans_page) self.retranslateUi(controlStackedWidget) - controlStackedWidget.setCurrentIndex(0) + controlStackedWidget.setCurrentIndex(7) QtCore.QMetaObject.connectSlotsByName(controlStackedWidget) def retranslateUi(self, controlStackedWidget): @@ -1991,17 +2062,19 @@ def retranslateUi(self, controlStackedWidget): controlStackedWidget.setWindowTitle(_translate("controlStackedWidget", "StackedWidget")) self.cp_header_title.setText(_translate("controlStackedWidget", "Control")) self.cp_header_title.setProperty("class", _translate("controlStackedWidget", "title_text")) - self.cp_motion_btn.setText(_translate("controlStackedWidget", "Motion\n" + self.cp_z_tilt_btn.setText(_translate("controlStackedWidget", "Z-Tilt")) + self.cp_z_tilt_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) + self.cp_temperature_btn.setText(_translate("controlStackedWidget", "Temp.\n" "Control")) - self.cp_motion_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) + self.cp_temperature_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) self.cp_nozzles_calibration_btn.setText(_translate("controlStackedWidget", "Nozzle\n" "Calibration")) self.cp_nozzles_calibration_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) - self.cp_temperature_btn.setText(_translate("controlStackedWidget", "Temp.\n" + self.cp_motion_btn.setText(_translate("controlStackedWidget", "Motion\n" "Control")) - self.cp_temperature_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) - self.cp_z_tilt_btn.setText(_translate("controlStackedWidget", "Z-Tilt")) - self.cp_z_tilt_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) + self.cp_motion_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) + self.cp_fans_btn.setText(_translate("controlStackedWidget", "Fans")) + self.cp_fans_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) self.cp_switch_print_core_btn.setText(_translate("controlStackedWidget", "Swap\n" "Print Core")) self.cp_switch_print_core_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) @@ -2109,8 +2182,13 @@ def retranslateUi(self, controlStackedWidget): self.printer_settings_back_btn.setText(_translate("controlStackedWidget", "Back")) self.printer_settings_back_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) self.printer_settings_back_btn.setProperty("button_type", _translate("controlStackedWidget", "icon")) + self.fans_title_label.setText(_translate("controlStackedWidget", "Fans")) + self.fans_title_label.setProperty("class", _translate("controlStackedWidget", "title_text")) + self.fans_back_btn.setText(_translate("controlStackedWidget", "Back")) + self.fans_back_btn.setProperty("class", _translate("controlStackedWidget", "menu_btn")) + self.fans_back_btn.setProperty("button_type", _translate("controlStackedWidget", "icon")) from lib.utils.blocks_button import BlocksCustomButton from lib.utils.blocks_label import BlocksLabel from lib.utils.display_button import DisplayButton -from lib.utils.group_button import GroupButton +from lib.utils.check_button import BlocksCustomCheckButton from lib.utils.icon_button import IconButton diff --git a/BlocksScreen/lib/ui/instructionsWindow.ui b/BlocksScreen/lib/ui/instructionsWindow.ui deleted file mode 100644 index 17c4c46f..00000000 --- a/BlocksScreen/lib/ui/instructionsWindow.ui +++ /dev/null @@ -1,377 +0,0 @@ - - - utilitiesStackedWidget - - - - 0 - 0 - 798 - 417 - - - - StackedWidget - - - 1 - - - - - - 300 - 60 - 241 - 61 - - - - - Momcake - 24 - - - - background: transparent; color: white; - - - Switch Print Cores - - - title_text - - - - - true - - - - 200 - 170 - 411 - 91 - - - - - Montserrat - 14 - - - - background: transparent; color: white; - - - Printer Heating, wait to switch - - - - - - 680 - 40 - 101 - 71 - - - - - 10 - 10 - - - - - Momcake - 20 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - Cancel - - - false - - - true - - - menu_btn - - - :/button_borders/media/buttons/btn_part1.svg - - - :/button_borders/media/buttons/btn_part2.svg - - - :/button_borders/media/buttons/btn_part3.svg - - - normal - - - - - - - - 270 - 60 - 271 - 61 - - - - - Momcake - 24 - - - - background: transparent; color: white; - - - ROUTINE CHECK - - - title_text - - - - - true - - - - 200 - 180 - 411 - 91 - - - - - Montserrat - 14 - - - - background: transparent; color: white; - - - Do this - - - - - - 680 - 40 - 101 - 71 - - - - - 10 - 10 - - - - - Momcake - 20 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - Cancel - - - false - - - true - - - menu_btn - - - :/button_borders/media/buttons/btn_part1.svg - - - :/button_borders/media/buttons/btn_part2.svg - - - :/button_borders/media/buttons/btn_part3.svg - - - normal - - - - - - - - 240 - 60 - 271 - 61 - - - - - Momcake - 24 - - - - background: transparent; color: white; - - - AXES MAINTENANCE - - - title_text - - - - - true - - - - 170 - 180 - 411 - 91 - - - - - Montserrat - 14 - - - - background: transparent; color: white; - - - Use oil here - - - - - - 650 - 40 - 101 - 71 - - - - - 10 - 10 - - - - - Momcake - 20 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - Cancel - - - false - - - true - - - menu_btn - - - :/button_borders/media/buttons/btn_part1.svg - - - :/button_borders/media/buttons/btn_part2.svg - - - :/button_borders/media/buttons/btn_part3.svg - - - normal - - - - - - - BlocksCustomButton - QPushButton -
lib.utils.ui
-
-
- - - - - -
diff --git a/BlocksScreen/lib/ui/instructionsWindow_ui.py b/BlocksScreen/lib/ui/instructionsWindow_ui.py deleted file mode 100644 index 1198cb0c..00000000 --- a/BlocksScreen/lib/ui/instructionsWindow_ui.py +++ /dev/null @@ -1,162 +0,0 @@ -# Form implementation generated from reading ui file '/home/bugo/github/Blocks_Screen/BlocksScreen/lib/ui/instructionsWindow.ui' -# -# Created by: PyQt6 UI code generator 6.4.2 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_utilitiesStackedWidget(object): - def setupUi(self, utilitiesStackedWidget): - utilitiesStackedWidget.setObjectName("utilitiesStackedWidget") - utilitiesStackedWidget.resize(798, 417) - self.switch_pc_page = QtWidgets.QWidget() - self.switch_pc_page.setObjectName("switch_pc_page") - self.switch_pc_title_label = QtWidgets.QLabel(parent=self.switch_pc_page) - self.switch_pc_title_label.setGeometry(QtCore.QRect(300, 60, 241, 61)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(24) - self.switch_pc_title_label.setFont(font) - self.switch_pc_title_label.setStyleSheet("background: transparent; color: white;") - self.switch_pc_title_label.setObjectName("switch_pc_title_label") - self.switch_pc_text_label = QtWidgets.QLabel(parent=self.switch_pc_page) - self.switch_pc_text_label.setEnabled(True) - self.switch_pc_text_label.setGeometry(QtCore.QRect(200, 170, 411, 91)) - font = QtGui.QFont() - font.setFamily("Montserrat") - font.setPointSize(14) - self.switch_pc_text_label.setFont(font) - self.switch_pc_text_label.setStyleSheet("background: transparent; color: white;") - self.switch_pc_text_label.setObjectName("switch_pc_text_label") - self.switch_pc_cancel_btn = BlocksCustomButton(parent=self.switch_pc_page) - self.switch_pc_cancel_btn.setGeometry(QtCore.QRect(680, 40, 101, 71)) - self.switch_pc_cancel_btn.setMinimumSize(QtCore.QSize(10, 10)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(20) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.switch_pc_cancel_btn.setFont(font) - self.switch_pc_cancel_btn.setMouseTracking(False) - self.switch_pc_cancel_btn.setTabletTracking(True) - self.switch_pc_cancel_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.switch_pc_cancel_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.switch_pc_cancel_btn.setStyleSheet("") - self.switch_pc_cancel_btn.setAutoDefault(False) - self.switch_pc_cancel_btn.setFlat(True) - self.switch_pc_cancel_btn.setProperty("borderLeftPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part1.svg")) - self.switch_pc_cancel_btn.setProperty("borderCenterPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part2.svg")) - self.switch_pc_cancel_btn.setProperty("borderRightPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part3.svg")) - self.switch_pc_cancel_btn.setObjectName("switch_pc_cancel_btn") - utilitiesStackedWidget.addWidget(self.switch_pc_page) - self.routine_check_page = QtWidgets.QWidget() - self.routine_check_page.setObjectName("routine_check_page") - self.routine_check_calib_title_label = QtWidgets.QLabel(parent=self.routine_check_page) - self.routine_check_calib_title_label.setGeometry(QtCore.QRect(270, 60, 271, 61)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(24) - self.routine_check_calib_title_label.setFont(font) - self.routine_check_calib_title_label.setStyleSheet("background: transparent; color: white;") - self.routine_check_calib_title_label.setObjectName("routine_check_calib_title_label") - self.routine_check_text_label = QtWidgets.QLabel(parent=self.routine_check_page) - self.routine_check_text_label.setEnabled(True) - self.routine_check_text_label.setGeometry(QtCore.QRect(200, 180, 411, 91)) - font = QtGui.QFont() - font.setFamily("Montserrat") - font.setPointSize(14) - self.routine_check_text_label.setFont(font) - self.routine_check_text_label.setStyleSheet("background: transparent; color: white;") - self.routine_check_text_label.setObjectName("routine_check_text_label") - self.routine_check_cancel_btn = BlocksCustomButton(parent=self.routine_check_page) - self.routine_check_cancel_btn.setGeometry(QtCore.QRect(680, 40, 101, 71)) - self.routine_check_cancel_btn.setMinimumSize(QtCore.QSize(10, 10)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(20) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.routine_check_cancel_btn.setFont(font) - self.routine_check_cancel_btn.setMouseTracking(False) - self.routine_check_cancel_btn.setTabletTracking(True) - self.routine_check_cancel_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.routine_check_cancel_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.routine_check_cancel_btn.setStyleSheet("") - self.routine_check_cancel_btn.setAutoDefault(False) - self.routine_check_cancel_btn.setFlat(True) - self.routine_check_cancel_btn.setProperty("borderLeftPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part1.svg")) - self.routine_check_cancel_btn.setProperty("borderCenterPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part2.svg")) - self.routine_check_cancel_btn.setProperty("borderRightPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part3.svg")) - self.routine_check_cancel_btn.setObjectName("routine_check_cancel_btn") - utilitiesStackedWidget.addWidget(self.routine_check_page) - self.axes_maintenance_page = QtWidgets.QWidget() - self.axes_maintenance_page.setObjectName("axes_maintenance_page") - self.axes_maintenance_calib_title_label = QtWidgets.QLabel(parent=self.axes_maintenance_page) - self.axes_maintenance_calib_title_label.setGeometry(QtCore.QRect(240, 60, 271, 61)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(24) - self.axes_maintenance_calib_title_label.setFont(font) - self.axes_maintenance_calib_title_label.setStyleSheet("background: transparent; color: white;") - self.axes_maintenance_calib_title_label.setObjectName("axes_maintenance_calib_title_label") - self.axes_maintenance_text_label = QtWidgets.QLabel(parent=self.axes_maintenance_page) - self.axes_maintenance_text_label.setEnabled(True) - self.axes_maintenance_text_label.setGeometry(QtCore.QRect(170, 180, 411, 91)) - font = QtGui.QFont() - font.setFamily("Montserrat") - font.setPointSize(14) - self.axes_maintenance_text_label.setFont(font) - self.axes_maintenance_text_label.setStyleSheet("background: transparent; color: white;") - self.axes_maintenance_text_label.setObjectName("axes_maintenance_text_label") - self.axes_maintenance_cancel_btn = BlocksCustomButton(parent=self.axes_maintenance_page) - self.axes_maintenance_cancel_btn.setGeometry(QtCore.QRect(650, 40, 101, 71)) - self.axes_maintenance_cancel_btn.setMinimumSize(QtCore.QSize(10, 10)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(20) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.axes_maintenance_cancel_btn.setFont(font) - self.axes_maintenance_cancel_btn.setMouseTracking(False) - self.axes_maintenance_cancel_btn.setTabletTracking(True) - self.axes_maintenance_cancel_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.axes_maintenance_cancel_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.axes_maintenance_cancel_btn.setStyleSheet("") - self.axes_maintenance_cancel_btn.setAutoDefault(False) - self.axes_maintenance_cancel_btn.setFlat(True) - self.axes_maintenance_cancel_btn.setProperty("borderLeftPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part1.svg")) - self.axes_maintenance_cancel_btn.setProperty("borderCenterPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part2.svg")) - self.axes_maintenance_cancel_btn.setProperty("borderRightPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part3.svg")) - self.axes_maintenance_cancel_btn.setObjectName("axes_maintenance_cancel_btn") - utilitiesStackedWidget.addWidget(self.axes_maintenance_page) - - self.retranslateUi(utilitiesStackedWidget) - utilitiesStackedWidget.setCurrentIndex(1) - QtCore.QMetaObject.connectSlotsByName(utilitiesStackedWidget) - - def retranslateUi(self, utilitiesStackedWidget): - _translate = QtCore.QCoreApplication.translate - utilitiesStackedWidget.setWindowTitle(_translate("utilitiesStackedWidget", "StackedWidget")) - self.switch_pc_title_label.setText(_translate("utilitiesStackedWidget", "Switch Print Cores")) - self.switch_pc_title_label.setProperty("class", _translate("utilitiesStackedWidget", "title_text")) - self.switch_pc_text_label.setText(_translate("utilitiesStackedWidget", "Printer Heating, wait to switch")) - self.switch_pc_cancel_btn.setText(_translate("utilitiesStackedWidget", "Cancel")) - self.switch_pc_cancel_btn.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.switch_pc_cancel_btn.setProperty("button_type", _translate("utilitiesStackedWidget", "normal")) - self.routine_check_calib_title_label.setText(_translate("utilitiesStackedWidget", "ROUTINE CHECK")) - self.routine_check_calib_title_label.setProperty("class", _translate("utilitiesStackedWidget", "title_text")) - self.routine_check_text_label.setText(_translate("utilitiesStackedWidget", "Do this")) - self.routine_check_cancel_btn.setText(_translate("utilitiesStackedWidget", "Cancel")) - self.routine_check_cancel_btn.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.routine_check_cancel_btn.setProperty("button_type", _translate("utilitiesStackedWidget", "normal")) - self.axes_maintenance_calib_title_label.setText(_translate("utilitiesStackedWidget", "AXES MAINTENANCE")) - self.axes_maintenance_calib_title_label.setProperty("class", _translate("utilitiesStackedWidget", "title_text")) - self.axes_maintenance_text_label.setText(_translate("utilitiesStackedWidget", "Use oil here")) - self.axes_maintenance_cancel_btn.setText(_translate("utilitiesStackedWidget", "Cancel")) - self.axes_maintenance_cancel_btn.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.axes_maintenance_cancel_btn.setProperty("button_type", _translate("utilitiesStackedWidget", "normal")) -from lib.utils.ui import BlocksCustomButton diff --git a/BlocksScreen/lib/ui/resources/icon_resources.qrc b/BlocksScreen/lib/ui/resources/icon_resources.qrc index 3022fd4d..a62dda06 100644 --- a/BlocksScreen/lib/ui/resources/icon_resources.qrc +++ b/BlocksScreen/lib/ui/resources/icon_resources.qrc @@ -10,6 +10,10 @@ media/btn_icons/no_wifi.svg media/btn_icons/retry_wifi.svg + + media/btn_icons/move_nozzle_away.svg + media/btn_icons/move_nozzle_close.svg + media/btn_icons/blocks_contacts.svg media/btn_icons/logo_BLOCKS.svg @@ -36,6 +40,7 @@ media/btn_icons/fan.svg media/btn_icons/fan_cage.svg + media/btn_icons/blower.svg media/btn_icons/standart_temperature.svg @@ -88,9 +93,9 @@ media/btn_icons/center_arrows.svg media/btn_icons/confirm_stl_window.svg media/btn_icons/horizontal_scroll_bar.svg - media/btn_icons/info.svg - media/btn_icons/error.svg media/btn_icons/info_prints.svg + media/btn_icons/error.svg + media/btn_icons/info.svg media/btn_icons/LCD_settings.svg media/btn_icons/LEDs.svg media/btn_icons/LEDs_off.svg diff --git a/BlocksScreen/lib/ui/resources/icon_resources_rc.py b/BlocksScreen/lib/ui/resources/icon_resources_rc.py index 14285978..9df24546 100644 --- a/BlocksScreen/lib/ui/resources/icon_resources_rc.py +++ b/BlocksScreen/lib/ui/resources/icon_resources_rc.py @@ -9278,6 +9278,78 @@ \x37\x34\x2e\x32\x31\x2c\x30\x2c\x30\x2c\x30\x2d\x39\x39\x2e\x36\ \x39\x2c\x37\x36\x2e\x32\x36\x5a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\ \x3e\ +\x00\x00\x02\x27\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\ +\x61\x79\x65\x72\x5f\x31\x22\x20\x64\x61\x74\x61\x2d\x6e\x61\x6d\ +\x65\x3d\x22\x4c\x61\x79\x65\x72\x20\x31\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\ +\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x76\ +\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x36\x30\x30\x20\ +\x36\x30\x30\x22\x3e\x0a\x20\x20\x3c\x64\x65\x66\x73\x3e\x0a\x20\ +\x20\x20\x20\x3c\x73\x74\x79\x6c\x65\x3e\x0a\x20\x20\x20\x20\x20\ +\x20\x2e\x63\x6c\x73\x2d\x31\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x20\x66\x69\x6c\x6c\x3a\x20\x23\x65\x30\x65\x30\x64\x66\x3b\ +\x0a\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x73\ +\x74\x79\x6c\x65\x3e\x0a\x20\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0a\ +\x20\x20\x3c\x67\x3e\x0a\x20\x20\x20\x20\x3c\x72\x65\x63\x74\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x78\x3d\ +\x22\x32\x33\x38\x2e\x39\x31\x22\x20\x79\x3d\x22\x37\x33\x2e\x37\ +\x34\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x38\x31\x2e\x39\x32\x22\ +\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x34\x39\x33\x2e\x34\x37\x22\ +\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x74\x72\x61\x6e\ +\x73\x6c\x61\x74\x65\x28\x33\x30\x38\x2e\x35\x38\x20\x2d\x31\x30\ +\x34\x2e\x30\x34\x29\x20\x72\x6f\x74\x61\x74\x65\x28\x34\x35\x29\ +\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x33\x34\x31\x2e\x33\x38\x20\x39\x31\ +\x2e\x38\x32\x20\x35\x34\x34\x2e\x36\x32\x20\x35\x34\x2e\x30\x32\ +\x20\x35\x30\x37\x2e\x31\x34\x20\x32\x35\x38\x2e\x39\x37\x20\x33\ +\x34\x31\x2e\x33\x38\x20\x39\x31\x2e\x38\x32\x22\x2f\x3e\x0a\x20\ +\x20\x3c\x2f\x67\x3e\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x32\x35\x38\x2e\x36\x32\x20\x35\x30\ +\x38\x2e\x31\x38\x20\x35\x35\x2e\x33\x38\x20\x35\x34\x35\x2e\x39\ +\x38\x20\x39\x32\x2e\x38\x36\x20\x33\x34\x31\x2e\x30\x33\x20\x32\ +\x35\x38\x2e\x36\x32\x20\x35\x30\x38\x2e\x31\x38\x22\x2f\x3e\x0a\ +\x3c\x2f\x73\x76\x67\x3e\ +\x00\x00\x02\x02\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\ +\x61\x79\x65\x72\x5f\x31\x22\x20\x64\x61\x74\x61\x2d\x6e\x61\x6d\ +\x65\x3d\x22\x4c\x61\x79\x65\x72\x20\x31\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\ +\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x76\ +\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x36\x30\x30\x20\ +\x36\x30\x30\x22\x3e\x0a\x20\x20\x3c\x64\x65\x66\x73\x3e\x0a\x20\ +\x20\x20\x20\x3c\x73\x74\x79\x6c\x65\x3e\x0a\x20\x20\x20\x20\x20\ +\x20\x2e\x63\x6c\x73\x2d\x31\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x20\x66\x69\x6c\x6c\x3a\x20\x23\x65\x30\x65\x30\x64\x66\x3b\ +\x0a\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x73\ +\x74\x79\x6c\x65\x3e\x0a\x20\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0a\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x35\x37\x38\x2e\x30\x35\x20\x37\x38\x2e\x33\x20\x35\x31\x39\ +\x2e\x36\x33\x20\x32\x30\x2e\x38\x36\x20\x33\x39\x39\x2e\x31\x37\ +\x20\x31\x34\x31\x2e\x33\x33\x20\x33\x34\x35\x2e\x34\x38\x20\x38\ +\x37\x2e\x31\x39\x20\x33\x30\x37\x2e\x39\x39\x20\x32\x39\x32\x2e\ +\x31\x33\x20\x35\x31\x31\x2e\x32\x34\x20\x32\x35\x34\x2e\x33\x34\ +\x20\x34\x35\x36\x2e\x38\x35\x20\x31\x39\x39\x2e\x35\x20\x35\x37\ +\x38\x2e\x30\x35\x20\x37\x38\x2e\x33\x22\x2f\x3e\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\ +\x6c\x73\x2d\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x35\ +\x2e\x33\x38\x20\x35\x33\x31\x2e\x33\x37\x20\x37\x33\x2e\x38\x20\ +\x35\x38\x38\x2e\x38\x31\x20\x31\x39\x39\x2e\x33\x33\x20\x34\x36\ +\x33\x2e\x32\x37\x20\x32\x35\x33\x2e\x33\x37\x20\x35\x31\x37\x2e\ +\x37\x36\x20\x32\x39\x30\x2e\x38\x36\x20\x33\x31\x32\x2e\x38\x31\ +\x20\x38\x37\x2e\x36\x31\x20\x33\x35\x30\x2e\x36\x31\x20\x31\x34\ +\x31\x2e\x36\x35\x20\x34\x30\x35\x2e\x31\x20\x31\x35\x2e\x33\x38\ +\x20\x35\x33\x31\x2e\x33\x37\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\ +\x3e\ \x00\x00\x0a\x60\ \x3c\ \x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\ @@ -11621,6 +11693,166 @@ \x36\x36\x2c\x33\x31\x37\x2e\x33\x2c\x32\x39\x39\x2e\x37\x36\x2c\ \x33\x31\x37\x2e\x34\x32\x5a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\ \ +\x00\x00\x09\xd1\ +\x3c\ +\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\ +\x20\x64\x61\x74\x61\x2d\x6e\x61\x6d\x65\x3d\x22\x4c\x61\x79\x65\ +\x72\x20\x31\x22\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\ +\x30\x30\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\ +\x22\x30\x20\x30\x20\x36\x30\x30\x20\x36\x30\x30\x22\x3e\x3c\x64\ +\x65\x66\x73\x3e\x3c\x73\x74\x79\x6c\x65\x3e\x2e\x63\x6c\x73\x2d\ +\x31\x7b\x66\x69\x6c\x6c\x3a\x23\x65\x30\x65\x30\x64\x66\x3b\x7d\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x3c\x2f\x64\x65\x66\x73\x3e\x3c\ +\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\ +\x31\x22\x20\x64\x3d\x22\x4d\x34\x35\x36\x2e\x31\x37\x2c\x33\x31\ +\x37\x2e\x38\x35\x63\x2e\x34\x36\x2d\x31\x31\x2e\x38\x39\x2c\x35\ +\x2e\x31\x38\x2d\x31\x37\x2e\x34\x32\x2c\x31\x34\x2e\x38\x36\x2d\ +\x31\x36\x2e\x37\x39\x2c\x31\x30\x2e\x37\x35\x2e\x36\x39\x2c\x31\ +\x33\x2e\x32\x34\x2c\x37\x2e\x37\x37\x2c\x31\x33\x2e\x32\x32\x2c\ +\x31\x37\x2e\x32\x2d\x2e\x31\x31\x2c\x36\x36\x2c\x30\x2c\x31\x33\ +\x32\x2d\x2e\x31\x2c\x31\x39\x38\x2c\x30\x2c\x31\x31\x2d\x35\x2c\ +\x31\x36\x2e\x35\x32\x2d\x31\x34\x2e\x31\x38\x2c\x31\x36\x2e\x32\ +\x2d\x31\x30\x2d\x2e\x33\x34\x2d\x31\x34\x2e\x34\x34\x2d\x36\x2e\ +\x35\x31\x2d\x31\x33\x2e\x35\x39\x2d\x31\x35\x2e\x36\x2c\x31\x2e\ +\x30\x35\x2d\x31\x31\x2e\x32\x32\x2d\x33\x2e\x33\x38\x2d\x31\x33\ +\x2e\x36\x32\x2d\x31\x34\x2d\x31\x33\x2e\x35\x34\x2d\x36\x34\x2e\ +\x34\x39\x2e\x34\x37\x2d\x31\x32\x39\x2c\x2e\x33\x38\x2d\x31\x39\ +\x33\x2e\x34\x39\x2e\x31\x32\x2d\x34\x33\x2e\x36\x33\x2d\x2e\x31\ +\x37\x2d\x38\x31\x2e\x35\x2d\x31\x35\x2e\x36\x2d\x31\x31\x34\x2e\ +\x32\x33\x2d\x34\x34\x2e\x33\x32\x61\x31\x39\x32\x2e\x31\x38\x2c\ +\x31\x39\x32\x2e\x31\x38\x2c\x30\x2c\x30\x2c\x30\x2d\x32\x32\x2e\ +\x34\x34\x2d\x31\x36\x2e\x38\x39\x41\x32\x30\x34\x2e\x38\x32\x2c\ +\x32\x30\x34\x2e\x38\x32\x2c\x30\x2c\x30\x2c\x31\x2c\x32\x32\x2e\ +\x33\x32\x2c\x32\x33\x33\x2e\x38\x36\x43\x34\x34\x2e\x31\x31\x2c\ +\x31\x31\x35\x2e\x36\x39\x2c\x31\x36\x35\x2c\x34\x31\x2e\x37\x2c\ +\x32\x38\x30\x2e\x34\x34\x2c\x37\x35\x2e\x38\x37\x2c\x33\x36\x32\ +\x2e\x32\x35\x2c\x31\x30\x30\x2e\x30\x38\x2c\x34\x32\x30\x2e\x32\ +\x38\x2c\x31\x37\x31\x2e\x37\x37\x2c\x34\x32\x35\x2c\x32\x35\x37\ +\x63\x31\x2e\x33\x32\x2c\x32\x33\x2e\x36\x35\x2d\x32\x2e\x32\x2c\ +\x34\x37\x2e\x35\x36\x2d\x33\x2e\x35\x33\x2c\x37\x31\x2e\x38\x38\ +\x68\x33\x34\x2e\x30\x38\x43\x34\x35\x35\x2e\x38\x32\x2c\x33\x32\ +\x34\x2e\x34\x38\x2c\x34\x35\x36\x2c\x33\x32\x31\x2e\x31\x36\x2c\ +\x34\x35\x36\x2e\x31\x37\x2c\x33\x31\x37\x2e\x38\x35\x5a\x4d\x32\ +\x32\x33\x2e\x37\x38\x2c\x39\x36\x43\x31\x32\x37\x2c\x39\x35\x2e\ +\x35\x36\x2c\x34\x36\x2e\x36\x34\x2c\x31\x37\x36\x2c\x34\x37\x2e\ +\x35\x39\x2c\x32\x37\x32\x2e\x32\x32\x63\x2e\x39\x34\x2c\x39\x35\ +\x2e\x38\x34\x2c\x37\x39\x2e\x34\x31\x2c\x31\x37\x33\x2e\x39\x34\ +\x2c\x31\x37\x34\x2e\x37\x38\x2c\x31\x37\x33\x2e\x39\x34\x61\x31\ +\x37\x35\x2e\x32\x38\x2c\x31\x37\x35\x2e\x32\x38\x2c\x30\x2c\x30\ +\x2c\x30\x2c\x31\x37\x35\x2e\x34\x34\x2d\x31\x37\x35\x2e\x34\x43\ +\x33\x39\x37\x2e\x38\x31\x2c\x31\x37\x35\x2e\x37\x32\x2c\x33\x31\ +\x38\x2e\x38\x36\x2c\x39\x36\x2e\x34\x34\x2c\x32\x32\x33\x2e\x37\ +\x38\x2c\x39\x36\x5a\x22\x2f\x3e\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\ +\x33\x33\x32\x2e\x32\x35\x2c\x31\x37\x39\x2e\x39\x34\x63\x2d\x32\ +\x30\x2e\x34\x39\x2d\x32\x2e\x30\x37\x2d\x33\x39\x2e\x33\x35\x2e\ +\x37\x39\x2d\x35\x37\x2e\x32\x36\x2c\x39\x2e\x31\x38\x61\x38\x30\ +\x2c\x38\x30\x2c\x30\x2c\x30\x2c\x30\x2d\x33\x36\x2c\x33\x32\x2e\ +\x34\x38\x63\x2d\x32\x2e\x37\x36\x2c\x34\x2e\x37\x31\x2d\x32\x2e\ +\x37\x39\x2c\x37\x2e\x39\x33\x2c\x31\x2e\x35\x34\x2c\x31\x32\x2c\ +\x39\x2e\x32\x34\x2c\x38\x2e\x35\x39\x2c\x38\x2e\x39\x31\x2c\x38\ +\x2e\x36\x33\x2c\x32\x30\x2e\x32\x33\x2c\x34\x2e\x31\x38\x2c\x33\ +\x34\x2e\x36\x39\x2d\x31\x33\x2e\x36\x33\x2c\x36\x34\x2e\x38\x32\ +\x2d\x34\x2c\x39\x33\x2e\x33\x32\x2c\x31\x37\x2e\x34\x2c\x31\x30\ +\x2e\x33\x34\x2c\x37\x2e\x37\x34\x2c\x39\x2e\x37\x31\x2c\x31\x36\ +\x2e\x36\x31\x2c\x38\x2e\x31\x38\x2c\x32\x37\x2e\x30\x36\x71\x2d\ +\x37\x2e\x34\x34\x2c\x35\x30\x2e\x39\x33\x2d\x34\x36\x2e\x36\x35\ +\x2c\x38\x34\x2e\x30\x35\x61\x37\x2e\x36\x31\x2c\x37\x2e\x36\x31\ +\x2c\x30\x2c\x30\x2c\x31\x2d\x31\x2e\x38\x36\x2c\x31\x63\x2d\x2e\ +\x31\x37\x2e\x30\x38\x2d\x2e\x34\x36\x2d\x2e\x31\x32\x2d\x31\x2e\ +\x30\x36\x2d\x2e\x33\x31\x2c\x32\x2e\x30\x36\x2d\x31\x39\x2e\x36\ +\x34\x2e\x31\x31\x2d\x33\x38\x2e\x38\x38\x2d\x38\x2e\x31\x33\x2d\ +\x35\x37\x2e\x31\x33\x2d\x37\x2e\x32\x36\x2d\x31\x36\x2e\x30\x38\ +\x2d\x31\x38\x2e\x33\x34\x2d\x32\x38\x2e\x38\x32\x2d\x33\x33\x2e\ +\x38\x2d\x33\x37\x2e\x36\x35\x2d\x33\x2e\x34\x37\x2d\x32\x2d\x35\ +\x2e\x34\x39\x2d\x31\x2e\x35\x33\x2d\x38\x2e\x38\x32\x2c\x31\x2e\ +\x31\x36\x2d\x38\x2e\x37\x2c\x37\x2d\x39\x2e\x36\x33\x2c\x31\x33\ +\x2e\x31\x31\x2d\x35\x2e\x31\x33\x2c\x32\x34\x2e\x33\x32\x2c\x31\ +\x32\x2e\x37\x39\x2c\x33\x31\x2e\x37\x37\x2c\x32\x2e\x31\x32\x2c\ +\x36\x30\x2e\x32\x32\x2d\x31\x37\x2e\x30\x38\x2c\x38\x36\x2e\x35\ +\x31\x2d\x37\x2e\x33\x31\x2c\x31\x30\x2d\x31\x35\x2e\x34\x33\x2c\ +\x31\x34\x2e\x30\x37\x2d\x32\x38\x2e\x35\x37\x2c\x31\x31\x2e\x37\ +\x31\x2d\x33\x32\x2e\x31\x39\x2d\x35\x2e\x37\x38\x2d\x35\x39\x2d\ +\x32\x30\x2e\x32\x33\x2d\x38\x30\x2e\x36\x39\x2d\x34\x34\x2e\x35\ +\x31\x61\x31\x37\x2e\x32\x2c\x31\x37\x2e\x32\x2c\x30\x2c\x30\x2c\ +\x31\x2d\x31\x2e\x34\x33\x2d\x32\x2e\x37\x2c\x31\x31\x31\x2e\x38\ +\x2c\x31\x31\x31\x2e\x38\x2c\x30\x2c\x30\x2c\x30\x2c\x34\x31\x2e\ +\x34\x34\x2d\x33\x2e\x34\x36\x63\x32\x32\x2e\x31\x32\x2d\x36\x2e\ +\x31\x34\x2c\x33\x39\x2e\x38\x33\x2d\x31\x38\x2e\x32\x33\x2c\x35\ +\x31\x2e\x38\x38\x2d\x33\x38\x2e\x32\x35\x2c\x33\x2d\x35\x2c\x32\ +\x2e\x35\x34\x2d\x38\x2e\x31\x33\x2d\x31\x2e\x34\x37\x2d\x31\x32\ +\x2e\x30\x36\x2d\x39\x2e\x31\x31\x2d\x38\x2e\x39\x34\x2d\x38\x2e\ +\x39\x2d\x39\x2d\x32\x30\x2e\x39\x32\x2d\x34\x2e\x31\x35\x2d\x33\ +\x33\x2c\x31\x33\x2e\x31\x38\x2d\x36\x31\x2e\x38\x38\x2c\x34\x2d\ +\x38\x39\x2e\x37\x35\x2d\x31\x35\x2e\x31\x34\x2d\x31\x32\x2e\x38\ +\x31\x2d\x38\x2e\x37\x39\x2d\x31\x32\x2e\x39\x31\x2d\x31\x39\x2e\ +\x34\x34\x2d\x31\x30\x2e\x36\x34\x2d\x33\x32\x2e\x35\x31\x2c\x35\ +\x2e\x36\x34\x2d\x33\x32\x2e\x34\x36\x2c\x32\x30\x2e\x38\x2d\x35\ +\x39\x2e\x32\x38\x2c\x34\x35\x2e\x37\x39\x2d\x38\x30\x2e\x36\x34\ +\x2e\x36\x36\x2d\x2e\x35\x36\x2c\x31\x2e\x34\x32\x2d\x31\x2c\x32\ +\x2e\x38\x37\x2d\x32\x2c\x2e\x37\x37\x2c\x31\x32\x2e\x33\x36\x2d\ +\x2e\x37\x2c\x32\x33\x2e\x38\x34\x2c\x31\x2e\x35\x36\x2c\x33\x35\ +\x2e\x31\x39\x2c\x35\x2e\x30\x38\x2c\x32\x35\x2e\x34\x36\x2c\x31\ +\x37\x2e\x31\x32\x2c\x34\x36\x2e\x30\x39\x2c\x33\x39\x2e\x37\x31\ +\x2c\x35\x39\x2e\x39\x35\x2c\x34\x2e\x31\x38\x2c\x32\x2e\x35\x36\ +\x2c\x36\x2e\x38\x38\x2c\x32\x2e\x38\x31\x2c\x31\x30\x2e\x36\x31\ +\x2d\x31\x2e\x30\x39\x2c\x39\x2e\x32\x38\x2d\x39\x2e\x37\x34\x2c\ +\x39\x2e\x32\x2d\x39\x2e\x34\x2c\x34\x2e\x35\x35\x2d\x32\x32\x2e\ +\x31\x36\x2d\x31\x31\x2e\x36\x39\x2d\x33\x32\x2e\x30\x37\x2d\x33\ +\x2d\x36\x30\x2e\x34\x32\x2c\x31\x36\x2e\x31\x33\x2d\x38\x37\x2c\ +\x37\x2e\x36\x36\x2d\x31\x30\x2e\x36\x33\x2c\x31\x36\x2d\x31\x35\ +\x2e\x35\x35\x2c\x33\x30\x2e\x32\x34\x2d\x31\x32\x2e\x38\x32\x2c\ +\x33\x31\x2e\x38\x32\x2c\x36\x2e\x30\x39\x2c\x35\x38\x2e\x33\x38\ +\x2c\x32\x30\x2e\x34\x34\x2c\x38\x30\x2c\x34\x34\x2e\x33\x33\x43\ +\x33\x33\x31\x2e\x34\x38\x2c\x31\x37\x37\x2e\x33\x39\x2c\x33\x33\ +\x31\x2e\x35\x37\x2c\x31\x37\x38\x2e\x31\x38\x2c\x33\x33\x32\x2e\ +\x32\x35\x2c\x31\x37\x39\x2e\x39\x34\x5a\x4d\x32\x33\x30\x2e\x35\ +\x39\x2c\x32\x38\x30\x2e\x37\x32\x63\x33\x2e\x35\x39\x2e\x31\x2c\ +\x31\x36\x2e\x33\x37\x2d\x31\x32\x2e\x34\x39\x2c\x31\x36\x2e\x35\ +\x37\x2d\x31\x36\x2e\x33\x34\x53\x32\x33\x35\x2e\x31\x36\x2c\x32\ +\x34\x38\x2c\x32\x33\x31\x2c\x32\x34\x37\x2e\x37\x35\x63\x2d\x33\ +\x2e\x35\x32\x2d\x2e\x31\x39\x2d\x31\x36\x2e\x35\x34\x2c\x31\x32\ +\x2e\x35\x2d\x31\x36\x2e\x36\x35\x2c\x31\x36\x2e\x32\x33\x53\x32\ +\x32\x36\x2e\x36\x39\x2c\x32\x38\x30\x2e\x36\x31\x2c\x32\x33\x30\ +\x2e\x35\x39\x2c\x32\x38\x30\x2e\x37\x32\x5a\x22\x2f\x3e\x3c\x70\ +\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\ +\x22\x20\x64\x3d\x22\x4d\x35\x38\x31\x2c\x33\x33\x32\x2e\x38\x32\ +\x63\x2d\x33\x2e\x36\x31\x2c\x32\x2e\x31\x33\x2d\x37\x2e\x32\x33\ +\x2c\x34\x2e\x32\x35\x2d\x31\x30\x2e\x38\x33\x2c\x36\x2e\x33\x39\ +\x6c\x2d\x34\x35\x2e\x34\x31\x2c\x32\x37\x63\x2d\x2e\x31\x36\x2e\ +\x31\x2d\x2e\x33\x33\x2e\x31\x39\x2d\x2e\x36\x36\x2e\x33\x37\x2c\ +\x30\x2d\x32\x2e\x33\x31\x2d\x2e\x30\x36\x2d\x31\x39\x2e\x33\x34\ +\x2d\x2e\x30\x36\x2d\x31\x39\x2e\x33\x34\x68\x2d\x32\x33\x56\x33\ +\x31\x39\x2e\x34\x68\x32\x33\x73\x30\x2d\x32\x30\x2e\x33\x36\x2c\ +\x30\x2d\x32\x30\x2e\x35\x31\x63\x2e\x30\x37\x2c\x30\x2c\x31\x30\ +\x2e\x31\x38\x2c\x35\x2e\x39\x31\x2c\x31\x34\x2e\x38\x34\x2c\x38\ +\x2e\x36\x38\x4c\x35\x38\x31\x2c\x33\x33\x32\x2e\x36\x34\x5a\x22\ +\x2f\x3e\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\ +\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x35\x38\x31\x2c\x34\x39\ +\x34\x63\x2d\x33\x2e\x36\x31\x2c\x32\x2e\x31\x33\x2d\x37\x2e\x32\ +\x33\x2c\x34\x2e\x32\x35\x2d\x31\x30\x2e\x38\x33\x2c\x36\x2e\x33\ +\x39\x6c\x2d\x34\x35\x2e\x34\x31\x2c\x32\x37\x63\x2d\x2e\x31\x36\ +\x2e\x31\x2d\x2e\x33\x33\x2e\x31\x39\x2d\x2e\x36\x36\x2e\x33\x37\ +\x2c\x30\x2d\x32\x2e\x33\x31\x2d\x2e\x30\x36\x2d\x31\x39\x2e\x33\ +\x34\x2d\x2e\x30\x36\x2d\x31\x39\x2e\x33\x34\x68\x2d\x32\x33\x56\ +\x34\x38\x30\x2e\x36\x31\x68\x32\x33\x73\x30\x2d\x32\x30\x2e\x33\ +\x36\x2c\x30\x2d\x32\x30\x2e\x35\x31\x2c\x31\x30\x2e\x31\x38\x2c\ +\x35\x2e\x39\x31\x2c\x31\x34\x2e\x38\x34\x2c\x38\x2e\x36\x39\x4c\ +\x35\x38\x31\x2c\x34\x39\x33\x2e\x38\x35\x5a\x22\x2f\x3e\x3c\x70\ +\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\ +\x22\x20\x64\x3d\x22\x4d\x35\x38\x31\x2c\x34\x31\x33\x2e\x34\x33\ +\x63\x2d\x33\x2e\x36\x31\x2c\x32\x2e\x31\x33\x2d\x37\x2e\x32\x33\ +\x2c\x34\x2e\x32\x34\x2d\x31\x30\x2e\x38\x33\x2c\x36\x2e\x33\x38\ +\x6c\x2d\x34\x35\x2e\x34\x31\x2c\x32\x37\x63\x2d\x2e\x31\x36\x2e\ +\x31\x2d\x2e\x33\x33\x2e\x31\x38\x2d\x2e\x36\x36\x2e\x33\x37\x2c\ +\x30\x2d\x32\x2e\x33\x32\x2d\x2e\x30\x36\x2d\x31\x39\x2e\x33\x35\ +\x2d\x2e\x30\x36\x2d\x31\x39\x2e\x33\x35\x68\x2d\x32\x33\x56\x34\ +\x30\x30\x68\x32\x33\x73\x30\x2d\x32\x30\x2e\x33\x36\x2c\x30\x2d\ +\x32\x30\x2e\x35\x31\x2c\x31\x30\x2e\x31\x38\x2c\x35\x2e\x39\x31\ +\x2c\x31\x34\x2e\x38\x34\x2c\x38\x2e\x36\x38\x4c\x35\x38\x31\x2c\ +\x34\x31\x33\x2e\x32\x34\x5a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\ +\ \x00\x00\x04\xf7\ \x3c\ \x73\x76\x67\x20\x69\x64\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\ @@ -22179,54 +22411,75 @@ \x20\x34\x38\x31\x2e\x32\x32\x20\x35\x31\x30\x2e\x37\x37\x20\x32\ \x38\x36\x2e\x30\x36\x20\x38\x39\x2e\x32\x33\x20\x31\x31\x34\x2e\ \x34\x31\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\ -\x00\x00\x02\xe0\ +\x00\x00\x04\x25\ \x3c\ -\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\ -\x20\x64\x61\x74\x61\x2d\x6e\x61\x6d\x65\x3d\x22\x4c\x61\x79\x65\ -\x72\x20\x31\x22\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\ -\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\ -\x30\x30\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\ -\x22\x30\x20\x30\x20\x36\x30\x30\x20\x36\x30\x30\x22\x3e\x3c\x64\ -\x65\x66\x73\x3e\x3c\x73\x74\x79\x6c\x65\x3e\x2e\x63\x6c\x73\x2d\ -\x31\x7b\x66\x69\x6c\x6c\x3a\x23\x65\x30\x65\x30\x64\x66\x3b\x7d\ -\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x3c\x2f\x64\x65\x66\x73\x3e\x3c\ -\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\ -\x31\x22\x20\x64\x3d\x22\x4d\x35\x36\x31\x2e\x34\x39\x2c\x33\x30\ -\x30\x63\x32\x2e\x36\x38\x2c\x31\x34\x31\x2e\x35\x32\x2d\x31\x31\ -\x38\x2e\x37\x39\x2c\x32\x36\x33\x2e\x36\x38\x2d\x32\x36\x34\x2e\ -\x36\x2c\x32\x36\x31\x2e\x36\x35\x2d\x31\x34\x31\x2e\x37\x31\x2d\ -\x32\x2d\x32\x35\x38\x2e\x35\x31\x2d\x31\x31\x38\x2e\x35\x39\x2d\ -\x32\x35\x38\x2e\x34\x32\x2d\x32\x36\x31\x2e\x38\x36\x43\x33\x38\ -\x2e\x35\x36\x2c\x31\x35\x34\x2e\x35\x35\x2c\x31\x35\x35\x2e\x36\ -\x38\x2c\x33\x38\x2e\x31\x36\x2c\x33\x30\x31\x2e\x35\x39\x2c\x33\ -\x38\x2e\x33\x32\x2c\x34\x34\x35\x2e\x34\x34\x2c\x33\x38\x2e\x34\ -\x37\x2c\x35\x36\x31\x2e\x34\x39\x2c\x31\x35\x35\x2e\x33\x33\x2c\ -\x35\x36\x31\x2e\x34\x39\x2c\x33\x30\x30\x5a\x4d\x32\x34\x37\x2e\ -\x32\x37\x2c\x33\x38\x35\x2e\x32\x32\x63\x30\x2c\x34\x36\x2e\x36\ -\x31\x2e\x31\x38\x2c\x39\x33\x2e\x32\x33\x2d\x2e\x31\x38\x2c\x31\ -\x33\x39\x2e\x38\x34\x2d\x2e\x30\x36\x2c\x37\x2e\x32\x39\x2c\x32\ -\x2c\x39\x2c\x39\x2c\x38\x2e\x38\x37\x2c\x32\x39\x2e\x36\x36\x2d\ -\x2e\x33\x39\x2c\x35\x39\x2e\x33\x33\x2d\x2e\x33\x2c\x38\x39\x2c\ -\x30\x2c\x35\x2e\x37\x33\x2e\x30\x35\x2c\x37\x2e\x37\x2d\x31\x2e\ -\x32\x35\x2c\x37\x2e\x36\x39\x2d\x37\x2e\x34\x33\x71\x2d\x2e\x33\ -\x31\x2d\x31\x34\x31\x2e\x36\x35\x2c\x30\x2d\x32\x38\x33\x2e\x33\ -\x63\x30\x2d\x36\x2e\x32\x31\x2d\x32\x2d\x37\x2e\x33\x36\x2d\x37\ -\x2e\x36\x35\x2d\x37\x2e\x33\x31\x2d\x32\x39\x2e\x36\x36\x2e\x32\ -\x36\x2d\x35\x39\x2e\x33\x34\x2e\x34\x36\x2d\x38\x39\x2d\x2e\x31\ -\x2d\x38\x2d\x2e\x31\x35\x2d\x39\x2e\x31\x32\x2c\x32\x2e\x35\x32\ -\x2d\x39\x2e\x30\x37\x2c\x39\x2e\x36\x34\x43\x32\x34\x37\x2e\x34\ -\x33\x2c\x32\x39\x32\x2c\x32\x34\x37\x2e\x32\x37\x2c\x33\x33\x38\ -\x2e\x36\x31\x2c\x32\x34\x37\x2e\x32\x37\x2c\x33\x38\x35\x2e\x32\ -\x32\x5a\x6d\x35\x34\x2e\x30\x38\x2d\x33\x30\x36\x63\x2d\x33\x30\ -\x2e\x31\x33\x2d\x2e\x34\x31\x2d\x35\x36\x2e\x33\x35\x2c\x31\x38\ -\x2d\x36\x33\x2e\x32\x36\x2c\x34\x34\x2e\x34\x33\x2d\x38\x2e\x33\ -\x2c\x33\x31\x2e\x37\x39\x2c\x31\x30\x2e\x30\x38\x2c\x36\x33\x2e\ -\x33\x34\x2c\x34\x32\x2e\x33\x32\x2c\x37\x32\x2e\x36\x33\x2c\x33\ -\x34\x2e\x38\x2c\x31\x30\x2c\x37\x31\x2e\x37\x33\x2d\x38\x2e\x31\ -\x31\x2c\x38\x31\x2e\x35\x35\x2d\x34\x30\x2e\x30\x35\x43\x33\x37\ -\x33\x2e\x38\x35\x2c\x31\x31\x37\x2e\x36\x33\x2c\x33\x34\x34\x2e\ -\x31\x31\x2c\x37\x39\x2e\x38\x35\x2c\x33\x30\x31\x2e\x33\x35\x2c\ -\x37\x39\x2e\x32\x37\x5a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\ +\x61\x79\x65\x72\x5f\x31\x22\x20\x64\x61\x74\x61\x2d\x6e\x61\x6d\ +\x65\x3d\x22\x4c\x61\x79\x65\x72\x20\x31\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\ +\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x76\ +\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x36\x30\x30\x20\ +\x36\x30\x30\x22\x3e\x0a\x20\x20\x3c\x64\x65\x66\x73\x3e\x0a\x20\ +\x20\x20\x20\x3c\x73\x74\x79\x6c\x65\x3e\x0a\x20\x20\x20\x20\x20\ +\x20\x2e\x63\x6c\x73\x2d\x31\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x20\x66\x69\x6c\x6c\x3a\x20\x23\x65\x30\x65\x30\x64\x66\x3b\ +\x0a\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x73\ +\x74\x79\x6c\x65\x3e\x0a\x20\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0a\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\ +\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x33\x33\x37\x2e\x39\x35\ +\x2c\x32\x35\x31\x2e\x37\x35\x63\x2d\x32\x34\x2e\x39\x32\x2e\x32\ +\x32\x2d\x34\x39\x2e\x38\x34\x2e\x33\x39\x2d\x37\x34\x2e\x37\x35\ +\x2d\x2e\x30\x38\x2d\x36\x2e\x37\x34\x2d\x2e\x31\x33\x2d\x37\x2e\ +\x36\x37\x2c\x32\x2e\x31\x32\x2d\x37\x2e\x36\x32\x2c\x38\x2e\x31\ +\x2e\x32\x37\x2c\x33\x39\x2e\x31\x35\x2e\x31\x34\x2c\x37\x38\x2e\ +\x33\x2e\x31\x34\x2c\x31\x31\x37\x2e\x34\x35\x73\x2e\x31\x35\x2c\ +\x37\x38\x2e\x33\x2d\x2e\x31\x35\x2c\x31\x31\x37\x2e\x34\x35\x63\ +\x2d\x2e\x30\x35\x2c\x36\x2e\x31\x33\x2c\x31\x2e\x37\x31\x2c\x37\ +\x2e\x35\x33\x2c\x37\x2e\x36\x2c\x37\x2e\x34\x36\x2c\x32\x34\x2e\ +\x39\x32\x2d\x2e\x33\x33\x2c\x34\x39\x2e\x38\x34\x2d\x2e\x32\x36\ +\x2c\x37\x34\x2e\x37\x36\x2d\x2e\x30\x34\x2c\x34\x2e\x38\x32\x2e\ +\x30\x34\x2c\x36\x2e\x34\x37\x2d\x31\x2e\x30\x35\x2c\x36\x2e\x34\ +\x36\x2d\x36\x2e\x32\x34\x2d\x2e\x31\x37\x2d\x37\x39\x2e\x33\x32\ +\x2d\x2e\x31\x37\x2d\x31\x35\x38\x2e\x36\x34\x2c\x30\x2d\x32\x33\ +\x37\x2e\x39\x36\x2e\x30\x31\x2d\x35\x2e\x32\x32\x2d\x31\x2e\x36\ +\x36\x2d\x36\x2e\x31\x39\x2d\x36\x2e\x34\x33\x2d\x36\x2e\x31\x34\ +\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x35\ +\x37\x34\x2e\x32\x34\x2c\x33\x30\x35\x2e\x36\x34\x63\x30\x2d\x31\ +\x35\x31\x2e\x37\x34\x2d\x31\x32\x31\x2e\x37\x31\x2d\x32\x37\x34\ +\x2e\x32\x39\x2d\x32\x37\x32\x2e\x35\x37\x2d\x32\x37\x34\x2e\x34\ +\x36\x2d\x31\x35\x33\x2e\x30\x33\x2d\x2e\x31\x36\x2d\x32\x37\x35\ +\x2e\x38\x36\x2c\x31\x32\x31\x2e\x39\x2d\x32\x37\x35\x2e\x39\x36\ +\x2c\x32\x37\x34\x2e\x32\x34\x2d\x2e\x31\x2c\x31\x35\x30\x2e\x32\ +\x36\x2c\x31\x32\x32\x2e\x34\x2c\x32\x37\x32\x2e\x35\x36\x2c\x32\ +\x37\x31\x2e\x30\x32\x2c\x32\x37\x34\x2e\x36\x33\x2c\x31\x35\x32\ +\x2e\x39\x32\x2c\x32\x2e\x31\x33\x2c\x32\x38\x30\x2e\x33\x32\x2d\ +\x31\x32\x35\x2e\x39\x39\x2c\x32\x37\x37\x2e\x35\x31\x2d\x32\x37\ +\x34\x2e\x34\x5a\x4d\x32\x39\x37\x2e\x33\x38\x2c\x35\x32\x35\x2e\ +\x34\x31\x63\x2d\x31\x31\x39\x2e\x30\x33\x2d\x31\x2e\x36\x36\x2d\ +\x32\x31\x37\x2e\x31\x33\x2d\x39\x39\x2e\x36\x31\x2d\x32\x31\x37\ +\x2e\x30\x36\x2d\x32\x31\x39\x2e\x39\x35\x2e\x30\x38\x2d\x31\x32\ +\x32\x2c\x39\x38\x2e\x34\x35\x2d\x32\x31\x39\x2e\x37\x36\x2c\x32\ +\x32\x31\x2e\x30\x31\x2d\x32\x31\x39\x2e\x36\x33\x2c\x31\x32\x30\ +\x2e\x38\x32\x2e\x31\x33\x2c\x32\x31\x38\x2e\x33\x2c\x39\x38\x2e\ +\x32\x38\x2c\x32\x31\x38\x2e\x33\x2c\x32\x31\x39\x2e\x38\x31\x2c\ +\x32\x2e\x32\x35\x2c\x31\x31\x38\x2e\x38\x37\x2d\x39\x39\x2e\x37\ +\x38\x2c\x32\x32\x31\x2e\x34\x37\x2d\x32\x32\x32\x2e\x32\x35\x2c\ +\x32\x31\x39\x2e\x37\x37\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\ +\x20\x64\x3d\x22\x4d\x33\x30\x31\x2e\x31\x33\x2c\x31\x32\x30\x2e\ +\x32\x33\x63\x2d\x32\x35\x2e\x33\x31\x2d\x2e\x33\x35\x2d\x34\x37\ +\x2e\x33\x33\x2c\x31\x35\x2e\x31\x32\x2d\x35\x33\x2e\x31\x33\x2c\ +\x33\x37\x2e\x33\x32\x2d\x36\x2e\x39\x38\x2c\x32\x36\x2e\x37\x2c\ +\x38\x2e\x34\x36\x2c\x35\x33\x2e\x32\x2c\x33\x35\x2e\x35\x35\x2c\ +\x36\x31\x2e\x30\x31\x2c\x32\x39\x2e\x32\x33\x2c\x38\x2e\x34\x32\ +\x2c\x36\x30\x2e\x32\x35\x2d\x36\x2e\x38\x31\x2c\x36\x38\x2e\x35\ +\x2d\x33\x33\x2e\x36\x34\x2c\x39\x2e\x39\x38\x2d\x33\x32\x2e\x34\ +\x36\x2d\x31\x34\x2e\x39\x39\x2d\x36\x34\x2e\x31\x39\x2d\x35\x30\ +\x2e\x39\x31\x2d\x36\x34\x2e\x36\x38\x5a\x22\x2f\x3e\x0a\x3c\x2f\ +\x73\x76\x67\x3e\ \x00\x00\x06\x33\ \x3c\ \x73\x76\x67\x20\x69\x64\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\ @@ -22953,160 +23206,74 @@ \x35\x33\x32\x2e\x39\x36\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ \x34\x31\x38\x2e\x33\x36\x22\x20\x72\x78\x3d\x22\x32\x39\x2e\x31\ \x37\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\ -\x00\x00\x09\x7d\ +\x00\x00\x04\x1d\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ -\x2d\x38\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\ -\x6e\x6f\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\x43\x72\x65\x61\x74\ -\x65\x64\x20\x77\x69\x74\x68\x20\x49\x6e\x6b\x73\x63\x61\x70\x65\ -\x20\x28\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x69\x6e\x6b\ -\x73\x63\x61\x70\x65\x2e\x6f\x72\x67\x2f\x29\x20\x2d\x2d\x3e\x0a\ -\x0a\x3c\x73\x76\x67\x0a\x20\x20\x20\x77\x69\x64\x74\x68\x3d\x22\ -\x31\x33\x38\x2e\x33\x39\x33\x39\x31\x6d\x6d\x22\x0a\x20\x20\x20\ -\x68\x65\x69\x67\x68\x74\x3d\x22\x31\x33\x38\x2e\x34\x37\x31\x30\ -\x35\x6d\x6d\x22\x0a\x20\x20\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\ -\x22\x30\x20\x30\x20\x31\x33\x38\x2e\x33\x39\x33\x39\x31\x20\x31\ -\x33\x38\x2e\x34\x37\x31\x30\x35\x22\x0a\x20\x20\x20\x76\x65\x72\ -\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x0a\x20\x20\x20\x69\x64\ -\x3d\x22\x73\x76\x67\x31\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\ -\x61\x70\x65\x3a\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x34\ -\x20\x28\x38\x36\x61\x38\x61\x64\x37\x2c\x20\x32\x30\x32\x34\x2d\ -\x31\x30\x2d\x31\x31\x29\x22\x0a\x20\x20\x20\x73\x6f\x64\x69\x70\ -\x6f\x64\x69\x3a\x64\x6f\x63\x6e\x61\x6d\x65\x3d\x22\x65\x72\x72\ -\x6f\x72\x2e\x73\x76\x67\x22\x0a\x20\x20\x20\x78\x6d\x6c\x3a\x73\ -\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x0a\ -\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x69\x6e\x6b\x73\x63\x61\x70\ -\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x69\x6e\ -\x6b\x73\x63\x61\x70\x65\x2e\x6f\x72\x67\x2f\x6e\x61\x6d\x65\x73\ -\x70\x61\x63\x65\x73\x2f\x69\x6e\x6b\x73\x63\x61\x70\x65\x22\x0a\ -\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x6f\x64\x69\x70\x6f\x64\ -\x69\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x73\x6f\x64\x69\x70\x6f\ -\x64\x69\x2e\x73\x6f\x75\x72\x63\x65\x66\x6f\x72\x67\x65\x2e\x6e\ -\x65\x74\x2f\x44\x54\x44\x2f\x73\x6f\x64\x69\x70\x6f\x64\x69\x2d\ -\x30\x2e\x64\x74\x64\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3d\ -\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\ -\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x20\x20\ -\x78\x6d\x6c\x6e\x73\x3a\x73\x76\x67\x3d\x22\x68\x74\x74\x70\x3a\ -\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\ -\x30\x2f\x73\x76\x67\x22\x3e\x3c\x73\x6f\x64\x69\x70\x6f\x64\x69\ -\x3a\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x0a\x20\x20\x20\x20\x20\ -\x69\x64\x3d\x22\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x31\x22\x0a\ -\x20\x20\x20\x20\x20\x70\x61\x67\x65\x63\x6f\x6c\x6f\x72\x3d\x22\ -\x23\x66\x66\x66\x66\x66\x66\x22\x0a\x20\x20\x20\x20\x20\x62\x6f\ -\x72\x64\x65\x72\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x30\x30\x30\x30\ -\x30\x30\x22\x0a\x20\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x6f\ -\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x2e\x32\x35\x22\x0a\x20\x20\ -\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x73\x68\x6f\x77\ -\x70\x61\x67\x65\x73\x68\x61\x64\x6f\x77\x3d\x22\x32\x22\x0a\x20\ -\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\x61\x67\ -\x65\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x2e\x30\x22\x0a\x20\ -\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\x61\x67\ -\x65\x63\x68\x65\x63\x6b\x65\x72\x62\x6f\x61\x72\x64\x3d\x22\x30\ -\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\ -\x64\x65\x73\x6b\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x64\x31\x64\x31\ -\x64\x31\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\ -\x65\x3a\x64\x6f\x63\x75\x6d\x65\x6e\x74\x2d\x75\x6e\x69\x74\x73\ -\x3d\x22\x6d\x6d\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\ -\x61\x70\x65\x3a\x7a\x6f\x6f\x6d\x3d\x22\x30\x2e\x37\x33\x34\x39\ -\x35\x33\x37\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x3a\x63\x78\x3d\x22\x34\x34\x32\x2e\x38\x38\x35\x30\x34\ -\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\ -\x63\x79\x3d\x22\x32\x39\x38\x2e\x36\x35\x38\x32\x37\x22\x0a\x20\ -\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\ -\x64\x6f\x77\x2d\x77\x69\x64\x74\x68\x3d\x22\x31\x39\x32\x30\x22\ -\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\ -\x69\x6e\x64\x6f\x77\x2d\x68\x65\x69\x67\x68\x74\x3d\x22\x31\x30\ -\x32\x37\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\ -\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x78\x3d\x22\x31\x39\x31\x32\ -\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\ -\x77\x69\x6e\x64\x6f\x77\x2d\x79\x3d\x22\x32\x32\x22\x0a\x20\x20\ -\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\x64\ -\x6f\x77\x2d\x6d\x61\x78\x69\x6d\x69\x7a\x65\x64\x3d\x22\x31\x22\ -\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\ -\x75\x72\x72\x65\x6e\x74\x2d\x6c\x61\x79\x65\x72\x3d\x22\x73\x76\ -\x67\x31\x22\x3e\x3c\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\x61\ -\x67\x65\x0a\x20\x20\x20\x20\x20\x20\x20\x78\x3d\x22\x30\x22\x0a\ -\x20\x20\x20\x20\x20\x20\x20\x79\x3d\x22\x30\x22\x0a\x20\x20\x20\ -\x20\x20\x20\x20\x77\x69\x64\x74\x68\x3d\x22\x31\x33\x38\x2e\x33\ -\x39\x33\x39\x31\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x68\x65\x69\ -\x67\x68\x74\x3d\x22\x31\x33\x38\x2e\x34\x37\x31\x30\x35\x22\x0a\ -\x20\x20\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x70\x61\x67\x65\x33\ -\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x6d\x61\x72\x67\x69\x6e\x3d\ -\x22\x30\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x62\x6c\x65\x65\x64\ -\x3d\x22\x30\x22\x20\x2f\x3e\x3c\x2f\x73\x6f\x64\x69\x70\x6f\x64\ -\x69\x3a\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x3e\x3c\x64\x65\x66\ -\x73\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x64\x65\x66\x73\x31\ -\x22\x3e\x3c\x73\x74\x79\x6c\x65\x0a\x20\x20\x20\x20\x20\x20\x20\ -\x69\x64\x3d\x22\x73\x74\x79\x6c\x65\x31\x22\x3e\x2e\x63\x6c\x73\ -\x2d\x31\x7b\x66\x69\x6c\x6c\x3a\x23\x65\x30\x65\x30\x64\x66\x3b\ -\x7d\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x3c\x2f\x64\x65\x66\x73\x3e\ -\x3c\x70\x61\x74\x68\x0a\x20\x20\x20\x20\x20\x63\x6c\x61\x73\x73\ -\x3d\x22\x63\x6c\x73\x2d\x31\x22\x0a\x20\x20\x20\x20\x20\x64\x3d\ -\x22\x4d\x20\x30\x2e\x30\x31\x31\x35\x31\x36\x30\x33\x2c\x36\x39\ -\x2e\x32\x33\x34\x38\x33\x31\x20\x43\x20\x2d\x30\x2e\x36\x39\x37\ -\x35\x36\x33\x39\x37\x2c\x33\x31\x2e\x37\x39\x31\x30\x30\x31\x20\ -\x33\x31\x2e\x34\x34\x31\x33\x37\x35\x2c\x2d\x30\x2e\x35\x33\x30\ -\x35\x30\x30\x31\x34\x20\x37\x30\x2e\x30\x32\x30\x32\x36\x35\x2c\ -\x30\x2e\x30\x30\x36\x35\x39\x39\x34\x36\x20\x31\x30\x37\x2e\x35\ -\x31\x34\x33\x38\x2c\x30\x2e\x35\x33\x35\x37\x36\x39\x34\x36\x20\ -\x31\x33\x38\x2e\x34\x31\x37\x37\x31\x2c\x33\x31\x2e\x33\x38\x33\ -\x35\x34\x31\x20\x31\x33\x38\x2e\x33\x39\x33\x39\x2c\x36\x39\x2e\ -\x32\x39\x30\x33\x39\x31\x20\x31\x33\x38\x2e\x33\x37\x30\x31\x2c\ -\x31\x30\x37\x2e\x37\x31\x38\x34\x38\x20\x31\x30\x37\x2e\x33\x38\ -\x32\x30\x39\x2c\x31\x33\x38\x2e\x35\x31\x33\x33\x33\x20\x36\x38\ -\x2e\x37\x37\x36\x37\x32\x35\x2c\x31\x33\x38\x2e\x34\x37\x31\x20\ -\x33\x30\x2e\x37\x31\x36\x34\x31\x35\x2c\x31\x33\x38\x2e\x34\x33\ -\x31\x33\x20\x30\x2e\x30\x31\x31\x35\x31\x36\x30\x33\x2c\x31\x30\ -\x37\x2e\x35\x31\x32\x31\x20\x30\x2e\x30\x31\x31\x35\x31\x36\x30\ -\x33\x2c\x36\x39\x2e\x32\x33\x34\x38\x33\x31\x20\x5a\x20\x4d\x20\ -\x38\x33\x2e\x31\x34\x38\x38\x39\x35\x2c\x34\x36\x2e\x36\x38\x37\ -\x30\x34\x31\x20\x63\x20\x30\x2c\x2d\x31\x32\x2e\x33\x33\x32\x32\ -\x33\x20\x2d\x30\x2e\x30\x34\x37\x36\x2c\x2d\x32\x34\x2e\x36\x36\ -\x37\x31\x31\x20\x30\x2e\x30\x34\x37\x36\x2c\x2d\x33\x36\x2e\x39\ -\x39\x39\x33\x34\x31\x36\x20\x30\x2e\x30\x31\x35\x39\x2c\x2d\x31\ -\x2e\x39\x32\x38\x38\x31\x20\x2d\x30\x2e\x35\x32\x39\x31\x36\x2c\ -\x2d\x32\x2e\x33\x38\x31\x32\x35\x20\x2d\x32\x2e\x33\x38\x31\x32\ -\x35\x2c\x2d\x32\x2e\x33\x34\x36\x38\x35\x20\x2d\x37\x2e\x38\x34\ -\x37\x35\x34\x2c\x30\x2e\x31\x30\x33\x31\x39\x20\x2d\x31\x35\x2e\ -\x36\x39\x37\x37\x33\x2c\x30\x2e\x30\x37\x39\x34\x20\x2d\x32\x33\ -\x2e\x35\x34\x37\x39\x31\x2c\x30\x20\x2d\x31\x2e\x35\x31\x36\x30\ -\x37\x2c\x2d\x30\x2e\x30\x31\x33\x32\x20\x2d\x32\x2e\x30\x33\x37\ -\x32\x39\x2c\x30\x2e\x33\x33\x30\x37\x33\x20\x2d\x32\x2e\x30\x33\ -\x34\x36\x35\x2c\x31\x2e\x39\x36\x35\x38\x35\x20\x71\x20\x30\x2e\ -\x30\x38\x32\x2c\x33\x37\x2e\x34\x37\x38\x32\x33\x31\x36\x20\x30\ -\x2c\x37\x34\x2e\x39\x35\x36\x34\x36\x31\x36\x20\x63\x20\x30\x2c\ -\x31\x2e\x36\x34\x33\x30\x36\x20\x30\x2e\x35\x32\x39\x31\x37\x2c\ -\x31\x2e\x39\x34\x37\x33\x34\x20\x32\x2e\x30\x32\x34\x30\x36\x2c\ -\x31\x2e\x39\x33\x34\x31\x31\x20\x37\x2e\x38\x34\x37\x35\x35\x2c\ -\x2d\x30\x2e\x30\x36\x38\x38\x20\x31\x35\x2e\x37\x30\x30\x33\x38\ -\x2c\x2d\x30\x2e\x31\x32\x31\x37\x31\x20\x32\x33\x2e\x35\x34\x37\ -\x39\x32\x2c\x30\x2e\x30\x32\x36\x35\x20\x32\x2e\x31\x31\x36\x36\ -\x37\x2c\x30\x2e\x30\x33\x39\x37\x20\x32\x2e\x34\x31\x33\x2c\x2d\ -\x30\x2e\x36\x36\x36\x37\x35\x20\x32\x2e\x33\x39\x39\x37\x37\x2c\ -\x2d\x32\x2e\x35\x35\x30\x35\x39\x20\x2d\x30\x2e\x30\x39\x37\x39\ -\x2c\x2d\x31\x32\x2e\x33\x32\x31\x36\x34\x20\x2d\x30\x2e\x30\x35\ -\x35\x36\x2c\x2d\x32\x34\x2e\x36\x35\x33\x38\x37\x20\x2d\x30\x2e\ -\x30\x35\x35\x36\x2c\x2d\x33\x36\x2e\x39\x38\x36\x31\x20\x7a\x20\ -\x6d\x20\x2d\x31\x34\x2e\x33\x30\x38\x36\x37\x2c\x38\x30\x2e\x39\ -\x36\x32\x34\x39\x39\x20\x63\x20\x37\x2e\x39\x37\x31\x39\x2c\x30\ -\x2e\x31\x30\x38\x34\x38\x20\x31\x34\x2e\x39\x30\x39\x32\x37\x2c\ -\x2d\x34\x2e\x37\x36\x32\x35\x20\x31\x36\x2e\x37\x33\x37\x35\x34\ -\x2c\x2d\x31\x31\x2e\x37\x35\x35\x34\x34\x20\x32\x2e\x31\x39\x36\ -\x30\x35\x2c\x2d\x38\x2e\x34\x31\x31\x31\x20\x2d\x32\x2e\x36\x36\ -\x37\x2c\x2d\x31\x36\x2e\x37\x35\x38\x37\x30\x37\x20\x2d\x31\x31\ -\x2e\x31\x39\x37\x31\x36\x2c\x2d\x31\x39\x2e\x32\x31\x36\x36\x38\ -\x37\x20\x2d\x39\x2e\x32\x30\x37\x35\x2c\x2d\x32\x2e\x36\x34\x35\ -\x38\x33\x20\x2d\x31\x38\x2e\x39\x37\x38\x35\x37\x2c\x32\x2e\x31\ -\x34\x35\x37\x37\x20\x2d\x32\x31\x2e\x35\x37\x36\x37\x37\x2c\x31\ -\x30\x2e\x35\x39\x36\x35\x36\x37\x20\x2d\x33\x2e\x31\x34\x35\x39\ -\x2c\x31\x30\x2e\x32\x31\x32\x39\x31\x20\x34\x2e\x37\x32\x32\x38\ -\x31\x2c\x32\x30\x2e\x32\x30\x38\x38\x37\x20\x31\x36\x2e\x30\x33\ -\x36\x33\x39\x2c\x32\x30\x2e\x33\x36\x32\x33\x33\x20\x7a\x22\x0a\ -\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x70\x61\x74\x68\x31\x22\x0a\ -\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x72\x6f\ -\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x30\x2e\x32\x36\x34\x35\x38\ -\x33\x22\x20\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\x0a\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\ +\x61\x79\x65\x72\x5f\x31\x22\x20\x64\x61\x74\x61\x2d\x6e\x61\x6d\ +\x65\x3d\x22\x4c\x61\x79\x65\x72\x20\x31\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\ +\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x76\ +\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x36\x30\x30\x20\ +\x36\x30\x30\x22\x3e\x0a\x20\x20\x3c\x64\x65\x66\x73\x3e\x0a\x20\ +\x20\x20\x20\x3c\x73\x74\x79\x6c\x65\x3e\x0a\x20\x20\x20\x20\x20\ +\x20\x2e\x63\x6c\x73\x2d\x31\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x20\x66\x69\x6c\x6c\x3a\x20\x23\x65\x30\x65\x30\x64\x66\x3b\ +\x0a\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x73\ +\x74\x79\x6c\x65\x3e\x0a\x20\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0a\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\ +\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x32\x36\x32\x2e\x30\x35\ +\x2c\x33\x35\x39\x2e\x35\x31\x63\x32\x34\x2e\x39\x32\x2d\x2e\x32\ +\x32\x2c\x34\x39\x2e\x38\x34\x2d\x2e\x33\x39\x2c\x37\x34\x2e\x37\ +\x35\x2e\x30\x38\x2c\x36\x2e\x37\x34\x2e\x31\x33\x2c\x37\x2e\x36\ +\x37\x2d\x32\x2e\x31\x32\x2c\x37\x2e\x36\x32\x2d\x38\x2e\x31\x2d\ +\x2e\x32\x37\x2d\x33\x39\x2e\x31\x35\x2d\x2e\x31\x34\x2d\x37\x38\ +\x2e\x33\x2d\x2e\x31\x34\x2d\x31\x31\x37\x2e\x34\x35\x73\x2d\x2e\ +\x31\x35\x2d\x37\x38\x2e\x33\x2e\x31\x35\x2d\x31\x31\x37\x2e\x34\ +\x35\x63\x2e\x30\x35\x2d\x36\x2e\x31\x33\x2d\x31\x2e\x37\x31\x2d\ +\x37\x2e\x35\x33\x2d\x37\x2e\x36\x2d\x37\x2e\x34\x36\x2d\x32\x34\ +\x2e\x39\x32\x2e\x33\x33\x2d\x34\x39\x2e\x38\x34\x2e\x32\x36\x2d\ +\x37\x34\x2e\x37\x36\x2e\x30\x34\x2d\x34\x2e\x38\x32\x2d\x2e\x30\ +\x34\x2d\x36\x2e\x34\x37\x2c\x31\x2e\x30\x35\x2d\x36\x2e\x34\x36\ +\x2c\x36\x2e\x32\x34\x2e\x31\x37\x2c\x37\x39\x2e\x33\x32\x2e\x31\ +\x37\x2c\x31\x35\x38\x2e\x36\x34\x2c\x30\x2c\x32\x33\x37\x2e\x39\ +\x36\x2d\x2e\x30\x31\x2c\x35\x2e\x32\x32\x2c\x31\x2e\x36\x36\x2c\ +\x36\x2e\x31\x39\x2c\x36\x2e\x34\x33\x2c\x36\x2e\x31\x34\x5a\x22\ +\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x32\x35\x2e\ +\x37\x36\x2c\x33\x30\x35\x2e\x36\x32\x63\x30\x2c\x31\x35\x31\x2e\ +\x37\x34\x2c\x31\x32\x31\x2e\x37\x31\x2c\x32\x37\x34\x2e\x32\x39\ +\x2c\x32\x37\x32\x2e\x35\x37\x2c\x32\x37\x34\x2e\x34\x36\x2c\x31\ +\x35\x33\x2e\x30\x33\x2e\x31\x36\x2c\x32\x37\x35\x2e\x38\x36\x2d\ +\x31\x32\x31\x2e\x39\x2c\x32\x37\x35\x2e\x39\x36\x2d\x32\x37\x34\ +\x2e\x32\x34\x2e\x31\x2d\x31\x35\x30\x2e\x32\x36\x2d\x31\x32\x32\ +\x2e\x34\x2d\x32\x37\x32\x2e\x35\x36\x2d\x32\x37\x31\x2e\x30\x32\ +\x2d\x32\x37\x34\x2e\x36\x33\x43\x31\x35\x30\x2e\x33\x35\x2c\x32\ +\x39\x2e\x30\x38\x2c\x32\x32\x2e\x39\x35\x2c\x31\x35\x37\x2e\x32\ +\x2c\x32\x35\x2e\x37\x36\x2c\x33\x30\x35\x2e\x36\x32\x5a\x4d\x33\ +\x30\x32\x2e\x36\x32\x2c\x38\x35\x2e\x38\x35\x63\x31\x31\x39\x2e\ +\x30\x33\x2c\x31\x2e\x36\x36\x2c\x32\x31\x37\x2e\x31\x33\x2c\x39\ +\x39\x2e\x36\x31\x2c\x32\x31\x37\x2e\x30\x36\x2c\x32\x31\x39\x2e\ +\x39\x35\x2d\x2e\x30\x38\x2c\x31\x32\x32\x2d\x39\x38\x2e\x34\x35\ +\x2c\x32\x31\x39\x2e\x37\x36\x2d\x32\x32\x31\x2e\x30\x31\x2c\x32\ +\x31\x39\x2e\x36\x33\x2d\x31\x32\x30\x2e\x38\x32\x2d\x2e\x31\x33\ +\x2d\x32\x31\x38\x2e\x33\x2d\x39\x38\x2e\x32\x38\x2d\x32\x31\x38\ +\x2e\x33\x2d\x32\x31\x39\x2e\x38\x31\x2d\x32\x2e\x32\x35\x2d\x31\ +\x31\x38\x2e\x38\x37\x2c\x39\x39\x2e\x37\x38\x2d\x32\x32\x31\x2e\ +\x34\x37\x2c\x32\x32\x32\x2e\x32\x35\x2d\x32\x31\x39\x2e\x37\x37\ +\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x32\ +\x39\x38\x2e\x38\x37\x2c\x34\x39\x31\x2e\x30\x33\x63\x32\x35\x2e\ +\x33\x31\x2e\x33\x35\x2c\x34\x37\x2e\x33\x33\x2d\x31\x35\x2e\x31\ +\x32\x2c\x35\x33\x2e\x31\x33\x2d\x33\x37\x2e\x33\x32\x2c\x36\x2e\ +\x39\x38\x2d\x32\x36\x2e\x37\x2d\x38\x2e\x34\x36\x2d\x35\x33\x2e\ +\x32\x2d\x33\x35\x2e\x35\x35\x2d\x36\x31\x2e\x30\x31\x2d\x32\x39\ +\x2e\x32\x33\x2d\x38\x2e\x34\x32\x2d\x36\x30\x2e\x32\x35\x2c\x36\ +\x2e\x38\x31\x2d\x36\x38\x2e\x35\x2c\x33\x33\x2e\x36\x34\x2d\x39\ +\x2e\x39\x38\x2c\x33\x32\x2e\x34\x36\x2c\x31\x34\x2e\x39\x39\x2c\ +\x36\x34\x2e\x31\x39\x2c\x35\x30\x2e\x39\x31\x2c\x36\x34\x2e\x36\ +\x38\x5a\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\ \x00\x00\x05\xf3\ \x3c\ \x73\x76\x67\x20\x69\x64\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\ @@ -25210,6 +25377,10 @@ \x08\xf1\x7e\xd4\ \x00\x66\ \x00\x69\x00\x6c\x00\x61\x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x5f\x00\x72\x00\x65\x00\x6c\x00\x61\x00\x74\x00\x65\x00\x64\ +\x00\x09\ +\x09\xf6\x7a\x20\ +\x00\x62\ +\x00\x61\x00\x62\x00\x79\x00\x5f\x00\x73\x00\x74\x00\x65\x00\x70\ \x00\x11\ \x0b\x8b\xba\x63\ \x00\x6c\ @@ -25365,6 +25536,16 @@ \x0c\x4a\x5a\x07\ \x00\x65\ \x00\x6e\x00\x67\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x14\ +\x02\x84\x9e\x27\ +\x00\x6d\ +\x00\x6f\x00\x76\x00\x65\x00\x5f\x00\x6e\x00\x6f\x00\x7a\x00\x7a\x00\x6c\x00\x65\x00\x5f\x00\x61\x00\x77\x00\x61\x00\x79\x00\x2e\ +\x00\x73\x00\x76\x00\x67\ +\x00\x15\ +\x06\x1c\x09\x47\ +\x00\x6d\ +\x00\x6f\x00\x76\x00\x65\x00\x5f\x00\x6e\x00\x6f\x00\x7a\x00\x7a\x00\x6c\x00\x65\x00\x5f\x00\x63\x00\x6c\x00\x6f\x00\x73\x00\x65\ +\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x06\ \x07\xb6\x68\x82\ \x00\x74\ @@ -25442,6 +25623,10 @@ \x0c\x81\x5a\x07\ \x00\x66\ \x00\x61\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0a\ +\x0d\xc8\x7c\x07\ +\x00\x62\ +\x00\x6c\x00\x6f\x00\x77\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x11\ \x00\xdf\x07\x87\ \x00\x63\ @@ -25691,10 +25876,6 @@ \x0b\xad\x9f\xa7\ \x00\x63\ \x00\x6f\x00\x6f\x00\x6c\x00\x64\x00\x6f\x00\x77\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ -\x00\x0a\ -\x0d\xc8\x7c\x07\ -\x00\x62\ -\x00\x6c\x00\x6f\x00\x77\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x11\ \x0f\x7b\x93\xc7\ \x00\x68\ @@ -25875,579 +26056,597 @@ " qt_resource_struct_v1 = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0f\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x94\ -\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x91\ -\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x85\ -\x00\x00\x00\x46\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7a\ -\x00\x00\x00\x5a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x76\ -\x00\x00\x00\x6c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x70\ -\x00\x00\x00\x7e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x62\ -\x00\x00\x00\x90\x00\x02\x00\x00\x00\x01\x00\x00\x00\x58\ -\x00\x00\x00\xa2\x00\x02\x00\x00\x00\x01\x00\x00\x00\x49\ -\x00\x00\x00\xc0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x45\ -\x00\x00\x00\xdc\x00\x02\x00\x00\x00\x01\x00\x00\x00\x35\ -\x00\x00\x01\x02\x00\x02\x00\x00\x00\x01\x00\x00\x00\x31\ -\x00\x00\x01\x2a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x25\ -\x00\x00\x01\x50\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1f\ -\x00\x00\x01\x6c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x10\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x11\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x12\ -\x00\x00\x01\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x08\x66\ -\x00\x00\x01\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x09\x52\ -\x00\x00\x02\x20\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x35\ -\x00\x00\x02\x48\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x18\ -\x00\x00\x02\x70\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x06\ -\x00\x00\x02\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x14\x42\ -\x00\x00\x02\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xf7\ -\x00\x00\x02\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x22\x41\ -\x00\x00\x03\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x26\x90\ -\x00\x00\x03\x36\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6e\ -\x00\x00\x03\x52\x00\x00\x00\x00\x00\x01\x00\x00\x30\xca\ -\x00\x00\x03\x7a\x00\x00\x00\x00\x00\x01\x00\x00\x32\xb1\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x21\ -\x00\x00\x03\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x55\ -\x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x3c\xcf\ -\x00\x00\x03\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x43\ -\x00\x00\x03\xfe\x00\x00\x00\x00\x00\x01\x00\x00\x41\xbf\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x26\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x27\ -\x00\x00\x04\x20\x00\x00\x00\x00\x00\x01\x00\x00\x44\x3b\ -\x00\x00\x04\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x45\x3c\ -\x00\x00\x04\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x54\ -\x00\x00\x04\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x56\x16\ -\x00\x00\x04\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x58\x86\ -\x00\x00\x04\xea\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x1e\ -\x00\x00\x05\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x62\x7a\ -\x00\x00\x05\x48\x00\x00\x00\x00\x00\x01\x00\x00\x69\x07\ -\x00\x00\x05\x64\x00\x00\x00\x00\x00\x01\x00\x00\x6c\x03\ -\x00\x00\x05\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x6d\x01\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x32\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x33\ -\x00\x00\x05\xbe\x00\x01\x00\x00\x00\x01\x00\x00\x77\x6b\ -\x00\x00\x05\xd0\x00\x00\x00\x00\x00\x01\x00\x02\x3a\xc0\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x02\x00\x00\x00\x36\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x06\x00\x00\x00\x3f\ -\x00\x00\x05\xe4\x00\x02\x00\x00\x00\x07\x00\x00\x00\x38\ -\x00\x00\x05\xf6\x00\x00\x00\x00\x00\x01\x00\x02\x3f\x46\ -\x00\x00\x06\x2a\x00\x00\x00\x00\x00\x01\x00\x02\x49\xaa\ -\x00\x00\x06\x5e\x00\x00\x00\x00\x00\x01\x00\x02\x53\xd3\ -\x00\x00\x06\x98\x00\x00\x00\x00\x00\x01\x00\x02\x5e\x2b\ -\x00\x00\x06\xcc\x00\x00\x00\x00\x00\x01\x00\x02\x68\x43\ -\x00\x00\x07\x02\x00\x00\x00\x00\x00\x01\x00\x02\x72\x6c\ -\x00\x00\x07\x3a\x00\x00\x00\x00\x00\x01\x00\x02\x7c\x82\ -\x00\x00\x07\x70\x00\x00\x00\x00\x00\x01\x00\x02\x86\xe8\ -\x00\x00\x07\x98\x00\x00\x00\x00\x00\x01\x00\x02\x91\x17\ -\x00\x00\x07\xc4\x00\x00\x00\x00\x00\x01\x00\x02\x9b\x5e\ -\x00\x00\x08\x00\x00\x00\x00\x00\x00\x01\x00\x02\x9d\x5f\ -\x00\x00\x08\x2c\x00\x00\x00\x00\x00\x01\x00\x02\xb8\xa4\ -\x00\x00\x08\x58\x00\x00\x00\x00\x00\x01\x00\x02\xbd\xed\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x46\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x47\ -\x00\x00\x08\x8c\x00\x00\x00\x00\x00\x01\x00\x02\xc1\x65\ -\x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x02\xca\x47\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4a\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x4b\ -\x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x02\xcf\x7c\ -\x00\x00\x08\xe6\x00\x00\x00\x00\x00\x01\x00\x02\xd4\x77\ -\x00\x00\x09\x1e\x00\x00\x00\x00\x00\x01\x00\x02\xdd\x4b\ -\x00\x00\x09\x56\x00\x00\x00\x00\x00\x01\x00\x02\xe5\xef\ -\x00\x00\x09\x86\x00\x00\x00\x00\x00\x01\x00\x02\xed\x8e\ -\x00\x00\x09\xaa\x00\x00\x00\x00\x00\x01\x00\x02\xf5\x6a\ -\x00\x00\x09\xce\x00\x00\x00\x00\x00\x01\x00\x02\xfd\x20\ -\x00\x00\x0a\x02\x00\x00\x00\x00\x00\x01\x00\x03\x05\x2e\ -\x00\x00\x0a\x36\x00\x00\x00\x00\x00\x01\x00\x03\x0d\x10\ -\x00\x00\x0a\x6a\x00\x00\x00\x00\x00\x01\x00\x03\x14\xda\ -\x00\x00\x0a\xa4\x00\x00\x00\x00\x00\x01\x00\x03\x1c\x41\ -\x00\x00\x0a\xc8\x00\x00\x00\x00\x00\x01\x00\x03\x1f\xfe\ -\x00\x00\x0a\xf6\x00\x00\x00\x00\x00\x01\x00\x03\x23\xd7\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x59\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x08\x00\x00\x00\x5a\ -\x00\x00\x0b\x1c\x00\x00\x00\x00\x00\x01\x00\x03\x29\xe0\ -\x00\x00\x0b\x48\x00\x00\x00\x00\x00\x01\x00\x03\x4c\x64\ -\x00\x00\x0b\x76\x00\x00\x00\x00\x00\x01\x00\x03\x52\x51\ -\x00\x00\x0b\xa0\x00\x00\x00\x00\x00\x01\x00\x03\x54\x71\ -\x00\x00\x0b\xc8\x00\x00\x00\x00\x00\x01\x00\x03\x5d\x09\ -\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x6c\x52\ -\x00\x00\x0c\x0e\x00\x00\x00\x00\x00\x01\x00\x03\x72\xd0\ -\x00\x00\x0c\x36\x00\x00\x00\x00\x00\x01\x00\x03\x7d\x4a\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x63\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x64\ -\x00\x00\x0c\x6c\x00\x00\x00\x00\x00\x01\x00\x03\x86\x91\ -\x00\x00\x0c\x9a\x00\x00\x00\x00\x00\x01\x00\x03\x89\x38\ -\x00\x00\x0c\xc8\x00\x01\x00\x00\x00\x01\x00\x03\x95\xb5\ -\x00\x00\x0c\xf4\x00\x00\x00\x00\x00\x01\x00\x03\xc3\x34\ -\x00\x00\x0d\x14\x00\x00\x00\x00\x00\x01\x00\x03\xc7\xeb\ -\x00\x00\x0d\x46\x00\x01\x00\x00\x00\x01\x00\x04\x20\xe4\ -\x00\x00\x0d\x78\x00\x00\x00\x00\x00\x01\x00\x04\x55\x7e\ -\x00\x00\x0d\x92\x00\x00\x00\x00\x00\x01\x00\x04\x5a\xc8\ -\x00\x00\x0d\xac\x00\x00\x00\x00\x00\x01\x00\x04\x60\x57\ -\x00\x00\x0d\xc6\x00\x00\x00\x00\x00\x01\x00\x04\x65\xc0\ -\x00\x00\x0d\xde\x00\x00\x00\x00\x00\x01\x00\x04\x71\x9e\ -\x00\x00\x0d\xfc\x00\x00\x00\x00\x00\x01\x00\x04\x77\xa2\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x71\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x72\ -\x00\x00\x0e\x24\x00\x00\x00\x00\x00\x01\x00\x04\x7c\xd7\ -\x00\x00\x0e\x38\x00\x00\x00\x00\x00\x01\x00\x04\x82\xd4\ -\x00\x00\x0e\x4a\x00\x00\x00\x00\x00\x01\x00\x04\x84\x5a\ -\x00\x00\x0e\x5c\x00\x00\x00\x00\x00\x01\x00\x04\x8a\x54\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x77\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x78\ -\x00\x00\x0e\x70\x00\x00\x00\x00\x00\x01\x00\x04\x8c\xaa\ -\x00\x00\x0e\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x93\x91\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7b\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x09\x00\x00\x00\x7c\ -\x00\x00\x0e\xc0\x00\x00\x00\x00\x00\x01\x00\x04\x9c\xf5\ -\x00\x00\x0e\xe0\x00\x01\x00\x00\x00\x01\x00\x04\x9f\xa1\ -\x00\x00\x0f\x04\x00\x00\x00\x00\x00\x01\x00\x04\xaa\xf2\ -\x00\x00\x0f\x26\x00\x00\x00\x00\x00\x01\x00\x04\xb2\x97\ -\x00\x00\x0f\x42\x00\x00\x00\x00\x00\x01\x00\x04\xc3\x97\ -\x00\x00\x0f\x66\x00\x00\x00\x00\x00\x01\x00\x04\xcd\x18\ -\x00\x00\x0f\x86\x00\x00\x00\x00\x00\x01\x00\x04\xd2\xb5\ -\x00\x00\x0f\xae\x00\x00\x00\x00\x00\x01\x00\x04\xdc\x7e\ -\x00\x00\x0f\xce\x00\x00\x00\x00\x00\x01\x00\x04\xe0\x61\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x86\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x87\ -\x00\x00\x0f\xea\x00\x00\x00\x00\x00\x01\x00\x04\xe6\x9c\ -\x00\x00\x10\x1a\x00\x00\x00\x00\x00\x01\x00\x04\xf0\x32\ -\x00\x00\x10\x4a\x00\x00\x00\x00\x00\x01\x00\x04\xfc\x0e\ -\x00\x00\x10\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x02\x52\ -\x00\x00\x10\x98\x00\x00\x00\x00\x00\x01\x00\x05\x09\xdb\ -\x00\x00\x10\xc4\x00\x00\x00\x00\x00\x01\x00\x05\x10\x39\ -\x00\x00\x10\xfa\x00\x00\x00\x00\x00\x01\x00\x05\x18\x28\ -\x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x25\xcb\ -\x00\x00\x11\x18\x00\x00\x00\x00\x00\x01\x00\x05\x2b\x00\ -\x00\x00\x11\x32\x00\x00\x00\x00\x00\x01\x00\x05\x34\xd5\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x92\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x01\x00\x00\x00\x93\ -\x00\x00\x11\x5a\x00\x00\x00\x00\x00\x01\x00\x05\x3c\x9f\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x95\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x28\x00\x00\x00\x96\ -\x00\x00\x11\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x41\x65\ -\x00\x00\x11\x90\x00\x00\x00\x00\x00\x01\x00\x05\x49\x19\ -\x00\x00\x11\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x4b\x3e\ -\x00\x00\x11\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x4c\xbe\ -\x00\x00\x11\xfc\x00\x00\x00\x00\x00\x01\x00\x05\x54\x6b\ -\x00\x00\x12\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x59\x40\ -\x00\x00\x12\x32\x00\x00\x00\x00\x00\x01\x00\x05\x5a\x30\ -\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x5d\x14\ -\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x63\x4b\ -\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x78\x58\ -\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x7d\x48\ -\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x80\x47\ -\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x86\x51\ -\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x89\xa0\ -\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x93\x21\ -\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\x99\x18\ -\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\x9b\x29\ -\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\x9e\xec\ -\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xa0\ -\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xab\xee\ -\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xaf\x7c\ -\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xb4\x1d\ -\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xbd\xef\ -\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xc3\x39\ -\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xc9\x40\ -\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xca\x24\ -\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xcc\x6d\ -\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd3\x1d\ -\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x61\ -\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xd7\x8d\ -\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xdd\x4e\ -\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xde\x70\ -\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xe4\x63\ -\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xe7\x67\ -\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x88\ -\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xeb\x5c\ -\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xf3\xc8\ -\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x05\xfb\x88\ -\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x00\xa3\ -\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x01\xf6\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x10\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x9a\ +\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x97\ +\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x8b\ +\x00\x00\x00\x46\x00\x02\x00\x00\x00\x01\x00\x00\x00\x80\ +\x00\x00\x00\x5a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7c\ +\x00\x00\x00\x6c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x76\ +\x00\x00\x00\x7e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x68\ +\x00\x00\x00\x90\x00\x02\x00\x00\x00\x01\x00\x00\x00\x5e\ +\x00\x00\x00\xa2\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4f\ +\x00\x00\x00\xc0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4a\ +\x00\x00\x00\xdc\x00\x02\x00\x00\x00\x01\x00\x00\x00\x3a\ +\x00\x00\x01\x02\x00\x02\x00\x00\x00\x01\x00\x00\x00\x36\ +\x00\x00\x01\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x32\ +\x00\x00\x01\x42\x00\x02\x00\x00\x00\x01\x00\x00\x00\x26\ +\x00\x00\x01\x68\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\ +\x00\x00\x01\x84\x00\x02\x00\x00\x00\x01\x00\x00\x00\x11\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x12\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x13\ +\x00\x00\x01\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x08\x66\ +\x00\x00\x02\x10\x00\x00\x00\x00\x00\x01\x00\x00\x09\x52\ +\x00\x00\x02\x38\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x35\ +\x00\x00\x02\x60\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x18\ +\x00\x00\x02\x88\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x06\ +\x00\x00\x02\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x14\x42\ +\x00\x00\x02\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xf7\ +\x00\x00\x03\x12\x00\x00\x00\x00\x00\x01\x00\x00\x22\x41\ +\x00\x00\x03\x32\x00\x00\x00\x00\x00\x01\x00\x00\x26\x90\ +\x00\x00\x03\x4e\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6e\ +\x00\x00\x03\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x30\xca\ +\x00\x00\x03\x92\x00\x00\x00\x00\x00\x01\x00\x00\x32\xb1\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x21\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x04\x00\x00\x00\x22\ +\x00\x00\x03\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x55\ +\x00\x00\x03\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x3c\xcf\ +\x00\x00\x03\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x43\ +\x00\x00\x04\x16\x00\x00\x00\x00\x00\x01\x00\x00\x41\xbf\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x27\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x28\ +\x00\x00\x04\x38\x00\x00\x00\x00\x00\x01\x00\x00\x44\x3b\ +\x00\x00\x04\x52\x00\x00\x00\x00\x00\x01\x00\x00\x45\x3c\ +\x00\x00\x04\x82\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x54\ +\x00\x00\x04\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x56\x16\ +\x00\x00\x04\xdc\x00\x00\x00\x00\x00\x01\x00\x00\x58\x86\ +\x00\x00\x05\x02\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x1e\ +\x00\x00\x05\x26\x00\x00\x00\x00\x00\x01\x00\x00\x62\x7a\ +\x00\x00\x05\x60\x00\x00\x00\x00\x00\x01\x00\x00\x69\x07\ +\x00\x00\x05\x7c\x00\x00\x00\x00\x00\x01\x00\x00\x6c\x03\ +\x00\x00\x05\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x6d\x01\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x33\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x34\ +\x00\x00\x05\xd6\x00\x01\x00\x00\x00\x01\x00\x00\x77\x6b\ +\x00\x00\x05\xe8\x00\x00\x00\x00\x00\x01\x00\x02\x3a\xc0\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x37\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x38\ +\x00\x00\x05\xfc\x00\x00\x00\x00\x00\x01\x00\x02\x3f\x46\ +\x00\x00\x06\x2a\x00\x00\x00\x00\x00\x01\x00\x02\x41\x71\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x3b\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x06\x00\x00\x00\x44\ +\x00\x00\x06\x5a\x00\x02\x00\x00\x00\x07\x00\x00\x00\x3d\ +\x00\x00\x06\x6c\x00\x00\x00\x00\x00\x01\x00\x02\x43\x77\ +\x00\x00\x06\xa0\x00\x00\x00\x00\x00\x01\x00\x02\x4d\xdb\ +\x00\x00\x06\xd4\x00\x00\x00\x00\x00\x01\x00\x02\x58\x04\ +\x00\x00\x07\x0e\x00\x00\x00\x00\x00\x01\x00\x02\x62\x5c\ +\x00\x00\x07\x42\x00\x00\x00\x00\x00\x01\x00\x02\x6c\x74\ +\x00\x00\x07\x78\x00\x00\x00\x00\x00\x01\x00\x02\x76\x9d\ +\x00\x00\x07\xb0\x00\x00\x00\x00\x00\x01\x00\x02\x80\xb3\ +\x00\x00\x07\xe6\x00\x00\x00\x00\x00\x01\x00\x02\x8b\x19\ +\x00\x00\x08\x0e\x00\x00\x00\x00\x00\x01\x00\x02\x95\x48\ +\x00\x00\x08\x3a\x00\x00\x00\x00\x00\x01\x00\x02\x9f\x8f\ +\x00\x00\x08\x76\x00\x00\x00\x00\x00\x01\x00\x02\xa1\x90\ +\x00\x00\x08\xa2\x00\x00\x00\x00\x00\x01\x00\x02\xbc\xd5\ +\x00\x00\x08\xce\x00\x00\x00\x00\x00\x01\x00\x02\xc2\x1e\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4b\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x03\x00\x00\x00\x4c\ +\x00\x00\x09\x02\x00\x00\x00\x00\x00\x01\x00\x02\xc5\x96\ +\x00\x00\x09\x20\x00\x00\x00\x00\x00\x01\x00\x02\xce\x78\ +\x00\x00\x09\x34\x00\x00\x00\x00\x00\x01\x00\x02\xd3\xad\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x50\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x51\ +\x00\x00\x09\x4e\x00\x00\x00\x00\x00\x01\x00\x02\xdd\x82\ +\x00\x00\x09\x76\x00\x00\x00\x00\x00\x01\x00\x02\xe2\x7d\ +\x00\x00\x09\xae\x00\x00\x00\x00\x00\x01\x00\x02\xeb\x51\ +\x00\x00\x09\xe6\x00\x00\x00\x00\x00\x01\x00\x02\xf3\xf5\ +\x00\x00\x0a\x16\x00\x00\x00\x00\x00\x01\x00\x02\xfb\x94\ +\x00\x00\x0a\x3a\x00\x00\x00\x00\x00\x01\x00\x03\x03\x70\ +\x00\x00\x0a\x5e\x00\x00\x00\x00\x00\x01\x00\x03\x0b\x26\ +\x00\x00\x0a\x92\x00\x00\x00\x00\x00\x01\x00\x03\x13\x34\ +\x00\x00\x0a\xc6\x00\x00\x00\x00\x00\x01\x00\x03\x1b\x16\ +\x00\x00\x0a\xfa\x00\x00\x00\x00\x00\x01\x00\x03\x22\xe0\ +\x00\x00\x0b\x34\x00\x00\x00\x00\x00\x01\x00\x03\x2a\x47\ +\x00\x00\x0b\x58\x00\x00\x00\x00\x00\x01\x00\x03\x2e\x04\ +\x00\x00\x0b\x86\x00\x00\x00\x00\x00\x01\x00\x03\x31\xdd\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x5f\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x08\x00\x00\x00\x60\ +\x00\x00\x0b\xac\x00\x00\x00\x00\x00\x01\x00\x03\x37\xe6\ +\x00\x00\x0b\xd8\x00\x00\x00\x00\x00\x01\x00\x03\x5a\x6a\ +\x00\x00\x0c\x06\x00\x00\x00\x00\x00\x01\x00\x03\x60\x57\ +\x00\x00\x0c\x30\x00\x00\x00\x00\x00\x01\x00\x03\x62\x77\ +\x00\x00\x0c\x58\x00\x00\x00\x00\x00\x01\x00\x03\x6b\x0f\ +\x00\x00\x0c\x72\x00\x00\x00\x00\x00\x01\x00\x03\x7a\x58\ +\x00\x00\x0c\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x80\xd6\ +\x00\x00\x0c\xc6\x00\x00\x00\x00\x00\x01\x00\x03\x8b\x50\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x69\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x6a\ +\x00\x00\x0c\xfc\x00\x00\x00\x00\x00\x01\x00\x03\x94\x97\ +\x00\x00\x0d\x2a\x00\x00\x00\x00\x00\x01\x00\x03\x97\x3e\ +\x00\x00\x0d\x58\x00\x01\x00\x00\x00\x01\x00\x03\xa3\xbb\ +\x00\x00\x0d\x84\x00\x00\x00\x00\x00\x01\x00\x03\xd1\x3a\ +\x00\x00\x0d\xa4\x00\x00\x00\x00\x00\x01\x00\x03\xd5\xf1\ +\x00\x00\x0d\xd6\x00\x01\x00\x00\x00\x01\x00\x04\x2e\xea\ +\x00\x00\x0e\x08\x00\x00\x00\x00\x00\x01\x00\x04\x63\x84\ +\x00\x00\x0e\x22\x00\x00\x00\x00\x00\x01\x00\x04\x68\xce\ +\x00\x00\x0e\x3c\x00\x00\x00\x00\x00\x01\x00\x04\x6e\x5d\ +\x00\x00\x0e\x56\x00\x00\x00\x00\x00\x01\x00\x04\x73\xc6\ +\x00\x00\x0e\x6e\x00\x00\x00\x00\x00\x01\x00\x04\x7f\xa4\ +\x00\x00\x0e\x8c\x00\x00\x00\x00\x00\x01\x00\x04\x85\xa8\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x77\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x04\x00\x00\x00\x78\ +\x00\x00\x0e\xb4\x00\x00\x00\x00\x00\x01\x00\x04\x8a\xdd\ +\x00\x00\x0e\xc8\x00\x00\x00\x00\x00\x01\x00\x04\x90\xda\ +\x00\x00\x0e\xda\x00\x00\x00\x00\x00\x01\x00\x04\x92\x60\ +\x00\x00\x0e\xec\x00\x00\x00\x00\x00\x01\x00\x04\x98\x5a\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7d\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x7e\ +\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x01\x00\x04\x9a\xb0\ +\x00\x00\x0f\x2c\x00\x00\x00\x00\x00\x01\x00\x04\xa1\x97\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x81\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x09\x00\x00\x00\x82\ +\x00\x00\x0f\x50\x00\x00\x00\x00\x00\x01\x00\x04\xaa\xfb\ +\x00\x00\x0f\x70\x00\x01\x00\x00\x00\x01\x00\x04\xad\xa7\ +\x00\x00\x0f\x94\x00\x00\x00\x00\x00\x01\x00\x04\xb8\xf8\ +\x00\x00\x0f\xb6\x00\x00\x00\x00\x00\x01\x00\x04\xc0\x9d\ +\x00\x00\x0f\xd2\x00\x00\x00\x00\x00\x01\x00\x04\xd1\x9d\ +\x00\x00\x0f\xf6\x00\x00\x00\x00\x00\x01\x00\x04\xdb\x1e\ +\x00\x00\x10\x16\x00\x00\x00\x00\x00\x01\x00\x04\xe0\xbb\ +\x00\x00\x10\x3e\x00\x00\x00\x00\x00\x01\x00\x04\xea\x84\ +\x00\x00\x10\x5e\x00\x00\x00\x00\x00\x01\x00\x04\xee\x67\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x8c\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x8d\ +\x00\x00\x10\x7a\x00\x00\x00\x00\x00\x01\x00\x04\xf4\xa2\ +\x00\x00\x10\xaa\x00\x00\x00\x00\x00\x01\x00\x04\xfe\x38\ +\x00\x00\x10\xda\x00\x00\x00\x00\x00\x01\x00\x05\x0a\x14\ +\x00\x00\x10\xfe\x00\x00\x00\x00\x00\x01\x00\x05\x10\x58\ +\x00\x00\x11\x28\x00\x00\x00\x00\x00\x01\x00\x05\x17\xe1\ +\x00\x00\x11\x54\x00\x00\x00\x00\x00\x01\x00\x05\x1e\x3f\ +\x00\x00\x11\x8a\x00\x00\x00\x00\x00\x01\x00\x05\x26\x2e\ +\x00\x00\x09\x20\x00\x00\x00\x00\x00\x01\x00\x05\x33\xd1\ +\x00\x00\x09\x34\x00\x00\x00\x00\x00\x01\x00\x05\x39\x06\ +\x00\x00\x11\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x42\xdb\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x98\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x99\ +\x00\x00\x11\xd0\x00\x00\x00\x00\x00\x01\x00\x05\x4a\xa5\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x9b\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x28\x00\x00\x00\x9c\ +\x00\x00\x11\xf0\x00\x00\x00\x00\x00\x01\x00\x05\x4f\x6b\ +\x00\x00\x12\x06\x00\x00\x00\x00\x00\x01\x00\x05\x57\x1f\ +\x00\x00\x12\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x59\x44\ +\x00\x00\x12\x56\x00\x00\x00\x00\x00\x01\x00\x05\x5a\xc4\ +\x00\x00\x12\x72\x00\x00\x00\x00\x00\x01\x00\x05\x62\x71\ +\x00\x00\x12\x92\x00\x00\x00\x00\x00\x01\x00\x05\x67\x46\ +\x00\x00\x12\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x68\x36\ +\x00\x00\x12\xbe\x00\x00\x00\x00\x00\x01\x00\x05\x6c\x5f\ +\x00\x00\x12\xe4\x00\x00\x00\x00\x00\x01\x00\x05\x72\x96\ +\x00\x00\x12\xfe\x00\x00\x00\x00\x00\x01\x00\x05\x87\xa3\ +\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x8c\x93\ +\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\x8f\x92\ +\x00\x00\x13\x4c\x00\x00\x00\x00\x00\x01\x00\x05\x95\x9c\ +\x00\x00\x13\x7e\x00\x00\x00\x00\x00\x01\x00\x05\x98\xeb\ +\x00\x00\x13\x96\x00\x00\x00\x00\x00\x01\x00\x05\x9d\x0c\ +\x00\x00\x13\xac\x00\x00\x00\x00\x00\x01\x00\x05\xa3\x03\ +\x00\x00\x13\xc0\x00\x00\x00\x00\x00\x01\x00\x05\xa5\x14\ +\x00\x00\x13\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xd7\ +\x00\x00\x13\xfe\x00\x00\x00\x00\x00\x01\x00\x05\xb2\x8b\ +\x00\x00\x14\x28\x00\x00\x00\x00\x00\x01\x00\x05\xb5\xd9\ +\x00\x00\x14\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xb9\x67\ +\x00\x00\x14\x70\x00\x00\x00\x00\x00\x01\x00\x05\xbe\x08\ +\x00\x00\x14\x84\x00\x00\x00\x00\x00\x01\x00\x05\xc7\xda\ +\x00\x00\x14\xb0\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x24\ +\x00\x00\x14\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xd3\x2b\ +\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xd4\x0f\ +\x00\x00\x15\x1a\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x58\ +\x00\x00\x15\x30\x00\x00\x00\x00\x00\x01\x00\x05\xdd\x08\ +\x00\x00\x15\x4c\x00\x00\x00\x00\x00\x01\x00\x05\xe0\x4c\ +\x00\x00\x15\x64\x00\x00\x00\x00\x00\x01\x00\x05\xe1\x78\ +\x00\x00\x15\x7e\x00\x00\x00\x00\x00\x01\x00\x05\xe7\x39\ +\x00\x00\x15\xa0\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x5b\ +\x00\x00\x15\xbe\x00\x00\x00\x00\x00\x01\x00\x05\xee\x4e\ +\x00\x00\x15\xde\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x52\ +\x00\x00\x16\x00\x00\x00\x00\x00\x00\x01\x00\x05\xf2\x73\ +\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x05\xf5\x47\ +\x00\x00\x16\x4e\x00\x00\x00\x00\x00\x01\x00\x05\xfd\xb3\ +\x00\x00\x16\x72\x00\x00\x00\x00\x00\x01\x00\x06\x05\x73\ +\x00\x00\x16\x96\x00\x00\x00\x00\x00\x01\x00\x06\x0a\x8e\ +\x00\x00\x16\xbe\x00\x00\x00\x00\x00\x01\x00\x06\x0b\xe1\ " qt_resource_struct_v2 = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0f\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x10\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x9a\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x97\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x8b\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x94\ +\x00\x00\x00\x46\x00\x02\x00\x00\x00\x01\x00\x00\x00\x80\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x91\ +\x00\x00\x00\x5a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7c\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x85\ +\x00\x00\x00\x6c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x76\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x46\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7a\ +\x00\x00\x00\x7e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x68\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x5a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x76\ +\x00\x00\x00\x90\x00\x02\x00\x00\x00\x01\x00\x00\x00\x5e\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x6c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x70\ +\x00\x00\x00\xa2\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4f\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x7e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x62\ +\x00\x00\x00\xc0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4a\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x90\x00\x02\x00\x00\x00\x01\x00\x00\x00\x58\ +\x00\x00\x00\xdc\x00\x02\x00\x00\x00\x01\x00\x00\x00\x3a\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\xa2\x00\x02\x00\x00\x00\x01\x00\x00\x00\x49\ +\x00\x00\x01\x02\x00\x02\x00\x00\x00\x01\x00\x00\x00\x36\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\xc0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x45\ +\x00\x00\x01\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x32\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\xdc\x00\x02\x00\x00\x00\x01\x00\x00\x00\x35\ +\x00\x00\x01\x42\x00\x02\x00\x00\x00\x01\x00\x00\x00\x26\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x02\x00\x02\x00\x00\x00\x01\x00\x00\x00\x31\ +\x00\x00\x01\x68\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x2a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x25\ +\x00\x00\x01\x84\x00\x02\x00\x00\x00\x01\x00\x00\x00\x11\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x50\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1f\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x12\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x6c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x10\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x13\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x11\ +\x00\x00\x01\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x08\x66\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x02\x10\x00\x00\x00\x00\x00\x01\x00\x00\x09\x52\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x02\x38\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x35\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x02\x60\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x18\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x02\x88\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x06\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x02\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x14\x42\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x02\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xf7\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x03\x12\x00\x00\x00\x00\x00\x01\x00\x00\x22\x41\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x03\x32\x00\x00\x00\x00\x00\x01\x00\x00\x26\x90\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x03\x4e\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6e\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x03\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x30\xca\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x03\x92\x00\x00\x00\x00\x00\x01\x00\x00\x32\xb1\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x21\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x12\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x04\x00\x00\x00\x22\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x08\x66\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x01\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x09\x52\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x02\x20\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x35\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x02\x48\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x18\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x02\x70\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x06\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x02\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x14\x42\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x02\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xf7\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x02\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x22\x41\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x03\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x26\x90\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x03\x36\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6e\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x03\x52\x00\x00\x00\x00\x00\x01\x00\x00\x30\xca\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x03\x7a\x00\x00\x00\x00\x00\x01\x00\x00\x32\xb1\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\ +\x00\x00\x03\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x55\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x03\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x3c\xcf\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x03\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x43\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x04\x16\x00\x00\x00\x00\x00\x01\x00\x00\x41\xbf\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x27\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x21\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x28\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x03\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x55\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x3c\xcf\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x03\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x43\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x03\xfe\x00\x00\x00\x00\x00\x01\x00\x00\x41\xbf\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x26\ +\x00\x00\x04\x38\x00\x00\x00\x00\x00\x01\x00\x00\x44\x3b\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x04\x52\x00\x00\x00\x00\x00\x01\x00\x00\x45\x3c\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x04\x82\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x54\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x04\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x56\x16\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x04\xdc\x00\x00\x00\x00\x00\x01\x00\x00\x58\x86\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x05\x02\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x1e\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x05\x26\x00\x00\x00\x00\x00\x01\x00\x00\x62\x7a\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x05\x60\x00\x00\x00\x00\x00\x01\x00\x00\x69\x07\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x05\x7c\x00\x00\x00\x00\x00\x01\x00\x00\x6c\x03\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x05\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x6d\x01\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x33\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x27\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x34\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x04\x20\x00\x00\x00\x00\x00\x01\x00\x00\x44\x3b\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x04\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x45\x3c\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x04\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x54\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x04\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x56\x16\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x04\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x58\x86\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x04\xea\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x1e\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x05\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x62\x7a\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x05\x48\x00\x00\x00\x00\x00\x01\x00\x00\x69\x07\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x05\x64\x00\x00\x00\x00\x00\x01\x00\x00\x6c\x03\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x05\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x6d\x01\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x32\ +\x00\x00\x05\xd6\x00\x01\x00\x00\x00\x01\x00\x00\x77\x6b\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x05\xe8\x00\x00\x00\x00\x00\x01\x00\x02\x3a\xc0\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x37\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x33\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x38\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x05\xbe\x00\x01\x00\x00\x00\x01\x00\x00\x77\x6b\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x05\xd0\x00\x00\x00\x00\x00\x01\x00\x02\x3a\xc0\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x02\x00\x00\x00\x36\ +\x00\x00\x05\xfc\x00\x00\x00\x00\x00\x01\x00\x02\x3f\x46\ +\x00\x00\x01\x9b\xbc\x28\x2f\x35\ +\x00\x00\x06\x2a\x00\x00\x00\x00\x00\x01\x00\x02\x41\x71\ +\x00\x00\x01\x9b\xbc\x28\x2f\x35\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x3b\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x06\x00\x00\x00\x3f\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x06\x00\x00\x00\x44\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x05\xe4\x00\x02\x00\x00\x00\x07\x00\x00\x00\x38\ +\x00\x00\x06\x5a\x00\x02\x00\x00\x00\x07\x00\x00\x00\x3d\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x05\xf6\x00\x00\x00\x00\x00\x01\x00\x02\x3f\x46\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ -\x00\x00\x06\x2a\x00\x00\x00\x00\x00\x01\x00\x02\x49\xaa\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ -\x00\x00\x06\x5e\x00\x00\x00\x00\x00\x01\x00\x02\x53\xd3\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ -\x00\x00\x06\x98\x00\x00\x00\x00\x00\x01\x00\x02\x5e\x2b\ -\x00\x00\x01\x98\xe1\xb8\x63\x9a\ -\x00\x00\x06\xcc\x00\x00\x00\x00\x00\x01\x00\x02\x68\x43\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ -\x00\x00\x07\x02\x00\x00\x00\x00\x00\x01\x00\x02\x72\x6c\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ -\x00\x00\x07\x3a\x00\x00\x00\x00\x00\x01\x00\x02\x7c\x82\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ -\x00\x00\x07\x70\x00\x00\x00\x00\x00\x01\x00\x02\x86\xe8\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x07\x98\x00\x00\x00\x00\x00\x01\x00\x02\x91\x17\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x07\xc4\x00\x00\x00\x00\x00\x01\x00\x02\x9b\x5e\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x08\x00\x00\x00\x00\x00\x00\x01\x00\x02\x9d\x5f\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x08\x2c\x00\x00\x00\x00\x00\x01\x00\x02\xb8\xa4\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x08\x58\x00\x00\x00\x00\x00\x01\x00\x02\xbd\xed\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x46\ +\x00\x00\x06\x6c\x00\x00\x00\x00\x00\x01\x00\x02\x43\x77\ +\x00\x00\x01\x9a\x72\xe1\x95\x8f\ +\x00\x00\x06\xa0\x00\x00\x00\x00\x00\x01\x00\x02\x4d\xdb\ +\x00\x00\x01\x9a\x72\xe1\x95\x93\ +\x00\x00\x06\xd4\x00\x00\x00\x00\x00\x01\x00\x02\x58\x04\ +\x00\x00\x01\x9a\x72\xe1\x95\x8f\ +\x00\x00\x07\x0e\x00\x00\x00\x00\x00\x01\x00\x02\x62\x5c\ +\x00\x00\x01\x9a\x72\xe1\x95\x93\ +\x00\x00\x07\x42\x00\x00\x00\x00\x00\x01\x00\x02\x6c\x74\ +\x00\x00\x01\x9a\x72\xe1\x95\x8f\ +\x00\x00\x07\x78\x00\x00\x00\x00\x00\x01\x00\x02\x76\x9d\ +\x00\x00\x01\x9a\x72\xe1\x95\x93\ +\x00\x00\x07\xb0\x00\x00\x00\x00\x00\x01\x00\x02\x80\xb3\ +\x00\x00\x01\x9a\x72\xe1\x95\x93\ +\x00\x00\x07\xe6\x00\x00\x00\x00\x00\x01\x00\x02\x8b\x19\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x08\x0e\x00\x00\x00\x00\x00\x01\x00\x02\x95\x48\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x08\x3a\x00\x00\x00\x00\x00\x01\x00\x02\x9f\x8f\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x08\x76\x00\x00\x00\x00\x00\x01\x00\x02\xa1\x90\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x08\xa2\x00\x00\x00\x00\x00\x01\x00\x02\xbc\xd5\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x08\xce\x00\x00\x00\x00\x00\x01\x00\x02\xc2\x1e\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4b\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x47\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x03\x00\x00\x00\x4c\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x08\x8c\x00\x00\x00\x00\x00\x01\x00\x02\xc1\x65\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x02\xca\x47\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4a\ +\x00\x00\x09\x02\x00\x00\x00\x00\x00\x01\x00\x02\xc5\x96\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x09\x20\x00\x00\x00\x00\x00\x01\x00\x02\xce\x78\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x09\x34\x00\x00\x00\x00\x00\x01\x00\x02\xd3\xad\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x50\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x4b\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x51\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x02\xcf\x7c\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x08\xe6\x00\x00\x00\x00\x00\x01\x00\x02\xd4\x77\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x09\x1e\x00\x00\x00\x00\x00\x01\x00\x02\xdd\x4b\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x09\x56\x00\x00\x00\x00\x00\x01\x00\x02\xe5\xef\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x09\x86\x00\x00\x00\x00\x00\x01\x00\x02\xed\x8e\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x09\xaa\x00\x00\x00\x00\x00\x01\x00\x02\xf5\x6a\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x09\xce\x00\x00\x00\x00\x00\x01\x00\x02\xfd\x20\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0a\x02\x00\x00\x00\x00\x00\x01\x00\x03\x05\x2e\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0a\x36\x00\x00\x00\x00\x00\x01\x00\x03\x0d\x10\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x0a\x6a\x00\x00\x00\x00\x00\x01\x00\x03\x14\xda\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0a\xa4\x00\x00\x00\x00\x00\x01\x00\x03\x1c\x41\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0a\xc8\x00\x00\x00\x00\x00\x01\x00\x03\x1f\xfe\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0a\xf6\x00\x00\x00\x00\x00\x01\x00\x03\x23\xd7\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x59\ +\x00\x00\x09\x4e\x00\x00\x00\x00\x00\x01\x00\x02\xdd\x82\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x09\x76\x00\x00\x00\x00\x00\x01\x00\x02\xe2\x7d\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x09\xae\x00\x00\x00\x00\x00\x01\x00\x02\xeb\x51\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x09\xe6\x00\x00\x00\x00\x00\x01\x00\x02\xf3\xf5\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0a\x16\x00\x00\x00\x00\x00\x01\x00\x02\xfb\x94\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0a\x3a\x00\x00\x00\x00\x00\x01\x00\x03\x03\x70\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0a\x5e\x00\x00\x00\x00\x00\x01\x00\x03\x0b\x26\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0a\x92\x00\x00\x00\x00\x00\x01\x00\x03\x13\x34\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0a\xc6\x00\x00\x00\x00\x00\x01\x00\x03\x1b\x16\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0a\xfa\x00\x00\x00\x00\x00\x01\x00\x03\x22\xe0\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0b\x34\x00\x00\x00\x00\x00\x01\x00\x03\x2a\x47\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0b\x58\x00\x00\x00\x00\x00\x01\x00\x03\x2e\x04\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0b\x86\x00\x00\x00\x00\x00\x01\x00\x03\x31\xdd\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x5f\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x08\x00\x00\x00\x5a\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x08\x00\x00\x00\x60\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x0b\x1c\x00\x00\x00\x00\x00\x01\x00\x03\x29\xe0\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x0b\x48\x00\x00\x00\x00\x00\x01\x00\x03\x4c\x64\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x0b\x76\x00\x00\x00\x00\x00\x01\x00\x03\x52\x51\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0b\xa0\x00\x00\x00\x00\x00\x01\x00\x03\x54\x71\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0b\xc8\x00\x00\x00\x00\x00\x01\x00\x03\x5d\x09\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x6c\x52\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x0c\x0e\x00\x00\x00\x00\x00\x01\x00\x03\x72\xd0\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x0c\x36\x00\x00\x00\x00\x00\x01\x00\x03\x7d\x4a\ -\x00\x00\x01\x99\xe8\x24\xab\x4a\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x63\ +\x00\x00\x0b\xac\x00\x00\x00\x00\x00\x01\x00\x03\x37\xe6\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0b\xd8\x00\x00\x00\x00\x00\x01\x00\x03\x5a\x6a\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0c\x06\x00\x00\x00\x00\x00\x01\x00\x03\x60\x57\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0c\x30\x00\x00\x00\x00\x00\x01\x00\x03\x62\x77\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0c\x58\x00\x00\x00\x00\x00\x01\x00\x03\x6b\x0f\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0c\x72\x00\x00\x00\x00\x00\x01\x00\x03\x7a\x58\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0c\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x80\xd6\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x0c\xc6\x00\x00\x00\x00\x00\x01\x00\x03\x8b\x50\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x69\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x64\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x6a\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x0c\x6c\x00\x00\x00\x00\x00\x01\x00\x03\x86\x91\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0c\x9a\x00\x00\x00\x00\x00\x01\x00\x03\x89\x38\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0c\xc8\x00\x01\x00\x00\x00\x01\x00\x03\x95\xb5\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0c\xf4\x00\x00\x00\x00\x00\x01\x00\x03\xc3\x34\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0d\x14\x00\x00\x00\x00\x00\x01\x00\x03\xc7\xeb\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0d\x46\x00\x01\x00\x00\x00\x01\x00\x04\x20\xe4\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0d\x78\x00\x00\x00\x00\x00\x01\x00\x04\x55\x7e\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0d\x92\x00\x00\x00\x00\x00\x01\x00\x04\x5a\xc8\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0d\xac\x00\x00\x00\x00\x00\x01\x00\x04\x60\x57\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0d\xc6\x00\x00\x00\x00\x00\x01\x00\x04\x65\xc0\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x0d\xde\x00\x00\x00\x00\x00\x01\x00\x04\x71\x9e\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0d\xfc\x00\x00\x00\x00\x00\x01\x00\x04\x77\xa2\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x71\ +\x00\x00\x0c\xfc\x00\x00\x00\x00\x00\x01\x00\x03\x94\x97\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x0d\x2a\x00\x00\x00\x00\x00\x01\x00\x03\x97\x3e\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0d\x58\x00\x01\x00\x00\x00\x01\x00\x03\xa3\xbb\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0d\x84\x00\x00\x00\x00\x00\x01\x00\x03\xd1\x3a\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x0d\xa4\x00\x00\x00\x00\x00\x01\x00\x03\xd5\xf1\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x0d\xd6\x00\x01\x00\x00\x00\x01\x00\x04\x2e\xea\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x0e\x08\x00\x00\x00\x00\x00\x01\x00\x04\x63\x84\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0e\x22\x00\x00\x00\x00\x00\x01\x00\x04\x68\xce\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0e\x3c\x00\x00\x00\x00\x00\x01\x00\x04\x6e\x5d\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0e\x56\x00\x00\x00\x00\x00\x01\x00\x04\x73\xc6\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0e\x6e\x00\x00\x00\x00\x00\x01\x00\x04\x7f\xa4\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0e\x8c\x00\x00\x00\x00\x00\x01\x00\x04\x85\xa8\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x77\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x72\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x04\x00\x00\x00\x78\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x0e\x24\x00\x00\x00\x00\x00\x01\x00\x04\x7c\xd7\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x0e\x38\x00\x00\x00\x00\x00\x01\x00\x04\x82\xd4\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x0e\x4a\x00\x00\x00\x00\x00\x01\x00\x04\x84\x5a\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x0e\x5c\x00\x00\x00\x00\x00\x01\x00\x04\x8a\x54\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x77\ +\x00\x00\x0e\xb4\x00\x00\x00\x00\x00\x01\x00\x04\x8a\xdd\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0e\xc8\x00\x00\x00\x00\x00\x01\x00\x04\x90\xda\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0e\xda\x00\x00\x00\x00\x00\x01\x00\x04\x92\x60\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0e\xec\x00\x00\x00\x00\x00\x01\x00\x04\x98\x5a\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7d\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x78\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x7e\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x0e\x70\x00\x00\x00\x00\x00\x01\x00\x04\x8c\xaa\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0e\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x93\x91\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7b\ +\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x01\x00\x04\x9a\xb0\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0f\x2c\x00\x00\x00\x00\x00\x01\x00\x04\xa1\x97\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x81\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x09\x00\x00\x00\x7c\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x09\x00\x00\x00\x82\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x0e\xc0\x00\x00\x00\x00\x00\x01\x00\x04\x9c\xf5\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0e\xe0\x00\x01\x00\x00\x00\x01\x00\x04\x9f\xa1\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x0f\x04\x00\x00\x00\x00\x00\x01\x00\x04\xaa\xf2\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x0f\x26\x00\x00\x00\x00\x00\x01\x00\x04\xb2\x97\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x0f\x42\x00\x00\x00\x00\x00\x01\x00\x04\xc3\x97\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x0f\x66\x00\x00\x00\x00\x00\x01\x00\x04\xcd\x18\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0f\x86\x00\x00\x00\x00\x00\x01\x00\x04\xd2\xb5\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x0f\xae\x00\x00\x00\x00\x00\x01\x00\x04\xdc\x7e\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x0f\xce\x00\x00\x00\x00\x00\x01\x00\x04\xe0\x61\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x86\ +\x00\x00\x0f\x50\x00\x00\x00\x00\x00\x01\x00\x04\xaa\xfb\ +\x00\x00\x01\x9b\x7f\x73\xe2\xad\ +\x00\x00\x0f\x70\x00\x01\x00\x00\x00\x01\x00\x04\xad\xa7\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x0f\x94\x00\x00\x00\x00\x00\x01\x00\x04\xb8\xf8\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0f\xb6\x00\x00\x00\x00\x00\x01\x00\x04\xc0\x9d\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0f\xd2\x00\x00\x00\x00\x00\x01\x00\x04\xd1\x9d\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x0f\xf6\x00\x00\x00\x00\x00\x01\x00\x04\xdb\x1e\ +\x00\x00\x01\x9b\x7f\x73\xe2\xad\ +\x00\x00\x10\x16\x00\x00\x00\x00\x00\x01\x00\x04\xe0\xbb\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x10\x3e\x00\x00\x00\x00\x00\x01\x00\x04\xea\x84\ +\x00\x00\x01\x9b\x7f\x73\xe2\xad\ +\x00\x00\x10\x5e\x00\x00\x00\x00\x00\x01\x00\x04\xee\x67\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x8c\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x87\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x8d\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x0f\xea\x00\x00\x00\x00\x00\x01\x00\x04\xe6\x9c\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x10\x1a\x00\x00\x00\x00\x00\x01\x00\x04\xf0\x32\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x10\x4a\x00\x00\x00\x00\x00\x01\x00\x04\xfc\x0e\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x10\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x02\x52\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x10\x98\x00\x00\x00\x00\x00\x01\x00\x05\x09\xdb\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x10\xc4\x00\x00\x00\x00\x00\x01\x00\x05\x10\x39\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x10\xfa\x00\x00\x00\x00\x00\x01\x00\x05\x18\x28\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x25\xcb\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x11\x18\x00\x00\x00\x00\x00\x01\x00\x05\x2b\x00\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x11\x32\x00\x00\x00\x00\x00\x01\x00\x05\x34\xd5\ -\x00\x00\x01\x99\xe8\x24\xab\x4a\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x92\ +\x00\x00\x10\x7a\x00\x00\x00\x00\x00\x01\x00\x04\xf4\xa2\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x10\xaa\x00\x00\x00\x00\x00\x01\x00\x04\xfe\x38\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x10\xda\x00\x00\x00\x00\x00\x01\x00\x05\x0a\x14\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x10\xfe\x00\x00\x00\x00\x00\x01\x00\x05\x10\x58\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x11\x28\x00\x00\x00\x00\x00\x01\x00\x05\x17\xe1\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x11\x54\x00\x00\x00\x00\x00\x01\x00\x05\x1e\x3f\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x11\x8a\x00\x00\x00\x00\x00\x01\x00\x05\x26\x2e\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x09\x20\x00\x00\x00\x00\x00\x01\x00\x05\x33\xd1\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x09\x34\x00\x00\x00\x00\x00\x01\x00\x05\x39\x06\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x11\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x42\xdb\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x98\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x01\x00\x00\x00\x93\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x99\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x11\x5a\x00\x00\x00\x00\x00\x01\x00\x05\x3c\x9f\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x95\ +\x00\x00\x11\xd0\x00\x00\x00\x00\x00\x01\x00\x05\x4a\xa5\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x9b\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x28\x00\x00\x00\x96\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x28\x00\x00\x00\x9c\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x11\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x41\x65\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x11\x90\x00\x00\x00\x00\x00\x01\x00\x05\x49\x19\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x11\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x4b\x3e\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x11\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x4c\xbe\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x11\xfc\x00\x00\x00\x00\x00\x01\x00\x05\x54\x6b\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x12\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x59\x40\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x12\x32\x00\x00\x00\x00\x00\x01\x00\x05\x5a\x30\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x5d\x14\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x63\x4b\ -\x00\x00\x01\x99\x96\xf9\x85\x18\ -\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x78\x58\ -\x00\x00\x01\x99\xe8\x24\xab\x4a\ -\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x7d\x48\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x80\x47\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x86\x51\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x89\xa0\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x93\x21\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\x99\x18\ -\x00\x00\x01\x99\x96\xf9\x85\x18\ -\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\x9b\x29\ -\x00\x00\x01\x99\x96\xf9\x85\x18\ -\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\x9e\xec\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xa0\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xab\xee\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xaf\x7c\ -\x00\x00\x01\x99\xed\x4d\xf1\x14\ -\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xb4\x1d\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xbd\xef\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xc3\x39\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xc9\x40\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xca\x24\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ -\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xcc\x6d\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ -\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd3\x1d\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x61\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xd7\x8d\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xdd\x4e\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xde\x70\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xe4\x63\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xe7\x67\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x88\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ -\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xeb\x5c\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ -\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xf3\xc8\ -\x00\x00\x01\x99\x7d\x04\xc2\x80\ -\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x05\xfb\x88\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ -\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x00\xa3\ -\x00\x00\x01\x99\x4c\xf0\xd6\xbc\ -\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x01\xf6\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ +\x00\x00\x11\xf0\x00\x00\x00\x00\x00\x01\x00\x05\x4f\x6b\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x12\x06\x00\x00\x00\x00\x00\x01\x00\x05\x57\x1f\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x12\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x59\x44\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x12\x56\x00\x00\x00\x00\x00\x01\x00\x05\x5a\xc4\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x12\x72\x00\x00\x00\x00\x00\x01\x00\x05\x62\x71\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x12\x92\x00\x00\x00\x00\x00\x01\x00\x05\x67\x46\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x12\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x68\x36\ +\x00\x00\x01\x9b\xbc\x0f\x8a\x2e\ +\x00\x00\x12\xbe\x00\x00\x00\x00\x00\x01\x00\x05\x6c\x5f\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x12\xe4\x00\x00\x00\x00\x00\x01\x00\x05\x72\x96\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x12\xfe\x00\x00\x00\x00\x00\x01\x00\x05\x87\xa3\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x8c\x93\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\x8f\x92\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x13\x4c\x00\x00\x00\x00\x00\x01\x00\x05\x95\x9c\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x13\x7e\x00\x00\x00\x00\x00\x01\x00\x05\x98\xeb\ +\x00\x00\x01\x9b\xbc\x0f\x8a\x2e\ +\x00\x00\x13\x96\x00\x00\x00\x00\x00\x01\x00\x05\x9d\x0c\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x13\xac\x00\x00\x00\x00\x00\x01\x00\x05\xa3\x03\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x13\xc0\x00\x00\x00\x00\x00\x01\x00\x05\xa5\x14\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x13\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xd7\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x13\xfe\x00\x00\x00\x00\x00\x01\x00\x05\xb2\x8b\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x14\x28\x00\x00\x00\x00\x00\x01\x00\x05\xb5\xd9\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x14\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xb9\x67\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x14\x70\x00\x00\x00\x00\x00\x01\x00\x05\xbe\x08\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x14\x84\x00\x00\x00\x00\x00\x01\x00\x05\xc7\xda\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x14\xb0\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x24\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x14\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xd3\x2b\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xd4\x0f\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x15\x1a\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x58\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x15\x30\x00\x00\x00\x00\x00\x01\x00\x05\xdd\x08\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x15\x4c\x00\x00\x00\x00\x00\x01\x00\x05\xe0\x4c\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x15\x64\x00\x00\x00\x00\x00\x01\x00\x05\xe1\x78\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x15\x7e\x00\x00\x00\x00\x00\x01\x00\x05\xe7\x39\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x15\xa0\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x5b\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x15\xbe\x00\x00\x00\x00\x00\x01\x00\x05\xee\x4e\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x15\xde\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x52\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x16\x00\x00\x00\x00\x00\x00\x01\x00\x05\xf2\x73\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x05\xf5\x47\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x16\x4e\x00\x00\x00\x00\x00\x01\x00\x05\xfd\xb3\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x16\x72\x00\x00\x00\x00\x00\x01\x00\x06\x05\x73\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x16\x96\x00\x00\x00\x00\x00\x01\x00\x06\x0a\x8e\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x16\xbe\x00\x00\x00\x00\x00\x01\x00\x06\x0b\xe1\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ " qt_version = [int(v) for v in QtCore.qVersion().split('.')] diff --git a/BlocksScreen/lib/ui/resources/media/btn_icons/error.svg b/BlocksScreen/lib/ui/resources/media/btn_icons/error.svg index fbf6eca0..0bb1f19f 100644 --- a/BlocksScreen/lib/ui/resources/media/btn_icons/error.svg +++ b/BlocksScreen/lib/ui/resources/media/btn_icons/error.svg @@ -1,47 +1,13 @@ - - - - + + + + + + + + + \ No newline at end of file diff --git a/BlocksScreen/lib/ui/resources/media/btn_icons/info.svg b/BlocksScreen/lib/ui/resources/media/btn_icons/info.svg index 2404aa83..6a4426ae 100644 --- a/BlocksScreen/lib/ui/resources/media/btn_icons/info.svg +++ b/BlocksScreen/lib/ui/resources/media/btn_icons/info.svg @@ -1 +1,13 @@ - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/BlocksScreen/lib/ui/resources/media/btn_icons/move_nozzle_away.svg b/BlocksScreen/lib/ui/resources/media/btn_icons/move_nozzle_away.svg new file mode 100644 index 00000000..ac52e9a5 --- /dev/null +++ b/BlocksScreen/lib/ui/resources/media/btn_icons/move_nozzle_away.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/BlocksScreen/lib/ui/resources/media/btn_icons/move_nozzle_close.svg b/BlocksScreen/lib/ui/resources/media/btn_icons/move_nozzle_close.svg new file mode 100644 index 00000000..6b633e62 --- /dev/null +++ b/BlocksScreen/lib/ui/resources/media/btn_icons/move_nozzle_close.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/BlocksScreen/lib/ui/utilitiesStackedWidget.ui b/BlocksScreen/lib/ui/utilitiesStackedWidget.ui index 79a79b88..bcb4a7dc 100644 --- a/BlocksScreen/lib/ui/utilitiesStackedWidget.ui +++ b/BlocksScreen/lib/ui/utilitiesStackedWidget.ui @@ -32,7 +32,7 @@ StackedWidget - 9 + 6 @@ -798,6 +798,13 @@ Shaper
+ + + + Qt::Vertical + + + @@ -862,7 +869,7 @@ Shaper - + 0 0 @@ -1955,569 +1962,15 @@ Shaper - - - - - - - - 0 - 0 - - - - - 250 - 80 - - - - - 250 - 80 - - - - - Momcake - 19 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - Y - - - false - - - true - - - menu_btn - - - :/input_shaper/media/btn_icons/frequency_Y.svg - - - - - - - - 0 - 0 - - - - - 250 - 80 - - - - - 250 - 80 - - - - - Momcake - 19 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - X - - - false - - - true - - - menu_btn - - - :/input_shaper/media/btn_icons/frequency_X.svg - - - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - - 80 - 80 - - - - - Momcake - 20 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - 2 - - - true - - - false - - - true - - - menu_btn - - - :/button_borders/media/buttons/btn_part1.svg - - - :/button_borders/media/buttons/btn_part2.svg - - - :/button_borders/media/buttons/btn_part3.svg - - - normal - - - isc_btn_group - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - - 80 - 80 - - - - - Momcake - 20 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - 3 - - - true - - - false - - - true - - - menu_btn - - - :/button_borders/media/buttons/btn_part1.svg - - - :/button_borders/media/buttons/btn_part2.svg - - - :/button_borders/media/buttons/btn_part3.svg - - - normal - - - isc_btn_group - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - - 80 - 80 - - - - - Momcake - 20 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - 5 - - - true - - - false - - - true - - - menu_btn - - - :/button_borders/media/buttons/btn_part1.svg - - - :/button_borders/media/buttons/btn_part2.svg - - - :/button_borders/media/buttons/btn_part3.svg - - - normal - - - isc_btn_group - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - - 80 - 80 - - - - - Momcake - 20 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - 1 - - - true - - - true - - - false - - - true - - - menu_btn - - - :/button_borders/media/buttons/btn_part1.svg - - - :/button_borders/media/buttons/btn_part2.svg - - - :/button_borders/media/buttons/btn_part3.svg - - - normal - - - isc_btn_group - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - - 80 - 80 - - - - - Momcake - 20 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - 4 - - - true - - - false - - - true - - - menu_btn - - - :/button_borders/media/buttons/btn_part1.svg - - - :/button_borders/media/buttons/btn_part2.svg - - - :/button_borders/media/buttons/btn_part3.svg - - - normal - - - isc_btn_group - - - - - - - - 11 - - - - color:white - - - Times: - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 60 - 20 - - - - - + - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 24 - - - - + + - + @@ -2528,7 +1981,7 @@ Shaper 60 - 0 + 60 @@ -2537,89 +1990,28 @@ Shaper 60 - - Insert Best Text Here - - - Qt::AlignCenter - - - - - - - - - 0 - - - - - - 0 - 0 - - - - - 250 - 80 - - - - - 250 - 80 - - - Momcake - 19 - false - PreferAntialias + 20 - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - + color:white - ZV + Input Shaper - - true - - - false - - - true - - - menu_btn - - - + + Qt::AlignCenter - - is_btn_group - - - + + + + + + 0 @@ -2662,7 +2054,7 @@ Shaper - EI + MZV true @@ -2679,13 +2071,10 @@ Shaper - - is_btn_group - - - + + 0 @@ -2728,7 +2117,7 @@ Shaper - MZV + EI true @@ -2745,13 +2134,10 @@ Shaper - - is_btn_group - - + 0 @@ -2811,13 +2197,10 @@ Shaper - - is_btn_group - - - + + 0 @@ -2856,14 +2239,11 @@ Shaper Qt::LeftToRight - - false - - user input + ZV true @@ -2880,13 +2260,10 @@ Shaper - - is_btn_group - - - + + 0 @@ -2946,13 +2323,14 @@ Shaper - - is_btn_group - - - + + + + + + 0 @@ -2995,7 +2373,7 @@ Shaper - Confirm + Cancel false @@ -3007,12 +2385,12 @@ Shaper menu_btn - :/dialog/media/btn_icons/yes.svg + :/dialog/media/btn_icons/no.svg - - + + 0 @@ -3055,7 +2433,7 @@ Shaper - Cancel + Confirm false @@ -3067,7 +2445,7 @@ Shaper menu_btn - :/dialog/media/btn_icons/no.svg + :/dialog/media/btn_icons/yes.svg @@ -3120,255 +2498,23 @@ Shaper - - - - - - 0 - 0 - - - - - 250 - 80 - - - - - 250 - 80 - - - - - Momcake - 19 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - Confirm - - - false - - - true - - - menu_btn - - - :/dialog/media/btn_icons/yes.svg - - - - - - - - - - 0 - 0 - - - - - 250 - 80 - - - - - 250 - 80 - - - - - Momcake - 19 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - Smoothing - - - false - - - true - - - menu_btn - - - display_secondary - - - - - - - - - - - - 0 - 0 - - - - - 250 - 80 - - - - - 250 - 80 - - - - - Momcake - 19 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - Frequency - - - false - - - true - - - display_secondary - - - - - - - + + + - + 0 0 - - - 250 - 80 - - - - - 250 - 80 - - - - - Momcake - 19 - false - PreferAntialias - - - - false - - - true - - - Qt::NoContextMenu - - - Qt::LeftToRight - - - - - - Back - - - false - - - true - - - menu_btn + + QFrame::StyledPanel - - :/ui/media/btn_icons/back.svg + + QFrame::Raised - - - @@ -3670,8 +2816,4 @@ Shaper - - - - diff --git a/BlocksScreen/lib/ui/utilitiesStackedWidget_ui.py b/BlocksScreen/lib/ui/utilitiesStackedWidget_ui.py index 987ed2f8..7c3c6223 100644 --- a/BlocksScreen/lib/ui/utilitiesStackedWidget_ui.py +++ b/BlocksScreen/lib/ui/utilitiesStackedWidget_ui.py @@ -1,4 +1,4 @@ -# Form implementation generated from reading ui file '/home/levi/main/Blocks_Screen/BlocksScreen/lib/ui/utilitiesStackedWidget.ui' +# Form implementation generated from reading ui file '/home/levi/BlocksScreen/BlocksScreen/lib/ui/utilitiesStackedWidget.ui' # # Created by: PyQt6 UI code generator 6.7.1 # @@ -325,6 +325,10 @@ def setupUi(self, utilitiesStackedWidget): self.verticalLayout_4.addLayout(self.leds_header_layout) spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) self.verticalLayout_4.addItem(spacerItem4) + self.verticalScrollBar = QtWidgets.QScrollBar(parent=self.leds_page) + self.verticalScrollBar.setOrientation(QtCore.Qt.Orientation.Vertical) + self.verticalScrollBar.setObjectName("verticalScrollBar") + self.verticalLayout_4.addWidget(self.verticalScrollBar) self.leds_content_layout = QtWidgets.QGridLayout() self.leds_content_layout.setObjectName("leds_content_layout") self.leds_widget = QtWidgets.QWidget(parent=self.leds_page) @@ -346,7 +350,7 @@ def setupUi(self, utilitiesStackedWidget): spacerItem7 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) self.routines_header_layout.addItem(spacerItem7) self.routines_page_title = QtWidgets.QLabel(parent=self.routines_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.routines_page_title.sizePolicy().hasHeightForWidth()) @@ -769,391 +773,180 @@ def setupUi(self, utilitiesStackedWidget): self.verticalLayout_13.addLayout(self.is_header_layout) self.is_content_layout = QtWidgets.QHBoxLayout() self.is_content_layout.setObjectName("is_content_layout") - self.is_xy_layout = QtWidgets.QGridLayout() - self.is_xy_layout.setObjectName("is_xy_layout") - self.is_Y_startis_btn = BlocksCustomButton(parent=self.input_shaper_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.is_Y_startis_btn.sizePolicy().hasHeightForWidth()) - self.is_Y_startis_btn.setSizePolicy(sizePolicy) - self.is_Y_startis_btn.setMinimumSize(QtCore.QSize(250, 80)) - self.is_Y_startis_btn.setMaximumSize(QtCore.QSize(250, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(19) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.is_Y_startis_btn.setFont(font) - self.is_Y_startis_btn.setMouseTracking(False) - self.is_Y_startis_btn.setTabletTracking(True) - self.is_Y_startis_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.is_Y_startis_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.is_Y_startis_btn.setStyleSheet("") - self.is_Y_startis_btn.setAutoDefault(False) - self.is_Y_startis_btn.setFlat(True) - self.is_Y_startis_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/input_shaper/media/btn_icons/frequency_Y.svg")) - self.is_Y_startis_btn.setObjectName("is_Y_startis_btn") - self.is_xy_layout.addWidget(self.is_Y_startis_btn, 1, 0, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.is_X_startis_btn = BlocksCustomButton(parent=self.input_shaper_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.is_X_startis_btn.sizePolicy().hasHeightForWidth()) - self.is_X_startis_btn.setSizePolicy(sizePolicy) - self.is_X_startis_btn.setMinimumSize(QtCore.QSize(250, 80)) - self.is_X_startis_btn.setMaximumSize(QtCore.QSize(250, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(19) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.is_X_startis_btn.setFont(font) - self.is_X_startis_btn.setMouseTracking(False) - self.is_X_startis_btn.setTabletTracking(True) - self.is_X_startis_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.is_X_startis_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.is_X_startis_btn.setStyleSheet("") - self.is_X_startis_btn.setAutoDefault(False) - self.is_X_startis_btn.setFlat(True) - self.is_X_startis_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/input_shaper/media/btn_icons/frequency_X.svg")) - self.is_X_startis_btn.setObjectName("is_X_startis_btn") - self.is_xy_layout.addWidget(self.is_X_startis_btn, 0, 0, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.gridLayout = QtWidgets.QGridLayout() - self.gridLayout.setObjectName("gridLayout") - self.btn2 = GroupButton(parent=self.input_shaper_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.btn2.sizePolicy().hasHeightForWidth()) - self.btn2.setSizePolicy(sizePolicy) - self.btn2.setMinimumSize(QtCore.QSize(80, 80)) - self.btn2.setMaximumSize(QtCore.QSize(80, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(20) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.btn2.setFont(font) - self.btn2.setMouseTracking(False) - self.btn2.setTabletTracking(True) - self.btn2.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.btn2.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.btn2.setStyleSheet("") - self.btn2.setCheckable(True) - self.btn2.setAutoDefault(False) - self.btn2.setFlat(True) - self.btn2.setProperty("borderLeftPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part1.svg")) - self.btn2.setProperty("borderCenterPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part2.svg")) - self.btn2.setProperty("borderRightPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part3.svg")) - self.btn2.setObjectName("btn2") - self.isc_btn_group = QtWidgets.QButtonGroup(utilitiesStackedWidget) - self.isc_btn_group.setObjectName("isc_btn_group") - self.isc_btn_group.addButton(self.btn2) - self.gridLayout.addWidget(self.btn2, 1, 1, 1, 1) - self.btn3 = GroupButton(parent=self.input_shaper_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.btn3.sizePolicy().hasHeightForWidth()) - self.btn3.setSizePolicy(sizePolicy) - self.btn3.setMinimumSize(QtCore.QSize(80, 80)) - self.btn3.setMaximumSize(QtCore.QSize(80, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(20) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.btn3.setFont(font) - self.btn3.setMouseTracking(False) - self.btn3.setTabletTracking(True) - self.btn3.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.btn3.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.btn3.setStyleSheet("") - self.btn3.setCheckable(True) - self.btn3.setAutoDefault(False) - self.btn3.setFlat(True) - self.btn3.setProperty("borderLeftPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part1.svg")) - self.btn3.setProperty("borderCenterPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part2.svg")) - self.btn3.setProperty("borderRightPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part3.svg")) - self.btn3.setObjectName("btn3") - self.isc_btn_group.addButton(self.btn3) - self.gridLayout.addWidget(self.btn3, 2, 0, 1, 1) - self.btn5 = GroupButton(parent=self.input_shaper_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.btn5.sizePolicy().hasHeightForWidth()) - self.btn5.setSizePolicy(sizePolicy) - self.btn5.setMinimumSize(QtCore.QSize(80, 80)) - self.btn5.setMaximumSize(QtCore.QSize(80, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(20) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.btn5.setFont(font) - self.btn5.setMouseTracking(False) - self.btn5.setTabletTracking(True) - self.btn5.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.btn5.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.btn5.setStyleSheet("") - self.btn5.setCheckable(True) - self.btn5.setAutoDefault(False) - self.btn5.setFlat(True) - self.btn5.setProperty("borderLeftPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part1.svg")) - self.btn5.setProperty("borderCenterPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part2.svg")) - self.btn5.setProperty("borderRightPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part3.svg")) - self.btn5.setObjectName("btn5") - self.isc_btn_group.addButton(self.btn5) - self.gridLayout.addWidget(self.btn5, 3, 0, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.btn1 = GroupButton(parent=self.input_shaper_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.btn1.sizePolicy().hasHeightForWidth()) - self.btn1.setSizePolicy(sizePolicy) - self.btn1.setMinimumSize(QtCore.QSize(80, 80)) - self.btn1.setMaximumSize(QtCore.QSize(80, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(20) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.btn1.setFont(font) - self.btn1.setMouseTracking(False) - self.btn1.setTabletTracking(True) - self.btn1.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.btn1.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.btn1.setStyleSheet("") - self.btn1.setCheckable(True) - self.btn1.setChecked(True) - self.btn1.setAutoDefault(False) - self.btn1.setFlat(True) - self.btn1.setProperty("borderLeftPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part1.svg")) - self.btn1.setProperty("borderCenterPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part2.svg")) - self.btn1.setProperty("borderRightPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part3.svg")) - self.btn1.setObjectName("btn1") - self.isc_btn_group.addButton(self.btn1) - self.gridLayout.addWidget(self.btn1, 1, 0, 1, 1) - self.btn4 = GroupButton(parent=self.input_shaper_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.btn4.sizePolicy().hasHeightForWidth()) - self.btn4.setSizePolicy(sizePolicy) - self.btn4.setMinimumSize(QtCore.QSize(80, 80)) - self.btn4.setMaximumSize(QtCore.QSize(80, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(20) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.btn4.setFont(font) - self.btn4.setMouseTracking(False) - self.btn4.setTabletTracking(True) - self.btn4.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.btn4.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.btn4.setStyleSheet("") - self.btn4.setCheckable(True) - self.btn4.setAutoDefault(False) - self.btn4.setFlat(True) - self.btn4.setProperty("borderLeftPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part1.svg")) - self.btn4.setProperty("borderCenterPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part2.svg")) - self.btn4.setProperty("borderRightPixmap", QtGui.QPixmap(":/button_borders/media/buttons/btn_part3.svg")) - self.btn4.setObjectName("btn4") - self.isc_btn_group.addButton(self.btn4) - self.gridLayout.addWidget(self.btn4, 2, 1, 1, 1) - self.label = QtWidgets.QLabel(parent=self.input_shaper_page) - font = QtGui.QFont() - font.setPointSize(11) - self.label.setFont(font) - self.label.setStyleSheet("color:white") - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 0, 0, 1, 2) - self.is_xy_layout.addLayout(self.gridLayout, 0, 2, 2, 1) - self.is_content_layout.addLayout(self.is_xy_layout) - spacerItem18 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - self.is_content_layout.addItem(spacerItem18) self.verticalLayout_13.addLayout(self.is_content_layout) utilitiesStackedWidget.addWidget(self.input_shaper_page) - self.is_page = QtWidgets.QWidget() - self.is_page.setObjectName("is_page") - self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.is_page) - self.verticalLayout_8.setObjectName("verticalLayout_8") - spacerItem19 = QtWidgets.QSpacerItem(20, 24, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - self.verticalLayout_8.addItem(spacerItem19) + self.manual_is_res_page = QtWidgets.QWidget() + self.manual_is_res_page.setObjectName("manual_is_res_page") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.manual_is_res_page) + self.verticalLayout_3.setObjectName("verticalLayout_3") self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.label_3 = QtWidgets.QLabel(parent=self.is_page) + self.label_3 = QtWidgets.QLabel(parent=self.manual_is_res_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) self.label_3.setSizePolicy(sizePolicy) - self.label_3.setMinimumSize(QtCore.QSize(60, 0)) + self.label_3.setMinimumSize(QtCore.QSize(60, 60)) self.label_3.setMaximumSize(QtCore.QSize(16777215, 60)) + font = QtGui.QFont() + font.setPointSize(20) + self.label_3.setFont(font) + self.label_3.setStyleSheet("color:white") self.label_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.label_3.setObjectName("label_3") - self.horizontalLayout_4.addWidget(self.label_3) - self.verticalLayout_8.addLayout(self.horizontalLayout_4) - self.gridLayout_4 = QtWidgets.QGridLayout() - self.gridLayout_4.setSpacing(0) - self.gridLayout_4.setObjectName("gridLayout_4") - self.am_zv = GroupButton(parent=self.is_page) + self.horizontalLayout_4.addWidget(self.label_3, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.verticalLayout_3.addLayout(self.horizontalLayout_4) + self.gridLayout_3 = QtWidgets.QGridLayout() + self.gridLayout_3.setObjectName("gridLayout_3") + self.am_mzv_2 = BlocksCustomCheckButton(parent=self.manual_is_res_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.am_zv.sizePolicy().hasHeightForWidth()) - self.am_zv.setSizePolicy(sizePolicy) - self.am_zv.setMinimumSize(QtCore.QSize(250, 80)) - self.am_zv.setMaximumSize(QtCore.QSize(250, 80)) + sizePolicy.setHeightForWidth(self.am_mzv_2.sizePolicy().hasHeightForWidth()) + self.am_mzv_2.setSizePolicy(sizePolicy) + self.am_mzv_2.setMinimumSize(QtCore.QSize(250, 80)) + self.am_mzv_2.setMaximumSize(QtCore.QSize(250, 80)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(19) font.setItalic(False) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.am_zv.setFont(font) - self.am_zv.setMouseTracking(False) - self.am_zv.setTabletTracking(True) - self.am_zv.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.am_zv.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.am_zv.setStyleSheet("") - self.am_zv.setCheckable(True) - self.am_zv.setAutoDefault(False) - self.am_zv.setFlat(True) - self.am_zv.setObjectName("am_zv") - self.is_btn_group = QtWidgets.QButtonGroup(utilitiesStackedWidget) - self.is_btn_group.setObjectName("is_btn_group") - self.is_btn_group.addButton(self.am_zv) - self.gridLayout_4.addWidget(self.am_zv, 0, 0, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.am_ei = GroupButton(parent=self.is_page) + self.am_mzv_2.setFont(font) + self.am_mzv_2.setMouseTracking(False) + self.am_mzv_2.setTabletTracking(True) + self.am_mzv_2.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.am_mzv_2.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.am_mzv_2.setStyleSheet("") + self.am_mzv_2.setCheckable(True) + self.am_mzv_2.setAutoDefault(False) + self.am_mzv_2.setFlat(True) + self.am_mzv_2.setObjectName("am_mzv_2") + self.gridLayout_3.addWidget(self.am_mzv_2, 0, 1, 1, 1) + self.am_ei_2 = BlocksCustomCheckButton(parent=self.manual_is_res_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.am_ei.sizePolicy().hasHeightForWidth()) - self.am_ei.setSizePolicy(sizePolicy) - self.am_ei.setMinimumSize(QtCore.QSize(250, 80)) - self.am_ei.setMaximumSize(QtCore.QSize(250, 80)) + sizePolicy.setHeightForWidth(self.am_ei_2.sizePolicy().hasHeightForWidth()) + self.am_ei_2.setSizePolicy(sizePolicy) + self.am_ei_2.setMinimumSize(QtCore.QSize(250, 80)) + self.am_ei_2.setMaximumSize(QtCore.QSize(250, 80)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(19) font.setItalic(False) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.am_ei.setFont(font) - self.am_ei.setMouseTracking(False) - self.am_ei.setTabletTracking(True) - self.am_ei.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.am_ei.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.am_ei.setStyleSheet("") - self.am_ei.setCheckable(True) - self.am_ei.setAutoDefault(False) - self.am_ei.setFlat(True) - self.am_ei.setObjectName("am_ei") - self.is_btn_group.addButton(self.am_ei) - self.gridLayout_4.addWidget(self.am_ei, 0, 2, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.am_mzv = GroupButton(parent=self.is_page) + self.am_ei_2.setFont(font) + self.am_ei_2.setMouseTracking(False) + self.am_ei_2.setTabletTracking(True) + self.am_ei_2.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.am_ei_2.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.am_ei_2.setStyleSheet("") + self.am_ei_2.setCheckable(True) + self.am_ei_2.setAutoDefault(False) + self.am_ei_2.setFlat(True) + self.am_ei_2.setObjectName("am_ei_2") + self.gridLayout_3.addWidget(self.am_ei_2, 1, 0, 1, 1) + self.am_3hump_ei_2 = BlocksCustomCheckButton(parent=self.manual_is_res_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.am_mzv.sizePolicy().hasHeightForWidth()) - self.am_mzv.setSizePolicy(sizePolicy) - self.am_mzv.setMinimumSize(QtCore.QSize(250, 80)) - self.am_mzv.setMaximumSize(QtCore.QSize(250, 80)) + sizePolicy.setHeightForWidth(self.am_3hump_ei_2.sizePolicy().hasHeightForWidth()) + self.am_3hump_ei_2.setSizePolicy(sizePolicy) + self.am_3hump_ei_2.setMinimumSize(QtCore.QSize(250, 80)) + self.am_3hump_ei_2.setMaximumSize(QtCore.QSize(250, 80)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(19) font.setItalic(False) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.am_mzv.setFont(font) - self.am_mzv.setMouseTracking(False) - self.am_mzv.setTabletTracking(True) - self.am_mzv.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.am_mzv.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.am_mzv.setStyleSheet("") - self.am_mzv.setCheckable(True) - self.am_mzv.setAutoDefault(False) - self.am_mzv.setFlat(True) - self.am_mzv.setObjectName("am_mzv") - self.is_btn_group.addButton(self.am_mzv) - self.gridLayout_4.addWidget(self.am_mzv, 1, 0, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.am_3hump_ei = GroupButton(parent=self.is_page) + self.am_3hump_ei_2.setFont(font) + self.am_3hump_ei_2.setMouseTracking(False) + self.am_3hump_ei_2.setTabletTracking(True) + self.am_3hump_ei_2.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.am_3hump_ei_2.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.am_3hump_ei_2.setStyleSheet("") + self.am_3hump_ei_2.setCheckable(True) + self.am_3hump_ei_2.setAutoDefault(False) + self.am_3hump_ei_2.setFlat(True) + self.am_3hump_ei_2.setObjectName("am_3hump_ei_2") + self.gridLayout_3.addWidget(self.am_3hump_ei_2, 2, 0, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) + self.am_zv_2 = BlocksCustomCheckButton(parent=self.manual_is_res_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.am_3hump_ei.sizePolicy().hasHeightForWidth()) - self.am_3hump_ei.setSizePolicy(sizePolicy) - self.am_3hump_ei.setMinimumSize(QtCore.QSize(250, 80)) - self.am_3hump_ei.setMaximumSize(QtCore.QSize(250, 80)) + sizePolicy.setHeightForWidth(self.am_zv_2.sizePolicy().hasHeightForWidth()) + self.am_zv_2.setSizePolicy(sizePolicy) + self.am_zv_2.setMinimumSize(QtCore.QSize(250, 80)) + self.am_zv_2.setMaximumSize(QtCore.QSize(250, 80)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(19) font.setItalic(False) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.am_3hump_ei.setFont(font) - self.am_3hump_ei.setMouseTracking(False) - self.am_3hump_ei.setTabletTracking(True) - self.am_3hump_ei.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.am_3hump_ei.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.am_3hump_ei.setStyleSheet("") - self.am_3hump_ei.setCheckable(True) - self.am_3hump_ei.setAutoDefault(False) - self.am_3hump_ei.setFlat(True) - self.am_3hump_ei.setObjectName("am_3hump_ei") - self.is_btn_group.addButton(self.am_3hump_ei) - self.gridLayout_4.addWidget(self.am_3hump_ei, 2, 0, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.am_user_input = GroupButton(parent=self.is_page) + self.am_zv_2.setFont(font) + self.am_zv_2.setMouseTracking(False) + self.am_zv_2.setTabletTracking(True) + self.am_zv_2.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.am_zv_2.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.am_zv_2.setStyleSheet("") + self.am_zv_2.setCheckable(True) + self.am_zv_2.setAutoDefault(False) + self.am_zv_2.setFlat(True) + self.am_zv_2.setObjectName("am_zv_2") + self.gridLayout_3.addWidget(self.am_zv_2, 0, 0, 1, 1) + self.am_2hump_ei_2 = BlocksCustomCheckButton(parent=self.manual_is_res_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.am_user_input.sizePolicy().hasHeightForWidth()) - self.am_user_input.setSizePolicy(sizePolicy) - self.am_user_input.setMinimumSize(QtCore.QSize(250, 80)) - self.am_user_input.setMaximumSize(QtCore.QSize(250, 80)) + sizePolicy.setHeightForWidth(self.am_2hump_ei_2.sizePolicy().hasHeightForWidth()) + self.am_2hump_ei_2.setSizePolicy(sizePolicy) + self.am_2hump_ei_2.setMinimumSize(QtCore.QSize(250, 80)) + self.am_2hump_ei_2.setMaximumSize(QtCore.QSize(250, 80)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(19) font.setItalic(False) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.am_user_input.setFont(font) - self.am_user_input.setMouseTracking(False) - self.am_user_input.setTabletTracking(True) - self.am_user_input.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.am_user_input.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.am_user_input.setAutoFillBackground(False) - self.am_user_input.setStyleSheet("") - self.am_user_input.setCheckable(True) - self.am_user_input.setAutoDefault(False) - self.am_user_input.setFlat(True) - self.am_user_input.setObjectName("am_user_input") - self.is_btn_group.addButton(self.am_user_input) - self.gridLayout_4.addWidget(self.am_user_input, 2, 2, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.am_2hump_ei = GroupButton(parent=self.is_page) + self.am_2hump_ei_2.setFont(font) + self.am_2hump_ei_2.setMouseTracking(False) + self.am_2hump_ei_2.setTabletTracking(True) + self.am_2hump_ei_2.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.am_2hump_ei_2.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.am_2hump_ei_2.setStyleSheet("") + self.am_2hump_ei_2.setCheckable(True) + self.am_2hump_ei_2.setAutoDefault(False) + self.am_2hump_ei_2.setFlat(True) + self.am_2hump_ei_2.setObjectName("am_2hump_ei_2") + self.gridLayout_3.addWidget(self.am_2hump_ei_2, 1, 1, 1, 1) + self.verticalLayout_3.addLayout(self.gridLayout_3) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.am_cancel = BlocksCustomButton(parent=self.manual_is_res_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.am_2hump_ei.sizePolicy().hasHeightForWidth()) - self.am_2hump_ei.setSizePolicy(sizePolicy) - self.am_2hump_ei.setMinimumSize(QtCore.QSize(250, 80)) - self.am_2hump_ei.setMaximumSize(QtCore.QSize(250, 80)) + sizePolicy.setHeightForWidth(self.am_cancel.sizePolicy().hasHeightForWidth()) + self.am_cancel.setSizePolicy(sizePolicy) + self.am_cancel.setMinimumSize(QtCore.QSize(250, 80)) + self.am_cancel.setMaximumSize(QtCore.QSize(250, 80)) font = QtGui.QFont() font.setFamily("Momcake") font.setPointSize(19) font.setItalic(False) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.am_2hump_ei.setFont(font) - self.am_2hump_ei.setMouseTracking(False) - self.am_2hump_ei.setTabletTracking(True) - self.am_2hump_ei.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.am_2hump_ei.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.am_2hump_ei.setStyleSheet("") - self.am_2hump_ei.setCheckable(True) - self.am_2hump_ei.setAutoDefault(False) - self.am_2hump_ei.setFlat(True) - self.am_2hump_ei.setObjectName("am_2hump_ei") - self.is_btn_group.addButton(self.am_2hump_ei) - self.gridLayout_4.addWidget(self.am_2hump_ei, 1, 2, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.am_confirm = BlocksCustomButton(parent=self.is_page) + self.am_cancel.setFont(font) + self.am_cancel.setMouseTracking(False) + self.am_cancel.setTabletTracking(True) + self.am_cancel.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + self.am_cancel.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.am_cancel.setStyleSheet("") + self.am_cancel.setAutoDefault(False) + self.am_cancel.setFlat(True) + self.am_cancel.setProperty("icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/no.svg")) + self.am_cancel.setObjectName("am_cancel") + self.horizontalLayout.addWidget(self.am_cancel) + self.am_confirm = BlocksCustomButton(parent=self.manual_is_res_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -1176,39 +969,15 @@ def setupUi(self, utilitiesStackedWidget): self.am_confirm.setFlat(True) self.am_confirm.setProperty("icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg")) self.am_confirm.setObjectName("am_confirm") - self.gridLayout_4.addWidget(self.am_confirm, 3, 2, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.am_cancel = BlocksCustomButton(parent=self.is_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.am_cancel.sizePolicy().hasHeightForWidth()) - self.am_cancel.setSizePolicy(sizePolicy) - self.am_cancel.setMinimumSize(QtCore.QSize(250, 80)) - self.am_cancel.setMaximumSize(QtCore.QSize(250, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(19) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.am_cancel.setFont(font) - self.am_cancel.setMouseTracking(False) - self.am_cancel.setTabletTracking(True) - self.am_cancel.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.am_cancel.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.am_cancel.setStyleSheet("") - self.am_cancel.setAutoDefault(False) - self.am_cancel.setFlat(True) - self.am_cancel.setProperty("icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/no.svg")) - self.am_cancel.setObjectName("am_cancel") - self.gridLayout_4.addWidget(self.am_cancel, 3, 0, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.verticalLayout_8.addLayout(self.gridLayout_4) - utilitiesStackedWidget.addWidget(self.is_page) + self.horizontalLayout.addWidget(self.am_confirm) + self.verticalLayout_3.addLayout(self.horizontalLayout) + utilitiesStackedWidget.addWidget(self.manual_is_res_page) self.user_input_page = QtWidgets.QWidget() self.user_input_page.setObjectName("user_input_page") self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.user_input_page) self.verticalLayout_9.setObjectName("verticalLayout_9") - spacerItem20 = QtWidgets.QSpacerItem(20, 24, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - self.verticalLayout_9.addItem(spacerItem20) + spacerItem18 = QtWidgets.QSpacerItem(20, 24, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.verticalLayout_9.addItem(spacerItem18) self.horizontalLayout_5 = QtWidgets.QHBoxLayout() self.horizontalLayout_5.setObjectName("horizontalLayout_5") self.label_8 = QtWidgets.QLabel(parent=self.user_input_page) @@ -1222,112 +991,19 @@ def setupUi(self, utilitiesStackedWidget): self.label_8.setObjectName("label_8") self.horizontalLayout_5.addWidget(self.label_8) self.verticalLayout_9.addLayout(self.horizontalLayout_5) - self.gridLayout_5 = QtWidgets.QGridLayout() - self.gridLayout_5.setObjectName("gridLayout_5") - self.is_confirm_btn = BlocksCustomButton(parent=self.user_input_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.is_confirm_btn.sizePolicy().hasHeightForWidth()) - self.is_confirm_btn.setSizePolicy(sizePolicy) - self.is_confirm_btn.setMinimumSize(QtCore.QSize(250, 80)) - self.is_confirm_btn.setMaximumSize(QtCore.QSize(250, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(19) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.is_confirm_btn.setFont(font) - self.is_confirm_btn.setMouseTracking(False) - self.is_confirm_btn.setTabletTracking(True) - self.is_confirm_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.is_confirm_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.is_confirm_btn.setStyleSheet("") - self.is_confirm_btn.setAutoDefault(False) - self.is_confirm_btn.setFlat(True) - self.is_confirm_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg")) - self.is_confirm_btn.setObjectName("is_confirm_btn") - self.gridLayout_5.addWidget(self.is_confirm_btn, 2, 2, 1, 1, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.horizontalLayout_8 = QtWidgets.QHBoxLayout() - self.horizontalLayout_8.setObjectName("horizontalLayout_8") - self.isui_sm = BlocksCustomButton(parent=self.user_input_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.isui_sm.sizePolicy().hasHeightForWidth()) - self.isui_sm.setSizePolicy(sizePolicy) - self.isui_sm.setMinimumSize(QtCore.QSize(250, 80)) - self.isui_sm.setMaximumSize(QtCore.QSize(250, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(19) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.isui_sm.setFont(font) - self.isui_sm.setMouseTracking(False) - self.isui_sm.setTabletTracking(True) - self.isui_sm.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.isui_sm.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.isui_sm.setStyleSheet("") - self.isui_sm.setAutoDefault(False) - self.isui_sm.setFlat(True) - self.isui_sm.setObjectName("isui_sm") - self.horizontalLayout_8.addWidget(self.isui_sm, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.gridLayout_5.addLayout(self.horizontalLayout_8, 0, 2, 1, 1) - self.horizontalLayout_9 = QtWidgets.QHBoxLayout() - self.horizontalLayout_9.setObjectName("horizontalLayout_9") - self.isui_fq = BlocksCustomButton(parent=self.user_input_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.isui_fq.sizePolicy().hasHeightForWidth()) - self.isui_fq.setSizePolicy(sizePolicy) - self.isui_fq.setMinimumSize(QtCore.QSize(250, 80)) - self.isui_fq.setMaximumSize(QtCore.QSize(250, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(19) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.isui_fq.setFont(font) - self.isui_fq.setMouseTracking(False) - self.isui_fq.setTabletTracking(True) - self.isui_fq.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.isui_fq.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.isui_fq.setStyleSheet("") - self.isui_fq.setAutoDefault(False) - self.isui_fq.setFlat(True) - self.isui_fq.setObjectName("isui_fq") - self.horizontalLayout_9.addWidget(self.isui_fq, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.gridLayout_5.addLayout(self.horizontalLayout_9, 1, 2, 1, 1) - self.is_back_btn = BlocksCustomButton(parent=self.user_input_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) + self.verticalLayout_10 = QtWidgets.QVBoxLayout() + self.verticalLayout_10.setObjectName("verticalLayout_10") + self.frame_4 = QtWidgets.QFrame(parent=self.user_input_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.is_back_btn.sizePolicy().hasHeightForWidth()) - self.is_back_btn.setSizePolicy(sizePolicy) - self.is_back_btn.setMinimumSize(QtCore.QSize(250, 80)) - self.is_back_btn.setMaximumSize(QtCore.QSize(250, 80)) - font = QtGui.QFont() - font.setFamily("Momcake") - font.setPointSize(19) - font.setItalic(False) - font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) - self.is_back_btn.setFont(font) - self.is_back_btn.setMouseTracking(False) - self.is_back_btn.setTabletTracking(True) - self.is_back_btn.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) - self.is_back_btn.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.is_back_btn.setStyleSheet("") - self.is_back_btn.setAutoDefault(False) - self.is_back_btn.setFlat(True) - self.is_back_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) - self.is_back_btn.setObjectName("is_back_btn") - self.gridLayout_5.addWidget(self.is_back_btn, 2, 0, 1, 2, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.horizontalLayout_6 = QtWidgets.QHBoxLayout() - self.horizontalLayout_6.setObjectName("horizontalLayout_6") - self.gridLayout_5.addLayout(self.horizontalLayout_6, 0, 0, 2, 2) - self.verticalLayout_9.addLayout(self.gridLayout_5) + sizePolicy.setHeightForWidth(self.frame_4.sizePolicy().hasHeightForWidth()) + self.frame_4.setSizePolicy(sizePolicy) + self.frame_4.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) + self.frame_4.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) + self.frame_4.setObjectName("frame_4") + self.verticalLayout_10.addWidget(self.frame_4) + self.verticalLayout_9.addLayout(self.verticalLayout_10) utilitiesStackedWidget.addWidget(self.user_input_page) self.leds_slider_page = QtWidgets.QWidget() self.leds_slider_page.setObjectName("leds_slider_page") @@ -1360,8 +1036,8 @@ def setupUi(self, utilitiesStackedWidget): self.leds_slider_header_layout = QtWidgets.QHBoxLayout(self.layoutWidget) self.leds_slider_header_layout.setContentsMargins(0, 0, 0, 0) self.leds_slider_header_layout.setObjectName("leds_slider_header_layout") - spacerItem21 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - self.leds_slider_header_layout.addItem(spacerItem21) + spacerItem19 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.leds_slider_header_layout.addItem(spacerItem19) self.leds_slider_tittle_label = QtWidgets.QLabel(parent=self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) @@ -1424,7 +1100,7 @@ def setupUi(self, utilitiesStackedWidget): utilitiesStackedWidget.addWidget(self.leds_slider_page) self.retranslateUi(utilitiesStackedWidget) - utilitiesStackedWidget.setCurrentIndex(9) + utilitiesStackedWidget.setCurrentIndex(6) QtCore.QMetaObject.connectSlotsByName(utilitiesStackedWidget) def retranslateUi(self, utilitiesStackedWidget): @@ -1495,53 +1171,22 @@ def retranslateUi(self, utilitiesStackedWidget): self.input_shaper_back_btn.setText(_translate("utilitiesStackedWidget", "Back")) self.input_shaper_back_btn.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) self.input_shaper_back_btn.setProperty("button_type", _translate("utilitiesStackedWidget", "icon")) - self.is_Y_startis_btn.setText(_translate("utilitiesStackedWidget", "Y")) - self.is_Y_startis_btn.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.is_X_startis_btn.setText(_translate("utilitiesStackedWidget", "X")) - self.is_X_startis_btn.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.btn2.setText(_translate("utilitiesStackedWidget", "2")) - self.btn2.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.btn2.setProperty("button_type", _translate("utilitiesStackedWidget", "normal")) - self.btn3.setText(_translate("utilitiesStackedWidget", "3")) - self.btn3.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.btn3.setProperty("button_type", _translate("utilitiesStackedWidget", "normal")) - self.btn5.setText(_translate("utilitiesStackedWidget", "5")) - self.btn5.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.btn5.setProperty("button_type", _translate("utilitiesStackedWidget", "normal")) - self.btn1.setText(_translate("utilitiesStackedWidget", "1")) - self.btn1.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.btn1.setProperty("button_type", _translate("utilitiesStackedWidget", "normal")) - self.btn4.setText(_translate("utilitiesStackedWidget", "4")) - self.btn4.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.btn4.setProperty("button_type", _translate("utilitiesStackedWidget", "normal")) - self.label.setText(_translate("utilitiesStackedWidget", "Times:")) - self.label_3.setText(_translate("utilitiesStackedWidget", "Insert Best Text Here")) - self.am_zv.setText(_translate("utilitiesStackedWidget", "ZV")) - self.am_zv.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.am_ei.setText(_translate("utilitiesStackedWidget", "EI")) - self.am_ei.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.am_mzv.setText(_translate("utilitiesStackedWidget", "MZV")) - self.am_mzv.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.am_3hump_ei.setText(_translate("utilitiesStackedWidget", "3hump_ei")) - self.am_3hump_ei.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.am_user_input.setText(_translate("utilitiesStackedWidget", "user input")) - self.am_user_input.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.am_2hump_ei.setText(_translate("utilitiesStackedWidget", "2hump_ei")) - self.am_2hump_ei.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.am_confirm.setText(_translate("utilitiesStackedWidget", "Confirm")) - self.am_confirm.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) + self.label_3.setText(_translate("utilitiesStackedWidget", "Input Shaper ")) + self.am_mzv_2.setText(_translate("utilitiesStackedWidget", "MZV")) + self.am_mzv_2.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) + self.am_ei_2.setText(_translate("utilitiesStackedWidget", "EI")) + self.am_ei_2.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) + self.am_3hump_ei_2.setText(_translate("utilitiesStackedWidget", "3hump_ei")) + self.am_3hump_ei_2.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) + self.am_zv_2.setText(_translate("utilitiesStackedWidget", "ZV")) + self.am_zv_2.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) + self.am_2hump_ei_2.setText(_translate("utilitiesStackedWidget", "2hump_ei")) + self.am_2hump_ei_2.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) self.am_cancel.setText(_translate("utilitiesStackedWidget", "Cancel")) self.am_cancel.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) + self.am_confirm.setText(_translate("utilitiesStackedWidget", "Confirm")) + self.am_confirm.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) self.label_8.setText(_translate("utilitiesStackedWidget", "User Input")) - self.is_confirm_btn.setText(_translate("utilitiesStackedWidget", "Confirm")) - self.is_confirm_btn.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.isui_sm.setText(_translate("utilitiesStackedWidget", "Smoothing")) - self.isui_sm.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) - self.isui_sm.setProperty("button_type", _translate("utilitiesStackedWidget", "display_secondary")) - self.isui_fq.setText(_translate("utilitiesStackedWidget", "Frequency")) - self.isui_fq.setProperty("button_type", _translate("utilitiesStackedWidget", "display_secondary")) - self.is_back_btn.setText(_translate("utilitiesStackedWidget", "Back")) - self.is_back_btn.setProperty("class", _translate("utilitiesStackedWidget", "menu_btn")) self.toggle_led_button.setText(_translate("utilitiesStackedWidget", "PushButton")) self.label_4.setText(_translate("utilitiesStackedWidget", "On")) self.label_5.setText(_translate("utilitiesStackedWidget", "Off")) @@ -1553,6 +1198,6 @@ def retranslateUi(self, utilitiesStackedWidget): from lib.utils.blocks_button import BlocksCustomButton from lib.utils.blocks_frame import BlocksCustomFrame from lib.utils.blocks_slider import BlocksSlider -from lib.utils.group_button import GroupButton +from lib.utils.check_button import BlocksCustomCheckButton from lib.utils.icon_button import IconButton from lib.utils.toggleAnimatedButton import ToggleAnimatedButton diff --git a/BlocksScreen/lib/ui/wifiConnectivityWindow.ui b/BlocksScreen/lib/ui/wifiConnectivityWindow.ui index 289cbe08..871ba2dd 100644 --- a/BlocksScreen/lib/ui/wifiConnectivityWindow.ui +++ b/BlocksScreen/lib/ui/wifiConnectivityWindow.ui @@ -40,7 +40,7 @@ - 2 + 4 @@ -1066,7 +1066,56 @@ using the buttons on the side. - + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 60 + 20 + + + + + + + + + 0 + 0 + + + + + 16777215 + 60 + + + + + 20 + + + + color: rgb(255, 255, 255); + + + + + + false + + + Qt::AlignCenter + + + + + 60 @@ -1080,7 +1129,7 @@ using the buttons on the side. - Delete + Back true @@ -1092,12 +1141,524 @@ using the buttons on the side. icon - :/ui/media/btn_icons/indf_svg.svg + :/ui/media/btn_icons/back.svg + + + + - + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 20 + + + + + + + + + + + + + 0 + 0 + + + + + 400 + 16777215 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + + + 120 + 120 + 120 + + + + + + + 120 + 120 + 120 + + + + + + + + + 15 + + + + Signal +Strength + + + Qt::AlignCenter + + + + + + + + 250 + 0 + + + + + 11 + + + + color: rgb(255, 255, 255); + + + TextLabel + + + Qt::AlignCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + + + 120 + 120 + 120 + + + + + + + 120 + 120 + 120 + + + + + + + + + 15 + + + + Security +Type + + + Qt::AlignCenter + + + + + + + + 250 + 0 + + + + + 11 + + + + color: rgb(255, 255, 255); + + + TextLabel + + + Qt::AlignCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + + + 120 + 120 + 120 + + + + + + + 120 + 120 + 120 + + + + + + + + + 15 + + + + Status + + + Qt::AlignCenter + + + + + + + + 250 + 0 + + + + + 11 + + + + color: rgb(255, 255, 255); + + + TextLabel + + + Qt::AlignCenter + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 250 + 80 + + + + + 250 + 80 + + + + + 15 + + + + Connect + + + true + + + + + + + + 250 + 80 + + + + + 250 + 80 + + + + + 15 + + + + Details + + + true + + + + + + + + 250 + 80 + + + + + 250 + 80 + + + + + 15 + + + + Forget + + + true + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 60 + 60 + + + + + + + + + 0 + 0 + + 16777215 @@ -1182,7 +1743,7 @@ using the buttons on the side. - + 60 @@ -1215,9 +1776,9 @@ using the buttons on the side. - + - + Qt::Vertical @@ -1227,19 +1788,25 @@ using the buttons on the side. 20 - 30 + 20 - + 0 0 + + + 0 + 70 + + 16777215 @@ -1252,7 +1819,7 @@ using the buttons on the side. QFrame::Raised - + 0 @@ -1261,9 +1828,9 @@ using the buttons on the side. 62 - + - + @@ -1399,394 +1966,189 @@ Password - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 200 - 150 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 0 - 20 - 201 - 119 - - - - - - - - 0 - 0 - - - - - 100 - 16777215 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - - - 120 - 120 - 120 - - - - - - - 120 - 120 - 120 - - - - - - - - - 15 - - - - Signal -Strength - - - Qt::AlignCenter - - - - - - - - 150 - 0 - - - - - 150 - 16777215 - - - - Qt::Horizontal - - - - - - - - 20 - - - - color:white - - - TextLabel - - - Qt::AlignCenter - - - - - - - - - - - - 200 - 100 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - 80 - 80 - - - - - 80 - 80 - - - - - - - true - - - - - - - - 60 - 60 - - - - - 80 - 80 - - - - - - - true - - - - - - + - - - - 0 - 0 - - - - - 0 - 0 - - - - - 200 - 150 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 0 - 20 - 201 - 119 - - - - - - - - 100 - 16777215 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - - - 120 - 120 - 120 - - - - - - - 120 - 120 - 120 - - - - - - - - - 15 - - - - Security -Type - - - Qt::AlignCenter - - - - - - - - 150 - 0 - - - - Qt::Horizontal - - - - - - - - 20 - - - - color:white - - - TextLabel - - - Qt::AlignCenter - - - - - - + + + + + + 0 + 0 + + + + + 400 + 160 + + + + + 400 + 99999 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + Network priority + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 10 + 10 + + + + + + + + + + + 100 + 100 + + + + + 100 + 100 + + + + Low + + + true + + + true + + + true + + + back_btn + + + icon + + + :/ui/media/btn_icons/indf_svg.svg + + + priority_btn_group + + + + + + + + 100 + 100 + + + + + 100 + 100 + + + + Medium + + + true + + + true + + + true + + + true + + + back_btn + + + icon + + + :/ui/media/btn_icons/indf_svg.svg + + + priority_btn_group + + + + + + + + 100 + 100 + + + + + 100 + 100 + + + + High + + + true + + + false + + + true + + + true + + + back_btn + + + icon + + + :/ui/media/btn_icons/indf_svg.svg + + + priority_btn_group + + + + + + + + + @@ -2754,6 +3116,11 @@ Type QLabel
lib.panels.widgets.loadWidget
+ + GroupButton + QPushButton +
lib.utils.group_button
+
@@ -2762,4 +3129,7 @@ Type + + + diff --git a/BlocksScreen/lib/ui/wifiConnectivityWindow_ui.py b/BlocksScreen/lib/ui/wifiConnectivityWindow_ui.py index fccaf1ae..e66053a9 100644 --- a/BlocksScreen/lib/ui/wifiConnectivityWindow_ui.py +++ b/BlocksScreen/lib/ui/wifiConnectivityWindow_ui.py @@ -1,4 +1,4 @@ -# Form implementation generated from reading ui file '/home/levi/main/BlocksScreen/BlocksScreen/lib/ui/wifiConnectivityWindow.ui' +# Form implementation generated from reading ui file '/home/levi/BlocksScreen/BlocksScreen/lib/ui/wifiConnectivityWindow.ui' # # Created by: PyQt6 UI code generator 6.7.1 # @@ -435,15 +435,55 @@ def setupUi(self, wifi_stacked_page): self.verticalLayout_11.setObjectName("verticalLayout_11") self.horizontalLayout_7 = QtWidgets.QHBoxLayout() self.horizontalLayout_7.setObjectName("horizontalLayout_7") - self.saved_connection_delete_network_button = IconButton(parent=self.saved_connection_page) - self.saved_connection_delete_network_button.setMinimumSize(QtCore.QSize(60, 60)) - self.saved_connection_delete_network_button.setMaximumSize(QtCore.QSize(60, 60)) - self.saved_connection_delete_network_button.setFlat(True) - self.saved_connection_delete_network_button.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/indf_svg.svg")) - self.saved_connection_delete_network_button.setObjectName("saved_connection_delete_network_button") - self.horizontalLayout_7.addWidget(self.saved_connection_delete_network_button) + spacerItem4 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_7.addItem(spacerItem4) self.saved_connection_network_name = QtWidgets.QLabel(parent=self.saved_connection_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.saved_connection_network_name.sizePolicy().hasHeightForWidth()) + self.saved_connection_network_name.setSizePolicy(sizePolicy) self.saved_connection_network_name.setMaximumSize(QtCore.QSize(16777215, 60)) + font = QtGui.QFont() + font.setPointSize(20) + self.saved_connection_network_name.setFont(font) + self.saved_connection_network_name.setStyleSheet("color: rgb(255, 255, 255);") + self.saved_connection_network_name.setText("") + self.saved_connection_network_name.setScaledContents(False) + self.saved_connection_network_name.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.saved_connection_network_name.setObjectName("saved_connection_network_name") + self.horizontalLayout_7.addWidget(self.saved_connection_network_name) + self.saved_connection_back_button = IconButton(parent=self.saved_connection_page) + self.saved_connection_back_button.setMinimumSize(QtCore.QSize(60, 60)) + self.saved_connection_back_button.setMaximumSize(QtCore.QSize(60, 60)) + self.saved_connection_back_button.setFlat(True) + self.saved_connection_back_button.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) + self.saved_connection_back_button.setObjectName("saved_connection_back_button") + self.horizontalLayout_7.addWidget(self.saved_connection_back_button, 0, QtCore.Qt.AlignmentFlag.AlignRight) + self.verticalLayout_11.addLayout(self.horizontalLayout_7) + self.verticalLayout_5 = QtWidgets.QVBoxLayout() + self.verticalLayout_5.setObjectName("verticalLayout_5") + spacerItem5 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.verticalLayout_5.addItem(spacerItem5) + self.horizontalLayout_9 = QtWidgets.QHBoxLayout() + self.horizontalLayout_9.setObjectName("horizontalLayout_9") + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.frame = BlocksCustomFrame(parent=self.saved_connection_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) + self.frame.setSizePolicy(sizePolicy) + self.frame.setMaximumSize(QtCore.QSize(400, 16777215)) + self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) + self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) + self.frame.setObjectName("frame") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.frame) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.netlist_strength_label_2 = QtWidgets.QLabel(parent=self.frame) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) @@ -463,42 +503,31 @@ def setupUi(self, wifi_stacked_page): brush = QtGui.QBrush(QtGui.QColor(120, 120, 120)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Text, brush) - self.saved_connection_network_name.setPalette(palette) + self.netlist_strength_label_2.setPalette(palette) font = QtGui.QFont() - font.setPointSize(20) - self.saved_connection_network_name.setFont(font) - self.saved_connection_network_name.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.saved_connection_network_name.setObjectName("saved_connection_network_name") - self.horizontalLayout_7.addWidget(self.saved_connection_network_name) - self.saved_connection_back_button = IconButton(parent=self.saved_connection_page) - self.saved_connection_back_button.setMinimumSize(QtCore.QSize(60, 60)) - self.saved_connection_back_button.setMaximumSize(QtCore.QSize(60, 60)) - self.saved_connection_back_button.setFlat(True) - self.saved_connection_back_button.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) - self.saved_connection_back_button.setObjectName("saved_connection_back_button") - self.horizontalLayout_7.addWidget(self.saved_connection_back_button) - self.verticalLayout_11.addLayout(self.horizontalLayout_7) - self.verticalLayout_5 = QtWidgets.QVBoxLayout() - self.verticalLayout_5.setObjectName("verticalLayout_5") - spacerItem4 = QtWidgets.QSpacerItem(20, 30, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - self.verticalLayout_5.addItem(spacerItem4) - self.frame_5 = BlocksCustomFrame(parent=self.saved_connection_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.frame_5.sizePolicy().hasHeightForWidth()) - self.frame_5.setSizePolicy(sizePolicy) - self.frame_5.setMaximumSize(QtCore.QSize(16777215, 70)) - self.frame_5.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) - self.frame_5.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) - self.frame_5.setObjectName("frame_5") - self.layoutWidget_5 = QtWidgets.QWidget(parent=self.frame_5) - self.layoutWidget_5.setGeometry(QtCore.QRect(0, 0, 776, 62)) - self.layoutWidget_5.setObjectName("layoutWidget_5") - self.horizontalLayout_8 = QtWidgets.QHBoxLayout(self.layoutWidget_5) - self.horizontalLayout_8.setContentsMargins(0, 0, 0, 0) - self.horizontalLayout_8.setObjectName("horizontalLayout_8") - self.saved_connection_change_password_label_2 = QtWidgets.QLabel(parent=self.layoutWidget_5) + font.setPointSize(15) + self.netlist_strength_label_2.setFont(font) + self.netlist_strength_label_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.netlist_strength_label_2.setObjectName("netlist_strength_label_2") + self.horizontalLayout.addWidget(self.netlist_strength_label_2) + self.saved_connection_signal_strength_info_frame = QtWidgets.QLabel(parent=self.frame) + self.saved_connection_signal_strength_info_frame.setMinimumSize(QtCore.QSize(250, 0)) + font = QtGui.QFont() + font.setPointSize(11) + self.saved_connection_signal_strength_info_frame.setFont(font) + self.saved_connection_signal_strength_info_frame.setStyleSheet("color: rgb(255, 255, 255);") + self.saved_connection_signal_strength_info_frame.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.saved_connection_signal_strength_info_frame.setObjectName("saved_connection_signal_strength_info_frame") + self.horizontalLayout.addWidget(self.saved_connection_signal_strength_info_frame) + self.verticalLayout_6.addLayout(self.horizontalLayout) + self.line_4 = QtWidgets.QFrame(parent=self.frame) + self.line_4.setFrameShape(QtWidgets.QFrame.Shape.HLine) + self.line_4.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.line_4.setObjectName("line_4") + self.verticalLayout_6.addWidget(self.line_4) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.netlist_security_label_2 = QtWidgets.QLabel(parent=self.frame) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) @@ -518,55 +547,31 @@ def setupUi(self, wifi_stacked_page): brush = QtGui.QBrush(QtGui.QColor(120, 120, 120)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Text, brush) - self.saved_connection_change_password_label_2.setPalette(palette) + self.netlist_security_label_2.setPalette(palette) font = QtGui.QFont() font.setPointSize(15) - self.saved_connection_change_password_label_2.setFont(font) - self.saved_connection_change_password_label_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.saved_connection_change_password_label_2.setObjectName("saved_connection_change_password_label_2") - self.horizontalLayout_8.addWidget(self.saved_connection_change_password_label_2, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignVCenter) - self.saved_connection_change_password_field = BlocksCustomLinEdit(parent=self.layoutWidget_5) - self.saved_connection_change_password_field.setMinimumSize(QtCore.QSize(500, 60)) - self.saved_connection_change_password_field.setMaximumSize(QtCore.QSize(500, 16777215)) + self.netlist_security_label_2.setFont(font) + self.netlist_security_label_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.netlist_security_label_2.setObjectName("netlist_security_label_2") + self.horizontalLayout_2.addWidget(self.netlist_security_label_2) + self.saved_connection_security_type_info_label = QtWidgets.QLabel(parent=self.frame) + self.saved_connection_security_type_info_label.setMinimumSize(QtCore.QSize(250, 0)) font = QtGui.QFont() - font.setPointSize(12) - self.saved_connection_change_password_field.setFont(font) - self.saved_connection_change_password_field.setObjectName("saved_connection_change_password_field") - self.horizontalLayout_8.addWidget(self.saved_connection_change_password_field, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.saved_connection_change_password_view = IconButton(parent=self.layoutWidget_5) - self.saved_connection_change_password_view.setMinimumSize(QtCore.QSize(60, 60)) - self.saved_connection_change_password_view.setMaximumSize(QtCore.QSize(60, 60)) - self.saved_connection_change_password_view.setFlat(True) - self.saved_connection_change_password_view.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/unsee.svg")) - self.saved_connection_change_password_view.setObjectName("saved_connection_change_password_view") - self.horizontalLayout_8.addWidget(self.saved_connection_change_password_view, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignVCenter) - self.verticalLayout_5.addWidget(self.frame_5) - self.horizontalLayout_9 = QtWidgets.QHBoxLayout() - self.horizontalLayout_9.setObjectName("horizontalLayout_9") - self.frame_3 = BlocksCustomFrame(parent=self.saved_connection_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.frame_3.sizePolicy().hasHeightForWidth()) - self.frame_3.setSizePolicy(sizePolicy) - self.frame_3.setMinimumSize(QtCore.QSize(0, 0)) - self.frame_3.setMaximumSize(QtCore.QSize(200, 150)) - self.frame_3.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) - self.frame_3.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) - self.frame_3.setObjectName("frame_3") - self.layoutWidget_3 = QtWidgets.QWidget(parent=self.frame_3) - self.layoutWidget_3.setGeometry(QtCore.QRect(0, 20, 201, 119)) - self.layoutWidget_3.setObjectName("layoutWidget_3") - self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.layoutWidget_3) - self.verticalLayout_6.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_6.setObjectName("verticalLayout_6") - self.sabed_connection_signal_strength_label = QtWidgets.QLabel(parent=self.layoutWidget_3) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.sabed_connection_signal_strength_label.sizePolicy().hasHeightForWidth()) - self.sabed_connection_signal_strength_label.setSizePolicy(sizePolicy) - self.sabed_connection_signal_strength_label.setMaximumSize(QtCore.QSize(100, 16777215)) + font.setPointSize(11) + self.saved_connection_security_type_info_label.setFont(font) + self.saved_connection_security_type_info_label.setStyleSheet("color: rgb(255, 255, 255);") + self.saved_connection_security_type_info_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.saved_connection_security_type_info_label.setObjectName("saved_connection_security_type_info_label") + self.horizontalLayout_2.addWidget(self.saved_connection_security_type_info_label) + self.verticalLayout_6.addLayout(self.horizontalLayout_2) + self.line_5 = QtWidgets.QFrame(parent=self.frame) + self.line_5.setFrameShape(QtWidgets.QFrame.Shape.HLine) + self.line_5.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.line_5.setObjectName("line_5") + self.verticalLayout_6.addWidget(self.line_5) + self.horizontalLayout_8 = QtWidgets.QHBoxLayout() + self.horizontalLayout_8.setObjectName("horizontalLayout_8") + self.netlist_security_label_4 = QtWidgets.QLabel(parent=self.frame) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) @@ -586,70 +591,77 @@ def setupUi(self, wifi_stacked_page): brush = QtGui.QBrush(QtGui.QColor(120, 120, 120)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Text, brush) - self.sabed_connection_signal_strength_label.setPalette(palette) + self.netlist_security_label_4.setPalette(palette) font = QtGui.QFont() font.setPointSize(15) - self.sabed_connection_signal_strength_label.setFont(font) - self.sabed_connection_signal_strength_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.sabed_connection_signal_strength_label.setObjectName("sabed_connection_signal_strength_label") - self.verticalLayout_6.addWidget(self.sabed_connection_signal_strength_label, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignBottom) - self.line_4 = QtWidgets.QFrame(parent=self.layoutWidget_3) - self.line_4.setMinimumSize(QtCore.QSize(150, 0)) - self.line_4.setMaximumSize(QtCore.QSize(150, 16777215)) - self.line_4.setFrameShape(QtWidgets.QFrame.Shape.HLine) - self.line_4.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - self.line_4.setObjectName("line_4") - self.verticalLayout_6.addWidget(self.line_4, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.saved_connection_signal_strength_info_frame = QtWidgets.QLabel(parent=self.layoutWidget_3) + self.netlist_security_label_4.setFont(font) + self.netlist_security_label_4.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.netlist_security_label_4.setObjectName("netlist_security_label_4") + self.horizontalLayout_8.addWidget(self.netlist_security_label_4) + self.sn_info = QtWidgets.QLabel(parent=self.frame) + self.sn_info.setMinimumSize(QtCore.QSize(250, 0)) font = QtGui.QFont() - font.setPointSize(20) - self.saved_connection_signal_strength_info_frame.setFont(font) - self.saved_connection_signal_strength_info_frame.setStyleSheet("color:white") - self.saved_connection_signal_strength_info_frame.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.saved_connection_signal_strength_info_frame.setObjectName("saved_connection_signal_strength_info_frame") - self.verticalLayout_6.addWidget(self.saved_connection_signal_strength_info_frame, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) - self.horizontalLayout_9.addWidget(self.frame_3) - self.frame = BlocksCustomFrame(parent=self.saved_connection_page) - self.frame.setMaximumSize(QtCore.QSize(200, 100)) - self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) - self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) - self.frame.setObjectName("frame") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame) - self.horizontalLayout.setObjectName("horizontalLayout") - self.network_activate_btn = IconButton(parent=self.frame) - self.network_activate_btn.setMinimumSize(QtCore.QSize(80, 80)) - self.network_activate_btn.setMaximumSize(QtCore.QSize(80, 80)) - self.network_activate_btn.setText("") + font.setPointSize(11) + self.sn_info.setFont(font) + self.sn_info.setStyleSheet("color: rgb(255, 255, 255);") + self.sn_info.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.sn_info.setObjectName("sn_info") + self.horizontalLayout_8.addWidget(self.sn_info) + self.verticalLayout_6.addLayout(self.horizontalLayout_8) + self.verticalLayout_2.addWidget(self.frame) + self.horizontalLayout_9.addLayout(self.verticalLayout_2) + self.frame_8 = BlocksCustomFrame(parent=self.saved_connection_page) + self.frame_8.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) + self.frame_8.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) + self.frame_8.setObjectName("frame_8") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.frame_8) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.network_activate_btn = BlocksCustomButton(parent=self.frame_8) + self.network_activate_btn.setMinimumSize(QtCore.QSize(250, 80)) + self.network_activate_btn.setMaximumSize(QtCore.QSize(250, 80)) + font = QtGui.QFont() + font.setPointSize(15) + self.network_activate_btn.setFont(font) self.network_activate_btn.setFlat(True) self.network_activate_btn.setObjectName("network_activate_btn") - self.horizontalLayout.addWidget(self.network_activate_btn) - self.network_delete_btn = IconButton(parent=self.frame) - self.network_delete_btn.setMinimumSize(QtCore.QSize(60, 60)) - self.network_delete_btn.setMaximumSize(QtCore.QSize(80, 80)) - self.network_delete_btn.setText("") + self.verticalLayout_4.addWidget(self.network_activate_btn, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.network_details_btn = BlocksCustomButton(parent=self.frame_8) + self.network_details_btn.setMinimumSize(QtCore.QSize(250, 80)) + self.network_details_btn.setMaximumSize(QtCore.QSize(250, 80)) + font = QtGui.QFont() + font.setPointSize(15) + self.network_details_btn.setFont(font) + self.network_details_btn.setFlat(True) + self.network_details_btn.setObjectName("network_details_btn") + self.verticalLayout_4.addWidget(self.network_details_btn, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) + self.network_delete_btn = BlocksCustomButton(parent=self.frame_8) + self.network_delete_btn.setMinimumSize(QtCore.QSize(250, 80)) + self.network_delete_btn.setMaximumSize(QtCore.QSize(250, 80)) + font = QtGui.QFont() + font.setPointSize(15) + self.network_delete_btn.setFont(font) self.network_delete_btn.setFlat(True) self.network_delete_btn.setObjectName("network_delete_btn") - self.horizontalLayout.addWidget(self.network_delete_btn) - self.horizontalLayout_9.addWidget(self.frame) - self.frame_4 = BlocksCustomFrame(parent=self.saved_connection_page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + self.verticalLayout_4.addWidget(self.network_delete_btn, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.horizontalLayout_9.addWidget(self.frame_8) + self.verticalLayout_5.addLayout(self.horizontalLayout_9) + self.verticalLayout_11.addLayout(self.verticalLayout_5) + wifi_stacked_page.addWidget(self.saved_connection_page) + self.saved_details_page = QtWidgets.QWidget() + self.saved_details_page.setObjectName("saved_details_page") + self.verticalLayout_19 = QtWidgets.QVBoxLayout(self.saved_details_page) + self.verticalLayout_19.setObjectName("verticalLayout_19") + self.horizontalLayout_14 = QtWidgets.QHBoxLayout() + self.horizontalLayout_14.setObjectName("horizontalLayout_14") + spacerItem6 = QtWidgets.QSpacerItem(60, 60, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_14.addItem(spacerItem6) + self.snd_name = QtWidgets.QLabel(parent=self.saved_details_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.frame_4.sizePolicy().hasHeightForWidth()) - self.frame_4.setSizePolicy(sizePolicy) - self.frame_4.setMinimumSize(QtCore.QSize(0, 0)) - self.frame_4.setMaximumSize(QtCore.QSize(200, 150)) - self.frame_4.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) - self.frame_4.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) - self.frame_4.setObjectName("frame_4") - self.layoutWidget_4 = QtWidgets.QWidget(parent=self.frame_4) - self.layoutWidget_4.setGeometry(QtCore.QRect(0, 20, 201, 119)) - self.layoutWidget_4.setObjectName("layoutWidget_4") - self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.layoutWidget_4) - self.verticalLayout_7.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_7.setObjectName("verticalLayout_7") - self.saved_connection_security_type_label = QtWidgets.QLabel(parent=self.layoutWidget_4) - self.saved_connection_security_type_label.setMaximumSize(QtCore.QSize(100, 16777215)) + sizePolicy.setHeightForWidth(self.snd_name.sizePolicy().hasHeightForWidth()) + self.snd_name.setSizePolicy(sizePolicy) + self.snd_name.setMaximumSize(QtCore.QSize(16777215, 60)) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) @@ -669,39 +681,154 @@ def setupUi(self, wifi_stacked_page): brush = QtGui.QBrush(QtGui.QColor(120, 120, 120)) brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Text, brush) - self.saved_connection_security_type_label.setPalette(palette) + self.snd_name.setPalette(palette) + font = QtGui.QFont() + font.setPointSize(20) + self.snd_name.setFont(font) + self.snd_name.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.snd_name.setObjectName("snd_name") + self.horizontalLayout_14.addWidget(self.snd_name) + self.snd_back = IconButton(parent=self.saved_details_page) + self.snd_back.setMinimumSize(QtCore.QSize(60, 60)) + self.snd_back.setMaximumSize(QtCore.QSize(60, 60)) + self.snd_back.setFlat(True) + self.snd_back.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/back.svg")) + self.snd_back.setObjectName("snd_back") + self.horizontalLayout_14.addWidget(self.snd_back) + self.verticalLayout_19.addLayout(self.horizontalLayout_14) + self.verticalLayout_8 = QtWidgets.QVBoxLayout() + self.verticalLayout_8.setObjectName("verticalLayout_8") + spacerItem7 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.verticalLayout_8.addItem(spacerItem7) + self.frame_9 = BlocksCustomFrame(parent=self.saved_details_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame_9.sizePolicy().hasHeightForWidth()) + self.frame_9.setSizePolicy(sizePolicy) + self.frame_9.setMinimumSize(QtCore.QSize(0, 70)) + self.frame_9.setMaximumSize(QtCore.QSize(16777215, 70)) + self.frame_9.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) + self.frame_9.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) + self.frame_9.setObjectName("frame_9") + self.layoutWidget_8 = QtWidgets.QWidget(parent=self.frame_9) + self.layoutWidget_8.setGeometry(QtCore.QRect(0, 0, 776, 62)) + self.layoutWidget_8.setObjectName("layoutWidget_8") + self.horizontalLayout_10 = QtWidgets.QHBoxLayout(self.layoutWidget_8) + self.horizontalLayout_10.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_10.setObjectName("horizontalLayout_10") + self.saved_connection_change_password_label_3 = QtWidgets.QLabel(parent=self.layoutWidget_8) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(120, 120, 120)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(120, 120, 120)) + brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern) + palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Text, brush) + self.saved_connection_change_password_label_3.setPalette(palette) font = QtGui.QFont() font.setPointSize(15) - self.saved_connection_security_type_label.setFont(font) - self.saved_connection_security_type_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.saved_connection_security_type_label.setObjectName("saved_connection_security_type_label") - self.verticalLayout_7.addWidget(self.saved_connection_security_type_label, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignBottom) - self.line_5 = QtWidgets.QFrame(parent=self.layoutWidget_4) - self.line_5.setMinimumSize(QtCore.QSize(150, 0)) - self.line_5.setFrameShape(QtWidgets.QFrame.Shape.HLine) - self.line_5.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - self.line_5.setObjectName("line_5") - self.verticalLayout_7.addWidget(self.line_5, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) - self.saved_connection_security_type_info_label = QtWidgets.QLabel(parent=self.layoutWidget_4) + self.saved_connection_change_password_label_3.setFont(font) + self.saved_connection_change_password_label_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.saved_connection_change_password_label_3.setObjectName("saved_connection_change_password_label_3") + self.horizontalLayout_10.addWidget(self.saved_connection_change_password_label_3, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.saved_connection_change_password_field = BlocksCustomLinEdit(parent=self.layoutWidget_8) + self.saved_connection_change_password_field.setMinimumSize(QtCore.QSize(500, 60)) + self.saved_connection_change_password_field.setMaximumSize(QtCore.QSize(500, 16777215)) font = QtGui.QFont() - font.setPointSize(20) - self.saved_connection_security_type_info_label.setFont(font) - self.saved_connection_security_type_info_label.setStyleSheet("color:white") - self.saved_connection_security_type_info_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.saved_connection_security_type_info_label.setObjectName("saved_connection_security_type_info_label") - self.verticalLayout_7.addWidget(self.saved_connection_security_type_info_label, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) - self.horizontalLayout_9.addWidget(self.frame_4) - self.verticalLayout_5.addLayout(self.horizontalLayout_9) - self.verticalLayout_11.addLayout(self.verticalLayout_5) - wifi_stacked_page.addWidget(self.saved_connection_page) + font.setPointSize(12) + self.saved_connection_change_password_field.setFont(font) + self.saved_connection_change_password_field.setObjectName("saved_connection_change_password_field") + self.horizontalLayout_10.addWidget(self.saved_connection_change_password_field, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) + self.saved_connection_change_password_view = IconButton(parent=self.layoutWidget_8) + self.saved_connection_change_password_view.setMinimumSize(QtCore.QSize(60, 60)) + self.saved_connection_change_password_view.setMaximumSize(QtCore.QSize(60, 60)) + self.saved_connection_change_password_view.setFlat(True) + self.saved_connection_change_password_view.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/unsee.svg")) + self.saved_connection_change_password_view.setObjectName("saved_connection_change_password_view") + self.horizontalLayout_10.addWidget(self.saved_connection_change_password_view, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.verticalLayout_8.addWidget(self.frame_9) + self.horizontalLayout_13 = QtWidgets.QHBoxLayout() + self.horizontalLayout_13.setObjectName("horizontalLayout_13") + self.verticalLayout_13 = QtWidgets.QVBoxLayout() + self.verticalLayout_13.setObjectName("verticalLayout_13") + self.frame_12 = BlocksCustomFrame(parent=self.saved_details_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame_12.sizePolicy().hasHeightForWidth()) + self.frame_12.setSizePolicy(sizePolicy) + self.frame_12.setMinimumSize(QtCore.QSize(400, 160)) + self.frame_12.setMaximumSize(QtCore.QSize(400, 99999)) + self.frame_12.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) + self.frame_12.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) + self.frame_12.setObjectName("frame_12") + self.verticalLayout_17 = QtWidgets.QVBoxLayout(self.frame_12) + self.verticalLayout_17.setObjectName("verticalLayout_17") + spacerItem8 = QtWidgets.QSpacerItem(10, 10, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.verticalLayout_17.addItem(spacerItem8) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.low_priority_btn = BlocksCustomCheckButton(parent=self.frame_12) + self.low_priority_btn.setMinimumSize(QtCore.QSize(100, 100)) + self.low_priority_btn.setMaximumSize(QtCore.QSize(100, 100)) + self.low_priority_btn.setCheckable(True) + self.low_priority_btn.setAutoExclusive(True) + self.low_priority_btn.setFlat(True) + self.low_priority_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/indf_svg.svg")) + self.low_priority_btn.setObjectName("low_priority_btn") + self.priority_btn_group = QtWidgets.QButtonGroup(wifi_stacked_page) + self.priority_btn_group.setObjectName("priority_btn_group") + self.priority_btn_group.addButton(self.low_priority_btn) + self.horizontalLayout_4.addWidget(self.low_priority_btn) + self.med_priority_btn = BlocksCustomCheckButton(parent=self.frame_12) + self.med_priority_btn.setMinimumSize(QtCore.QSize(100, 100)) + self.med_priority_btn.setMaximumSize(QtCore.QSize(100, 100)) + self.med_priority_btn.setCheckable(True) + self.med_priority_btn.setChecked(True) + self.med_priority_btn.setAutoExclusive(True) + self.med_priority_btn.setFlat(True) + self.med_priority_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/indf_svg.svg")) + self.med_priority_btn.setObjectName("med_priority_btn") + self.priority_btn_group.addButton(self.med_priority_btn) + self.horizontalLayout_4.addWidget(self.med_priority_btn) + self.high_priority_btn = BlocksCustomCheckButton(parent=self.frame_12) + self.high_priority_btn.setMinimumSize(QtCore.QSize(100, 100)) + self.high_priority_btn.setMaximumSize(QtCore.QSize(100, 100)) + self.high_priority_btn.setCheckable(True) + self.high_priority_btn.setChecked(False) + self.high_priority_btn.setAutoExclusive(True) + self.high_priority_btn.setFlat(True) + self.high_priority_btn.setProperty("icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/indf_svg.svg")) + self.high_priority_btn.setObjectName("high_priority_btn") + self.priority_btn_group.addButton(self.high_priority_btn) + self.horizontalLayout_4.addWidget(self.high_priority_btn) + self.verticalLayout_17.addLayout(self.horizontalLayout_4) + self.verticalLayout_13.addWidget(self.frame_12, 0, QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.horizontalLayout_13.addLayout(self.verticalLayout_13) + self.verticalLayout_8.addLayout(self.horizontalLayout_13) + self.verticalLayout_19.addLayout(self.verticalLayout_8) + wifi_stacked_page.addWidget(self.saved_details_page) self.hotspot_page = QtWidgets.QWidget() self.hotspot_page.setObjectName("hotspot_page") self.verticalLayout_12 = QtWidgets.QVBoxLayout(self.hotspot_page) self.verticalLayout_12.setObjectName("verticalLayout_12") self.hospot_page_header_layout = QtWidgets.QHBoxLayout() self.hospot_page_header_layout.setObjectName("hospot_page_header_layout") - spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - self.hospot_page_header_layout.addItem(spacerItem5) + spacerItem9 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.hospot_page_header_layout.addItem(spacerItem9) self.hotspot_header_title = QtWidgets.QLabel(parent=self.hotspot_page) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) @@ -740,8 +867,8 @@ def setupUi(self, wifi_stacked_page): self.hotspot_page_content_layout = QtWidgets.QVBoxLayout() self.hotspot_page_content_layout.setContentsMargins(-1, 5, -1, 5) self.hotspot_page_content_layout.setObjectName("hotspot_page_content_layout") - spacerItem6 = QtWidgets.QSpacerItem(20, 50, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - self.hotspot_page_content_layout.addItem(spacerItem6) + spacerItem10 = QtWidgets.QSpacerItem(20, 50, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.hotspot_page_content_layout.addItem(spacerItem10) self.frame_6 = BlocksCustomFrame(parent=self.hotspot_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) @@ -807,11 +934,11 @@ def setupUi(self, wifi_stacked_page): self.hotspot_name_input_field.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password) self.hotspot_name_input_field.setObjectName("hotspot_name_input_field") self.horizontalLayout_11.addWidget(self.hotspot_name_input_field, 0, QtCore.Qt.AlignmentFlag.AlignHCenter) - spacerItem7 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_11.addItem(spacerItem7) + spacerItem11 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_11.addItem(spacerItem11) self.hotspot_page_content_layout.addWidget(self.frame_6) - spacerItem8 = QtWidgets.QSpacerItem(773, 128, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - self.hotspot_page_content_layout.addItem(spacerItem8) + spacerItem12 = QtWidgets.QSpacerItem(773, 128, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + self.hotspot_page_content_layout.addItem(spacerItem12) self.frame_7 = BlocksCustomFrame(parent=self.hotspot_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) @@ -1017,7 +1144,7 @@ def setupUi(self, wifi_stacked_page): wifi_stacked_page.addWidget(self.hotspot_page) self.retranslateUi(wifi_stacked_page) - wifi_stacked_page.setCurrentIndex(2) + wifi_stacked_page.setCurrentIndex(4) QtCore.QMetaObject.connectSlotsByName(wifi_stacked_page) def retranslateUi(self, wifi_stacked_page): @@ -1052,24 +1179,39 @@ def retranslateUi(self, wifi_stacked_page): self.add_network_password_view.setProperty("class", _translate("wifi_stacked_page", "back_btn")) self.add_network_password_view.setProperty("button_type", _translate("wifi_stacked_page", "icon")) self.add_network_validation_button.setText(_translate("wifi_stacked_page", "Activate")) - self.saved_connection_delete_network_button.setText(_translate("wifi_stacked_page", "Delete")) - self.saved_connection_delete_network_button.setProperty("class", _translate("wifi_stacked_page", "back_btn")) - self.saved_connection_delete_network_button.setProperty("button_type", _translate("wifi_stacked_page", "icon")) - self.saved_connection_network_name.setText(_translate("wifi_stacked_page", "SSID")) self.saved_connection_back_button.setText(_translate("wifi_stacked_page", "Back")) self.saved_connection_back_button.setProperty("class", _translate("wifi_stacked_page", "back_btn")) self.saved_connection_back_button.setProperty("button_type", _translate("wifi_stacked_page", "icon")) - self.saved_connection_change_password_label_2.setText(_translate("wifi_stacked_page", "Change\n" -"Password")) - self.saved_connection_change_password_view.setText(_translate("wifi_stacked_page", "View")) - self.saved_connection_change_password_view.setProperty("class", _translate("wifi_stacked_page", "back_btn")) - self.saved_connection_change_password_view.setProperty("button_type", _translate("wifi_stacked_page", "icon")) - self.sabed_connection_signal_strength_label.setText(_translate("wifi_stacked_page", "Signal\n" + self.netlist_strength_label_2.setText(_translate("wifi_stacked_page", "Signal\n" "Strength")) self.saved_connection_signal_strength_info_frame.setText(_translate("wifi_stacked_page", "TextLabel")) - self.saved_connection_security_type_label.setText(_translate("wifi_stacked_page", "Security\n" + self.netlist_security_label_2.setText(_translate("wifi_stacked_page", "Security\n" "Type")) self.saved_connection_security_type_info_label.setText(_translate("wifi_stacked_page", "TextLabel")) + self.netlist_security_label_4.setText(_translate("wifi_stacked_page", "Status")) + self.sn_info.setText(_translate("wifi_stacked_page", "TextLabel")) + self.network_activate_btn.setText(_translate("wifi_stacked_page", "Connect")) + self.network_details_btn.setText(_translate("wifi_stacked_page", "Details")) + self.network_delete_btn.setText(_translate("wifi_stacked_page", "Forget")) + self.snd_name.setText(_translate("wifi_stacked_page", "SSID")) + self.snd_back.setText(_translate("wifi_stacked_page", "Back")) + self.snd_back.setProperty("class", _translate("wifi_stacked_page", "back_btn")) + self.snd_back.setProperty("button_type", _translate("wifi_stacked_page", "icon")) + self.saved_connection_change_password_label_3.setText(_translate("wifi_stacked_page", "Change\n" +"Password")) + self.saved_connection_change_password_view.setText(_translate("wifi_stacked_page", "View")) + self.saved_connection_change_password_view.setProperty("class", _translate("wifi_stacked_page", "back_btn")) + self.saved_connection_change_password_view.setProperty("button_type", _translate("wifi_stacked_page", "icon")) + self.frame_12.setProperty("text", _translate("wifi_stacked_page", "Network priority")) + self.low_priority_btn.setText(_translate("wifi_stacked_page", "Low")) + self.low_priority_btn.setProperty("class", _translate("wifi_stacked_page", "back_btn")) + self.low_priority_btn.setProperty("button_type", _translate("wifi_stacked_page", "icon")) + self.med_priority_btn.setText(_translate("wifi_stacked_page", "Medium")) + self.med_priority_btn.setProperty("class", _translate("wifi_stacked_page", "back_btn")) + self.med_priority_btn.setProperty("button_type", _translate("wifi_stacked_page", "icon")) + self.high_priority_btn.setText(_translate("wifi_stacked_page", "High")) + self.high_priority_btn.setProperty("class", _translate("wifi_stacked_page", "back_btn")) + self.high_priority_btn.setProperty("button_type", _translate("wifi_stacked_page", "icon")) self.hotspot_header_title.setText(_translate("wifi_stacked_page", "Hotspot")) self.hotspot_back_button.setText(_translate("wifi_stacked_page", "Back")) self.hotspot_back_button.setProperty("class", _translate("wifi_stacked_page", "back_btn")) @@ -1085,4 +1227,5 @@ def retranslateUi(self, wifi_stacked_page): from lib.utils.blocks_frame import BlocksCustomFrame from lib.utils.blocks_linedit import BlocksCustomLinEdit from lib.utils.blocks_togglebutton import NetworkWidgetbuttons +from lib.utils.check_button import BlocksCustomCheckButton from lib.utils.icon_button import IconButton diff --git a/BlocksScreen/lib/utils/RepeatedTimer.py b/BlocksScreen/lib/utils/RepeatedTimer.py index ff96a0f8..42b42aa0 100644 --- a/BlocksScreen/lib/utils/RepeatedTimer.py +++ b/BlocksScreen/lib/utils/RepeatedTimer.py @@ -31,6 +31,7 @@ def _run(self): self._function(*self._args, **self._kwargs) def startTimer(self): + """Start timer""" if self.running is False: try: self._timer = threading.Timer(self._timeout, self._run) @@ -47,6 +48,7 @@ def startTimer(self): self.running = True def stopTimer(self): + """Stop timer""" if self._timer is None: return if self.running: @@ -55,15 +57,3 @@ def stopTimer(self): self._timer = None self.stopEvent.clear() self.running = False - - @staticmethod - def pauseTimer(self): - # TODO never tested - self.stopEvent.clear() - self.running = False - - @staticmethod - def resumeTimer(self): - # TODO: never tested - self.stopEvent.set() - self.running = True diff --git a/BlocksScreen/lib/utils/RoutingQueue.py b/BlocksScreen/lib/utils/RoutingQueue.py index c7b2b08f..4efde01a 100644 --- a/BlocksScreen/lib/utils/RoutingQueue.py +++ b/BlocksScreen/lib/utils/RoutingQueue.py @@ -22,6 +22,7 @@ def __init__(self): @property def resend(self): + """Resend queue""" return self._resend @resend.setter @@ -30,10 +31,12 @@ def resend(self, new_resend): self._resend = new_resend def block(self): + """Blocks queue""" # Sets the flag to false self._clear_to_move.clear() def unblock(self): + """Unblock queue""" # Sets the flag to True self._clear_to_move.set() @@ -62,8 +65,8 @@ def add_command( self._read_lines += 1 except Exception as e: raise ValueError( - "Unexpected error while adding a command to queue, and argument " - ) + "Unexpected error while adding a command to queue, and argument %s" + ) from e def get_command(self, block=True, timeout=None, resend=False): """ diff --git a/BlocksScreen/lib/utils/blocks_Scrollbar.py b/BlocksScreen/lib/utils/blocks_Scrollbar.py index 08dc6b68..38e571e0 100644 --- a/BlocksScreen/lib/utils/blocks_Scrollbar.py +++ b/BlocksScreen/lib/utils/blocks_Scrollbar.py @@ -8,6 +8,7 @@ def __init__(self, parent=None): self.setFixedWidth(40) def paintEvent(self, event): + """Re-implemented method, paint widget""" painter = QtGui.QPainter(self) painter.setRenderHint(painter.RenderHint.Antialiasing, True) painter.setRenderHint(painter.RenderHint.SmoothPixmapTransform, True) @@ -43,15 +44,10 @@ def paintEvent(self, event): / (max_val - min_val) ) else: - val = ( - np.interp((handle_percentage), [15, 85], [0, 100]) - / 100 - * max_val - ) + val = np.interp((handle_percentage), [15, 85], [0, 100]) / 100 * max_val base_handle_length = int( - (groove.height() * page_step / (max_val - min_val + page_step)) - + 40 + (groove.height() * page_step / (max_val - min_val + page_step)) + 40 ) handle_pos = int( (groove.height() - base_handle_length) diff --git a/BlocksScreen/lib/utils/blocks_button.py b/BlocksScreen/lib/utils/blocks_button.py index dc8a79f6..292b5125 100644 --- a/BlocksScreen/lib/utils/blocks_button.py +++ b/BlocksScreen/lib/utils/blocks_button.py @@ -4,6 +4,8 @@ class ButtonColors(enum.Enum): + """Standard button colors""" + NORMAL_BG = (223, 223, 223) PRESSED_BG = (169, 169, 169) DISABLED_BG = (169, 169, 169) @@ -15,15 +17,15 @@ class ButtonColors(enum.Enum): class BlocksCustomButton(QtWidgets.QAbstractButton): def __init__( self, - parent: QtWidgets.QWidget = None, + parent: QtWidgets.QWidget | None = None, ) -> None: if parent: - super(BlocksCustomButton, self).__init__(parent) + super().__init__(parent) else: - super(BlocksCustomButton, self).__init__() - + super().__init__() self.icon_pixmap: QtGui.QPixmap = QtGui.QPixmap() self._icon_rect: QtCore.QRectF = QtCore.QRectF() + self._is_flat: bool = False self.button_background = None self.button_ellipse = None self._text: str = "" @@ -33,13 +35,14 @@ def __init__( self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) def setShowNotification(self, show: bool) -> None: + """Set notification on button""" if self._show_notification != show: self._show_notification = show - self.repaint() self.update() @property def name(self): + """Widget name""" return self._name @name.setter @@ -48,49 +51,86 @@ def name(self, new_name) -> None: self.setObjectName(new_name) def text(self) -> str | None: + """Button text""" return self._text def setText(self, text: str) -> None: + """Set button text""" self._text = text self.update() - return def setPixmap(self, pixmap: QtGui.QPixmap) -> None: + """Set button pixmap""" self.icon_pixmap = pixmap - self.repaint() + self.update() def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: + """Handle mouse press events""" if not self.isEnabled(): e.ignore() return - - if self.button_background is not None: + if self.button_background: pos_f = QtCore.QPointF(e.pos()) if self.button_background.contains(pos_f): super().mousePressEvent(e) return - else: - e.ignore() - return - return super().mousePressEvent(e) + e.ignore() + return + super().mousePressEvent(e) - def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): - opt = QtWidgets.QStyleOptionButton() - # self.initStyleOption(opt) + def setFlat(self, flat) -> None: + """Enable 'flat' appearance to the button""" + if self._is_flat != flat: + self._is_flat = flat + self.update() # Schedule repaint + + def isFlat(self) -> bool: + """Get flat property + + Returns: + bool: Button has 'flat' appearance enabled + """ + return self._is_flat + + def setAutoDefault(self, _): + """Disable auto default behavior""" + return + + def setProperty(self, name: str, value: typing.Any): + """Set widget properties""" + if name == "icon_pixmap": + self.icon_pixmap = value + if name == "name": + self._name = name + if name == "text_color": + self.text_color = QtGui.QColor(value) + self.update() + def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): + """Re-implemented method, paint widget""" painter = QtGui.QPainter(self) painter.setRenderHint(painter.RenderHint.Antialiasing, True) painter.setRenderHint(painter.RenderHint.SmoothPixmapTransform, True) painter.setRenderHint(painter.RenderHint.LosslessImageRendering, True) - _rect = self.rect() _style = self.style() - - if _style is None or _rect is None: + if not _style or not _rect: return - - - margin = _style.pixelMetric(_style.PixelMetric.PM_ButtonMargin, opt, self) + # Flat button control + opt = QtWidgets.QStyleOptionButton() + draw_frame = ( + not self._is_flat + or self.underMouse() + or opt.state & QtWidgets.QStyle.StateFlag.State_Sunken + ) + if draw_frame: + _style.drawControl( + QtWidgets.QStyle.ControlElement.CE_PushButtonLabel, opt, painter, self + ) + _style.drawControl( + QtWidgets.QStyle.ControlElement.CE_PushButtonLabel, opt, painter, self + ) + self.setStyle(_style) # Determine background and text colors based on state if not self.isEnabled(): bg_color_tuple = ButtonColors.DISABLED_BG.value @@ -131,7 +171,6 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): painter.setPen(QtCore.Qt.PenStyle.NoPen) painter.setBrush(bg_color) painter.fillPath(self.button_background, bg_color) - _parent_rect = self.button_ellipse.toRect() _icon_rect = QtCore.QRectF( _parent_rect.left() * 2.8, @@ -145,7 +184,6 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation, ) - scaled_width = _icon_scaled.width() scaled_height = _icon_scaled.height() adjusted_x = (_icon_rect.width() - scaled_width) / 2.0 @@ -156,24 +194,17 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): scaled_width, scaled_height, ) - tinted_icon_pixmap = QtGui.QPixmap(_icon_scaled.size()) tinted_icon_pixmap.fill(QtCore.Qt.GlobalColor.transparent) - - margin = _style.pixelMetric(_style.PixelMetric.PM_ButtonMargin, opt, self) - if not self.isEnabled(): tinted_icon_pixmap = QtGui.QPixmap(_icon_scaled.size()) tinted_icon_pixmap.fill(QtCore.Qt.GlobalColor.transparent) - icon_painter = QtGui.QPainter(tinted_icon_pixmap) icon_painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) icon_painter.setRenderHint( QtGui.QPainter.RenderHint.SmoothPixmapTransform ) - icon_painter.drawPixmap(0, 0, _icon_scaled) - icon_painter.setCompositionMode( QtGui.QPainter.CompositionMode.CompositionMode_SourceAtop ) @@ -182,107 +213,46 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): ) icon_painter.fillRect(tinted_icon_pixmap.rect(), tint) icon_painter.end() - final_pixmap = tinted_icon_pixmap else: final_pixmap = _icon_scaled - destination_point = adjusted_icon_rect.toRect().topLeft() painter.drawPixmap(destination_point, final_pixmap) - - if self.text(): - font_metrics = self.fontMetrics() - self.text_width = font_metrics.horizontalAdvance(self._text) - self.label_width = self.contentsRect().width() - - margin = _style.pixelMetric( - _style.PixelMetric.PM_ButtonMargin, opt, self - ) - - _start_text_position = int(self.button_ellipse.width()) - _text_rect = _rect - - - _text_rect2 = _rect - _text_rect2.setWidth( - self.width() - int(self.button_ellipse.width()) - ) - _text_rect2.setLeft(int(self.button_ellipse.width())) - - _text_rect.setWidth(self.width() - int(self.button_ellipse.width())) - _text_rect.setLeft(int(self.button_ellipse.width())) - _pen = painter.pen() - _pen.setStyle(QtCore.Qt.PenStyle.SolidLine) - _pen.setWidth(1) - _pen.setColor(current_text_color) - painter.setPen(_pen) - - - # if self.text_width < _text_rect2.width()*0.6: - _text_rect.setWidth( - self.width() - int(self.button_ellipse.width()*1.4) - ) - _text_rect.setLeft(int(self.button_ellipse.width())) - - painter.drawText( - _text_rect, - QtCore.Qt.TextFlag.TextShowMnemonic - | QtCore.Qt.AlignmentFlag.AlignCenter, - str(self.text()), - ) - # else: - # _text_rect.setLeft(_start_text_position + margin) - - # _text_rect.setWidth(self.width() - int(self.button_ellipse.width())) - - # painter.drawText( - # _text_rect, - # QtCore.Qt.TextFlag.TextShowMnemonic - # | QtCore.Qt.AlignmentFlag.AlignLeft - # | QtCore.Qt.AlignmentFlag.AlignVCenter, - # str(self.text()), - # ) - painter.setPen(QtCore.Qt.PenStyle.NoPen) - + self._paint_text(painter, _rect, current_text_color) if self._show_notification: - dot_diameter = self.height() * 0.4 - dot_x = self.width() - dot_diameter - notification_color = QtGui.QColor(*ButtonColors.NOTIFICATION_DOT.value) - painter.setBrush(notification_color) - painter.setPen(QtCore.Qt.PenStyle.NoPen) - dot_rect = QtCore.QRectF(dot_x, 0, dot_diameter, dot_diameter) - painter.drawEllipse(dot_rect) + self._paint_notification(painter) painter.end() - def setProperty(self, name: str, value: typing.Any): - if name == "icon_pixmap": - self.icon_pixmap = value - elif name == "name": - self._name = name - elif name == "text_color": - self.text_color = QtGui.QColor(value) - self.update() - - def handleTouchBegin(self, e: QtCore.QEvent): ... - def handleTouchUpdate(self, e: QtCore.QEvent): ... - def handleTouchEnd(self, e: QtCore.QEvent): ... - def handleTouchCancel(self, e: QtCore.QEvent): ... - def setAutoDefault(self, bool): ... - def setFlat(self, bool): ... + def _paint_text( + self, painter: QtGui.QPainter, rect: QtCore.QRect, text_color: QtGui.QColor + ) -> None: + _text_rect = rect + _text_rect2 = rect + _text_rect2.setWidth(self.width() - int(self.button_ellipse.width())) + _text_rect2.setLeft(int(self.button_ellipse.width())) + _text_rect.setWidth(self.width() - int(self.button_ellipse.width())) + _text_rect.setLeft(int(self.button_ellipse.width())) + _pen = painter.pen() + _pen.setStyle(QtCore.Qt.PenStyle.SolidLine) + _pen.setWidth(1) + _pen.setColor(text_color) + painter.setPen(_pen) + _text_rect.setWidth(self.width() - int(self.button_ellipse.width() * 1.4)) + _text_rect.setLeft(int(self.button_ellipse.width())) + painter.drawText( + _text_rect, + QtCore.Qt.TextFlag.TextShowMnemonic | QtCore.Qt.AlignmentFlag.AlignCenter, + str(self.text()), + ) + painter.setPen(QtCore.Qt.PenStyle.NoPen) - def event(self, e: QtCore.QEvent) -> bool: - if e.type() == QtCore.QEvent.Type.TouchBegin: - self.handleTouchBegin(e) - return False - elif e.type() == QtCore.QEvent.Type.TouchUpdate: - self.handleTouchUpdate(e) - return False - elif e.type() == QtCore.QEvent.Type.TouchEnd: - self.handleTouchEnd(e) - return False - elif e.type() == QtCore.QEvent.Type.TouchCancel: - self.handleTouchCancel(e) - return False - return super().event(e) + def _paint_notification(self, painter: QtGui.QPainter) -> None: + dot_diameter = self.height() * 0.4 + dot_x = self.width() - dot_diameter + notification_color = QtGui.QColor(*ButtonColors.NOTIFICATION_DOT.value) + painter.setBrush(notification_color) + painter.setPen(QtCore.Qt.PenStyle.NoPen) + dot_rect = QtCore.QRectF(dot_x, 0, dot_diameter, dot_diameter) + painter.drawEllipse(dot_rect) diff --git a/BlocksScreen/lib/utils/blocks_frame.py b/BlocksScreen/lib/utils/blocks_frame.py index 05810d8c..7de7514e 100644 --- a/BlocksScreen/lib/utils/blocks_frame.py +++ b/BlocksScreen/lib/utils/blocks_frame.py @@ -1,28 +1,94 @@ -from PyQt6.QtWidgets import QFrame -from PyQt6.QtGui import QPainter, QPen, QBrush, QColor -from PyQt6.QtCore import QRectF +from PyQt6 import QtCore, QtGui, QtWidgets +import typing -class BlocksCustomFrame(QFrame): +class BlocksCustomFrame(QtWidgets.QFrame): def __init__(self, parent=None): super().__init__(parent) - self._radius = 20 + + self._radius = 10 + self._left_line_width = 15 + self._is_centered = False + self.text = "" + + self.setMinimumHeight(40) + self.setMinimumWidth(300) def setRadius(self, radius: int): + """Set widget frame radius""" self._radius = radius self.update() - def radius(self): - return self._radius + def setLeftLineWidth(self, width: int): + """Set widget left line width""" + self._left_line_width = width + self.update() + + def setCentered(self, centered: bool): + """Set if text is centered or left-aligned""" + self._is_centered = centered + self.update() - def paintEvent(self, event): - painter = QPainter(self) - painter.setRenderHint(QPainter.RenderHint.Antialiasing) - rect = QRectF(self.rect()) - pen = QPen(QColor(20, 20, 20, 70)) + def setProperty(self, name: str | None, value: typing.Any) -> bool: + if name == "text": + self.text = value + self.update() + return True + return super().setProperty(name, value) + + def paintEvent(self, a0): + painter = QtGui.QPainter(self) + painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) + + rect = QtCore.QRectF(self.rect()) + pen = QtGui.QPen(QtGui.QColor(20, 20, 20, 70)) pen.setWidth(2) painter.setPen(pen) - painter.setBrush(QBrush(QColor(50, 50, 50, 100))) - painter.drawRoundedRect( - rect.adjusted(1, 1, -1, -1), self._radius, self._radius - ) + painter.setBrush(QtGui.QBrush(QtGui.QColor(50, 50, 50, 100))) + painter.drawRoundedRect(rect.adjusted(1, 1, -1, -1), self._radius, self._radius) + + if self.text: + painter.setPen(QtGui.QColor("white")) + font = QtGui.QFont() + font.setPointSize(12) + painter.setFont(font) + fm = painter.fontMetrics() + text_width = fm.horizontalAdvance(self.text) + baseline = fm.ascent() + + margin = 10 + spacing = 8 + line_center_y = margin + baseline // 2 + + if self._is_centered: + left_line_width = self._left_line_width + right_line_width = self._left_line_width + + total_content_width = ( + left_line_width + spacing + text_width + spacing + right_line_width + ) + + start_x = (self.width() - total_content_width) // 2 + x = max(margin, start_x) + + else: + left_line_width = self._left_line_width + x = margin + right_line_width = 0 + + small_rect = QtCore.QRect(x, line_center_y - 1, left_line_width, 3) + painter.fillRect(small_rect, QtGui.QColor("white")) + x += left_line_width + spacing + + painter.drawText(x, margin + baseline, self.text) + x += text_width + spacing + + if self._is_centered: + big_rect_width = right_line_width + else: + remaining_width = self.width() - x - margin + big_rect_width = max(0, remaining_width) + + big_rect = QtCore.QRect(x, line_center_y - 1, big_rect_width, 3) + + painter.fillRect(big_rect, QtGui.QColor("white")) diff --git a/BlocksScreen/lib/utils/blocks_label.py b/BlocksScreen/lib/utils/blocks_label.py index 4df5a045..1aaa173e 100644 --- a/BlocksScreen/lib/utils/blocks_label.py +++ b/BlocksScreen/lib/utils/blocks_label.py @@ -4,7 +4,7 @@ class BlocksLabel(QtWidgets.QLabel): def __init__(self, parent: QtWidgets.QWidget = None, *args, **kwargs): - super(BlocksLabel, self).__init__(parent, *args, **kwargs) + super().__init__(parent, *args, **kwargs) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) self.icon_pixmap: typing.Optional[QtGui.QPixmap] = None @@ -15,41 +15,39 @@ def __init__(self, parent: QtWidgets.QWidget = None, *args, **kwargs): self._marquee: bool = True self.timer = QtCore.QTimer() self.timer.timeout.connect(self._scroll_text) - self.delay_timer = QtCore.QTimer() - self.delay_timer.setSingleShot(True) - self.delay_timer.timeout.connect(self._start_marquee) - self.scroll_pos = 0.0 - self.marquee_spacing = 20 + self.marquee_spacing = 40 + self.scroll_speed = 40 + self.scroll_animation_speed = 30 + self.max_loops = 2 + self.loop_count = 0 self.paused = False - self.scroll_speed = 20 - self.scroll_animation_speed = 50 self.setMouseTracking(True) self.setTabletTracking(True) self.setSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding, ) - self._glow_color: QtGui.QColor = QtGui.QColor("#E95757") self._animation_speed: int = 300 self.glow_animation = QtCore.QPropertyAnimation(self, b"glow_color") self.glow_animation.setEasingCurve(QtCore.QEasingCurve().Type.InOutQuart) self.glow_animation.setDuration(self.animation_speed) - self.glow_animation.finished.connect(self.change_glow_direction) self.glow_animation.finished.connect(self.repaint) - self.total_scroll_width: float = 0.0 - self.marquee_delay = 5000 - self.loop_count = 0 + self.text_width: float = 0.0 + self.label_width: float = 0.0 + self.icon_margin: int = 5 self.first_run = True def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: + """Re-implemented method, handle widget resize event""" self.update_text_metrics() return super().resizeEvent(a0) def mousePressEvent(self, ev: QtGui.QMouseEvent) -> None: + """Re-implemented method, handle mouse press event""" if ( ev.button() == QtCore.Qt.MouseButton.LeftButton and not self.timer.isActive() @@ -58,15 +56,19 @@ def mousePressEvent(self, ev: QtGui.QMouseEvent) -> None: self.start_scroll() def setPixmap(self, a0: QtGui.QPixmap) -> None: + """Set widget pixmap""" self.icon_pixmap = a0 self.update() def setText(self, text: str) -> None: + """Set widget text""" self._text = text + self.scroll_pos = 0.0 self.update_text_metrics() @property def background_color(self) -> typing.Optional[QtGui.QColor]: + """Widget background color""" return self._background_color @background_color.setter @@ -75,6 +77,7 @@ def background_color(self, color: QtGui.QColor) -> None: @property def border_color(self) -> typing.Optional[QtGui.QColor]: + """Widget border color""" return self._border_color @border_color.setter @@ -83,6 +86,7 @@ def border_color(self, color: QtGui.QColor) -> None: @property def rounded(self) -> bool: + """Widget rounded property""" return self._rounded @rounded.setter @@ -91,15 +95,17 @@ def rounded(self, on: bool) -> None: @property def marquee(self) -> bool: + """Widget enable marquee effect""" return self._marquee @marquee.setter - def marquee(self, activate) -> None: + def marquee(self, activate: bool) -> None: self._marquee = activate self.update_text_metrics() @QtCore.pyqtProperty(int) def animation_speed(self) -> int: + """Widget animation speed property""" return self._animation_speed @animation_speed.setter @@ -108,6 +114,7 @@ def animation_speed(self, new_speed: int) -> None: @QtCore.pyqtProperty(QtGui.QColor) def glow_color(self) -> QtGui.QColor: + """Widget glow color property""" return self._glow_color @glow_color.setter @@ -117,18 +124,19 @@ def glow_color(self, color: QtGui.QColor) -> None: @QtCore.pyqtSlot(name="start_glow_animation") def start_glow_animation(self) -> None: + """Start glow animation""" self.glow_animation.setDuration(self.animation_speed) start_color = QtGui.QColor("#00000000") + end_color = QtGui.QColor("#E95757") self.glow_animation.setStartValue(start_color) - base_end_color = QtGui.QColor("#E95757") - self.glow_animation.setEndValue(base_end_color) - + self.glow_animation.setEndValue(end_color) self.glow_animation.setDirection(QtCore.QPropertyAnimation.Direction.Forward) self.glow_animation.setLoopCount(-1) self.glow_animation.start() @QtCore.pyqtSlot(name="change_glow_direction") def change_glow_direction(self) -> None: + """Handle Change glow direction""" current_direction = self.glow_animation.direction() if current_direction == self.glow_animation.Direction.Forward: self.glow_animation.setDirection(self.glow_animation.Direction.Backward) @@ -136,160 +144,139 @@ def change_glow_direction(self) -> None: self.glow_animation.setDirection(self.glow_animation.Direction.Forward) def update_text_metrics(self) -> None: + """Handle widget text metrics""" font_metrics = self.fontMetrics() self.text_width = font_metrics.horizontalAdvance(self._text) self.label_width = self.contentsRect().width() self.total_scroll_width = float(self.text_width + self.marquee_spacing) if self._marquee and self.text_width > self.label_width: - self.start_scroll() + self.scroll_pos = 0.0 + QtCore.QTimer.singleShot(2000, self.start_scroll) else: self.stop_scroll() self.scroll_pos = 0.0 self.update() def start_scroll(self) -> None: - if not self.delay_timer.isActive() and not self.timer.isActive(): + """Start or restart the scrolling.""" + if not self.timer.isActive(): self.scroll_pos = 0 self.loop_count = 0 - if self.first_run: - self.delay_timer.start(self.marquee_delay) - self.first_run = False - else: - self._start_marquee() - - def _start_marquee(self) -> None: - """Starts the actual marquee animation after the delay or immediately.""" - if not self.timer.isActive(): self.timer.start(self.scroll_animation_speed) def stop_scroll(self) -> None: + """Stop marquee text scroll effect""" self.timer.stop() - self.delay_timer.stop() + self.repaint() def _scroll_text(self) -> None: - if self.paused: + """Smoothly scroll the text leftwards.""" + if not self._marquee or self.paused: return - p_to_m = self.scroll_speed * (self.scroll_animation_speed / 1000.0) self.scroll_pos -= p_to_m - if self.scroll_pos <= -self.total_scroll_width: self.loop_count += 1 - if self.loop_count >= 2: + if self.loop_count >= self.max_loops: self.stop_scroll() - else: - self.scroll_pos = 0 - - self.repaint() + self.scroll_pos = 0.0 + self.update() def paintEvent(self, a0: QtGui.QPaintEvent) -> None: - qp = QtWidgets.QStylePainter(self) - opt = QtWidgets.QStyleOption() - opt.initFrom(self) - + """Re-implemented method, paint widget""" + qp = QtGui.QPainter(self) qp.setRenderHint(qp.RenderHint.Antialiasing, True) qp.setRenderHint(qp.RenderHint.SmoothPixmapTransform, True) qp.setRenderHint(qp.RenderHint.LosslessImageRendering, True) - _rect = self.rect() - _style = self.style() - - icon_margin = _style.pixelMetric(_style.PixelMetric.PM_HeaderMargin, opt, self) - if not _style or _rect.isNull(): - return + rect = self.contentsRect() + if self._background_color: + qp.setBrush(self._background_color) + qp.setPen(QtCore.Qt.PenStyle.NoPen) + if self._rounded: + path = QtGui.QPainterPath() + path.addRoundedRect(QtCore.QRectF(rect), 10, 10) + qp.fillPath(path, self._background_color) + else: + qp.fillRect(rect, self._background_color) if self.icon_pixmap: - qp.setCompositionMode(qp.CompositionMode.CompositionMode_SourceOver) - _icon_rect = QtCore.QRectF( - 0.0 + icon_margin, - 0.0 + icon_margin, - self.width() - icon_margin, - self.height() - icon_margin, + icon_rect = QtCore.QRectF( + 0.0 + self.icon_margin, + 0.0 + self.icon_margin, + self.width() - self.icon_margin, + self.height() - self.icon_margin, ) _icon_scaled = self.icon_pixmap.scaled( - _icon_rect.size().toSize(), + icon_rect.size().toSize(), QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation, ) scaled_width = _icon_scaled.width() scaled_height = _icon_scaled.height() - adjusted_x = (_icon_rect.width() - scaled_width) / 2.0 - adjusted_y = (_icon_rect.height() - scaled_height) / 2.0 - adjusted_icon_rect = QtCore.QRectF( - _icon_rect.x() + adjusted_x, - _icon_rect.y() + adjusted_y, + adjusted_x = (icon_rect.width() - scaled_width) // 2.0 + adjusted_y = (icon_rect.height() - scaled_height) // 2.0 + adjusted_icon = QtCore.QRectF( + icon_rect.x() + adjusted_x, + icon_rect.y() + adjusted_y, scaled_width, scaled_height, ) - qp.drawPixmap( - adjusted_icon_rect, _icon_scaled, _icon_scaled.rect().toRectF() - ) - - big_rect = QtGui.QPainterPath() - rect = self.contentsRect().toRectF() - big_rect.addRoundedRect(rect, 10.0, 10.0, QtCore.Qt.SizeMode.AbsoluteSize) - mini_rect = QtCore.QRectF( - (rect.width() - rect.width() * 0.99) / 2, - (rect.height() - rect.height() * 0.85) / 2, - rect.width() * 0.99, - rect.height() * 0.85, - ) - mini_path = QtGui.QPainterPath() - mini_path.addRoundedRect(mini_rect, 10.0, 10.0, QtCore.Qt.SizeMode.AbsoluteSize) - subtracted = big_rect.subtracted(mini_path) - + qp.drawPixmap(adjusted_icon, _icon_scaled, _icon_scaled.rect().toRectF()) if self.glow_animation.state() == self.glow_animation.State.Running: - qp.setCompositionMode(qp.CompositionMode.CompositionMode_SourceAtop) + big_rect = QtGui.QPainterPath() + rect = self.contentsRect().toRectF() + big_rect.addRoundedRect(rect, 10.0, 10.0, QtCore.Qt.SizeMode.AbsoluteSize) + sub_rect = QtCore.QRectF( + (rect.width() - rect.width() * 0.99) / 2, + (rect.height() - rect.height() * 0.85) / 2, + rect.width() * 0.99, + rect.height() * 0.85, + ) + sub_path = QtGui.QPainterPath() + sub_path.addRoundedRect( + sub_rect, 10.0, 10.0, QtCore.Qt.SizeMode.AbsoluteSize + ) + subtracted = big_rect.subtracted(sub_path) + qp.setCompositionMode(qp.CompositionMode.CompositionMode_SourceOver) subtracted.setFillRule(QtCore.Qt.FillRule.OddEvenFill) qp.fillPath(subtracted, self.glow_color) if self._text: - qp.setCompositionMode(qp.CompositionMode.CompositionMode_SourceOver) - - text_rect = self.contentsRect() - text_rect.translate(int(self.scroll_pos), 0) - text_path = QtGui.QPainterPath() - text_path.addRect(self.contentsRect().toRectF()) - qp.setClipPath(text_path) - text_option = QtGui.QTextOption(self.alignment()) text_option.setWrapMode(QtGui.QTextOption.WrapMode.NoWrap) - qp.drawText( - QtCore.QRectF(text_rect), - self._text, - text_option, - ) - if self._marquee and self.text_width > self.label_width: - second_text_rect = self.rect() - second_text_rect.translate( - int(self.scroll_pos + self.text_width + self.label_width / 2), - 0, + qp.save() + qp.setClipRect(rect) + baseline_y = ( + rect.y() + + ( + rect.height() + + self.fontMetrics().ascent() + - self.fontMetrics().descent() ) - # Draw the main text instance - draw_rect = QtCore.QRectF( - self.contentsRect().x() + self.scroll_pos, - self.contentsRect().y(), - self.text_width, - self.contentsRect().height(), + / 2 + ) + + if self.text_width > self.label_width: + qp.drawText( + QtCore.QPointF(rect.x() + self.scroll_pos, baseline_y), self._text ) - qp.drawText(QtCore.QRectF(second_text_rect), self._text, text_option) - - draw_rect2 = QtCore.QRectF( - self.contentsRect().x() - + self.scroll_pos - + self.text_width - + self.marquee_spacing, - self.contentsRect().y(), - self.text_width, - self.contentsRect().height(), + # Draw scrolling repeater text + qp.drawText( + QtCore.QPointF( + rect.x() + self.scroll_pos + self.total_scroll_width, baseline_y + ), + self._text, ) - qp.drawText(draw_rect2, self._text, text_option) else: - text_rect = self.contentsRect().toRectF() - qp.drawText(text_rect, self._text, text_option) + center_x = rect.x() + (rect.width() - self.text_width) / 2 + + qp.drawText(QtCore.QPointF(center_x, baseline_y), self._text) + qp.restore() qp.end() def setProperty(self, name: str, value: typing.Any) -> bool: + """Re-implemented method, set widget properties""" if name == "icon_pixmap": self.setPixmap(value) return super().setProperty(name, value) diff --git a/BlocksScreen/lib/utils/blocks_linedit.py b/BlocksScreen/lib/utils/blocks_linedit.py index c40cabfb..242e4b0d 100644 --- a/BlocksScreen/lib/utils/blocks_linedit.py +++ b/BlocksScreen/lib/utils/blocks_linedit.py @@ -3,7 +3,7 @@ class BlocksCustomLinEdit(QtWidgets.QLineEdit): - clicked = QtCore.pyqtSignal() + clicked = QtCore.pyqtSignal() def __init__( self, @@ -17,11 +17,12 @@ def __init__( self.placeholder_str = "Type here" self._name: str = "" self.text_color: QtGui.QColor = QtGui.QColor(0, 0, 0) - self.secret: bool = False + self.secret: bool = False self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) @property def name(self): + """Widget name""" return self._name @name.setter @@ -30,18 +31,21 @@ def name(self, new_name) -> None: self.setObjectName(new_name) def setText(self, text: str) -> None: + """Set widget text""" super().setText(text) def setHidden(self, hidden: bool) -> None: + """Hide widget text""" self.secret = hidden self.update() def mousePressEvent(self, event: QtGui.QMouseEvent) -> None: - self.clicked.emit() - super().mousePressEvent(event) - + """Re-implemented method, handle mouse press events""" + self.clicked.emit() + super().mousePressEvent(event) def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): + """Re-implemented method, paint widget""" painter = QtGui.QPainter(self) painter.setRenderHint(painter.RenderHint.Antialiasing, True) @@ -51,7 +55,7 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): painter.setPen(QtCore.Qt.PenStyle.NoPen) painter.drawRoundedRect(self.rect(), 8, 8) - margin = 5 + margin = 5 display_text = self.text() if self.secret and display_text: display_text = "*" * len(display_text) diff --git a/BlocksScreen/lib/utils/blocks_progressbar.py b/BlocksScreen/lib/utils/blocks_progressbar.py index 7688bf82..414097bb 100644 --- a/BlocksScreen/lib/utils/blocks_progressbar.py +++ b/BlocksScreen/lib/utils/blocks_progressbar.py @@ -1,89 +1,194 @@ -from PyQt6 import QtWidgets ,QtGui ,QtCore +import typing +from PyQt6 import QtWidgets, QtGui, QtCore class CustomProgressBar(QtWidgets.QProgressBar): + """Custom circular progress bar for tracking print jobs + + Args: + QtWidgets (QtWidget): Parent widget + + Raises: + ValueError: Thrown when setting progress is not between 0.0 and 1.0 + ValueError: Thrown when setting bar color is not between 0 and 255. + + """ + + thumbnail_clicked: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + name="thumbnail-clicked" + ) + def __init__(self, parent=None): super().__init__(parent) self.progress_value = 0 - self.bar_color = QtGui.QColor(223, 223, 223) + self._pen_width = 20 + self._padding = 50 + self._pixmap: QtGui.QPixmap = QtGui.QPixmap() + self._pixmap_cached: QtGui.QPixmap = QtGui.QPixmap() + self._pixmap_dirty: bool = True + self._bar_color = QtGui.QColor(223, 223, 223) self.setMinimumSize(100, 100) - self.set_padding(50) - self.set_pen_width(20) + self._inner_rect: QtCore.QRectF = QtCore.QRectF() - def set_padding(self, value): - self.padding = value + def set_padding(self, value) -> None: + """Set widget padding""" + self._padding = value self.update() - def set_pen_width(self, value): - self.pen_width = value + def set_pen_width(self, value) -> None: + """Set widget text pen width""" + self._pen_width = value self.update() - def paintEvent(self, event): - painter = QtGui.QPainter(self) - painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) + def _scale_pixmap(self) -> None: + self._inner_rect = self._calculate_inner_geometry() + self._pixmap_cached = self._pixmap.scaled( + self._inner_rect.size().toSize(), + QtCore.Qt.AspectRatioMode.KeepAspectRatio, + QtCore.Qt.TransformationMode.SmoothTransformation, + ) + + def set_inner_pixmap(self, pixmap: QtGui.QPixmap) -> None: + """Set the inner icon pixmap on the progress bar + circumference. + """ + self._pixmap = pixmap + self._scale_pixmap() + + def resizeEvent(self, a0) -> None: + """Reimplemented method, handle widget resize Events + + Currently rescales the set pixmap so it has the optimal + size. + """ + self._scale_pixmap() + self.update() - self._draw_circular_bar(painter, self.width(), self.height()) + def sizeHint(self) -> QtCore.QSize: + """Re-implemented method, preferable widget size""" + self._inner_rect = self._calculate_inner_geometry() + return QtCore.QSize(100, 100) + + def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: + """Re-implemented method, check if thumbnail was clicked, + filter clicks inside inner section of the widget, + if a mouse event happens there we know that the thumbnail + was pressed. + """ + if self._inner_rect.contains(a0.pos().x(), a0.pos().y()): + self.thumbnail_clicked.emit() + return super().mousePressEvent(a0) + + def minimumSizeHint(self) -> QtCore.QSize: + """Re-implemented method, minimum widget size""" + self._inner_rect = self._calculate_inner_geometry() + return QtCore.QSize(100, 100) + + def setValue(self, value: float) -> None: + """Set progress value + + Args: + value (float): Progress value between 0.0 and 1.0 + + Raises: + ValueError: If provided value in not between 0.0 and 1.0 + """ + if not (0 <= value <= 100): + raise ValueError("Argument `value` expected value between 0.0 and 1.0 ") + value *= 100 + self.progress_value = value + self.update() - def _draw_circular_bar(self, painter, width, height): - size = min(width, height) - (self.padding * 1.3) - x = (width - size) / 2 - y = (height - size) / 2 - arc_rect = QtCore.QRectF(x, y, size, size) + def set_bar_color(self, red: int, green: int, blue: int) -> None: + """Set widget progress bar color + Args: + red (int): red component value between 0 and 255 + green (int): green component value between 0 and 255 + blue (int): blue component value between 0 and 255 - arc1_start = 236* 16 - arc1_span = -290 * 16 + Raises: + ValueError: Raised if any provided argument value is not between 0 and 255 + """ + if not (0 <= red <= 255 and 0 <= green <= 255 and 0 <= blue <= 255): + raise ValueError("Color values must be between 0 and 255.") + self._bar_color = QtGui.QColor(red, green, blue) + self.update() + + def _calculate_inner_geometry(self) -> QtCore.QRectF: + size = min(self.width(), self.height()) - (self._padding * 1.3) + x = (self.width() - size) // 2 + y = (self.height() - size) // 2 + return QtCore.QRectF( + x + self._pen_width // 2, + y + self._pen_width // 2, + size - self._pen_width, + size - self._pen_width, + ) + + def _draw_cached_pixmap( + self, painter: QtGui.QPainter, pixmap: QtGui.QPixmap, inner_rect: QtCore.QRectF + ) -> None: + """Internal method draw already scaled pixmap on the widget inner section""" + if pixmap.isNull(): + return + scaled_width = pixmap.width() + scaled_height = pixmap.height() + adjusted_x = (inner_rect.width() - scaled_width) // 2.0 + adjusted_y = (inner_rect.height() - scaled_height) // 2.0 + adjusted_icon = QtCore.QRectF( + inner_rect.x() + adjusted_x, + inner_rect.y() + adjusted_y, + scaled_width, + scaled_height, + ) + painter.drawPixmap(adjusted_icon, pixmap, pixmap.rect().toRectF()) + + def _draw_circular_bar( + self, + painter: QtGui.QPainter, + ) -> None: + size = min(self.width(), self.height()) - (self._padding * 1.3) + x = (self.width() - size) / 2 + y = (self.height() - size) / 2 + arc_rect = QtCore.QRectF(x, y, size, size) + arc_start = 236 * 16 + arc_span = -290 * 16 bg_pen = QtGui.QPen(QtGui.QColor(20, 20, 20)) - bg_pen.setWidth(self.pen_width) + bg_pen.setWidth(self._pen_width) bg_pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap) painter.setPen(bg_pen) - painter.drawArc(arc_rect, arc1_start, arc1_span) - + painter.drawArc(arc_rect, arc_start, arc_span) if self.progress_value is not None: - gradient = QtGui.QConicalGradient(arc_rect.center(), -90) - gradient.setColorAt(0.0, self.bar_color) + gradient = QtGui.QConicalGradient(arc_rect.center(), -90) + gradient.setColorAt(0.0, self._bar_color) gradient.setColorAt(1.0, QtGui.QColor(100, 100, 100)) - progress_pen = QtGui.QPen() - progress_pen.setWidth(self.pen_width) + progress_pen.setWidth(self._pen_width) progress_pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap) progress_pen.setBrush(QtGui.QBrush(gradient)) painter.setPen(progress_pen) - - # scale only over arc1’s span - progress_span = int(arc1_span * self.progress_value/100) - painter.drawArc(arc_rect, arc1_start, progress_span) - + # scale only over arc span + progress_span = int(arc_span * self.progress_value / 100) + painter.drawArc(arc_rect, arc_start, progress_span) progress_text = f"{int(self.progress_value)}%" painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0))) font = painter.font() font.setPointSize(16) - bg_pen = QtGui.QPen(QtGui.QColor(255, 255, 255)) painter.setPen(bg_pen) painter.setFont(font) - text_x = arc_rect.center().x() text_y = arc_rect.center().y() - - # Draw centered text - text_rect = QtCore.QRectF(text_x - 30, text_y + arc_rect.height() / 2 - 25, 60, 40) + text_rect = QtCore.QRectF( + text_x - 30, text_y + arc_rect.height() / 2 - 25, 60, 40 + ) painter.drawText(text_rect, QtCore.Qt.AlignmentFlag.AlignCenter, progress_text) - - - - def setValue(self, value): - value*=100 - if 0 <= value <= 101: - self.progress_value = value - self.update() - else: - raise ValueError("Progress must be between 0.0 and 1.0.") - - def set_bar_color(self, red, green, blue): - if 0 <= red <= 255 and 0 <= green <= 255 and 0 <= blue <= 255: - self.bar_color = QtGui.QColor(red, green, blue) - self.update() - else: - raise ValueError("Color values must be between 0 and 255.") + def paintEvent(self, _) -> None: + """Re-implemented method, paint widget""" + painter = QtGui.QPainter(self) + painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) + self._draw_circular_bar(painter) + self._draw_cached_pixmap(painter, self._pixmap_cached, self._inner_rect) + painter.end() diff --git a/BlocksScreen/lib/utils/blocks_slider.py b/BlocksScreen/lib/utils/blocks_slider.py index 4d1078c7..f122589a 100644 --- a/BlocksScreen/lib/utils/blocks_slider.py +++ b/BlocksScreen/lib/utils/blocks_slider.py @@ -1,5 +1,3 @@ -import sys - from PyQt6 import QtCore, QtGui, QtWidgets @@ -16,12 +14,10 @@ def __init__(self, parent) -> None: self.setTickInterval(20) self.setMinimum(0) self.setMaximum(100) - - def setOrientation(self, a0: QtCore.Qt.Orientation) -> None: - return super().setOrientation(a0) + self.setPageStep(0) def mousePressEvent(self, ev: QtGui.QMouseEvent) -> None: - """Handle mouse press events""" + """Re-implemented method, Handle mouse press events""" if (ev.button() == QtCore.Qt.MouseButton.LeftButton) and self.hit_test( ev.position().toPoint().toPointF() ): @@ -69,31 +65,28 @@ def _set_slider_pos(self, pos: QtCore.QPointF): slider_start = self._groove_rect.x() pos_x = pos.x() new_val = ( - min_val - + (max_val - min_val) * (pos_x - slider_start) // slider_length + min_val + (max_val - min_val) * (pos_x - slider_start) // slider_length ) else: slider_length = self._groove_rect.height() slider_start = self._groove_rect.y() pos_y = pos.y() new_val = ( - min_val - + (max_val - min_val) * (pos_y - slider_start) / slider_length + min_val + (max_val - min_val) * (pos_y - slider_start) / slider_length ) self.setSliderPosition(int(round(new_val))) self.setValue(int(round(new_val))) self.update() def paintEvent(self, ev: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" opt = QtWidgets.QStyleOptionSlider() self.initStyleOption(opt) _style = self.style() # Clip the opt rect inside, so the handle and # groove doesn't exceed the limits - opt.rect = opt.rect.adjusted( - 12, 10, -18, 20 - ) # This is a bit hardcoded + opt.rect = opt.rect.adjusted(12, 10, -18, 20) # This is a bit hardcoded self._groove_rect = _style.subControlRect( QtWidgets.QStyle.ComplexControl.CC_Slider, @@ -162,9 +155,7 @@ def paintEvent(self, ev: QtGui.QPaintEvent) -> None: painter.setRenderHint(painter.RenderHint.TextAntialiasing, True) _color = QtGui.QColor(164, 164, 164) _color.setAlphaF(0.5) - painter.fillPath( - _groove_path, _color - ) # Primary groove background color + painter.fillPath(_groove_path, _color) # Primary groove background color _color = QtGui.QColor(self.highlight_color) _color_1 = QtGui.QColor(self.highlight_color) @@ -187,7 +178,6 @@ def paintEvent(self, ev: QtGui.QPaintEvent) -> None: QtWidgets.QStyle.SubControl.SC_SliderTickmarks, self, ) - tick_interval = self.tickInterval() or self.singleStep() min_v, max_v = self.minimum(), self.maximum() painter.setPen(QtGui.QColor("#888888")) fm = QtGui.QFontMetrics(painter.font()) diff --git a/BlocksScreen/lib/utils/blocks_tabwidget.py b/BlocksScreen/lib/utils/blocks_tabwidget.py index 274607a2..4696d967 100644 --- a/BlocksScreen/lib/utils/blocks_tabwidget.py +++ b/BlocksScreen/lib/utils/blocks_tabwidget.py @@ -2,17 +2,21 @@ class NotificationTabBar(QtWidgets.QTabBar): + """Re-implemented QTabBar so that the widget can have notifications""" + def __init__(self, parent=None): super().__init__(parent) self._notifications = {} # {tab_index: bool} def setNotification(self, index: int, show: bool): + """Set notification""" if index < 0 or index >= self.count(): return self._notifications[index] = show self.update(self.tabRect(index)) # repaint only that tab def paintEvent(self, event): + """Re-implemented method, paint widget""" super().paintEvent(event) painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) @@ -31,10 +35,13 @@ def paintEvent(self, event): class NotificationQTabWidget(QtWidgets.QTabWidget): + """Re-implemented QTabWidget so that we can have notifications""" + def __init__(self, parent=None): super().__init__(parent) self._custom_tabbar = NotificationTabBar() self.setTabBar(self._custom_tabbar) def setNotification(self, index: int, show: bool): + """Set tab notification""" self._custom_tabbar.setNotification(index, show) diff --git a/BlocksScreen/lib/utils/blocks_togglebutton.py b/BlocksScreen/lib/utils/blocks_togglebutton.py index 4363b655..c97e8f1f 100644 --- a/BlocksScreen/lib/utils/blocks_togglebutton.py +++ b/BlocksScreen/lib/utils/blocks_togglebutton.py @@ -12,39 +12,41 @@ def __init__(self, parent): self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) self._icon_label = None self._text_label = None - self._text: str = ("la test") + self._text: str = "la test" self.icon_pixmap_fp: QtGui.QPixmap = QtGui.QPixmap( ":/filament_related/media/btn_icons/filament_sensor_turn_on.svg" ) - - self.setupUI() + + self._setupUI() self.tb = self.toggle_button def text(self) -> str: + """Button text""" return self._text def setText(self, new_text) -> None: + """Set widget text""" if self._text_label is not None: self._text_label.setText(f"{new_text}") self._text = new_text - - def setPixmap(self,pixmap: QtGui.QPixmap): + def setPixmap(self, pixmap: QtGui.QPixmap): + """Set widget pixmap""" self.icon_pixmap_fp = pixmap def mousePressEvent(self, event: QtGui.QMouseEvent): + """Re-implemented method, handle mouse press events""" if self.toggle_button.geometry().contains(event.pos()): event.ignore() return if event.button() == QtCore.Qt.MouseButton.LeftButton: self.clicked.emit() - event.accept() + event.accept() def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" style_painter = QtWidgets.QStylePainter(self) - style_painter.setRenderHint( - style_painter.RenderHint.Antialiasing, True - ) + style_painter.setRenderHint(style_painter.RenderHint.Antialiasing, True) style_painter.setRenderHint( style_painter.RenderHint.SmoothPixmapTransform, True ) @@ -76,12 +78,13 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: style_painter.end() def setDisabled(self, a0: bool) -> None: + """Re-implemented method, disable widget""" self.toggle_button.setDisabled(a0) self.repaint() self.toggle_button.repaint() return super().setDisabled(a0) - def setupUI(self): + def _setupUI(self): _policy = QtWidgets.QSizePolicy.Policy.MinimumExpanding size_policy = QtWidgets.QSizePolicy(_policy, _policy) size_policy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) @@ -90,29 +93,21 @@ def setupUI(self): self.sensor_horizontal_layout.setGeometry(self.rect()) self.sensor_horizontal_layout.setObjectName("sensorHorizontalLayout") self._icon_label = BlocksLabel(self) - size_policy.setHeightForWidth( - self._icon_label.sizePolicy().hasHeightForWidth() - ) + size_policy.setHeightForWidth(self._icon_label.sizePolicy().hasHeightForWidth()) self._icon_label.setSizePolicy(size_policy) self._icon_label.setMinimumSize(60, 60) self._icon_label.setMaximumSize(60, 60) - self._icon_label.setPixmap( - self.icon_pixmap_fp - ) + self._icon_label.setPixmap(self.icon_pixmap_fp) self.sensor_horizontal_layout.addWidget(self._icon_label) self._text_label = QtWidgets.QLabel(parent=self) - size_policy.setHeightForWidth( - self._text_label.sizePolicy().hasHeightForWidth() - ) + size_policy.setHeightForWidth(self._text_label.sizePolicy().hasHeightForWidth()) self._text_label.setMinimumSize(100, 60) self._text_label.setMaximumSize(500, 60) _font = QtGui.QFont() _font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferAntialias) _font.setPointSize(18) palette = self._text_label.palette() - palette.setColor( - palette.ColorRole.WindowText, QtGui.QColorConstants.White - ) + palette.setColor(palette.ColorRole.WindowText, QtGui.QColorConstants.White) self._text_label.setPalette(palette) self._text_label.setFont(_font) self._text_label.setText(str(self._text)) diff --git a/BlocksScreen/lib/utils/check_button.py b/BlocksScreen/lib/utils/check_button.py new file mode 100644 index 00000000..e5b184d5 --- /dev/null +++ b/BlocksScreen/lib/utils/check_button.py @@ -0,0 +1,87 @@ +import typing +from PyQt6 import QtCore, QtGui, QtWidgets + + +class BlocksCustomCheckButton(QtWidgets.QAbstractButton): + """Custom Blocks QPushButton + Rounded button with a hole on the left side where an icon can be inserted + + Args: + parent (QWidget): Parent of the button + """ + + def __init__( + self, + parent: QtWidgets.QWidget, + ) -> None: + super().__init__(parent) + self.button_ellipse = None + self._text: str = "" + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) + + def setFlat(self, flat) -> None: + """Disable setFlat behavior""" + return + + def setAutoDefault(self, _): + """Disable auto default behavior""" + return + + def text(self) -> str: + """returns Widget text""" + return self._text + + def setText(self, text: str | None) -> None: + """Set widget text""" + if text is None: + return + self._text = text + self.update() + return + + def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): + """Re-implemented method, paint widget, optimized for performance.""" + + painter = QtGui.QPainter(self) + rect_f = self.rect().toRectF().normalized() + painter.setRenderHint(painter.RenderHint.Antialiasing, True) + height = rect_f.height() + + radius = height / 5.0 + self.button_ellipse = QtCore.QRectF( + rect_f.left() + height * 0.05, + rect_f.top() + height * 0.05, + (height * 0.40), + (height * 0.40), + ) + + if self.isChecked(): + bg_color = QtGui.QColor(223, 223, 223) + text_color = QtGui.QColor(0, 0, 0) + elif self.isDown(): + bg_color = QtGui.QColor(164, 164, 164, 90) + text_color = QtGui.QColor(255, 255, 255) + else: + bg_color = QtGui.QColor(0, 0, 0, 90) + text_color = QtGui.QColor(255, 255, 255) + + path = QtGui.QPainterPath() + path.addRoundedRect( + rect_f, + radius, + radius, + QtCore.Qt.SizeMode.AbsoluteSize, + ) + + painter.setPen(QtCore.Qt.PenStyle.NoPen) + painter.setBrush(bg_color) + painter.fillPath(path, bg_color) + + if self.text(): + painter.setPen(text_color) + painter.setFont(QtGui.QFont("Momcake", 14)) + painter.drawText( + rect_f, + QtCore.Qt.AlignmentFlag.AlignCenter, + str(self.text()), + ) diff --git a/BlocksScreen/lib/utils/display_button.py b/BlocksScreen/lib/utils/display_button.py index a0e5a886..5bda40aa 100644 --- a/BlocksScreen/lib/utils/display_button.py +++ b/BlocksScreen/lib/utils/display_button.py @@ -4,9 +4,7 @@ class DisplayButton(QtWidgets.QPushButton): - def __init__( - self, parent: typing.Optional["QtWidgets.QWidget"] = None - ) -> None: + def __init__(self, parent: typing.Optional["QtWidgets.QWidget"] = None) -> None: if parent: super().__init__(parent=parent) else: @@ -19,22 +17,23 @@ def __init__( self._text: str = "" self._secondary_text: str = "" self._name: str = "" - self.display_format: typing.Literal["normal", "upper_downer"] = ( - "normal" - ) + self.display_format: typing.Literal["normal", "upper_downer"] = "normal" self.text_color: QtGui.QColor = QtGui.QColor(0, 0, 0) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) @property def name(self): + """Widget name""" return self._name def setPixmap(self, pixmap: QtGui.QPixmap) -> None: + """Set widget pixmap""" self.icon_pixmap = pixmap self.repaint() @property def button_type(self) -> str: + """Widget button type""" return self._button_type @button_type.setter @@ -44,29 +43,28 @@ def button_type(self, type) -> None: self._button_type = type def text(self) -> str: + """Widget text""" return self._text def setText(self, text: str) -> None: + """Set widget text""" self._text = text self.update() super().setText(text) @property def secondary_text(self) -> str: + """Widget secondary text""" return self._secondary_text @secondary_text.setter def secondary_text(self, text: str) -> None: + """Set secondary text""" self._secondary_text = text self.update() - def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: - return super().resizeEvent(a0) - - def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: - return super().mousePressEvent(e) - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" opt = QtWidgets.QStyleOptionButton() self.initStyleOption(opt) painter = QtWidgets.QStylePainter(self) @@ -78,9 +76,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: if not _style or _rect is None: return - margin = _style.pixelMetric( - _style.PixelMetric.PM_ButtonMargin, opt, self - ) + margin = _style.pixelMetric(_style.PixelMetric.PM_ButtonMargin, opt, self) # Rounded background edges path = QtGui.QPainterPath() path.addRoundedRect( @@ -119,13 +115,11 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: _pen.setBrush(_gradient) painter.fillPath(path, _pen.brush()) - _icon_rect = ( - QtCore.QRectF( # x,y, width * size reduction factor, height - 0.0, - 0.0, - (_rect.width() * 0.3) - 5.0, - _rect.height() - 5, - ) + _icon_rect = QtCore.QRectF( # x,y, width * size reduction factor, height + 0.0, + 0.0, + (_rect.width() * 0.3) - 5.0, + _rect.height() - 5, ) _icon_scaled = self.icon_pixmap.scaled( @@ -180,6 +174,10 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: int(_mtl.width() / 2.0), _rect.height(), ) + font = QtGui.QFont() + font.setPointSize(12) + font.setFamily("Momcake-bold") + painter.setFont(font) painter.drawText( _ptl_rect, QtCore.Qt.TextFlag.TextShowMnemonic @@ -192,9 +190,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: QtCore.Qt.TextFlag.TextShowMnemonic | QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, - str(self.secondary_text) - if self.secondary_text - else str("?"), + str(self.secondary_text) if self.secondary_text else str("?"), ) painter.drawText( _mtl_rect, @@ -205,9 +201,9 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: ) elif self.display_format == "upper_downer": _mtl = QtCore.QRectF( - int(_icon_rect.width()) + margin , + int(_icon_rect.width()) + margin, 0.0, - int(_rect.width() - _icon_rect.width() - margin ), + int(_rect.width() - _icon_rect.width() - margin), _rect.height(), ) _upper_rect = QtCore.QRectF( @@ -226,7 +222,9 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: font.setPointSize(20) font.setFamily("Momcake-bold") painter.setFont(font) - painter.setCompositionMode(painter.CompositionMode.CompositionMode_SourceAtop) + painter.setCompositionMode( + painter.CompositionMode.CompositionMode_SourceAtop + ) painter.drawText( _upper_rect, # QtCore.Qt.AlignmentFlag.AlignCenter, @@ -237,7 +235,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: font.setPointSize(15) painter.setPen(QtGui.QColor("#b6b0b0")) painter.setFont(font) - + painter.drawText( _downer_rect, QtCore.Qt.AlignmentFlag.AlignRight @@ -246,6 +244,10 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: ) else: + font = QtGui.QFont() + font.setPointSize(12) + font.setFamily("Momcake-bold") + painter.setFont(font) painter.drawText( _mtl, QtCore.Qt.TextFlag.TextShowMnemonic @@ -258,6 +260,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: return def setProperty(self, name: str, value: typing.Any) -> bool: + """Re-implemented method, set widget properties""" if name == "icon_pixmap": self.icon_pixmap = value elif name == "button_type": diff --git a/BlocksScreen/lib/utils/group_button.py b/BlocksScreen/lib/utils/group_button.py deleted file mode 100644 index b9af752b..00000000 --- a/BlocksScreen/lib/utils/group_button.py +++ /dev/null @@ -1,154 +0,0 @@ -import typing -from PyQt6 import QtCore, QtGui, QtWidgets - - -class GroupButton(QtWidgets.QPushButton): - """Custom Blocks QPushButton - Rounded button with a hole on the left side where an icon can be inserted - - Args: - parent (QWidget): Parent of the button - """ - - def __init__( - self, - parent: QtWidgets.QWidget, - ) -> None: - super(GroupButton, self).__init__(parent) - - self.icon_pixmap: QtGui.QPixmap = QtGui.QPixmap() - self._icon_rect: QtCore.QRectF = QtCore.QRectF() - self.button_background = None - self.button_ellipse = None - self._text: str = "" - self._name: str = "" - self.text_color: QtGui.QColor = QtGui.QColor(0, 0, 0) - self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) - - @property - def name(self): - return self._name - - @name.setter - def name(self, new_name) -> None: - self._name = new_name - self.setObjectName(new_name) - - def text(self) -> str | None: - return self._text - - def setText(self, text: str) -> None: - self._text = text - self.update() # Force button update - return - - def setPixmap(self, pixmap: QtGui.QPixmap) -> None: - self.icon_pixmap = pixmap - self.repaint() - - def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): - opt = QtWidgets.QStyleOptionButton() - self.initStyleOption(opt) - - painter = QtGui.QPainter(self) - painter.setRenderHint(painter.RenderHint.Antialiasing, True) - painter.setRenderHint(painter.RenderHint.SmoothPixmapTransform, True) - painter.setRenderHint(painter.RenderHint.LosslessImageRendering, True) - - _rect = self.rect() - _style = self.style() - - if _style is None or _rect is None: - return - - bg_color = ( - QtGui.QColor(223, 223, 223) - if self.isChecked() - else QtGui.QColor(164, 164, 164, 90) - if self.isDown() - else QtGui.QColor(0, 0, 0, 90) - ) - - path = QtGui.QPainterPath() - xRadius = self.rect().toRectF().normalized().height() / 5.0 - yRadius = self.rect().toRectF().normalized().height() / 5.0 - painter.setBackgroundMode(QtCore.Qt.BGMode.TransparentMode) - path.addRoundedRect( - 0, - 0, - self.rect().toRectF().normalized().width(), - self.rect().toRectF().normalized().height(), - xRadius, - yRadius, - QtCore.Qt.SizeMode.AbsoluteSize, - ) - - self.button_ellipse = QtCore.QRectF( - self.rect().toRectF().normalized().left() - + self.rect().toRectF().normalized().height() * 0.05, - self.rect().toRectF().normalized().top() - + self.rect().toRectF().normalized().height() * 0.05, - (self.rect().toRectF().normalized().height() * 0.40), - (self.rect().toRectF().normalized().height() * 0.40), - ) - - painter.setPen(QtCore.Qt.PenStyle.NoPen) - painter.setBrush(bg_color) - painter.fillPath(path, bg_color) - - if self.text(): - if self.isChecked(): - painter.setPen(QtGui.QColor(0, 0, 0)) - else: - painter.setPen(QtGui.QColor(255, 255, 255)) - _start_text_position = int(self.button_ellipse.width() / 2) - _text_rect = _rect - _pen = painter.pen() - _pen.setStyle(QtCore.Qt.PenStyle.SolidLine) - _pen.setWidth(1) - painter.setPen(_pen) - painter.setFont(QtGui.QFont("Momcake-Thin", 14)) - - painter.drawText( - _text_rect, - QtCore.Qt.AlignmentFlag.AlignCenter, - str(self.text()), - ) - painter.setPen(QtCore.Qt.PenStyle.NoPen) - - def setProperty(self, name: str, value: typing.Any): - if name == "name": - self._name = name - elif name == "text_color": - self.text_color = QtGui.QColor(value) - # return super().setProperty(name, value) - - def handleTouchBegin(self, e: QtCore.QEvent): - ... - # if not self.button_background: - # if self.button_background.contains(e.pos()): # type: ignore - # # super().mousePressEvent(e) - # self.mousePressEvent(e) # type: ignore - # return - # else: - # e.ignore() - # return - - def handleTouchUpdate(self, e: QtCore.QEvent): ... - def handleTouchEnd(self, e: QtCore.QEvent): ... - def handleTouchCancel(self, e: QtCore.QEvent): ... - - def event(self, e: QtCore.QEvent) -> bool: - if e.type() == QtCore.QEvent.Type.TouchBegin: - self.handleTouchBegin(e) - return False - elif e.type() == QtCore.QEvent.Type.TouchUpdate: - self.handleTouchUpdate(e) - return False - elif e.type() == QtCore.QEvent.Type.TouchEnd: - self.handleTouchEnd(e) - return False - elif e.type() == QtCore.QEvent.Type.TouchCancel: - self.handleTouchCancel(e) - return False - return super().event(e) diff --git a/BlocksScreen/lib/utils/icon_button.py b/BlocksScreen/lib/utils/icon_button.py index 019471b4..3880d285 100644 --- a/BlocksScreen/lib/utils/icon_button.py +++ b/BlocksScreen/lib/utils/icon_button.py @@ -3,7 +3,7 @@ class IconButton(QtWidgets.QPushButton): - def __init__(self, parent: QtWidgets.QWidget) -> None: + def __init__(self, parent: QtWidgets.QWidget = None) -> None: super().__init__(parent) self.icon_pixmap: QtGui.QPixmap = QtGui.QPixmap() @@ -13,24 +13,34 @@ def __init__(self, parent: QtWidgets.QWidget) -> None: self._name: str = "" self.text_color: QtGui.QColor = QtGui.QColor(255, 255, 255) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) + self.pressed_bg_color = QtGui.QColor(223, 223, 223, 70) # Set to solid white @property def name(self): + """Widget name""" return self._name def text(self) -> str: + """Widget text""" return self._text def setPixmap(self, pixmap: QtGui.QPixmap) -> None: + """Set widget pixmap""" self.icon_pixmap = pixmap self.repaint() + def clearPixmap(self) -> None: + """Clear widget pixmap""" + self.icon_pixmap = QtGui.QPixmap() + self.repaint() + def setText(self, text: str) -> None: + """Set widget text""" self._text = text self.update() - # super().setText(text) def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" opt = QtWidgets.QStyleOptionButton() self.initStyleOption(opt) painter = QtWidgets.QStylePainter(self) @@ -38,6 +48,10 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.setRenderHint(painter.RenderHint.SmoothPixmapTransform, True) painter.setRenderHint(painter.RenderHint.LosslessImageRendering, True) + if self.isDown(): + painter.setBrush(QtGui.QBrush(self.pressed_bg_color)) + painter.setPen(QtCore.Qt.PenStyle.NoPen) + painter.drawRoundedRect(self.rect().toRectF(), 6, 6) _pen = QtGui.QPen() _pen.setStyle(QtCore.Qt.PenStyle.NoPen) _pen.setColor(self.text_color) @@ -45,40 +59,36 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.setPen(_pen) - # bg_color = ( - # QtGui.QColor(164, 164, 164) - # if self.isDown() - # else QtGui.QColor(223, 223, 223) - # ) - - # * Build icon - x = y = 15.0 if self.text_formatting else 5.0 - _icon_rect = QtCore.QRectF( - 0.0, 0.0, (self.width() - x), (self.height() - y) - ) - - _icon_scaled = self.icon_pixmap.scaled( - _icon_rect.size().toSize(), - QtCore.Qt.AspectRatioMode.KeepAspectRatio, - QtCore.Qt.TransformationMode.SmoothTransformation, - ) - # Calculate the actual QRect for the scaled pixmap (centering it if needed) - scaled_width = _icon_scaled.width() - scaled_height = _icon_scaled.height() - adjusted_x = (_icon_rect.width() - scaled_width) / 2.0 - adjusted_y = (_icon_rect.height() - scaled_height) / 2.0 - adjusted_icon_rect = QtCore.QRectF( - _icon_rect.x() + adjusted_x, - _icon_rect.y() + adjusted_y, - scaled_width, - scaled_height, - ) - - painter.drawPixmap( - adjusted_icon_rect, # Target area (center adjusted) - _icon_scaled, # Scaled pixmap - _icon_scaled.rect().toRectF(), # Entire source (scaled) pixmap - ) + y = 15.0 if self.text_formatting else 5.0 + if self.isDown(): + _icon_rect = QtCore.QRectF( + 2.5, 2.5, (self.width() - 5), (self.height() - 5 - y) + ) + else: + _icon_rect = QtCore.QRectF(0.0, 0.0, (self.width()), (self.height() - y)) + + if not self.icon_pixmap.isNull(): + _icon_scaled = self.icon_pixmap.scaled( + _icon_rect.size().toSize(), + QtCore.Qt.AspectRatioMode.KeepAspectRatio, + QtCore.Qt.TransformationMode.SmoothTransformation, + ) + scaled_width = _icon_scaled.width() + scaled_height = _icon_scaled.height() + adjusted_x = (_icon_rect.width() - scaled_width) / 2.0 + adjusted_y = (_icon_rect.height() - scaled_height) / 2.0 + adjusted_icon_rect = QtCore.QRectF( + _icon_rect.x() + adjusted_x, + _icon_rect.y() + adjusted_y, + scaled_width, + scaled_height, + ) + + painter.drawPixmap( + adjusted_icon_rect, + _icon_scaled, + _icon_scaled.rect().toRectF(), + ) if self.has_text: painter.setCompositionMode( @@ -97,9 +107,9 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: scaled_height, ) elif self.text_formatting == "bottom": - adjusted_x = (_icon_rect.width() - self.width() + 5.0) / 2.0 + # adjusted_x = 0#(_icon_rect.width() - self.width() + 5.0) / 2.0 adjusted_rectF = QtCore.QRectF( - _icon_rect.x() + adjusted_x, + 0, _icon_rect.height(), self.width(), self.height() - _icon_rect.height(), @@ -110,16 +120,14 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.drawText( adjusted_rectF, - QtCore.Qt.TextFlag.TextSingleLine - | QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.TextFlag.TextSingleLine | QtCore.Qt.AlignmentFlag.AlignCenter, str(self.text()), ) - painter.setPen(QtCore.Qt.PenStyle.NoPen) painter.end() def setProperty(self, name: str, value: typing.Any) -> bool: + """Re-implemented method, set widget properties""" if name == "icon_pixmap": self.icon_pixmap = value elif name == "text_formatting": diff --git a/BlocksScreen/lib/utils/list_button.py b/BlocksScreen/lib/utils/list_button.py index 0b7eab6c..deb01bf1 100644 --- a/BlocksScreen/lib/utils/list_button.py +++ b/BlocksScreen/lib/utils/list_button.py @@ -29,56 +29,66 @@ def __init__(self, parent=None) -> None: self.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) def setText(self, text: str) -> None: + """Set widget text""" self._text = text self.update() def text(self) -> str: + """Widget text""" return self._text def setRightText(self, text: str) -> None: + """Set widget right text""" self._right_text = text self.update() def rightText(self) -> str: + """Widget right text""" return self._right_text def setLeftFontSize(self, size: int) -> None: + """Set widget left text font size""" self._lfontsize = size self.update() def setRightFontSize(self, size: int) -> None: + """Set widget right text font size""" self._rfontsize = size self.update() def setPixmap(self, pixmap: QtGui.QPixmap) -> None: + """Set widget pixmap""" self.icon_pixmap = pixmap self.update() def setSecondPixmap(self, pixmap: QtGui.QPixmap) -> None: + """Set widget secondary pixmap""" self.second_icon_pixmap = pixmap self.update() def mousePressEvent(self, event: QtGui.QMouseEvent) -> None: + """Re-implemented method, handle mouse press event""" self._is_pressed = True self.update() super().mousePressEvent(event) def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None: + """Re-implemented method, handle mouse release event""" self._is_pressed = False self.update() super().mouseReleaseEvent(event) def leaveEvent(self, event: QtCore.QEvent) -> None: + """Re-implemented method, handle leave event""" self._is_hovered = False self.update() super().leaveEvent(event) def paintEvent(self, e: QtGui.QPaintEvent | None) -> None: + """Re-implemented method, paint widget""" painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) - painter.setRenderHint( - QtGui.QPainter.RenderHint.SmoothPixmapTransform, True - ) + painter.setRenderHint(QtGui.QPainter.RenderHint.SmoothPixmapTransform, True) rect = self.rect() radius = rect.height() / 5.0 @@ -140,12 +150,9 @@ def paintEvent(self, e: QtGui.QPaintEvent | None) -> None: QtCore.Qt.TransformationMode.SmoothTransformation, ) # Center the icon in the ellipse - adjusted_x = ( - icon_rect.x() + (icon_rect.width() - icon_scaled.width()) / 2.0 - ) + adjusted_x = icon_rect.x() + (icon_rect.width() - icon_scaled.width()) / 2.0 adjusted_y = ( - icon_rect.y() - + (icon_rect.height() - icon_scaled.height()) / 2.0 + icon_rect.y() + (icon_rect.height() - icon_scaled.height()) / 2.0 ) adjusted_icon_rect = QtCore.QRectF( adjusted_x, @@ -185,9 +192,7 @@ def paintEvent(self, e: QtGui.QPaintEvent | None) -> None: left_icon_scaled, left_icon_scaled.rect().toRectF(), ) - left_margin = ( - left_icon_margin + left_icon_size + 8 - ) # 8px gap after icon + left_margin = left_icon_margin + left_icon_size + 8 # 8px gap after icon # Draw text, area before the ellipse (adjusted for left icon) text_margin = int( @@ -209,11 +214,7 @@ def paintEvent(self, e: QtGui.QPaintEvent | None) -> None: main_text_height = metrics.height() # Vertically center text - text_y = ( - rect.top() - + (rect.height() + main_text_height) / 2 - - metrics.descent() - ) + text_y = rect.top() + (rect.height() + main_text_height) / 2 - metrics.descent() # Calculate where to start the right text: just left of the right icon ellipse gap = 10 # gap between right text and icon ellipse diff --git a/BlocksScreen/lib/utils/list_model.py b/BlocksScreen/lib/utils/list_model.py index a143f90b..2f4cbc3a 100644 --- a/BlocksScreen/lib/utils/list_model.py +++ b/BlocksScreen/lib/utils/list_model.py @@ -6,7 +6,7 @@ @dataclass class ListItem: - """Data for a list item""" + """List item data""" text: str right_text: str = "" @@ -142,12 +142,6 @@ def paint( rect = option.rect rect.setHeight(item.height) button = QtWidgets.QStyleOptionButton() - style = QtWidgets.QApplication.style() - if not style: - return - style.drawControl( - QtWidgets.QStyle.ControlElement.CE_PushButton, button, painter - ) button.rect = rect painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) painter.setRenderHint(QtGui.QPainter.RenderHint.SmoothPixmapTransform, True) diff --git a/BlocksScreen/lib/utils/loadAnimatedLabel.py b/BlocksScreen/lib/utils/loadAnimatedLabel.py deleted file mode 100644 index 4e091b48..00000000 --- a/BlocksScreen/lib/utils/loadAnimatedLabel.py +++ /dev/null @@ -1,6 +0,0 @@ -from PyQt6 import QtGui, QtWidgets, QtCore - -class LoadAnimatedLabel(QtWidgets.QLabel): - def __init__(self, parent) -> None: - super().__init__(parent) - \ No newline at end of file diff --git a/BlocksScreen/lib/utils/numpad_button.py b/BlocksScreen/lib/utils/numpad_button.py index 5f0ebafe..feaf3c61 100644 --- a/BlocksScreen/lib/utils/numpad_button.py +++ b/BlocksScreen/lib/utils/numpad_button.py @@ -9,12 +9,15 @@ def __init__(self, parent=None): self._position: str = "" def get_position(self): + """Get numpad button position""" return self._position def set_position(self, value): + """Set position""" self._position = str(value).lower() def paintEvent(self, e: QtGui.QPaintEvent | None): + """Re-implemented method, paint widget""" opt = QtWidgets.QStyleOptionButton() self.initStyleOption(opt) @@ -28,9 +31,7 @@ def paintEvent(self, e: QtGui.QPaintEvent | None): if _style is None or _rect is None: return - margin = _style.pixelMetric( - _style.PixelMetric.PM_ButtonMargin, opt, self - ) + margin = _style.pixelMetric(_style.PixelMetric.PM_ButtonMargin, opt, self) bg_color = ( QtGui.QColor(164, 164, 164) if self.isDown() @@ -143,6 +144,7 @@ def paintEvent(self, e: QtGui.QPaintEvent | None): painter.setPen(QtCore.Qt.PenStyle.NoPen) def setProperty(self, name: str, value: typing.Any): + """Re-implemented method, set widget properties""" if name == "position": self.set_position(value) diff --git a/BlocksScreen/lib/utils/others.py b/BlocksScreen/lib/utils/others.py deleted file mode 100644 index f2b1e600..00000000 --- a/BlocksScreen/lib/utils/others.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/python -import logging -import queue -import threading -import typing -from functools import partial - -from PyQt6 import QtCore, QtGui, QtWidgets -from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot -from PyQt6.QtWidgets import QPushButton, QStackedWidget, QStyle, QWidget - -# from qt_ui.customNumpad_ui import Ui_customNumpad - -_logger = logging.getLogger(__name__) - - - - -# PYTHON 'is' checks if the object points to the same object, which is different than == - - - - - - -# TODO: Create a method that checks if the application requirements -# TODO: Create a method that validates the working directory of the GUI - - -def validate_requirements(): ... -def validate_working_dir(): ... -def scan_dir(dir: str) -> typing.Dict: ... -def scan_file(filename: str, dir: str) -> bool: ... - - -def validate_directory() -> bool: ... - - -def scan_directory(dir: str, ext: str = None): - ... - # """Scan a directory for files and nested directories""" - # if not isinstance(dir, str): - # raise ValueError("dir expected str type") - - # if os.access(dir, os.X_OK and os.W_OK and os.R_OK): - # for root, dirs, files in os.walk(dir): - - # for diff --git a/BlocksScreen/lib/utils/toggleAnimatedButton.py b/BlocksScreen/lib/utils/toggleAnimatedButton.py index 557c01a4..b9555876 100644 --- a/BlocksScreen/lib/utils/toggleAnimatedButton.py +++ b/BlocksScreen/lib/utils/toggleAnimatedButton.py @@ -16,9 +16,7 @@ def __init__(self, parent) -> None: super().__init__(parent) self.setMinimumSize(QtCore.QSize(80, 40)) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) - self.setAttribute( - QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True - ) + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True) self.setMaximumHeight(80) self.setMouseTracking(True) @@ -38,6 +36,10 @@ def __init__(self, parent) -> None: self.icon_pixmap: QtGui.QPixmap = QtGui.QPixmap() self._backgroundColor: QtGui.QColor = QtGui.QColor(223, 223, 223) self._handleColor: QtGui.QColor = QtGui.QColor(255, 100, 10) + + self._handleONcolor: QtGui.QColor = QtGui.QColor(0, 200, 0) + self._handleOFFcolor: QtGui.QColor = QtGui.QColor(200, 0, 0) + self.disable_bg_color: QtGui.QColor = QtGui.QColor("#A9A9A9") self.disable_handle_color: QtGui.QColor = QtGui.QColor("#666666") self._state = ToggleAnimatedButton.State.OFF @@ -49,17 +51,14 @@ def __init__(self, parent) -> None: else self._handle_OFFPosition ) - self.slide_animation = QtCore.QPropertyAnimation( - self, b"handle_position" - ) + self.slide_animation = QtCore.QPropertyAnimation(self, b"handle_position") self.slide_animation.setDuration(self._animation_speed) - self.slide_animation.setEasingCurve( - QtCore.QEasingCurve().Type.InOutQuart - ) + self.slide_animation.setEasingCurve(QtCore.QEasingCurve().Type.InOutQuart) self.pressed.connect(self.setup_animation) def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: + """Re-implemented method, handle widget resize event""" self.handle_radius = ( self.contentsRect().toRectF().normalized().height() * 0.80 ) // 2 @@ -74,10 +73,12 @@ def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: return super().resizeEvent(a0) def sizeHint(self) -> QtCore.QSize: + """Re-implemented method, widget size hint""" return QtCore.QSize(80, 40) @QtCore.pyqtProperty(int) def animation_speed(self) -> int: + """Widget property animation speed""" return self._animation_speed @animation_speed.setter @@ -87,12 +88,13 @@ def animation_speed(self, new_speed: int) -> None: @property def state(self) -> State: + """Widget property, toggle state""" return self._state @state.setter def state(self, new_state: State) -> None: - if self._state == new_state: - return + if self._state == new_state: + return self._state = new_state if self.isVisible(): self.stateChange.emit(self._state) @@ -101,6 +103,7 @@ def state(self, new_state: State) -> None: @QtCore.pyqtProperty(float) def handle_position(self) -> float: + """Widget property handle position""" return self._handle_position @handle_position.setter @@ -110,6 +113,7 @@ def handle_position(self, new_pos: float) -> None: @QtCore.pyqtProperty(QtGui.QColor) def backgroundColor(self) -> QtGui.QColor: + """Widget property background color""" return self._backgroundColor @backgroundColor.setter @@ -119,6 +123,7 @@ def backgroundColor(self, new_color: QtGui.QColor) -> None: @QtCore.pyqtProperty(QtGui.QColor) def handleColor(self) -> QtGui.QColor: + """Widget property handle color""" return self._handleColor @handleColor.setter @@ -127,6 +132,7 @@ def handleColor(self, new_color: QtGui.QColor) -> None: self.update() def showEvent(self, a0: QtGui.QShowEvent) -> None: + """Re-implemented method, widget show""" _rect = self.contentsRect() self.trailPath: QtGui.QPainterPath = QtGui.QPainterPath() self.handlePath: QtGui.QPainterPath = QtGui.QPainterPath() @@ -155,16 +161,15 @@ def showEvent(self, a0: QtGui.QShowEvent) -> None: return super().showEvent(a0) def setPixmap(self, pixmap: QtGui.QPixmap) -> None: + """Set widget pixmap""" self.icon_pixmap = pixmap # self.repaint() self.update() @QtCore.pyqtSlot(name="clicked") def setup_animation(self) -> None: - if ( - not self.slide_animation.state - == self.slide_animation.State.Running - ): + """Setup widget animation""" + if not self.slide_animation.state == self.slide_animation.State.Running: self.slide_animation.setEndValue( self._handle_ONPosition if self.state == ToggleAnimatedButton.State.OFF @@ -173,23 +178,17 @@ def setup_animation(self) -> None: self.slide_animation.start() def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: + """Re-implemented method, handle mouse press events""" if self.trailPath: - if ( - self.trailPath.contains(e.pos().toPointF()) - and self.underMouse() - ): - if ( - not self.slide_animation.state - == self.slide_animation.State.Running - ): - self._state = ToggleAnimatedButton.State( - not self._state.value - ) + if self.trailPath.contains(e.pos().toPointF()) and self.underMouse(): + if not self.slide_animation.state == self.slide_animation.State.Running: + self._state = ToggleAnimatedButton.State(not self._state.value) self.stateChange.emit(self._state) super().mousePressEvent(e) e.ignore() def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" option = QtWidgets.QStyleOptionButton() option.initFrom(self) option.state |= QtWidgets.QStyle.StateFlag.State_Off @@ -197,7 +196,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: option.state |= QtWidgets.QStyle.StateFlag.State_Active _rect = self.contentsRect() - bg_color = (self.backgroundColor) + bg_color = self.backgroundColor self.handlePath: QtGui.QPainterPath = QtGui.QPainterPath() self.handle_ellipseRect = QtCore.QRectF( self._handle_position, @@ -211,8 +210,33 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.setRenderHint(painter.RenderHint.SmoothPixmapTransform) painter.setBackgroundMode(QtCore.Qt.BGMode.TransparentMode) painter.setRenderHint(painter.RenderHint.LosslessImageRendering) - - + + rect_norm = _rect.toRectF().normalized() + min_x = rect_norm.x() + max_x = rect_norm.x() + rect_norm.width() - rect_norm.height() * 0.80 + progress = (self._handle_position - min_x) / (max_x - min_x) + progress = max(0.0, min(1.0, progress)) + + # Inline color interpolation (no separate functions) + r = ( + self._handleOFFcolor.red() + + (self._handleONcolor.red() - self._handleOFFcolor.red()) * progress + ) + g = ( + self._handleOFFcolor.green() + + (self._handleONcolor.green() - self._handleOFFcolor.green()) * progress + ) + b = ( + self._handleOFFcolor.blue() + + (self._handleONcolor.blue() - self._handleOFFcolor.blue()) * progress + ) + a = ( + self._handleOFFcolor.alpha() + + (self._handleONcolor.alpha() - self._handleOFFcolor.alpha()) * progress + ) + + self.handleColor = QtGui.QColor(int(r), int(g), int(b), int(a)) + painter.fillPath( self.trailPath, bg_color if self.isEnabled() else self.disable_bg_color, diff --git a/BlocksScreen/lib/utils/ui.py b/BlocksScreen/lib/utils/ui.py deleted file mode 100644 index f74bd5df..00000000 --- a/BlocksScreen/lib/utils/ui.py +++ /dev/null @@ -1,2 +0,0 @@ -import typing - diff --git a/BlocksScreen/lib/utils/url.py b/BlocksScreen/lib/utils/url.py deleted file mode 100644 index 727cbb49..00000000 --- a/BlocksScreen/lib/utils/url.py +++ /dev/null @@ -1,76 +0,0 @@ - -class URLTYPE(object): - _prefix_type = ["ws://", "wss://", "http://", "https://"] - link_type = ["rest", "websocket"] - - def __init__(self, host: str, port=None, type: str = "rest"): - # self._prefix:str = - if isinstance(port, int) is False and port is not None: - raise AttributeError("If port is specified it can only be an integer") - - if type not in self.link_type: - raise AttributeError(f"Url type can only be of: {self.link_type}") - - self._websocket_suffix: str = "/websocket" - - self._host: str = host - self._port = port - self._type = type.lower() - self._build_url - # self._url = self._prefix_type[self._type] + self._host + ":" + str(self._port) + self._websocket_suffix - - def _build_url(self) -> None: - if self._type == "rest": - self._url = ( - self.link_type[2] + self._host - if self._host.endswith(".com") - else self.link_type[2] + self._host + ".com" - ) - - if self._type == "websocket": - self._url = ( - self.link_type[0] - + self._host - + ":" - + str(self._port) - + self._websocket_suffix - ) - - def type(self) -> str: - return self.__class__.__name__ - - @property - def url_link(self): - return self._url - - @url_link.setter - def url_link(self, host, port, type): - if self._type == "rest": - if port is None: - self._url = ( - self.link_type[2] + host - if host.endswith(".com") - else self.link_type[2] + host + ".com" - ) - else: - self._url = ( - self.link_type[2] + host + ":" + port - if host.endswith(".com") - else self.link_type[2] + host + ":" + port + ".com" - ) - - if self._type == "websocket": - self._url = ( - self.link_type[0] - + self._host - + ":" - + str(self._port) - + self._websocket_suffix - ) - - def __repr__(self) -> str: - cls = self.__class__.__name__ - return f"{cls}(host = {self._host}, port= {self._port}, type= {self._type})" - - def __str__(self) -> str: - return self._url \ No newline at end of file diff --git a/BlocksScreen/logger.py b/BlocksScreen/logger.py index 22de8402..e2aa90ca 100644 --- a/BlocksScreen/logger.py +++ b/BlocksScreen/logger.py @@ -21,6 +21,7 @@ def __init__( self.setLevel(level) def emit(self, record): + """Emit logging record""" try: msg = self.format(record) record = copy.copy(record) @@ -31,11 +32,8 @@ def emit(self, record): except Exception: self.handleError(record) - def flush(self): ... - - # TODO: Implement this - def setFormatter(self, fmt: logging.Formatter | None) -> None: + """Set logging formatter""" return super().setFormatter(fmt) @@ -44,12 +42,17 @@ class QueueListener(logging.handlers.TimedRotatingFileHandler): def __init__(self, filename, encoding="utf-8"): super(QueueListener, self).__init__( - filename=filename, when="MIDNIGHT", backupCount=10, encoding=encoding, delay=True + filename=filename, + when="MIDNIGHT", + backupCount=10, + encoding=encoding, + delay=True, ) self.queue = queue.Queue() - self._thread = threading.Thread(name=f"log.{filename}",target=self._run, daemon=True) + self._thread = threading.Thread( + name=f"log.{filename}", target=self._run, daemon=True + ) self._thread.start() - def _run(self): while True: @@ -62,26 +65,23 @@ def _run(self): break def close(self): + """Close logger listener""" if self._thread is None: return self.queue.put_nowait(None) self._thread.join() self._thread = None - def doRollover(self) -> None: ... - - # TODO: Implement this - def getFilesToDelete(self) -> list[str]: ... +global MainLoggingHandler - # TODO: Delete files that one month old -global MainLoggingHandler def create_logger( name: str = "log", level=logging.INFO, format: str = "'[%(levelname)s] | %(asctime)s | %(name)s | %(relativeCreated)6d | %(threadName)s : %(message)s", ): + """Create amd return logger""" global MainLoggingHandler logger = logging.getLogger(name) logger.setLevel(level) @@ -89,13 +89,3 @@ def create_logger( MainLoggingHandler = QueueHandler(ql.queue, format, level) logger.addHandler(MainLoggingHandler) return ql - - -def destroy_logger(name): ... # TODO: Implement this - - - - - - - diff --git a/BlocksScreen/screensaver.py b/BlocksScreen/screensaver.py index 8c733f0a..de02ba02 100644 --- a/BlocksScreen/screensaver.py +++ b/BlocksScreen/screensaver.py @@ -4,15 +4,9 @@ class ScreenSaver(QtCore.QObject): timer = QtCore.QTimer() - dpms_off_timeout = helper_methods.get_dpms_timeouts().get("off_timeout") - dpms_suspend_timeout = helper_methods.get_dpms_timeouts().get( - "suspend_timeout" - ) - dpms_standby_timeout = helper_methods.get_dpms_timeouts().get( - "standby_timeout" - ) - + dpms_suspend_timeout = helper_methods.get_dpms_timeouts().get("suspend_timeout") + dpms_standby_timeout = helper_methods.get_dpms_timeouts().get("standby_timeout") touch_blocked: bool = False def __init__(self, parent) -> None: @@ -23,9 +17,7 @@ def __init__(self, parent) -> None: ) if not self.screensaver_config: self.blank_timeout = ( - self.dpms_standby_timeout - if self.dpms_standby_timeout - else 900000 + self.dpms_standby_timeout if self.dpms_standby_timeout else 900000 ) else: self.blank_timeout = self.screensaver_config.getint( @@ -66,9 +58,6 @@ def eventFilter(self, object, event) -> bool: self.timer.start() return False - def timerEvent(self, a0: QtCore.QTimerEvent) -> None: - return super().timerEvent(a0) - def check_dpms(self) -> None: """Checks the X11 extension dpms for the status of the screen""" self.touch_blocked = True diff --git a/pyproject.toml b/pyproject.toml index f3be8e2f..9f7e1cfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,35 +5,96 @@ description = "GUI for BLOCKS Printers running Klipper" authors = [ { name = "Hugo do Carmo Costa", email = "hugo.santos.costa@gmail.com" }, ] +maintainers = [ + { name = "Guilherme Costa", email = "guilherme.costa@blockstec.com" }, + { name = "Roberto Martins ", email = "roberto.martins@blockstec.com" }, +] dependencies = [ 'altgraph==0.17.4', - 'certifi==2024.7.4', - 'charset-normalizer==3.3.2', - 'idna==3.8', - 'numpy==2.1.0', - 'pefile==2023.2.7', - 'PyQt6==6.7.1', - 'PyQt6-Qt6==6.7.2', - 'PyQt6_sip==13.8.0', - 'requests==2.32.3', - 'sdbus==0.12.0', + 'certifi==2025.10.5', + 'charset-normalizer==3.4.4', + 'idna==3.11', + 'numpy==2.3.4', + 'pefile==2024.8.26', + 'PyQt6==6.10.0', + 'PyQt6-Qt6==6.10.0', + 'PyQt6_sip==13.10.2', + 'requests>=2.32.5', + 'sdbus==0.14.1', 'sdbus-networkmanager==2.0.0', 'typing==3.7.4.3', - 'websocket-client==1.8.0', - 'opencv-python-headless==4.11.0.86', - 'qrcode==8.2' + 'websocket-client==1.9.0', + 'qrcode==8.2', ] -requires-python = ">=3.11.2" +requires-python = "==3.11.2" readme = "README.md" license = { text = "GNU Affero General Public License v3.0 or later" } keywords = ["GUI", "klipper", "BlocksScreen", "BLOCKS"] +[project.optional-dependencies] +dev = ["ruff", "pylint", "pytest", "pytest-cov", "docstr_coverage"] +stage = ["bandit"] +full-dev = ["BlockScreen[dev,stage]"] + + +[project.urls] +Homepage = "https://blockstec.com" +Issues = "https://github.com/BlocksTechnology/BlocksScreen/issues" + [tool.ruff] line-length = 88 indent-width = 4 +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "BlocksScreen/lib/ui", + "extras", + "tests" +] + +[tool.ruff.lint] +ignore = ["F403"] [tool.ruff.format] indent-style = "space" line-ending = 'auto' docstring-code-format = true docstring-code-line-length = 94 + +[tool.pylint] +fail-under = 7 +jobs = 16 +ignore = ["tests", "scripts", "ui", "extras"] +ignore-paths = ["BlocksScreen/lib/ui"] +py-version = "3.11" +max-line-length = 88 + +[tool.pytest.ini_options] +addopts = "--cov=BlocksScreen --cov-report=html" + +[tool.bandit] +exclude_dirs = ["tests", "BlocksScreen/lib/ui/resources/"] diff --git a/scripts/dev-requirements.txt b/scripts/dev-requirements.txt deleted file mode 100644 index f88d7484..00000000 --- a/scripts/dev-requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pycodestyle -pygobject-stubs \ No newline at end of file diff --git a/scripts/requirements-dev.txt b/scripts/requirements-dev.txt new file mode 100644 index 00000000..61706f13 --- /dev/null +++ b/scripts/requirements-dev.txt @@ -0,0 +1,6 @@ +ruff +pylint +pytest +pytest-cov +docstr_coverage +bandit \ No newline at end of file diff --git a/scripts/requirements.txt b/scripts/requirements.txt index ca9730a2..9dfdbd57 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,16 +1,15 @@ altgraph==0.17.4 -certifi==2024.7.4 -charset-normalizer==3.3.2 -idna==3.8 -numpy==2.1.0 -pefile==2023.2.7 -PyQt6==6.7.1 -PyQt6-Qt6==6.7.2 -PyQt6_sip==13.8.0 -requests==2.32.4 -sdbus==0.12.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 +idna==3.11 +numpy==2.3.4 +pefile==2024.8.26 +PyQt6==6.10.0 +PyQt6-Qt6==6.10.0 +PyQt6_sip==13.10.2 +requests>=2.32.5 +sdbus==0.14.1 sdbus-networkmanager==2.0.0 typing==3.7.4.3 -websocket-client==1.8.0 -opencv-python-headless==4.11.0.86 +websocket-client==1.9.0 qrcode==8.2 \ No newline at end of file diff --git a/tools/.pylintrc b/tools/.pylintrc new file mode 100644 index 00000000..603b7b30 --- /dev/null +++ b/tools/.pylintrc @@ -0,0 +1,659 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked and +# will not be imported (useful for modules/projects where namespaces are +# manipulated during runtime and thus existing member attributes cannot be +# deduced by static analysis). It supports qualified module names, as well as +# Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Resolve imports to .pyi stubs if available. May reduce no-member messages and +# increase not-an-iterable messages. +prefer-stubs=no + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.11 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +class-rgx=[A-Z][a-z]+ + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Regular expression matching correct parameter specification variable names. +# If left empty, parameter specification variable names will be checked with +# the set naming style. +#paramspec-rgx= + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Regular expression matching correct type variable tuple names. If left empty, +# type variable tuple names will be checked with the set naming style. +#typevartuple-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of positional arguments for function / method. +max-positional-arguments=5 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. Pylint's default of 100 is +# based on PEP 8's guidance that teams may choose line lengths up to 99 +# characters. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + use-implicit-booleaness-not-comparison-to-string, + use-implicit-booleaness-not-comparison-to-zero, + bare-except, + invalid-name + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# Whether or not to search for fixme's in docstrings. +check-fixme-in-docstring=no + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + +# Let 'consider-using-join' be raised when the separator to join on would be +# non-empty (resulting in expected fixes of the type: ``"- " + " - +# ".join(items)``) +suggest-join-with-non-empty-separator=yes + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: 'text', 'parseable', +# 'colorized', 'json2' (improved json format), 'json' (old json format), msvs +# (visual studio) and 'github' (GitHub actions). You can also give a reporter +# class, e.g. mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The maximum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io