From 94cf29d5bc56648e71202ff47b90d8f8ff02ed43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:07:42 +0000 Subject: [PATCH 01/43] Build(deps): Bump requests from 2.32.3 to 2.32.4 in /scripts (#105) Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index c6526130..ca9730a2 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -7,7 +7,7 @@ pefile==2023.2.7 PyQt6==6.7.1 PyQt6-Qt6==6.7.2 PyQt6_sip==13.8.0 -requests==2.32.3 +requests==2.32.4 sdbus==0.12.0 sdbus-networkmanager==2.0.0 typing==3.7.4.3 From 3e3583b83f7fafe54fb35ec31cb270571c61ba59 Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Fri, 28 Nov 2025 11:26:35 +0000 Subject: [PATCH 02/43] Hugoclsc/feature/GitHub actions (#107) * initial actions * Update CodeQL workflow to ignore certain paths Ignore specific paths for CodeQL analysis on push and pull request events. * experimenting workflows * Add pylintrc configuration file, configure dev test code workflow * split into multiple jobs * Fail job on error * pylint rc file, jobs refactores for dev workflow * Remove upload artifacts for now, move pylintrc file to root dir * Added upload artifacts with different files * Rem: branch naming workflow, enviroment handles this * Added fail-fast to false * Run only on PR to dev * Change file name --- .github/workflows/dev-ci.yml | 89 +++++ .pylintrc.dev | 11 + scripts/dev-requirements.txt | 2 - scripts/requirements-dev.txt | 4 + tools/.pylintrc | 659 +++++++++++++++++++++++++++++++++++ 5 files changed, 763 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/dev-ci.yml create mode 100644 .pylintrc.dev delete mode 100644 scripts/dev-requirements.txt create mode 100644 scripts/requirements-dev.txt create mode 100644 tools/.pylintrc diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml new file mode 100644 index 00000000..8fa61f99 --- /dev/null +++ b/.github/workflows/dev-ci.yml @@ -0,0 +1,89 @@ +name: dev-test-code + +on: + push: + branches: + - dev + paths-ignore: + - "scripts/**" + - "BlocksScreen/lib/ui/**" + - "extras/**" + 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] + 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" + - name: Install dependencies + run: | + echo "Installing dependencies" + python -m pip install --upgrade pip + pip install scripts -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 Code Test" + pylint -j$(nproc) --recursive=y --rcfile=.pylintrc.dev . > 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/'*.py' --doctest-modules --junitxml=junit/test-results.xml --cov=com --conv-report=xml --cov-report=html > pytest-output.txt 2>&1 + else + echo "No tests directory no need to proceed with tests" + fi + 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' && hashFiles('pytest-output.txt', 'junit/test-results.xml', 'coverage.xml') + uses: actions/upload-artifact@v4 + with: + name: pytest-results + path: | + pytest_output.txt + junit/test-results.xml + coverage.xml + htmlcov/ + \ 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/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..5cbd2e7d --- /dev/null +++ b/scripts/requirements-dev.txt @@ -0,0 +1,4 @@ +ruff +pylint +pytest +pytest-cov 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 From 420a3e197566c610ca42414c5590e5ffa368fa4c Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Fri, 5 Dec 2025 14:55:36 +0000 Subject: [PATCH 03/43] Hugoclsc/feature/GitHub actions (#113) * initial actions * Update CodeQL workflow to ignore certain paths Ignore specific paths for CodeQL analysis on push and pull request events. * experimenting workflows * Add pylintrc configuration file, configure dev test code workflow * split into multiple jobs * Fail job on error * pylint rc file, jobs refactores for dev workflow * Remove upload artifacts for now, move pylintrc file to root dir * Added upload artifacts with different files * Rem: branch naming workflow, enviroment handles this * Added fail-fast to false * Run only on PR to dev * Change file name * Initial stage-ci workflow configuration * Changed worflow name, added docstr-coverage tool to the workflow * Added dependencies cache, reduces redundant installations, fixes incorrect pytest file * Exclude F403 ruff error * Added docstr-coverage dependency * Fix incorrect requirements installation command * bugfix on cli command for docstr-coverage * Uncomment artifact upload * Update pyproject.toml * Added bandig config, bump requests version * Bump requests version * Bump requirement versions * Add dev, stage requirements, bump all requirements * Migrate pylint config options to pyproject.toml file * Deleted pylintrc.dev file * Remove unused dependencie * uncomment upload artifacts * Added bandit security tests * Standardize bandit output to json * Add environment --------- Signed-off-by: Hugo Costa --- .github/workflows/dev-ci.yml | 57 +++++++++++++++++++++++++-------- .github/workflows/stage-ci.yml | 23 ++++++++++++++ pyproject.toml | 58 ++++++++++++++++++++++++++-------- scripts/requirements-dev.txt | 2 ++ scripts/requirements.txt | 23 +++++++------- 5 files changed, 123 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/stage-ci.yml diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml index 8fa61f99..609990a4 100644 --- a/.github/workflows/dev-ci.yml +++ b/.github/workflows/dev-ci.yml @@ -1,9 +1,9 @@ -name: dev-test-code +name: CI-dev-pipeline on: push: branches: - - dev + - dev paths-ignore: - "scripts/**" - "BlocksScreen/lib/ui/**" @@ -22,7 +22,9 @@ jobs: fail-fast: false matrix: python-version: ["3.11.2"] - test-type: [ruff, pylint, pytest] + test-type: [ruff, pylint, pytest, docstrcov, security] + environment: Dev + steps: - name: Checkout repo uses: actions/checkout@v4 @@ -31,13 +33,15 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - cache: "pip" + cache: pip + cache-dependency-path: scripts/requirements-dev.txt + - name: Install dependencies run: | echo "Installing dependencies" python -m pip install --upgrade pip - pip install scripts -r scripts/requirements-dev.txt - + pip install -r scripts/requirements-dev.txt + - name: Run Test ${{ matrix.test-type }} run: | echo "Starting test runs" @@ -48,21 +52,32 @@ jobs: echo "Ruff finished" fi if [ "${{ matrix.test-type }}" == "pylint" ]; then - echo "Running Code Test" - pylint -j$(nproc) --recursive=y --rcfile=.pylintrc.dev . > pylint-output.txt 2>&1 + 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/'*.py' --doctest-modules --junitxml=junit/test-results.xml --cov=com --conv-report=xml --cov-report=html > pytest-output.txt 2>&1 + pytest tests/*.py --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 - - name: Upload ruff artifact + 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: @@ -75,15 +90,29 @@ jobs: with: name: pylint-results path: pylint-output.txt - + - name: Upload Pytest Artifacts - if: always() && matrix.test-type == 'pytest' && hashFiles('pytest-output.txt', 'junit/test-results.xml', 'coverage.xml') + if: always() && matrix.test-type == 'pytest' uses: actions/upload-artifact@v4 with: name: pytest-results path: | - pytest_output.txt + pytest-output.txt junit/test-results.xml coverage.xml htmlcov/ - \ No newline at end of file + 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.txt 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/pyproject.toml b/pyproject.toml index f3be8e2f..eaa6152e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,35 +5,65 @@ 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 +[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/requirements-dev.txt b/scripts/requirements-dev.txt index 5cbd2e7d..61706f13 100644 --- a/scripts/requirements-dev.txt +++ b/scripts/requirements-dev.txt @@ -2,3 +2,5 @@ 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 From bc1156ccfabd449b05f3602e91cd9671922e70d8 Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Fri, 5 Dec 2025 15:19:51 +0000 Subject: [PATCH 04/43] Hugoclsc/feature/GitHub actions (#114) * initial actions * Update CodeQL workflow to ignore certain paths Ignore specific paths for CodeQL analysis on push and pull request events. * experimenting workflows * Add pylintrc configuration file, configure dev test code workflow * split into multiple jobs * Fail job on error * pylint rc file, jobs refactores for dev workflow * Remove upload artifacts for now, move pylintrc file to root dir * Added upload artifacts with different files * Rem: branch naming workflow, enviroment handles this * Added fail-fast to false * Run only on PR to dev * Change file name * Initial stage-ci workflow configuration * Changed worflow name, added docstr-coverage tool to the workflow * Added dependencies cache, reduces redundant installations, fixes incorrect pytest file * Exclude F403 ruff error * Added docstr-coverage dependency * Fix incorrect requirements installation command * bugfix on cli command for docstr-coverage * Uncomment artifact upload * Update pyproject.toml * Added bandig config, bump requests version * Bump requests version * Bump requirement versions * Add dev, stage requirements, bump all requirements * Migrate pylint config options to pyproject.toml file * Deleted pylintrc.dev file * Remove unused dependencie * uncomment upload artifacts * Added bandit security tests * Standardize bandit output to json * Add environment * Fix incorrect file extension for bandit --------- Signed-off-by: Hugo Costa --- .github/workflows/dev-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml index 609990a4..d6e0bebd 100644 --- a/.github/workflows/dev-ci.yml +++ b/.github/workflows/dev-ci.yml @@ -60,7 +60,7 @@ jobs: if [ "${{ matrix.test-type }}" == "pytest" ]; then if [ -d "tests/" ] && [ "$(ls -A tests/)" ]; then echo "Running Python unit tests" - pytest tests/*.py --doctest-modules --junitxml=junit/test-results.xml --cov=BlocksScreen --cov-report=xml --cov-report=html > pytest-output.txt 2>&1 + 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 @@ -115,4 +115,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: bandit-output - path: bandit-output.txt + path: bandit-output.json From ca9eb864695afdb3f1ff205cce73415dbe842f82 Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Fri, 5 Dec 2025 16:39:20 +0000 Subject: [PATCH 05/43] Hugoclsc/feature/GitHub actions (#115) * initial actions * Update CodeQL workflow to ignore certain paths Ignore specific paths for CodeQL analysis on push and pull request events. * experimenting workflows * Add pylintrc configuration file, configure dev test code workflow * split into multiple jobs * Fail job on error * pylint rc file, jobs refactores for dev workflow * Remove upload artifacts for now, move pylintrc file to root dir * Added upload artifacts with different files * Rem: branch naming workflow, enviroment handles this * Added fail-fast to false * Run only on PR to dev * Change file name * Initial stage-ci workflow configuration * Changed worflow name, added docstr-coverage tool to the workflow * Added dependencies cache, reduces redundant installations, fixes incorrect pytest file * Exclude F403 ruff error * Added docstr-coverage dependency * Fix incorrect requirements installation command * bugfix on cli command for docstr-coverage * Uncomment artifact upload * Update pyproject.toml * Added bandig config, bump requests version * Bump requests version * Bump requirement versions * Add dev, stage requirements, bump all requirements * Migrate pylint config options to pyproject.toml file * Deleted pylintrc.dev file * Remove unused dependencie * uncomment upload artifacts * Added bandit security tests * Standardize bandit output to json * Add environment * Fix incorrect file extension for bandit * Add exclude section to ruff config * Fix docstr-converage exclude regex --------- Signed-off-by: Hugo Costa --- .github/workflows/dev-ci.yml | 2 +- pyproject.toml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml index d6e0bebd..7cba017e 100644 --- a/.github/workflows/dev-ci.yml +++ b/.github/workflows/dev-ci.yml @@ -68,7 +68,7 @@ jobs: 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 + 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 diff --git a/pyproject.toml b/pyproject.toml index eaa6152e..9f7e1cfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,37 @@ 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"] From 69deb3b8a9f608de4c1ca7376a5e989d334e7e74 Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Fri, 5 Dec 2025 17:07:28 +0000 Subject: [PATCH 06/43] Hugoclsc/feature/GitHub actions (#116) * initial actions * Update CodeQL workflow to ignore certain paths Ignore specific paths for CodeQL analysis on push and pull request events. * experimenting workflows * Add pylintrc configuration file, configure dev test code workflow * split into multiple jobs * Fail job on error * pylint rc file, jobs refactores for dev workflow * Remove upload artifacts for now, move pylintrc file to root dir * Added upload artifacts with different files * Rem: branch naming workflow, enviroment handles this * Added fail-fast to false * Run only on PR to dev * Change file name * Initial stage-ci workflow configuration * Changed worflow name, added docstr-coverage tool to the workflow * Added dependencies cache, reduces redundant installations, fixes incorrect pytest file * Exclude F403 ruff error * Added docstr-coverage dependency * Fix incorrect requirements installation command * bugfix on cli command for docstr-coverage * Uncomment artifact upload * Update pyproject.toml * Added bandig config, bump requests version * Bump requests version * Bump requirement versions * Add dev, stage requirements, bump all requirements * Migrate pylint config options to pyproject.toml file * Deleted pylintrc.dev file * Remove unused dependencie * uncomment upload artifacts * Added bandit security tests * Standardize bandit output to json * Add environment * Fix incorrect file extension for bandit * Add exclude section to ruff config * Fix docstr-converage exclude regex * Separate CI from CD --------- Signed-off-by: Hugo Costa --- .github/workflows/dev-ci.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml index 7cba017e..556119c2 100644 --- a/.github/workflows/dev-ci.yml +++ b/.github/workflows/dev-ci.yml @@ -1,13 +1,6 @@ name: CI-dev-pipeline on: - push: - branches: - - dev - paths-ignore: - - "scripts/**" - - "BlocksScreen/lib/ui/**" - - "extras/**" pull_request: branches: - dev @@ -23,7 +16,6 @@ jobs: matrix: python-version: ["3.11.2"] test-type: [ruff, pylint, pytest, docstrcov, security] - environment: Dev steps: - name: Checkout repo @@ -41,7 +33,7 @@ jobs: 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" @@ -70,13 +62,12 @@ jobs: 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 From 8229438f9f9eb61e33ec29e071283aa28b6f2cd5 Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Wed, 10 Dec 2025 14:18:11 +0000 Subject: [PATCH 07/43] Refactor/tests compliance (#117) * Build(deps): Bump requests from 2.32.3 to 2.32.4 in /scripts (#112) Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Removed unused method * Remove unused imports * Remove unused import, f-string without placeholders * Remove unused import * Removed unused module * Removed f-string without placeholder * Removed unused code on method * Removed placeholder file for new module * Removed assigned variable but never used * Removed unused import * Comment unused variable * Removed unused code line * Removed unused file * Explicity re-raise with from * Removed unused code line * Refactor text box painting * Removed unused import * Removed unused code line * Removed unused module * Removed unused module placeholder * Added docstrings * Add docstrings to methods * Add docstring to methods and class * Add docstring to methods * Removed unused module * Added docstrings * Added docstrings * Added docstrings, deleted commented code * Added docstrings, deleted commented code * Added docstrings, deleted commented code * Added docstrings, deleted commented code * deleted commented code * Added docstrings, deleted commented code * Deleted code from unused window * Added docstring to methods, deleted unused code * Added docstring to methods, deleted unused code * Add docsctring, delete commented code * Add docsctring * Add docsctring, delete unused code * Add docstring, change method name * Add docstring, change method name, delete unused code * Add docstring, delete unused code * Add docstring, delete unused code, changed method name * Add docstring, changed method name * Add docstring, changed method name * Add docstring, changed method name * Add docstring, changed method name * Add docstring, changed method name * Formatting * Add docstring * Add docstring, delete unused code, changed method name * Add docstring, delete unused code, changed method name * Add docstring, changed method name * Add docstring, changed method name * Add docstring, delete commented code, change method name * Add docstring, delete commented code, change method name * Let troubleshoot page decide where it wants to go * Add docstring, change method name * Add docstring, delete unused and untested code * Add docstring * Add docstring * Add docstring, delete unused code * Add docstring * Add docstring * Add docstring * Add docstring * Add docstring * Add docstrings * Add docstring, change method name * Add docstring * Add docstring, delete unused code * Add docstring, delete unused code * Add docstring * Change list item docstring * Add docstring * Add docstring * Deleted unused module * Add docstring * Deleted unused method * Deleted unused code line * Delete unused code line * Delete unused code line * Security patch subprocess shell = True, security issue * Remove argument from method * Ruff formatting * Ruff formatting * Surpress Reviewed security issues * Formatting --------- Signed-off-by: dependabot[bot] Signed-off-by: Hugo Costa Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- BlocksScreen/BlocksScreen.py | 7 +- BlocksScreen/configfile.py | 134 ++++--- BlocksScreen/events.py | 50 ++- BlocksScreen/helper_methods.py | 52 ++- BlocksScreen/lib/async_network_monitor.py | 157 -------- BlocksScreen/lib/filament.py | 19 +- BlocksScreen/lib/files.py | 90 ++--- BlocksScreen/lib/machine.py | 84 ++-- BlocksScreen/lib/moonrakerComm.py | 142 +++---- BlocksScreen/lib/moonrest.py | 9 +- BlocksScreen/lib/network.py | 39 +- BlocksScreen/lib/panels/controlTab.py | 31 +- BlocksScreen/lib/panels/filamentTab.py | 32 +- BlocksScreen/lib/panels/instructionsWindow.py | 30 -- BlocksScreen/lib/panels/mainWindow.py | 42 +- BlocksScreen/lib/panels/networkWindow.py | 126 +++--- BlocksScreen/lib/panels/printTab.py | 110 ++--- BlocksScreen/lib/panels/userauthWindow.py | 6 - BlocksScreen/lib/panels/utilitiesTab.py | 54 ++- .../lib/panels/widgets/babystepPage.py | 103 ++--- .../lib/panels/widgets/confirmPage.py | 8 +- .../lib/panels/widgets/connectionPage.py | 34 +- BlocksScreen/lib/panels/widgets/dialogPage.py | 46 +-- BlocksScreen/lib/panels/widgets/fansPage.py | 14 +- BlocksScreen/lib/panels/widgets/filesPage.py | 80 ++-- .../lib/panels/widgets/jobStatusPage.py | 28 +- .../lib/panels/widgets/keyboardPage.py | 18 +- BlocksScreen/lib/panels/widgets/loadPage.py | 40 +- BlocksScreen/lib/panels/widgets/loadWidget.py | 36 +- BlocksScreen/lib/panels/widgets/numpadPage.py | 153 +++---- .../lib/panels/widgets/optionCardWidget.py | 38 +- .../lib/panels/widgets/popupDialogWidget.py | 95 +++-- .../lib/panels/widgets/printcorePage.py | 62 +-- .../lib/panels/widgets/probeHelperPage.py | 298 +++++--------- .../lib/panels/widgets/sensorWidget.py | 45 +-- .../lib/panels/widgets/sensorsPanel.py | 94 ++--- .../panels/widgets/slider_selector_page.py | 51 +-- .../lib/panels/widgets/troubleshootPage.py | 73 ++-- BlocksScreen/lib/panels/widgets/tunePage.py | 108 ++--- BlocksScreen/lib/panels/widgets/updatePage.py | 2 +- BlocksScreen/lib/printer.py | 2 +- BlocksScreen/lib/qrcode_gen.py | 8 +- BlocksScreen/lib/ui/instructionsWindow.ui | 377 ------------------ BlocksScreen/lib/ui/instructionsWindow_ui.py | 162 -------- BlocksScreen/lib/utils/RepeatedTimer.py | 14 +- BlocksScreen/lib/utils/RoutingQueue.py | 7 +- BlocksScreen/lib/utils/blocks_Scrollbar.py | 10 +- BlocksScreen/lib/utils/blocks_button.py | 44 +- BlocksScreen/lib/utils/blocks_frame.py | 9 +- BlocksScreen/lib/utils/blocks_label.py | 31 +- BlocksScreen/lib/utils/blocks_linedit.py | 16 +- BlocksScreen/lib/utils/blocks_progressbar.py | 25 +- BlocksScreen/lib/utils/blocks_slider.py | 23 +- BlocksScreen/lib/utils/blocks_tabwidget.py | 7 + BlocksScreen/lib/utils/blocks_togglebutton.py | 39 +- BlocksScreen/lib/utils/display_button.py | 53 ++- BlocksScreen/lib/utils/group_button.py | 22 +- BlocksScreen/lib/utils/icon_button.py | 11 +- BlocksScreen/lib/utils/list_button.py | 33 +- BlocksScreen/lib/utils/list_model.py | 2 +- BlocksScreen/lib/utils/loadAnimatedLabel.py | 6 - BlocksScreen/lib/utils/numpad_button.py | 8 +- BlocksScreen/lib/utils/others.py | 48 --- .../lib/utils/toggleAnimatedButton.py | 52 ++- BlocksScreen/lib/utils/ui.py | 2 - BlocksScreen/lib/utils/url.py | 76 ---- BlocksScreen/logger.py | 36 +- BlocksScreen/screensaver.py | 17 +- 68 files changed, 1308 insertions(+), 2472 deletions(-) delete mode 100644 BlocksScreen/lib/async_network_monitor.py delete mode 100644 BlocksScreen/lib/panels/instructionsWindow.py delete mode 100644 BlocksScreen/lib/panels/userauthWindow.py delete mode 100644 BlocksScreen/lib/ui/instructionsWindow.ui delete mode 100644 BlocksScreen/lib/ui/instructionsWindow_ui.py delete mode 100644 BlocksScreen/lib/utils/loadAnimatedLabel.py delete mode 100644 BlocksScreen/lib/utils/others.py delete mode 100644 BlocksScreen/lib/utils/ui.py delete mode 100644 BlocksScreen/lib/utils/url.py 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..0dd2b2db 100644 --- a/BlocksScreen/helper_methods.py +++ b/BlocksScreen/helper_methods.py @@ -1,7 +1,9 @@ +# 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 @@ -18,6 +20,8 @@ libxext = ctypes.CDLL("libXext.so.6") class DPMSState(enum.Enum): + """Available DPMS states""" + FAIL = -1 ON = 0 STANDBY = 1 @@ -35,6 +39,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 +64,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 +86,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 +104,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 +120,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 +141,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 +155,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 +189,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 +217,7 @@ def check_dpms_capable(display: int): return _capable def disable_dpms() -> None: + """Disable DPMS""" set_dpms_mode(DPMSState.OFF) except OSError as e: @@ -255,6 +275,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 +311,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,20 +321,6 @@ 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_hash(data) -> hashlib._Hash: -# hash = hashlib.sha256() -# hash.update(data.encode()) -# hash.digest() -# 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..68a399c2 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,16 +45,10 @@ 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 @@ -62,15 +57,13 @@ def file_list(self): 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): @@ -89,8 +82,11 @@ def handle_message_received(self, method: str, data, params: dict) -> None: @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 + """ _data: dict = { "thumbnail_images": list, "filament_total": dict, @@ -122,34 +118,18 @@ 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,37 +139,24 @@ 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)} + {"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_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: + """Filter Klippy related events""" if a1.type() == events.KlippyDisconnected.type(): self.files_metadata.clear() self.files.clear() return False - elif a1.type() == events.KlippyReady.type(): + if a1.type() == events.KlippyReady.type(): # Request all files including in subdirectories # in order to get all metadata self.request_file_list.emit() @@ -199,6 +166,7 @@ def eventFilter(self, a0: QtCore.QObject, a1: QtCore.QEvent) -> bool: 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..b2f41446 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,6 +542,7 @@ 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}" return self._ws.send_request( method="server.files.delete_file", @@ -542,7 +551,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 +570,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 +579,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 +590,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 +601,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 +615,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 +628,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 +653,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 +666,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 +674,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 +707,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,15 +715,18 @@ 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} ) @@ -716,6 +734,7 @@ def update_status(self, refresh: bool = False) -> bool: @QtCore.pyqtSlot(name="update-refresh") @QtCore.pyqtSlot(str, name="update-refresh") def refresh_update_status(self, name: str = "") -> bool: + """Refresh packages state""" if not isinstance(name, str) or not name: return False return self._ws.send_request( @@ -724,29 +743,35 @@ def refresh_update_status(self, name: str = "") -> bool: @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 +781,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..312b60e8 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 ) @@ -1244,9 +1258,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 +1285,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 +1304,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 +1362,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 +1437,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..bef67b3f 100644 --- a/BlocksScreen/lib/panels/controlTab.py +++ b/BlocksScreen/lib/panels/controlTab.py @@ -14,6 +14,7 @@ from lib.panels.widgets.popupDialogWidget import Popup + class ControlTab(QtWidgets.QStackedWidget): """Printer Control Stacked Widget""" @@ -108,12 +109,6 @@ def __init__( partial(self.change_page, self.indexOf(self.panel.temperature_page)) ) 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 +253,7 @@ def __init__( ) ) - self.panel.cp_z_tilt_btn.clicked.connect( - lambda: self.handle_ztilt() - ) + self.panel.cp_z_tilt_btn.clicked.connect(lambda: self.handle_ztilt()) self.printcores_page.pc_accept.clicked.connect(self.handle_swapcore) @@ -274,9 +267,7 @@ def __init__( self.panel.cooldown_btn.hide() self.panel.cp_switch_print_core_btn.hide() - - def handle_printcoreupdate(self, value:dict): - + def handle_printcoreupdate(self, value: dict): if value["swapping"] == "idle": return @@ -289,14 +280,10 @@ def handle_printcoreupdate(self, value:dict): ) if value["swapping"] == "unloading": self.loadpage.set_status_message("Unloading print core") - + if value["swapping"] == "cleaning": self.loadpage.set_status_message("Cleaning print core") - - - - def _handle_gcode_response(self, messages: list): """Handle gcode response for Z-tilt adjustment""" pattern = r"Retries:\s*(\d+)/(\d+).*?range:\s*([\d.]+)\s*tolerance:\s*([\d.]+)" @@ -305,7 +292,11 @@ 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: + if ( + "Retries:" in msg_list + and "range:" in msg_list + and "tolerance:" in msg_list + ): print("Match candidate:", msg_list) match = re.search(pattern, msg_list) print("Regex match:", match) @@ -327,7 +318,6 @@ def _handle_gcode_response(self, messages: list): f"Retries: {retries_done}/{retries_total} | Range: {probed_range:.6f} | Tolerance: {tolerance:.6f}" ) - def handle_ztilt(self): """Handle Z-Tilt Adjustment""" self.loadpage.show() @@ -351,7 +341,6 @@ def show_swapcore(self): self.loadpage.show() self.loadpage.set_status_message("Preparing to swap print core") - def handle_swapcore(self): """Handle swap printcore routine finish""" self.printcores_page.setText("Executing \n Firmware Restart") @@ -521,7 +510,7 @@ 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": + if values[0] == "252,50" and values[1] == "250" and values[2] == "50": self.loadpage.hide self.toolhead_info.update({f"{field}": values}) diff --git a/BlocksScreen/lib/panels/filamentTab.py b/BlocksScreen/lib/panels/filamentTab.py index ceb77a2a..4ab358fa 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 @@ -52,7 +51,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 +93,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 +102,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,6 +123,7 @@ 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 @@ -134,16 +132,17 @@ def on_extruder_update( self.loadscreen.set_status_message("Extruder heated up \n Please wait") return if field == "temperature": - self.current_temp = round(new_value, 0) # somehow this works + self.current_temp = round(new_value, 0) self.loadscreen.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.target_temp = round(new_value, 0) self.loadscreen.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 +159,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 +177,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 +198,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 +220,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 +236,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..b7921e55 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -35,11 +35,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 @@ -156,25 +157,27 @@ def __init__(self): # @ Start websocket connection with moonraker self.bo_ws_startup.emit() self.reset_tab_indexes() + @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): @@ -492,14 +495,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( diff --git a/BlocksScreen/lib/panels/networkWindow.py b/BlocksScreen/lib/panels/networkWindow.py index 5df4b81d..57617efe 100644 --- a/BlocksScreen/lib/panels/networkWindow.py +++ b/BlocksScreen/lib/panels/networkWindow.py @@ -1,6 +1,6 @@ import logging import typing -import subprocess +import subprocess # nosec: B404 from functools import partial from lib.network import SdbusNetworkManagerAsync @@ -12,6 +12,7 @@ logger = logging.getLogger("logs/BlocksScreen.log") + class BuildNetworkList(QtCore.QThread): """Retrieves information from sdbus interface about scanned networks""" @@ -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,13 +316,8 @@ def __init__(self, parent: typing.Optional[QtWidgets.QWidget], /) -> None: QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg") ) - - self.panel.network_activate_btn.clicked.connect( - lambda: self.saved_wifi_option_selected() - ) - self.panel.network_delete_btn.clicked.connect( - lambda: self.saved_wifi_option_selected() - ) + self.panel.network_activate_btn.clicked.connect(self.saved_wifi_option_selected) + self.panel.network_delete_btn.clicked.connect(self.saved_wifi_option_selected) self.network_list_worker.build() self.request_network_scan.emit() @@ -356,30 +352,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 +397,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 +422,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 +452,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 +468,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 +542,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 +557,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 +598,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 +608,54 @@ 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", + "a", + "show", + "wlan0", + " |", + "grep", + " 'inet '", + "|", + "awk", + " '{{print $2}}'", + "|", + "cut", + "-d/", + "-f1", + ] 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(), + ) + raise + except subprocess.TimeoutExpired as e: + logging.error("Caught exception, failed to run command %s", e) + raise def close(self) -> bool: + """Close class, close network module""" self.sdbus_network.close() return super().close() @@ -656,14 +681,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 +737,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 +764,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,6 +797,7 @@ 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 @@ -778,6 +811,7 @@ def update_network( @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() diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py index d899c5e1..98301283 100644 --- a/BlocksScreen/lib/panels/printTab.py +++ b/BlocksScreen/lib/panels/printTab.py @@ -42,15 +42,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( @@ -79,7 +79,6 @@ 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) @@ -95,7 +94,6 @@ def __init__( self.dialogPage = DialogPage(self) - self.confirmPage_widget = ConfirmWidget(self) self.addWidget(self.confirmPage_widget) self.confirmPage_widget.back_btn.clicked.connect(self.back_button) @@ -124,9 +122,7 @@ 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) @@ -146,12 +142,8 @@ 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.request_query_print_stats.connect( self.ws.api.object_query @@ -176,15 +168,9 @@ def __init__( 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 @@ -222,12 +208,12 @@ def __init__( 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, @@ -262,20 +248,14 @@ def __init__( self.run_gcode_signal.connect(self.ws.api.run_gcode) - self.confirmPage_widget.on_delete.connect( - self.delete_file - ) + self.confirmPage_widget.on_delete.connect(self.delete_file) - self.change_page( - self.indexOf(self.print_page) - ) # force set the initial page + self.change_page(self.indexOf(self.print_page)) # force set the initial page @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 +264,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 +274,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 +284,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 +293,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,28 +301,24 @@ 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 + def delete_file(self, direcotry: str, name: str): + """Handle Delete file button clicked""" + 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.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 - """ + """Widget painting""" if self.babystepPage.isVisible(): _button_name_str = f"nozzle_offset_{self._z_offset}" if hasattr(self, _button_name_str): @@ -372,7 +343,7 @@ 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() @@ -394,6 +365,7 @@ def back_button(self) -> None: self.request_back.emit() def setupMainPrintPage(self) -> None: + """Setup UI for print page""" self.setObjectName("printStackedWidget") self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) self.resize(710, 410) @@ -409,9 +381,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 +390,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 +420,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) @@ -481,9 +447,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 +461,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..4797b2ff 100644 --- a/BlocksScreen/lib/panels/utilitiesTab.py +++ b/BlocksScreen/lib/panels/utilitiesTab.py @@ -41,6 +41,8 @@ def get_gcode(self, name: str) -> str: class Process(Enum): + """Printer Process""" + FAN = auto() AXIS = auto() BED_HEATER = auto() @@ -119,7 +121,7 @@ def __init__( self.update_page = UpdatePage(self) self.addWidget(self.update_page) - + self.panel.utilities_input_shaper_btn.hide() # --- Back Buttons --- for button in ( @@ -234,6 +236,7 @@ def __init__( @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 +249,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 +273,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,6 +281,7 @@ 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": @@ -290,12 +296,14 @@ def _connect_numpad_request(self, button: QtWidgets.QWidget, name: str, title: s ) def handle_numpad_change(self, name: str, new_value: typing.Union[int, float]): + """Handle numpad change""" 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 +333,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 +365,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 +399,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 +422,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 +477,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,30 +495,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: + """Perform Input Shaper Measure resonances test""" self.axis_in = axis path_map = { "x": "/tmp/resonances_x_axis_data.csv", @@ -530,7 +535,7 @@ def run_resonance_test(self, axis: str) -> None: self.x_inputshaper[panel_attr] = entry self.change_page(self.indexOf(self.panel.is_page)) - def _parse_shaper_csv(self, file_path: str) -> list: + def _parse_shaper_csv(self, file_path: str) -> list: results = [] try: with open(file_path, newline="") as csvfile: @@ -551,11 +556,12 @@ def _parse_shaper_csv(self, file_path: str) -> list: ) except FileNotFoundError: ... - except csv.Error as e: + except csv.Error: ... return results def apply_input_shaper_selection(self) -> None: + """Apply input shaper results""" if not (checked_button := self.panel.is_btn_group.checkedButton()): return selected_name = checked_button.objectName() @@ -575,6 +581,7 @@ def apply_input_shaper_selection(self) -> None: 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,10 +612,11 @@ 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): + """Show placeholder page""" self.loadPage.label.setText(label) self.loadPage.show() QtCore.QTimer.singleShot(time_ms, lambda: self.change_page(page_to_go_to)) @@ -618,6 +626,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 +634,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..b0fdfdca 100644 --- a/BlocksScreen/lib/panels/widgets/babystepPage.py +++ b/BlocksScreen/lib/panels/widgets/babystepPage.py @@ -34,11 +34,13 @@ def __init__(self, parent) -> None: 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.savebutton.clicked.connect(self.savevalue) + self.savebutton.clicked.connect(self.save_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 ) @@ -46,7 +48,9 @@ def on_move_nozzle_close(self) -> None: @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 ) @@ -69,30 +73,25 @@ def handle_z_offset_change(self) -> None: return self._z_offset = float(_sender.text()[:-3]) - def savevalue(self): + def save_value(self): + """Save new z offset value""" 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 + self.bbp_z_offset_title_label.setText(self.bbp_z_offset_current_value.text()) 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" - ) + 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_title_label.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( @@ -148,9 +147,7 @@ def setupUI(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.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/save.svg")) self.savebutton.setVisible(False) font = QtGui.QFont() font.setPointSize(15) @@ -178,16 +175,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) @@ -233,14 +227,11 @@ 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, ) # Line separator for 0.1mm - set size policy to expanding horizontally @@ -262,14 +253,11 @@ 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, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) # 0.05mm button @@ -289,14 +277,11 @@ def setupUI(self): 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_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 @@ -316,22 +301,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 +319,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 +335,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) @@ -385,9 +359,7 @@ def setupUI(self): # === 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 +379,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,9 +392,7 @@ 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)) @@ -442,9 +409,7 @@ 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)) diff --git a/BlocksScreen/lib/panels/widgets/confirmPage.py b/BlocksScreen/lib/panels/widgets/confirmPage.py index 525b0bbe..95a12579 100644 --- a/BlocksScreen/lib/panels/widgets/confirmPage.py +++ b/BlocksScreen/lib/panels/widgets/confirmPage.py @@ -43,6 +43,7 @@ def __init__(self, parent) -> None: @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 +102,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 +147,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: + """Setup widget ui""" sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding, diff --git a/BlocksScreen/lib/panels/widgets/connectionPage.py b/BlocksScreen/lib/panels/widgets/connectionPage.py index 7760cca5..33bd0576 100644 --- a/BlocksScreen/lib/panels/widgets/connectionPage.py +++ b/BlocksScreen/lib/panels/widgets/connectionPage.py @@ -3,20 +3,16 @@ 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") def __init__(self, parent: QtWidgets.QWidget, ws: MoonWebSocket, /): super().__init__(parent) @@ -31,7 +27,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()) @@ -51,6 +47,7 @@ def __init__(self, parent: QtWidgets.QWidget, ws: MoonWebSocket, /): 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 +55,9 @@ 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 - @QtCore.pyqtSlot(str, name="on_klippy_state") def on_klippy_state(self, state: str): + """Handle klippy state changes""" if state == "error": self.panel.connectionTextBox.setText("Klipper Connection Error") if not self.isVisible(): @@ -87,22 +81,24 @@ def on_klippy_state(self, state: str): @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" - ) + """Handle websocket connected state""" + self.panel.connectionTextBox.setText("Moonraker Connected\n Klippy not ready") self.hide() @QtCore.pyqtSlot(name="on_websocket_connection_lost") def on_websocket_connection_lost(self): + """Handle websocket connection lost state""" if not self.isVisible(): self.show() 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 @@ -142,7 +138,7 @@ def text_update(self, text: int | str | None = None): return False - def add_dot(self): + def _add_dot(self): if self.state == "shutdown" and self.message is not None: self.dot_timer.stop() return False @@ -156,13 +152,13 @@ def add_dot(self): @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(): if not self.isVisible(): self.panel.connectionTextBox.setText("Klippy Disconnected") diff --git a/BlocksScreen/lib/panels/widgets/dialogPage.py b/BlocksScreen/lib/panels/widgets/dialogPage.py index 7ed3d42c..65f7c727 100644 --- a/BlocksScreen/lib/panels/widgets/dialogPage.py +++ b/BlocksScreen/lib/panels/widgets/dialogPage.py @@ -2,31 +2,28 @@ class DialogPage(QtWidgets.QDialog): - button_clicked = QtCore.pyqtSignal( - str - ) # Signal to emit which button was clicked + 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 + QtCore.Qt.WindowType.Popup | QtCore.Qt.WindowType.FramelessWindowHint ) self.setAttribute( QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True ) # Make background transparent - - self.setupUI() + self._setupUI() self.repaint() def set_message(self, message: str) -> None: + """Set dialog text message""" self.label.setText(message) - def geometry_calc(self) -> None: + def _geometry_calc(self) -> None: + """Calculate dialog widget position relative to the window""" app_instance = QtWidgets.QApplication.instance() main_window = app_instance.activeWindow() if app_instance else None if main_window is None and app_instance: @@ -42,14 +39,13 @@ def geometry_calc(self) -> None: 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 - ) + 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() + """Re-implemented method, paint widget""" + self._geometry_calc() painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) @@ -74,10 +70,10 @@ def paintEvent(self, event: QtGui.QPaintEvent) -> None: painter.end() def sizeHint(self) -> QtCore.QSize: + """Re-implemented method, widget size hint""" 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) @@ -85,10 +81,8 @@ def sizeHint(self) -> QtCore.QSize: self.setMinimumSize(popup_width, popup_height) return super().sizeHint() - def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: - return - def resizeEvent(self, event: QtGui.QResizeEvent) -> None: + """Re-implemented method, handle resize event""" super().resizeEvent(event) label_width = self.testwidth @@ -112,10 +106,11 @@ def resizeEvent(self, event: QtGui.QResizeEvent) -> None: ) def show(self) -> None: - self.geometry_calc() + """Re-implemented method, show widget""" + self._geometry_calc() return super().show() - def setupUI(self) -> None: + def _setupUI(self) -> None: self.label = QtWidgets.QLabel("Test", self) font = QtGui.QFont() font.setPointSize(25) @@ -166,17 +161,12 @@ def setupUI(self) -> None: ) # 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") - ) + 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 + """Handle dialog buttons clicked""" + 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": 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..8de160c0 100644 --- a/BlocksScreen/lib/panels/widgets/filesPage.py +++ b/BlocksScreen/lib/panels/widgets/filesPage.py @@ -27,8 +27,8 @@ 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 = {} @@ -43,9 +43,7 @@ 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) @@ -54,31 +52,36 @@ def __init__(self, parent) -> None: @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: ... + def on_delete_file(self, filename: str) -> None: + """Handle file deleted""" + ... @QtCore.pyqtSlot(dict, name="on-fileinfo") def on_fileinfo(self, filedata: dict) -> None: + """Handle receive file information/metadata""" if not filedata or not self.isVisible(): return filename = filedata.get("filename", "") @@ -86,11 +89,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,9 +109,7 @@ 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()) - ] + list_items = [self.listWidget.item(i) for i in range(self.listWidget.count())] if not list_items: return for list_item in list_items: @@ -131,17 +128,13 @@ def _fileItemClicked(self, item: QtWidgets.QListWidgetItem) -> None: widget = self.listWidget.itemWidget(item) for file in self.file_list: path = ( - file.get("path") - if "path" in file.keys() - else file.get("filename") + 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) + path if not self.curr_dir else str(self.curr_dir + "/" + path) ) self.file_selected.emit( str(file_path.removeprefix("/")), @@ -151,9 +144,7 @@ def _fileItemClicked(self, item: QtWidgets.QListWidgetItem) -> None: ) @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, str, name="dir-item-clicked") - def _dirItemClicked( - self, item: QtWidgets.QListWidgetItem, directory: str - ) -> None: + def _dirItemClicked(self, item: QtWidgets.QListWidgetItem, directory: str) -> None: self.curr_dir = self.curr_dir + directory self.request_dir_info[str].emit(self.curr_dir) @@ -172,9 +163,7 @@ 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() @@ -188,9 +177,7 @@ def _add_directory_list_item(self, dir_data: dict) -> None: return button = ListCustomButton() button.setText(str(dir_data.get("dirname"))) - button.setSecondPixmap( - QtGui.QPixmap(":/ui/media/btn_icons/folderIcon.svg") - ) + button.setSecondPixmap(QtGui.QPixmap(":/ui/media/btn_icons/folderIcon.svg")) button.setMinimumSize(600, 80) button.setMaximumSize(700, 80) button.setLeftFontSize(17) @@ -199,16 +186,12 @@ def _add_directory_list_item(self, dir_data: dict) -> None: 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) - ) + button.clicked.connect(lambda: self._dirItemClicked(list_item, "/" + dir_name)) 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.setSecondPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back_folder.svg")) button.setMinimumSize(600, 80) button.setMaximumSize(700, 80) button.setLeftFontSize(17) @@ -241,9 +224,7 @@ def _add_file_list_item(self, file_data_item) -> None: return button = ListCustomButton() button.setText(name[:-6]) - button.setPixmap( - QtGui.QPixmap(":/arrow_icons/media/btn_icons/right_arrow.svg") - ) + button.setPixmap(QtGui.QPixmap(":/arrow_icons/media/btn_icons/right_arrow.svg")) button.setMinimumSize(600, 80) button.setMaximumSize(700, 80) button.setLeftFontSize(17) @@ -274,8 +255,7 @@ def _add_placeholder(self) -> None: placeholder_label.setFont(font) placeholder_label.setStyleSheet("color: gray;") placeholder_label.setAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter ) placeholder_label.setMinimumSize( QtCore.QSize(self.listWidget.width(), self.listWidget.height()) @@ -295,15 +275,9 @@ def _handle_scrollbar(self, 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() - ) + 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 +295,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() diff --git a/BlocksScreen/lib/panels/widgets/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py index e632dc2f..bb9754ea 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -12,12 +12,16 @@ class ClickableGraphicsView(QtWidgets.QGraphicsView): + """Re-implementation of QGraphicsView that adds clicked signal""" + clicked = QtCore.pyqtSignal() def mousePressEvent(self, event: QtGui.QMouseEvent) -> None: + """Filter mouse press events""" if event.button() == QtCore.Qt.MouseButton.LeftButton: self.clicked.emit() + return True # Issue event handled super(ClickableGraphicsView, self).mousePressEvent(event) @@ -59,7 +63,7 @@ def __init__(self, parent) -> None: super().__init__(parent) self.canceldialog = dialogPage.DialogPage(self) - self.setupUI() + self._setupUI() 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) @@ -77,6 +81,7 @@ def __init__(self, parent) -> None: self.CBVBigThumbnail.installEventFilter(self) def eventFilter(self, source, event): + """Re-implemented method, filter events""" if ( source == self.CBVSmallThumbnail and event.type() == QtCore.QEvent.Type.MouseButtonPress @@ -95,6 +100,7 @@ def eventFilter(self, source, event): @QtCore.pyqtSlot(name="show-thumbnail") def showthumbnail(self): + """Show print job fullscreen thumbnail""" self.contentWidget.hide() self.progressWidget.hide() self.headerWidget.hide() @@ -104,6 +110,7 @@ def showthumbnail(self): @QtCore.pyqtSlot(name="hide-thumbnail") def hidethumbnail(self): + """Hide print job fullscreen thumbnail""" self.contentWidget.show() self.progressWidget.show() self.headerWidget.show() @@ -113,7 +120,7 @@ def hidethumbnail(self): @QtCore.pyqtSlot(name="handle-cancel") def handleCancel(self) -> None: - """Handle the cancel print job dialog""" + """Handle cancel print job dialog""" self.canceldialog.set_message( "Are you sure you \n want to cancel \n this print job?" ) @@ -159,6 +166,7 @@ def on_print_start(self, file: str, thumbnails: list) -> None: @QtCore.pyqtSlot(dict, name="on_fileinfo") def on_fileinfo(self, fileinfo: dict) -> None: + """Handle received file information/metadata""" self.total_layers = str(fileinfo.get("layer_count", "?")) self.layer_display_button.setText("?") if ( @@ -175,6 +183,7 @@ def on_fileinfo(self, fileinfo: dict) -> None: @QtCore.pyqtSlot(name="pause_resume_print") def pause_resume_print(self) -> None: + """Handle pause/resume print job""" if not getattr(self, "_pause_locked", False): self._pause_locked = True self.pause_printing_btn.setEnabled(False) @@ -233,7 +242,6 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None: 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) @@ -278,13 +286,7 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None: @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 - # """ - + """Handle gcode move""" if isinstance(value, list): if "gcode_position" in field: # Without offsets if self._internal_print_status == "printing": @@ -307,7 +309,7 @@ def on_gcode_move_update(self, field: str, value: list) -> None: @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 @@ -321,6 +323,7 @@ def virtual_sdcard_update(self, field: str, value: float | bool) -> None: self.printing_progress_bar.setValue(self.print_progress) def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" _scene = QtWidgets.QGraphicsScene() if not self.smalthumbnail.isNull(): _graphics_rect = self.CBVSmallThumbnail.rect().toRectF() @@ -380,7 +383,8 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: _scene.addItem(_item_scaled) self.CBVBigThumbnail.setScene(_scene) - def setupUI(self) -> None: + def _setupUI(self) -> None: + """Setup widget ui""" sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding, 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 index d4ddcbcf..f5fd7963 100644 --- a/BlocksScreen/lib/panels/widgets/loadPage.py +++ b/BlocksScreen/lib/panels/widgets/loadPage.py @@ -5,9 +5,8 @@ class LoadScreen(QtWidgets.QDialog): class AnimationGIF(enum.Enum): - # [x]: WATHERE ARE NO GIFS IN LOADSCREEN PLEASE REMEMBER THIS IM WARNING + """Animation type""" - # TODO : add more types into LoadScreen DEFAULT = None PLACEHOLDER = "" @@ -31,10 +30,9 @@ def __init__( ) self.setWindowFlags( - QtCore.Qt.WindowType.Popup - | QtCore.Qt.WindowType.FramelessWindowHint + QtCore.Qt.WindowType.Popup | QtCore.Qt.WindowType.FramelessWindowHint ) - self.setupUI() + self._setupUI() config: BlocksScreenConfig = get_configparser() try: if config: @@ -61,10 +59,11 @@ def __init__( self.repaint() def set_status_message(self, message: str) -> None: + """Set widget status message""" self.label.setText(message) - def geometry_calc(self) -> None: - # REFACTOR: find another way to get mainwindow geometry , this version consumes too much ram + 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: @@ -79,6 +78,7 @@ def geometry_calc(self) -> None: self.setGeometry(x, y, width, height) def close(self) -> bool: + """Re-implemented method, close widget""" self.timer.stop() self.label.setText("Loading...") self._angle = 0 @@ -102,10 +102,10 @@ def _update_animation(self) -> None: self.update() def sizeHint(self) -> QtCore.QSize: + """Re-implemented method, size hint""" 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) @@ -113,10 +113,8 @@ def sizeHint(self) -> QtCore.QSize: self.setMinimumSize(popup_width, popup_height) return super().sizeHint() - def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: - return - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" painter = QtGui.QPainter(self) # loading circle draw if self.anim_type == LoadScreen.AnimationGIF.DEFAULT: @@ -124,12 +122,8 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.setRenderHint( QtGui.QPainter.RenderHint.LosslessImageRendering, True ) - painter.setRenderHint( - QtGui.QPainter.RenderHint.SmoothPixmapTransform, True - ) - painter.setRenderHint( - QtGui.QPainter.RenderHint.TextAntialiasing, 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")) @@ -143,20 +137,17 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.translate(center_x, center_y) painter.rotate(self._angle) - arc_rect = QtCore.QRectF( - -arc_size / 2, -arc_size / 2, arc_size, arc_size - ) + 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: + """Re-implemented method, handle widget resize event""" 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 @@ -167,14 +158,15 @@ def resizeEvent(self, event: QtGui.QResizeEvent) -> None: self.label.setGeometry(label_x, label_y, label_width, label_height) def show(self) -> None: - self.geometry_calc() + """Re-implemented method, show widget""" + 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: + def _setupUI(self) -> None: self.gifshow = QtWidgets.QLabel("", self) self.gifshow.setObjectName("gifshow") self.gifshow.setStyleSheet("background: transparent;") diff --git a/BlocksScreen/lib/panels/widgets/loadWidget.py b/BlocksScreen/lib/panels/widgets/loadWidget.py index 4001d900..9a5f3d68 100644 --- a/BlocksScreen/lib/panels/widgets/loadWidget.py +++ b/BlocksScreen/lib/panels/widgets/loadWidget.py @@ -1,8 +1,7 @@ - from PyQt6 import QtCore, QtGui, QtWidgets -class LoadingOverlayWidget(QtWidgets.QLabel): +class LoadingOverlayWidget(QtWidgets.QLabel): def __init__( self, parent: QtWidgets.QWidget, @@ -16,7 +15,7 @@ def __init__( self.max_length = 150.0 self.length_step = 2.5 - self.setupUI() + self._setupUI() self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self._update_animation) @@ -25,10 +24,11 @@ def __init__( self.repaint() 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 @@ -48,19 +48,13 @@ def _update_animation(self) -> None: self._is_span_growing = True self.update() - def paintEvent(self, a0: QtGui.QPaintEvent) -> 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 - ) + 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")) @@ -74,35 +68,31 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.translate(center_x, center_y) painter.rotate(self._angle) - arc_rect = QtCore.QRectF( - -arc_size / 2, -arc_size / 2, arc_size, arc_size - ) + 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: + """Re-implemented method, handle resize event""" 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: + """Re-implemented method, show widget""" self.timer.start() self.repaint() return super().show() - def setupUI(self) -> None: + def _setupUI(self) -> None: self.gifshow = QtWidgets.QLabel("", self) self.gifshow.setObjectName("gifshow") self.gifshow.setStyleSheet("background: transparent;") @@ -113,4 +103,4 @@ def setupUI(self) -> None: 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..711dcd02 100644 --- a/BlocksScreen/lib/panels/widgets/optionCardWidget.py +++ b/BlocksScreen/lib/panels/widgets/optionCardWidget.py @@ -25,30 +25,33 @@ 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._setupUi(self) + self.continue_button.clicked.connect(lambda: self.continue_clicked.emit(self)) 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: + """Set widget icon""" self.option_icon.setPixmap(pixmap) 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,32 +59,31 @@ 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.update() return super().mousePressEvent(a0) def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" # Rounded background edges self.background_path = QtGui.QPainterPath() self.background_path.addRoundedRect( @@ -136,7 +138,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: 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,9 +147,7 @@ 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)) @@ -169,8 +169,7 @@ 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)) @@ -180,8 +179,7 @@ def setupUi(self, option_card): ) self.continue_button = IconButton(parent=option_card) self.option_text.setAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter ) self.option_text.setWordWrap(True) _button_font = QtGui.QFont() @@ -211,10 +209,10 @@ def setupUi(self, option_card): self.continue_button.setObjectName("continue_button") self.verticalLayout.addWidget(self.continue_button) - self.retranslateUi(option_card) + 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..f878f612 100644 --- a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py +++ b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py @@ -9,31 +9,31 @@ 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.messages: Deque = deque() 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,31 +41,26 @@ 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.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) def on_slide_in_finished(self): + """Handle slide in animation finished""" self.timeout_timer.start() def on_slide_out_finished(self): + """Handle slide out animation finished""" self.close() - self.add_popup() - + self._add_popup() + def _calculate_target_geometry(self) -> QtCore.QRect: app_instance = QtWidgets.QApplication.instance() main_window = app_instance.activeWindow() if app_instance else None @@ -74,28 +69,31 @@ 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 = min(self.text_label.rect().height(), self.icon_label.rect().height()) + + 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""" self.timeout_timer.stop() self.slide_out_animation.start() def set_timeout(self, value: int) -> None: + """Set popup timeout""" if not isinstance(value, int): raise ValueError("Expected type int ") self.popup_timeout = value @@ -104,25 +102,36 @@ def new_message( self, message_type: MessageType = MessageType.INFO, message: str = "", - persistent: bool = False, timeout: int = 0, ): + """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. + + Returns: + _type_: _description_ + """ self.messages.append( {"message": message, "type": message_type, "timeout": timeout} ) - return self.add_popup() + return self._add_popup() - def add_popup(self) -> None: + def _add_popup(self) -> None: + """Add popup to queue""" 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) - match self.message_type: case Popup.MessageType.INFO: self.icon_label.setPixmap(self.info_icon) @@ -130,36 +139,31 @@ def add_popup(self) -> None: self.icon_label.setPixmap(self.warning_icon) case Popup.MessageType.ERROR: self.icon_label.setPixmap(self.error_icon) - - self.timeout_timer.setInterval( - self.popup_timeout - ) - + self.timeout_timer.setInterval(self.popup_timeout) end_rect = self._calculate_target_geometry() - - start_rect = end_rect.translated(0, -end_rect.height()) - 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() def showEvent(self, a0: QtGui.QShowEvent) -> None: + """Re-implementation, widget show""" self.slide_in_animation.start() 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,18 +172,17 @@ 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: + + def _setupUI(self) -> None: self.vertical_layout = QtWidgets.QVBoxLayout(self) self.horizontal_layout = QtWidgets.QHBoxLayout() self.horizontal_layout.setContentsMargins(5, 5, 5, 5) @@ -192,19 +195,23 @@ def setupUI(self) -> None: self.text_label = QtWidgets.QLabel(self) self.text_label.setWordWrap(True) - self.text_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignVCenter | QtCore.Qt.AlignmentFlag.AlignHCenter) - + 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.vertical_layout.addLayout(self.horizontal_layout) \ No newline at end of file + self.vertical_layout.addLayout(self.horizontal_layout) 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..5eeb3a3d 100644 --- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py +++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py @@ -7,7 +7,7 @@ from lib.utils.group_button import GroupButton from lib.utils.blocks_button import BlocksCustomButton -from lib.panels.widgets.loadPage import LoadScreen +from lib.panels.widgets.loadPage import LoadScreen class ProbeHelper(QtWidgets.QWidget): @@ -18,8 +18,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 +46,47 @@ 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.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 +114,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 +153,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 +172,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 +200,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 +231,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 +279,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 +302,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,7 +388,7 @@ def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None: """ if not sender: return - + for i in self.card_options.values(): i.setDisabled(True) @@ -420,9 +397,7 @@ def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None: 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 +437,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 +452,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 +468,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 +480,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 +495,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 +504,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 +512,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 +541,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 +561,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 +627,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 +637,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 +665,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 +688,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,9 +709,7 @@ def setupUi(self) -> None: self.bbp_offset_steps_buttons.setObjectName("bbp_offset_steps_buttons") # 0.1mm button - self.move_option_1 = GroupButton( - parent=self.bbp_offset_steps_buttons_group_box - ) + self.move_option_1 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) self.move_option_1.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_1.setMaximumSize(QtCore.QSize(100, 60)) self.move_option_1.setText("0.01 mm") @@ -763,22 +722,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( - parent=self.bbp_offset_steps_buttons_group_box - ) + self.move_option_2 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) self.move_option_2.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_2.setMaximumSize( QtCore.QSize(100, 60) @@ -792,20 +744,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( - parent=self.bbp_offset_steps_buttons_group_box - ) + self.move_option_3 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) self.move_option_3.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_3.setMaximumSize( QtCore.QSize(100, 60) @@ -819,20 +766,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( - parent=self.bbp_offset_steps_buttons_group_box - ) + self.move_option_4 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) self.move_option_4.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_4.setMaximumSize( QtCore.QSize(100, 60) @@ -846,20 +788,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( - parent=self.bbp_offset_steps_buttons_group_box - ) + # 0.01mm button + self.move_option_5 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) self.move_option_5.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_5.setMaximumSize( QtCore.QSize(100, 60) @@ -873,22 +810,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 +828,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 +837,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 +863,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 +873,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 +938,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..5223ac83 100644 --- a/BlocksScreen/lib/panels/widgets/sensorWidget.py +++ b/BlocksScreen/lib/panels/widgets/sensorWidget.py @@ -7,18 +7,26 @@ 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 @@ -40,14 +48,10 @@ def __init__(self, parent, sensor_name: str): self.filament_state: SensorWidget.FilamentState = ( SensorWidget.FilamentState.MISSING ) - self.sensor_state: SensorWidget.SensorState = ( - SensorWidget.SensorState.OFF - ) + self.sensor_state: SensorWidget.SensorState = SensorWidget.SensorState.OFF self._icon_label = None self._text_label = None - self._text: str = ( - str(self.sensor_type.name) + " Sensor: " + str(self.name) - ) + self._text: str = str(self.sensor_type.name) + " Sensor: " + str(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 +59,11 @@ 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() @property def type(self) -> SensorType: + """Sensor type""" return self._sensor_type @type.setter @@ -67,6 +72,7 @@ def type(self, type: SensorType): @property def flags(self) -> SensorFlags: + """Current filament sensor flags""" return self._flags @flags.setter @@ -75,6 +81,7 @@ def flags(self, flags: SensorFlags) -> None: @property def text(self) -> str: + """Filament sensor text""" return self._text @text.setter @@ -85,13 +92,12 @@ def text(self, new_text) -> None: @QtCore.pyqtSlot(bool, name="change_fil_sensor_state") def change_fil_sensor_state(self, state: FilamentState): + """Change filament sensor state""" if isinstance(state, SensorWidget.FilamentState): self.filament_state = state - def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: - return super().resizeEvent(a0) - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" # if ( # self._scaled_select_on_pixmap is not None # and self._scaled_select_off_pixmap is not None @@ -103,9 +109,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: # ) 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 ) @@ -142,6 +146,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: @property def toggle_sensor_gcode_command(self) -> str: + """Toggle filament sensor""" self.sensor_state = ( SensorWidget.SensorState.ON if self.sensor_state == SensorWidget.SensorState.OFF @@ -151,7 +156,7 @@ def toggle_sensor_gcode_command(self) -> str: f"SET_FILAMENT_SENSOR SENSOR={self.text} ENABLE={not self.sensor_state.value}" ) - def setupUI(self): + def _setupUI(self): _policy = QtWidgets.QSizePolicy.Policy.MinimumExpanding size_policy = QtWidgets.QSizePolicy(_policy, _policy) size_policy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) @@ -160,9 +165,7 @@ def setupUI(self): self.sensor_horizontal_layout.setGeometry(QtCore.QRect(0, 0, 640, 60)) 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) @@ -173,18 +176,14 @@ def setupUI(self): ) 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/panels/widgets/sensorsPanel.py b/BlocksScreen/lib/panels/widgets/sensorsPanel.py index a40247b8..51a5b2c1 100644 --- a/BlocksScreen/lib/panels/widgets/sensorsPanel.py +++ b/BlocksScreen/lib/panels/widgets/sensorsPanel.py @@ -9,10 +9,8 @@ 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" @@ -21,10 +19,8 @@ class SensorsWindow(QtWidgets.QWidget): def __init__(self, parent): super(SensorsWindow, self).__init__(parent) - self.setupUi() - self.setAttribute( - QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True - ) + 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) @@ -33,33 +29,36 @@ def __init__(self, parent): @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 - 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"), + 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 ] else: - self.fs_sensors_list.setRowHidden(self.fs_sensors_list.row(self.item), False) - - + self.fs_sensors_list.setRowHidden( + self.fs_sensors_list.row(self.item), False + ) @QtCore.pyqtSlot(str, str, bool, name="handle_fil_state_change") def handle_fil_state_change( self, sensor_name: str, parameter: str, value: bool ) -> None: + """Handle filament state chage""" if sensor_name in self.sensor_list: - state = SensorWidget.FilamentState(value) _split = sensor_name.split(" ") _item = self.fs_sensors_list.findChild( SensorWidget, @@ -70,26 +69,21 @@ def handle_fil_state_change( if isinstance(_item, SensorWidget) and hasattr( _item, "change_fil_sensor_state" ): - _item.change_fil_sensor_state( - SensorWidget.FilamentState.PRESENT - ) + _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.change_fil_sensor_state(SensorWidget.FilamentState.MISSING) _item.repaint() elif parameter == "enabled": if _item and isinstance(_item, SensorWidget): - self.run_gcode_signal.emit( - _item.toggle_sensor_gcode_command - ) + 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: + """Handle filament sensor clicked""" _item = self.fs_sensors_list.itemWidget(sensor) # FIXME: This is just not working _item.toggle_button.state = ~_item.toggle_button.state @@ -116,9 +110,7 @@ def create_sensor_widget(self, name: str) -> SensorWidget: return _item_widget - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: ... - - def setupUi(self): + def _setupUi(self): self.setObjectName("filament_sensors_page") sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, @@ -147,9 +139,7 @@ def setupUi(self): font = QtGui.QFont() font.setPointSize(22) palette = QtGui.QPalette() - palette.setColor( - palette.ColorRole.WindowText, QtGui.QColorConstants.White - ) + palette.setColor(palette.ColorRole.WindowText, QtGui.QColorConstants.White) self.fs_page_title.setPalette(palette) self.fs_page_title.setFont(font) self.fs_page_title.setObjectName("fs_page_title") @@ -162,9 +152,7 @@ def setupUi(self): 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.fs_back_button.setObjectName("fs_back_button") self.fs_header_layout.addWidget( self.fs_back_button, @@ -184,24 +172,17 @@ def setupUi(self): 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 - ) + self.fs_sensors_list.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) 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 - ) + self.fs_sensors_list.setViewMode(self.fs_sensors_list.ViewMode.ListMode) self.fs_sensors_list.setItemAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter ) 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 - ) + palette.setColor(palette.ColorRole.Base, QtGui.QColorConstants.Transparent) self.fs_sensors_list.setPalette(palette) self.fs_sensors_list.setDropIndicatorShown(False) self.fs_sensors_list.setAcceptDrops(False) @@ -211,34 +192,31 @@ def setupUi(self): self.content_vertical_layout.addWidget( self.fs_sensors_list, 1, - QtCore.Qt.AlignmentFlag.AlignHCenter - | QtCore.Qt.AlignmentFlag.AlignVCenter, + QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter, ) 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.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.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.fs_sensors_list.setItemWidget(self.item, self.label) self.content_vertical_layout.addSpacing(5) self.setLayout(self.content_vertical_layout) - self.retranslateUi() + self._retranslateUi() - def retranslateUi(self): + def _retranslateUi(self): _translate = QtCore.QCoreApplication.translate self.setWindowTitle(_translate("filament_sensors_page", "Form")) self.fs_page_title.setText( diff --git a/BlocksScreen/lib/panels/widgets/slider_selector_page.py b/BlocksScreen/lib/panels/widgets/slider_selector_page.py index 02f9006a..793b7044 100644 --- a/BlocksScreen/lib/panels/widgets/slider_selector_page.py +++ b/BlocksScreen/lib/panels/widgets/slider_selector_page.py @@ -27,26 +27,20 @@ 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.increase_button.pressed.connect( - lambda: ( - self.slider.setSliderPosition(self.slider.sliderPosition() + 5) - ) + lambda: (self.slider.setSliderPosition(self.slider.sliderPosition() + 5)) ) self.decrease_button.pressed.connect( - lambda: ( - self.slider.setSliderPosition(self.slider.sliderPosition() - 5) - ) + lambda: (self.slider.setSliderPosition(self.slider.sliderPosition() - 5)) ) self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) @@ -64,9 +58,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 +74,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)) @@ -131,18 +115,13 @@ def setupUI(self) -> None: 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.setMaximumSize(QtCore.QSize(self.width() - 60, 60)) 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 +139,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 +165,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..cb9a00c8 100644 --- a/BlocksScreen/lib/panels/widgets/tunePage.py +++ b/BlocksScreen/lib/panels/widgets/tunePage.py @@ -12,8 +12,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 +40,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 +54,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 +63,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 +74,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}") @@ -156,16 +150,12 @@ def on_fan_object_update( ) 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 +177,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 +215,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 +247,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 +259,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 +316,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 +337,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 +379,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 +392,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 +407,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 +426,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 +436,9 @@ 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() + self._retranslateUI() - def retranslateUI(self): + 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..534bbabe 100644 --- a/BlocksScreen/lib/panels/widgets/updatePage.py +++ b/BlocksScreen/lib/panels/widgets/updatePage.py @@ -239,7 +239,7 @@ 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 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/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/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..141ee1ec 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) @@ -33,6 +35,7 @@ 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() @@ -40,6 +43,7 @@ def setShowNotification(self, show: bool) -> None: @property def name(self): + """Button name""" return self._name @name.setter @@ -48,18 +52,22 @@ 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() def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: + """Handle mouse press events""" if not self.isEnabled(): e.ignore() return @@ -75,9 +83,7 @@ def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: return super().mousePressEvent(e) def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): - opt = QtWidgets.QStyleOptionButton() - # self.initStyleOption(opt) - + """Re-implemented method, paint widget""" painter = QtGui.QPainter(self) painter.setRenderHint(painter.RenderHint.Antialiasing, True) painter.setRenderHint(painter.RenderHint.SmoothPixmapTransform, True) @@ -89,8 +95,6 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): if _style is None or _rect is None: return - - margin = _style.pixelMetric(_style.PixelMetric.PM_ButtonMargin, opt, self) # Determine background and text colors based on state if not self.isEnabled(): bg_color_tuple = ButtonColors.DISABLED_BG.value @@ -160,8 +164,6 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): 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) @@ -190,24 +192,16 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): 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()) + # _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.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())) @@ -218,13 +212,10 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): _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.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 @@ -257,6 +248,7 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): painter.end() def setProperty(self, name: str, value: typing.Any): + """Set widget properties""" if name == "icon_pixmap": self.icon_pixmap = value elif name == "name": @@ -265,14 +257,8 @@ def setProperty(self, name: str, value: typing.Any): 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 event(self, e: QtCore.QEvent) -> bool: + """Re-implemented method, filter events""" if e.type() == QtCore.QEvent.Type.TouchBegin: self.handleTouchBegin(e) return False diff --git a/BlocksScreen/lib/utils/blocks_frame.py b/BlocksScreen/lib/utils/blocks_frame.py index 05810d8c..6783ad8a 100644 --- a/BlocksScreen/lib/utils/blocks_frame.py +++ b/BlocksScreen/lib/utils/blocks_frame.py @@ -9,13 +9,16 @@ def __init__(self, parent=None): self._radius = 20 def setRadius(self, radius: int): + """Set widget frame radius""" self._radius = radius self.update() def radius(self): + """Get widget frame radius""" return self._radius - def paintEvent(self, event): + def paintEvent(self, event): + """Re-implemented method, paint widget""" painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.Antialiasing) rect = QRectF(self.rect()) @@ -23,6 +26,4 @@ def paintEvent(self, event): 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.drawRoundedRect(rect.adjusted(1, 1, -1, -1), self._radius, self._radius) diff --git a/BlocksScreen/lib/utils/blocks_label.py b/BlocksScreen/lib/utils/blocks_label.py index 4df5a045..a2b05256 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 @@ -46,10 +46,12 @@ def __init__(self, parent: QtWidgets.QWidget = None, *args, **kwargs): 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 +60,18 @@ 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.update_text_metrics() @property def background_color(self) -> typing.Optional[QtGui.QColor]: + """Widget background color""" return self._background_color @background_color.setter @@ -75,6 +80,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 +89,7 @@ def border_color(self, color: QtGui.QColor) -> None: @property def rounded(self) -> bool: + """Widget rounded property""" return self._rounded @rounded.setter @@ -91,6 +98,7 @@ def rounded(self, on: bool) -> None: @property def marquee(self) -> bool: + """Widget enable marquee effect""" return self._marquee @marquee.setter @@ -100,6 +108,7 @@ def marquee(self, activate) -> None: @QtCore.pyqtProperty(int) def animation_speed(self) -> int: + """Widget animation speed property""" return self._animation_speed @animation_speed.setter @@ -108,6 +117,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,6 +127,7 @@ 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") self.glow_animation.setStartValue(start_color) @@ -129,6 +140,7 @@ def start_glow_animation(self) -> None: @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,6 +148,7 @@ 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() @@ -149,6 +162,7 @@ def update_text_metrics(self) -> None: self.update() def start_scroll(self) -> None: + """Start marquee text scroll effect""" if not self.delay_timer.isActive() and not self.timer.isActive(): self.scroll_pos = 0 self.loop_count = 0 @@ -164,6 +178,7 @@ def _start_marquee(self) -> None: self.timer.start(self.scroll_animation_speed) def stop_scroll(self) -> None: + """Stop marquee text scroll effect""" self.timer.stop() self.delay_timer.stop() @@ -184,6 +199,7 @@ def _scroll_text(self) -> None: self.repaint() def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" qp = QtWidgets.QStylePainter(self) opt = QtWidgets.QStyleOption() opt.initFrom(self) @@ -264,16 +280,8 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: int(self.scroll_pos + self.text_width + self.label_width / 2), 0, ) - # Draw the main text instance - draw_rect = QtCore.QRectF( - self.contentsRect().x() + self.scroll_pos, - self.contentsRect().y(), - self.text_width, - self.contentsRect().height(), - ) qp.drawText(QtCore.QRectF(second_text_rect), self._text, text_option) - - draw_rect2 = QtCore.QRectF( + draw_rect = QtCore.QRectF( self.contentsRect().x() + self.scroll_pos + self.text_width @@ -282,7 +290,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: self.text_width, self.contentsRect().height(), ) - qp.drawText(draw_rect2, self._text, text_option) + qp.drawText(draw_rect, self._text, text_option) else: text_rect = self.contentsRect().toRectF() qp.drawText(text_rect, self._text, text_option) @@ -290,6 +298,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: 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..98f7cc52 100644 --- a/BlocksScreen/lib/utils/blocks_progressbar.py +++ b/BlocksScreen/lib/utils/blocks_progressbar.py @@ -1,4 +1,4 @@ -from PyQt6 import QtWidgets ,QtGui ,QtCore +from PyQt6 import QtWidgets, QtGui, QtCore class CustomProgressBar(QtWidgets.QProgressBar): @@ -11,14 +11,17 @@ def __init__(self, parent=None): self.set_pen_width(20) def set_padding(self, value): + """Set widget padding""" self.padding = value self.update() def set_pen_width(self, value): + """Set widget text pen width""" self.pen_width = value self.update() def paintEvent(self, event): + """Re-implemented method, paint widget""" painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) @@ -30,9 +33,8 @@ def _draw_circular_bar(self, painter, width, height): y = (height - size) / 2 arc_rect = QtCore.QRectF(x, y, size, size) - - arc1_start = 236* 16 - arc1_span = -290 * 16 + arc1_start = 236 * 16 + arc1_span = -290 * 16 bg_pen = QtGui.QPen(QtGui.QColor(20, 20, 20)) bg_pen.setWidth(self.pen_width) bg_pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap) @@ -40,7 +42,7 @@ def _draw_circular_bar(self, painter, width, height): painter.drawArc(arc_rect, arc1_start, arc1_span) if self.progress_value is not None: - gradient = QtGui.QConicalGradient(arc_rect.center(), -90) + gradient = QtGui.QConicalGradient(arc_rect.center(), -90) gradient.setColorAt(0.0, self.bar_color) gradient.setColorAt(1.0, QtGui.QColor(100, 100, 100)) @@ -51,7 +53,7 @@ def _draw_circular_bar(self, painter, width, height): painter.setPen(progress_pen) # scale only over arc1’s span - progress_span = int(arc1_span * self.progress_value/100) + progress_span = int(arc1_span * self.progress_value / 100) painter.drawArc(arc_rect, arc1_start, progress_span) progress_text = f"{int(self.progress_value)}%" @@ -67,14 +69,14 @@ def _draw_circular_bar(self, painter, width, height): 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 + """Set value""" + value *= 100 if 0 <= value <= 101: self.progress_value = value self.update() @@ -82,6 +84,7 @@ def setValue(self, value): raise ValueError("Progress must be between 0.0 and 1.0.") def set_bar_color(self, red, green, blue): + """Set bar color""" if 0 <= red <= 255 and 0 <= green <= 255 and 0 <= blue <= 255: self.bar_color = QtGui.QColor(red, green, blue) self.update() diff --git a/BlocksScreen/lib/utils/blocks_slider.py b/BlocksScreen/lib/utils/blocks_slider.py index 4d1078c7..ee084a0a 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 @@ -17,11 +15,8 @@ def __init__(self, parent) -> None: self.setMinimum(0) self.setMaximum(100) - def setOrientation(self, a0: QtCore.Qt.Orientation) -> None: - return super().setOrientation(a0) - 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 +64,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 +154,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 +177,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/display_button.py b/BlocksScreen/lib/utils/display_button.py index a0e5a886..1f798dfe 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( @@ -192,9 +186,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 +197,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 +218,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 +231,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 @@ -258,6 +252,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 index b9af752b..79f4251e 100644 --- a/BlocksScreen/lib/utils/group_button.py +++ b/BlocksScreen/lib/utils/group_button.py @@ -27,6 +27,7 @@ def __init__( @property def name(self): + """Widget name""" return self._name @name.setter @@ -35,18 +36,22 @@ def name(self, new_name) -> None: self.setObjectName(new_name) def text(self) -> str | None: + """Widget text""" return self._text def setText(self, text: str) -> None: + """Set widget text""" self._text = text self.update() # Force button update return def setPixmap(self, pixmap: QtGui.QPixmap) -> None: + """Set widget pixmap""" self.icon_pixmap = pixmap self.repaint() def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): + """Re-implemented method, paint widget""" opt = QtWidgets.QStyleOptionButton() self.initStyleOption(opt) @@ -117,28 +122,15 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): painter.setPen(QtCore.Qt.PenStyle.NoPen) def setProperty(self, name: str, value: typing.Any): + """Re-implemented method, set widget properties""" 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: + """Re-implemented method, filter events""" if e.type() == QtCore.QEvent.Type.TouchBegin: self.handleTouchBegin(e) return False diff --git a/BlocksScreen/lib/utils/icon_button.py b/BlocksScreen/lib/utils/icon_button.py index 019471b4..a60a7f1d 100644 --- a/BlocksScreen/lib/utils/icon_button.py +++ b/BlocksScreen/lib/utils/icon_button.py @@ -16,21 +16,25 @@ def __init__(self, parent: QtWidgets.QWidget) -> None: @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 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) @@ -53,9 +57,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: # * 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_rect = QtCore.QRectF(0.0, 0.0, (self.width() - x), (self.height() - y)) _icon_scaled = self.icon_pixmap.scaled( _icon_rect.size().toSize(), @@ -120,6 +122,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: 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..24c9467f 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 = "" 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..8cf90f10 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) @@ -49,17 +47,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 +69,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 +84,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 +99,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 +109,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 +119,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 +128,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 +157,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 +174,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 +192,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 +206,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.setRenderHint(painter.RenderHint.SmoothPixmapTransform) painter.setBackgroundMode(QtCore.Qt.BGMode.TransparentMode) painter.setRenderHint(painter.RenderHint.LosslessImageRendering) - - + 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 From 8e48a21606a916a5f28302667edda5d945d26624 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Thu, 11 Dec 2025 10:36:01 +0000 Subject: [PATCH 08/43] ADD: added overriedCursor to blank cursor (#118) * ADD: added overriedCursor to blank cursor * Refactor: ran ruff formater --------- Co-authored-by: Roberto --- BlocksScreen/lib/panels/mainWindow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index b7921e55..18549a39 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -81,6 +81,8 @@ def __init__(self): 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) From e8523468113236bd0a533c6b884e740cee910a47 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Thu, 11 Dec 2025 12:42:11 +0000 Subject: [PATCH 09/43] ADD: color degrade when ON/OFF (#120) --- .../lib/utils/toggleAnimatedButton.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/BlocksScreen/lib/utils/toggleAnimatedButton.py b/BlocksScreen/lib/utils/toggleAnimatedButton.py index 8cf90f10..b9555876 100644 --- a/BlocksScreen/lib/utils/toggleAnimatedButton.py +++ b/BlocksScreen/lib/utils/toggleAnimatedButton.py @@ -36,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 @@ -207,6 +211,32 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: 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, From 868d0fd823c9f01fbf756a3c5e4ea94dd259cbe4 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Thu, 11 Dec 2025 15:59:43 +0000 Subject: [PATCH 10/43] Bugfix label overlap (#121) * bugfix: label overlapping when stoped * Upd: rollback some parts * ADD: added delay to marquee effect * bugfix: text when marquee wasnt needed and glow effect * Refactor: Ran ruff formatter * Remove unecessary lambda expression * Add docstring to paintEvent method --------- Co-authored-by: Roberto Co-authored-by: Hugo Costa --- BlocksScreen/lib/utils/blocks_label.py | 175 +++++++++---------------- 1 file changed, 63 insertions(+), 112 deletions(-) diff --git a/BlocksScreen/lib/utils/blocks_label.py b/BlocksScreen/lib/utils/blocks_label.py index a2b05256..7e64d805 100644 --- a/BlocksScreen/lib/utils/blocks_label.py +++ b/BlocksScreen/lib/utils/blocks_label.py @@ -15,15 +15,13 @@ 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( @@ -41,8 +39,8 @@ def __init__(self, parent: QtWidgets.QWidget = None, *args, **kwargs): 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.first_run = True def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: @@ -67,6 +65,7 @@ def setPixmap(self, a0: QtGui.QPixmap) -> None: def setText(self, text: str) -> None: """Set widget text""" self._text = text + self.scroll_pos = 0.0 self.update_text_metrics() @property @@ -102,7 +101,7 @@ def marquee(self) -> bool: return self._marquee @marquee.setter - def marquee(self, activate) -> None: + def marquee(self, activate: bool) -> None: self._marquee = activate self.update_text_metrics() @@ -130,10 +129,9 @@ 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() @@ -155,145 +153,98 @@ def update_text_metrics(self) -> None: 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: - """Start marquee text scroll effect""" - 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: """Re-implemented method, paint widget""" - qp = QtWidgets.QStylePainter(self) - opt = QtWidgets.QStyleOption() - opt.initFrom(self) - + 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_scaled = self.icon_pixmap.scaled( - _icon_rect.size().toSize(), + icon_rect = QtCore.QRectF(0, 0, self.height(), self.height()) + 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, - ) - 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(icon_rect.toRect(), scaled) if self.glow_animation.state() == self.glow_animation.State.Running: - qp.setCompositionMode(qp.CompositionMode.CompositionMode_SourceAtop) - subtracted.setFillRule(QtCore.Qt.FillRule.OddEvenFill) - qp.fillPath(subtracted, self.glow_color) + path = QtGui.QPainterPath() + path.addRoundedRect(QtCore.QRectF(rect), 10, 10) + qp.fillPath(path, 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, + qp.save() + qp.setClipRect(rect) + baseline_y = ( + rect.y() + + ( + rect.height() + + self.fontMetrics().ascent() + - self.fontMetrics().descent() + ) + / 2 ) - 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, + + 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_rect = 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_rect, 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() From eb1805525338cfd5c47c0e385a4b5aa216bb2309 Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Fri, 12 Dec 2025 11:13:53 +0000 Subject: [PATCH 11/43] Bugfix: Delete file handling and QDialog class refactoring (#128) * Refactor: Ran ruff formatter * Remove unecessary lambda expression * Add docstring to paintEvent method * Refactor back signal from reject to request_back * Change delete file button from reject to delete_file_button * Make setupUI method private * Change request back signal name * Make setupUI the last method on file * Separate logic, created get mainwindow widget method * Set dialog modal, add class vars, x and y dialog offsets * Use accept and reject signals for dialog result * implement open method, calculate dialog position relative to window * Refactor cancel print dialog * Simplify print cancel signal emition * Refactor delete file logic * Change delete file signal name * Fix empty directory * Fix directory handling for file deletion operationsFix empty directory * Dev debt --------- Co-authored-by: Roberto Martins Co-authored-by: Roberto --- BlocksScreen/lib/files.py | 14 ++ BlocksScreen/lib/moonrakerComm.py | 1 + BlocksScreen/lib/panels/printTab.py | 38 ++--- .../lib/panels/widgets/confirmPage.py | 44 +++--- BlocksScreen/lib/panels/widgets/dialogPage.py | 138 +++++++++--------- BlocksScreen/lib/panels/widgets/filesPage.py | 5 - .../lib/panels/widgets/jobStatusPage.py | 17 +-- 7 files changed, 122 insertions(+), 135 deletions(-) diff --git a/BlocksScreen/lib/files.py b/BlocksScreen/lib/files.py index 68a399c2..f080e6d7 100644 --- a/BlocksScreen/lib/files.py +++ b/BlocksScreen/lib/files.py @@ -54,6 +54,7 @@ def __init__( @property def file_list(self): + """Get the current list of files""" return self.files def handle_message_received(self, method: str, data, params: dict) -> None: @@ -80,6 +81,19 @@ def handle_message_received(self, method: str, data, params: dict) -> None: 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: """Requests metadata for a file diff --git a/BlocksScreen/lib/moonrakerComm.py b/BlocksScreen/lib/moonrakerComm.py index b2f41446..78fba08e 100644 --- a/BlocksScreen/lib/moonrakerComm.py +++ b/BlocksScreen/lib/moonrakerComm.py @@ -544,6 +544,7 @@ def get_gcode_thumbnail(self, filename_dir: str): 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}, diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py index 98301283..65f0030d 100644 --- a/BlocksScreen/lib/panels/printTab.py +++ b/BlocksScreen/lib/panels/printTab.py @@ -126,7 +126,6 @@ def __init__( 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 ) @@ -167,19 +166,15 @@ 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.gcode_move_update[str, list].connect( self.jobStatusPage_widget.on_gcode_move_update ) - 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( @@ -225,10 +220,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 ) @@ -245,11 +238,8 @@ 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 @QtCore.pyqtSlot(str, dict, name="on_print_stats_update") @@ -301,21 +291,22 @@ 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): - """Handle Delete file button clicked""" - self.directory: str = direcotry - self.filename: str = name + @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.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() + self.dialogPage.accepted.connect( + lambda: self._on_delete_file_confirmed(filename, directory) + ) + self.dialogPage.open() - def on_dialog_button_clicked(self, button_name: str) -> None: - """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 _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.dialogPage.disconnect() def paintEvent(self, a0: QtGui.QPaintEvent) -> None: """Widget painting""" @@ -349,6 +340,7 @@ def handle_cancel_print(self) -> None: self.ws.api.cancel_print() self.on_cancel_print.emit() self.loadscreen.show() + self.loadscreen.setModal(True) self.loadscreen.set_status_message("Cancelling print...\nPlease wait") def change_page(self, index: int) -> None: diff --git a/BlocksScreen/lib/panels/widgets/confirmPage.py b/BlocksScreen/lib/panels/widgets/confirmPage.py index 95a12579..c09f31b5 100644 --- a/BlocksScreen/lib/panels/widgets/confirmPage.py +++ b/BlocksScreen/lib/panels/widgets/confirmPage.py @@ -1,44 +1,42 @@ +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" ) - on_reject: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(name="on_reject") - + request_back: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + name="request-back" + ) 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 ) ) - 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") @@ -152,7 +150,7 @@ def showEvent(self, a0: QtGui.QShowEvent) -> None: self.cf_thumbnail.close() return super().showEvent(a0) - def setupUI(self) -> None: + def _setupUI(self) -> None: """Setup widget ui""" sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, @@ -263,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/dialogPage.py b/BlocksScreen/lib/panels/widgets/dialogPage.py index 65f7c727..45d067a4 100644 --- a/BlocksScreen/lib/panels/widgets/dialogPage.py +++ b/BlocksScreen/lib/panels/widgets/dialogPage.py @@ -1,79 +1,74 @@ +import typing + from PyQt6 import QtCore, QtGui, QtWidgets class DialogPage(QtWidgets.QDialog): - button_clicked = QtCore.pyqtSignal(str) # Signal to emit which button was clicked + """Simple confirmation dialog 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, ) -> None: super().__init__(parent) + self._setupUI() 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() + self.setWindowModality( # Force window modality to block input to other windows + QtCore.Qt.WindowModality.WindowModal + ) + self.confirm_button.clicked.connect(self.accept) + self.cancel_button.clicked.connect(self.reject) + self.setModal(True) + self.update() def set_message(self, message: str) -> None: """Set dialog text message""" self.label.setText(message) - def _geometry_calc(self) -> None: - """Calculate dialog widget position relative to the window""" + def _get_mainWindow_widget(self) -> typing.Optional[QtWidgets.QMainWindow]: + """Get the main application window""" app_instance = QtWidgets.QApplication.instance() - main_window = app_instance.activeWindow() if app_instance else None - if main_window is None and app_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 - 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 + def _geometry_calc(self) -> None: + """Calculate dialog widget position relative to the window""" + main_window = self._get_mainWindow_widget() + 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) - self.setGeometry(x, y, width, height) - def paintEvent(self, event: QtGui.QPaintEvent) -> None: - """Re-implemented method, paint widget""" - 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: """Re-implemented method, widget size hint""" 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) @@ -84,17 +79,14 @@ def sizeHint(self) -> QtCore.QSize: def resizeEvent(self, event: QtGui.QResizeEvent) -> None: """Re-implemented method, handle resize event""" 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 + main_window = self._get_mainWindow_widget() + if main_window is None: + return + width = int(main_window.width() * self.x_offset) + height = int(main_window.height() * self.y_offset) + label_x = (self.width() - width) // 2 + label_y = int(height / 4) - 20 # Move the label to the top (adjust as needed) + self.label.setGeometry(label_x, -label_y, width, height) self.confirm_button.setGeometry( int(0), self.height() - 70, int(self.width() / 2), 70 ) @@ -105,11 +97,32 @@ def resizeEvent(self, event: QtGui.QResizeEvent) -> None: 70, ) + def open(self): + """Re-implemented method, open widget""" + self._geometry_calc() + return super().open() + def show(self) -> None: """Re-implemented method, show widget""" self._geometry_calc() return super().show() + def paintEvent(self, event: QtGui.QPaintEvent) -> None: + """Re-implemented method, paint widget""" + 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)) + ) # Semi-transparent dark gray + border_color = QtGui.QColor(128, 128, 128) # Gray color + 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.label = QtWidgets.QLabel("Test", self) font = QtGui.QFont() @@ -118,18 +131,12 @@ def _setupUI(self) -> None: 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; @@ -148,7 +155,6 @@ def _setupUI(self) -> None: padding: 10px; """ ) - # Position buttons self.confirm_button.setGeometry( int(0), self.height() - 70, int(self.width() / 2), 70 @@ -159,15 +165,3 @@ def _setupUI(self) -> None: 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: - """Handle dialog buttons clicked""" - 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/filesPage.py b/BlocksScreen/lib/panels/widgets/filesPage.py index 8de160c0..fbfb3377 100644 --- a/BlocksScreen/lib/panels/widgets/filesPage.py +++ b/BlocksScreen/lib/panels/widgets/filesPage.py @@ -74,11 +74,6 @@ def on_directories(self, directories_data: list) -> None: if self.isVisible(): self._build_file_list() - @QtCore.pyqtSlot(str, name="on-delete-file") - def on_delete_file(self, filename: str) -> None: - """Handle file deleted""" - ... - @QtCore.pyqtSlot(dict, name="on-fileinfo") def on_fileinfo(self, filedata: dict) -> None: """Handle receive file information/metadata""" diff --git a/BlocksScreen/lib/panels/widgets/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py index bb9754ea..a09bdcd7 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -62,7 +62,7 @@ class JobStatusWidget(QtWidgets.QWidget): def __init__(self, parent) -> None: super().__init__(parent) - self.canceldialog = dialogPage.DialogPage(self) + self.cancel_print_dialog = dialogPage.DialogPage(self) self._setupUI() self.tune_menu_btn.clicked.connect(self.tune_clicked.emit) self.pause_printing_btn.clicked.connect(self.pause_resume_print) @@ -121,18 +121,11 @@ def hidethumbnail(self): @QtCore.pyqtSlot(name="handle-cancel") def handleCancel(self) -> None: """Handle cancel print job dialog""" - self.canceldialog.set_message( - "Are you sure you \n want to cancel \n this print job?" + 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() - - 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 + self.cancel_print_dialog.accepted.connect(self.print_cancel) + self.cancel_print_dialog.open() @QtCore.pyqtSlot(str, list, name="on_print_start") def on_print_start(self, file: str, thumbnails: list) -> None: From 260b126538c0efc9265371593ca3a31b274b985f Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Fri, 12 Dec 2025 14:41:39 +0000 Subject: [PATCH 12/43] Work fan page (#119) * ADD: added fans page * add: added fans into fans page * ADD: added line to fans page * Refacotr: changed layout type * UPD: updated options card to have 2 text . added cards into controltab fans * Rev: removed line from fans page * Refactor: refactor on_fan_object_update * Refactor: Ran ruff formatter * Fix incorrect method name * Fix incorrect method name * bugfix: option card clicklable area * Ran: ruff formatter * Bugfix: names being wrong and slider spaming gcodes on change * ADD: color degrade when ON/OFF (#120) * Del: deleted some prints * Bugfix: fan not updating when slider updates * Bugfix: label size * Bugfix: Delete file handling and QDialog class refactoring (#128) * Refactor: Ran ruff formatter * Remove unecessary lambda expression * Add docstring to paintEvent method * Refactor back signal from reject to request_back * Change delete file button from reject to delete_file_button * Make setupUI method private * Change request back signal name * Make setupUI the last method on file * Separate logic, created get mainwindow widget method * Set dialog modal, add class vars, x and y dialog offsets * Use accept and reject signals for dialog result * implement open method, calculate dialog position relative to window * Refactor cancel print dialog * Simplify print cancel signal emition * Refactor delete file logic * Change delete file signal name * Fix empty directory * Fix directory handling for file deletion operationsFix empty directory * Dev debt --------- Co-authored-by: Roberto Martins Co-authored-by: Roberto * bugfix: option card centering --------- Co-authored-by: Roberto Co-authored-by: Hugo Costa --- BlocksScreen/lib/panels/controlTab.py | 145 +++++++++- .../lib/panels/widgets/optionCardWidget.py | 106 +++++-- .../panels/widgets/slider_selector_page.py | 23 +- BlocksScreen/lib/ui/controlStackedWidget.ui | 262 +++++++++++++++--- .../lib/ui/controlStackedWidget_ui.py | 212 +++++++++----- BlocksScreen/lib/utils/blocks_slider.py | 1 + 6 files changed, 620 insertions(+), 129 deletions(-) diff --git a/BlocksScreen/lib/panels/controlTab.py b/BlocksScreen/lib/panels/controlTab.py index bef67b3f..99f8a3c5 100644 --- a/BlocksScreen/lib/panels/controlTab.py +++ b/BlocksScreen/lib/panels/controlTab.py @@ -13,6 +13,11 @@ from PyQt6 import QtCore, QtGui, QtWidgets from lib.panels.widgets.popupDialogWidget import Popup +from lib.utils.display_button import DisplayButton +from lib.panels.widgets.slider_selector_page import SliderPage + +from lib.panels.widgets.optionCardWidget import OptionCard +from helper_methods import normalize class ControlTab(QtWidgets.QStackedWidget): @@ -42,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, @@ -74,6 +81,11 @@ def __init__( self.addWidget(self.printcores_page) self.loadpage = LoadScreen(self, LoadScreen.AnimationGIF.DEFAULT) self.addWidget(self.loadpage) + + 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)) ) @@ -108,6 +120,10 @@ 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_nozzles_calibration_btn.clicked.connect( partial(self.change_page, self.indexOf(self.probe_helper_page)) @@ -267,6 +283,133 @@ 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. + + 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 + + if name == "fan_generic Auxiliary_Cooling_Fans": + name = "Auxiliary\ncooling fans" + elif name == "fan_generic CHAMBER_EXHAUST": + name = "Exhaust Fan" + elif name == "fan_generic Part_Cooling_Fan": + name = "Cooling fan" + else: + name = name.removeprefix("fan_generic") + fan_card = self.tune_display_buttons.get(name) + + if fan_card is None: + icon_path = ( + ":/temperature_related/media/btn_icons/blower.svg" + if "blower" in name.lower() + else ":/temperature_related/media/btn_icons/fan.svg" + ) + icon = QtGui.QPixmap(icon_path) + + 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 == "Auxiliary\ncooling fans": + name = "Auxiliary_Cooling_Fans" + elif name == "Exhaust Fan": + name = "CHAMBER_EXHAUST" + elif name == "Cooling fan": + name = "Part_Cooling_Fan" + else: + ... + 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: + 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 @@ -297,9 +440,7 @@ def _handle_gcode_response(self, messages: list): and "range:" in msg_list and "tolerance:" in msg_list ): - print("Match candidate:", msg_list) match = re.search(pattern, msg_list) - print("Regex match:", match) if match: retries_done = int(match.group(1)) diff --git a/BlocksScreen/lib/panels/widgets/optionCardWidget.py b/BlocksScreen/lib/panels/widgets/optionCardWidget.py index 711dcd02..789a9885 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,8 +24,22 @@ def __init__( self.icon_background_color = QtGui.QColor(150, 150, 130, 80) self.name = name self.card_text = text + self.doubleT: bool = False self._setupUi(self) - self.continue_button.clicked.connect(lambda: self.continue_clicked.emit(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.setMode(False) self.set_card_icon(icon) self.set_card_text(text) @@ -42,7 +55,13 @@ def enable_button(self) -> None: def set_card_icon(self, pixmap: QtGui.QPixmap) -> None: """Set widget icon""" - self.option_icon.setPixmap(pixmap) + 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: @@ -79,9 +98,49 @@ def leaveEvent(self, a0: QtCore.QEvent) -> None: 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 @@ -154,12 +213,20 @@ def _setupUi(self, option_card): 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) @@ -174,21 +241,15 @@ def _setupUi(self, option_card): 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, @@ -207,8 +268,13 @@ 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.spacer = QtWidgets.QSpacerItem( + 20, + 40, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Expanding, + ) self._retranslateUi(option_card) QtCore.QMetaObject.connectSlotsByName(option_card) diff --git a/BlocksScreen/lib/panels/widgets/slider_selector_page.py b/BlocksScreen/lib/panels/widgets/slider_selector_page.py index 793b7044..2f6c89f7 100644 --- a/BlocksScreen/lib/panels/widgets/slider_selector_page.py +++ b/BlocksScreen/lib/panels/widgets/slider_selector_page.py @@ -35,19 +35,26 @@ def __init__(self, parent) -> None: 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""" @@ -114,8 +121,8 @@ 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 ) 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..88817b36 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() @@ -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,6 +2182,11 @@ 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 diff --git a/BlocksScreen/lib/utils/blocks_slider.py b/BlocksScreen/lib/utils/blocks_slider.py index ee084a0a..f122589a 100644 --- a/BlocksScreen/lib/utils/blocks_slider.py +++ b/BlocksScreen/lib/utils/blocks_slider.py @@ -14,6 +14,7 @@ def __init__(self, parent) -> None: self.setTickInterval(20) self.setMinimum(0) self.setMaximum(100) + self.setPageStep(0) def mousePressEvent(self, ev: QtGui.QMouseEvent) -> None: """Re-implemented method, Handle mouse press events""" From 70e2c43a1d6cea14a642cd28ac8528e0968907d0 Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Fri, 12 Dec 2025 14:43:04 +0000 Subject: [PATCH 13/43] Fix issues intruduced in Bugfix label overlap #121 (#129) * ADD: added fans page * add: added fans into fans page * ADD: added line to fans page * Refacotr: changed layout type * UPD: updated options card to have 2 text . added cards into controltab fans * Rev: removed line from fans page * Refactor: refactor on_fan_object_update * Refactor: Ran ruff formatter * Fix incorrect method name * Fix incorrect method name * bugfix: option card clicklable area * Ran: ruff formatter * Bugfix: names being wrong and slider spaming gcodes on change * ADD: color degrade when ON/OFF (#120) * Del: deleted some prints * Bugfix: fan not updating when slider updates * Bugfix: label size * Fix integration problems #121 fix * Refactor variable into local method variable --------- Signed-off-by: Hugo Costa Co-authored-by: Roberto Martins Co-authored-by: Roberto --- .../lib/panels/widgets/optionCardWidget.py | 9 ++-- BlocksScreen/lib/utils/blocks_label.py | 45 +++++++++++++++---- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/optionCardWidget.py b/BlocksScreen/lib/panels/widgets/optionCardWidget.py index 789a9885..259179c2 100644 --- a/BlocksScreen/lib/panels/widgets/optionCardWidget.py +++ b/BlocksScreen/lib/panels/widgets/optionCardWidget.py @@ -144,8 +144,8 @@ def setMode(self, double_mode: bool = False): 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 ) @@ -167,7 +167,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) @@ -193,7 +193,7 @@ 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() @@ -216,6 +216,7 @@ def _setupUi(self, option_card): self.option_icon = IconButton(parent=option_card) self.option_icon.setMinimumSize(QtCore.QSize(200, 150)) self.option_icon.setObjectName("option_icon") + self.option_icon.setScaledContents(True) _button_font = QtGui.QFont() _button_font.setBold(True) _button_font.setPointSize(20) diff --git a/BlocksScreen/lib/utils/blocks_label.py b/BlocksScreen/lib/utils/blocks_label.py index 7e64d805..1aaa173e 100644 --- a/BlocksScreen/lib/utils/blocks_label.py +++ b/BlocksScreen/lib/utils/blocks_label.py @@ -28,19 +28,17 @@ def __init__(self, parent: QtWidgets.QWidget = None, *args, **kwargs): 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.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: @@ -203,17 +201,46 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: qp.fillRect(rect, self._background_color) if self.icon_pixmap: - icon_rect = QtCore.QRectF(0, 0, self.height(), self.height()) - scaled = self.icon_pixmap.scaled( + 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(), QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation, ) - qp.drawPixmap(icon_rect.toRect(), scaled) + 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 = QtCore.QRectF( + icon_rect.x() + adjusted_x, + icon_rect.y() + adjusted_y, + scaled_width, + scaled_height, + ) + qp.drawPixmap(adjusted_icon, _icon_scaled, _icon_scaled.rect().toRectF()) if self.glow_animation.state() == self.glow_animation.State.Running: - path = QtGui.QPainterPath() - path.addRoundedRect(QtCore.QRectF(rect), 10, 10) - qp.fillPath(path, self.glow_color) + 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: text_option = QtGui.QTextOption(self.alignment()) text_option.setWrapMode(QtGui.QTextOption.WrapMode.NoWrap) From f703fc68d86f333df4d8ade88930e4bfd1931b9f Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Mon, 15 Dec 2025 11:21:50 +0000 Subject: [PATCH 14/43] Fix Merge problems introduced on the previous pull requests (#131) * Add 'flat' visual property, seperate methods to their own methods * Delete attribute set, BlocksLabel does not have that attribute * Refactor: reorder methods * Fix missing slot for klippy signal, now reacts to klippy connected signal * Delete: touch handlers, will be implemented in the future --- .../lib/panels/widgets/connectionPage.py | 13 +- .../lib/panels/widgets/optionCardWidget.py | 1 - BlocksScreen/lib/utils/blocks_button.py | 188 ++++++++---------- 3 files changed, 98 insertions(+), 104 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/connectionPage.py b/BlocksScreen/lib/panels/widgets/connectionPage.py index 33bd0576..0b7e054d 100644 --- a/BlocksScreen/lib/panels/widgets/connectionPage.py +++ b/BlocksScreen/lib/panels/widgets/connectionPage.py @@ -43,7 +43,7 @@ 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): @@ -55,6 +55,17 @@ def show_panel(self, reason: str | None = None): self.text_update() return False + @QtCore.pyqtSlot(bool, name="on_klippy_connected") + def on_klippy_connection(self, connected: bool): + """Handle klippy connection state""" + 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""" diff --git a/BlocksScreen/lib/panels/widgets/optionCardWidget.py b/BlocksScreen/lib/panels/widgets/optionCardWidget.py index 259179c2..8c79ee68 100644 --- a/BlocksScreen/lib/panels/widgets/optionCardWidget.py +++ b/BlocksScreen/lib/panels/widgets/optionCardWidget.py @@ -216,7 +216,6 @@ def _setupUi(self, option_card): self.option_icon = IconButton(parent=option_card) self.option_icon.setMinimumSize(QtCore.QSize(200, 150)) self.option_icon.setObjectName("option_icon") - self.option_icon.setScaledContents(True) _button_font = QtGui.QFont() _button_font.setBold(True) _button_font.setPointSize(20) diff --git a/BlocksScreen/lib/utils/blocks_button.py b/BlocksScreen/lib/utils/blocks_button.py index 141ee1ec..292b5125 100644 --- a/BlocksScreen/lib/utils/blocks_button.py +++ b/BlocksScreen/lib/utils/blocks_button.py @@ -17,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 = "" @@ -38,12 +38,11 @@ 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): - """Button name""" + """Widget name""" return self._name @name.setter @@ -59,28 +58,53 @@ 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 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""" @@ -88,13 +112,25 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): 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 - + # 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 @@ -135,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, @@ -149,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 @@ -160,22 +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) - 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 ) @@ -184,91 +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() - - # _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): - """Set widget properties""" - 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 _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: - """Re-implemented method, filter events""" - 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) From ca9b7f0a9833a644ee4c8efa8b6a9252e59ad0de Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Mon, 15 Dec 2025 15:55:30 +0000 Subject: [PATCH 15/43] Added standard pull request template (#133) --- .../pull_request_template.md | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md 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 From 3b91e46ed43f1b49c23e481e8c23d1ae1d6a00ee Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Mon, 15 Dec 2025 16:09:06 +0000 Subject: [PATCH 16/43] Bugfix: fixed white dot on list_model.py (#130) Fix: white dot on widget lists built using **EntryDelegate** by deleting unused code --- BlocksScreen/lib/utils/list_model.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/BlocksScreen/lib/utils/list_model.py b/BlocksScreen/lib/utils/list_model.py index 24c9467f..2f4cbc3a 100644 --- a/BlocksScreen/lib/utils/list_model.py +++ b/BlocksScreen/lib/utils/list_model.py @@ -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) From 6f4c3e38563bd950a104b4f94b0d8a2899ad6d33 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Thu, 18 Dec 2025 18:59:20 +0000 Subject: [PATCH 17/43] Bugfix thumbnail not working (#123) * Refactor: reafactor how thumbnail are painted * Refactor: Ran ruff formatter * Refactor: resolved issues on the code * Adhere to snake casing * Adhere to snake casing * Return event handled * Requests files on websocket open Instead of only requesting the available files when Klippy is ready request them when websocket opens. Moonraker handles the files, so there is no need to wait until klippy is connected. This also ensures that when the screen connects with moonraker and klipper the information about all available print files are ready to be handled by the GUI. This stems from a problem loading the thumbnails. There were many instances of initializing the application mid print, when this happened a request for the available thumbnails is made, because the files are loaded after receiving the klippy ready event, the moment when asking about the current job thumbnail there are not files nor metadata loaded onto the GUI, so no thumbnail is fetched. Now with this change the same situation doesn't occur. * Change on_accept signal types. There is no need to add list type to the signal. The thumbnails were passed there before, we will load the print thumbnails when we get the metadata from the file. Passing the thumbnails here was unnecessary. * Add inner progress bar icon The progress bar fills a circumference until the print is complete, the inner part of the widget is blank and is suposed to have a thumbnail of the current print, before a QGraphicsView overlaped with the progress bar widget. Now we add the functionality to add a pixmap on the progress bar itself. It calculates the scaling for the pixmap to fill the inner part of the progress bar widget without overlaping anything. There are some bugs still, such as what happens when resizeEvent is emitted, the automatic scaling is not working properly yet. * Refactor Thumnail painting and widget building The solution provided by @Robert0Mart showed us that the thumbnail building needed some work, this commit intends to extend that effort to rewrite this feature. We will simplify thumbnail building, less widgets for this feature to work. We will also let the progress bar widget handle the small widget painting, and propagate the click to expand to a bigger thumbnail painting that fills the entire screen. * Final progress bar refactor, send signal on thumbnail click Refactored class method positions to add some structure to the class. Privatised some class variables, should add @property decorator with getter and setter for these variables since they are actually properties. Captured MousePressEvent of the widget and filtered the events only to those inside the inner rect of the widget, when triggered, send an event `thumbnail_clicked` to signal that the thumbnail was actually clicked. * Refactor `setValue`method Added type for argument, refactored method body readability * Added thumbnail expansion Added initial thumbnail expansion, it works, but it needs to be worked on. We need to hide all widgets so that the thumbnail doesn't have anything on the background while its expanded. this requires us to hide all widgets used one by one. it's not incorrect per-se but it's not the best. The thumbnail is loaded once when the thumbnail is received, so it just paints once and stays in memory until `.show()` and `.hide()` methods are called. Once the print stops for whatever reason. Since the widget is built when the thumbnail comes, the QGraphicsView and its childs are deleted, freeing memory. It'll be built again when a new print job is selected. * Added type for arguments, refactored `setValue` and `set_bar_color` methods. * Reduced conditional branching, added docstring to class Reduced branching on printer object handlers, Added simple docstring to the `jobStatusPage` class. * Conditional logic when thumbnail pixmaps are Null When the provided thumbnails are Null Pixmaps, the should not build the QGraphicsView Widget. Before , even when pixmaps were null, it whould be built, the user could click the progress widget and the scene whould expand, only without anything to show. Now the widget simply does not build anything when all pixmaps are Null. The click signal connections are now done inside the `_load_thumbnails` method, the progress widget pixmap is also set inside that method. Everything related to thumbnails (except the eventFilter method) is now handled inside the `_load_thumbnails` method. The next step, filtering the provided Null Pixmaps. Only load not Null pixmaps * Refactor filter null pixmaps during thumbnail loading Simplified thumbnail filtering. Now i can have any number of Null pixmaps, if no pixmap is usable, cancels thumbnail loading, if there is at least one usable pixmap. It'll load the thumbnails on the progress widget and on the QGraphicsView. This means that if only the smallest resolution is available (48x48) it'll paint with low resolution --------- Co-authored-by: Roberto Co-authored-by: Hugo Costa --- BlocksScreen/lib/files.py | 22 +- .../lib/panels/widgets/confirmPage.py | 4 +- .../lib/panels/widgets/jobStatusPage.py | 415 ++++++------------ BlocksScreen/lib/utils/blocks_progressbar.py | 200 ++++++--- 4 files changed, 303 insertions(+), 338 deletions(-) diff --git a/BlocksScreen/lib/files.py b/BlocksScreen/lib/files.py index f080e6d7..0eda561d 100644 --- a/BlocksScreen/lib/files.py +++ b/BlocksScreen/lib/files.py @@ -54,14 +54,12 @@ def __init__( @property def file_list(self): - """Get the current list of files""" + """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] @@ -73,8 +71,6 @@ 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", []) @@ -99,7 +95,7 @@ def on_request_fileinfo(self, filename: str) -> None: """Requests metadata for a file Args: - filename (str): file + filename (str): file to get metadata from """ _data: dict = { "thumbnail_images": list, @@ -134,7 +130,6 @@ def on_request_fileinfo(self, filename: str) -> None: ) _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({"layer_count": _file_metadata.get("layer_count", -1.0)}) @@ -165,18 +160,15 @@ def on_request_fileinfo(self, filename: str) -> None: self.fileinfo.emit(_data) def eventFilter(self, a0: QtCore.QObject, a1: QtCore.QEvent) -> bool: - """Filter Klippy related events""" + """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 - if 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: diff --git a/BlocksScreen/lib/panels/widgets/confirmPage.py b/BlocksScreen/lib/panels/widgets/confirmPage.py index c09f31b5..12432449 100644 --- a/BlocksScreen/lib/panels/widgets/confirmPage.py +++ b/BlocksScreen/lib/panels/widgets/confirmPage.py @@ -11,7 +11,7 @@ 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" @@ -31,7 +31,7 @@ def __init__(self, parent) -> None: 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.request_back.emit) diff --git a/BlocksScreen/lib/panels/widgets/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py index a09bdcd7..0d7c4e29 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -1,6 +1,5 @@ import logging import typing - import events from helper_methods import calculate_current_layer, estimate_print_time from lib.panels.widgets import dialogPage @@ -10,22 +9,19 @@ from lib.utils.display_button import DisplayButton from PyQt6 import QtCore, QtGui, QtWidgets +logger = logging.getLogger("logs/BlocksScreen.log") -class ClickableGraphicsView(QtWidgets.QGraphicsView): - """Re-implementation of QGraphicsView that adds clicked signal""" - clicked = QtCore.pyqtSignal() +class JobStatusWidget(QtWidgets.QWidget): + """Job status widget page, page shown when there is a active print job. + Enables mid print printer tuning and inspection of print progress. -def mousePressEvent(self, event: QtGui.QMouseEvent) -> None: - """Filter mouse press events""" - if event.button() == QtCore.Qt.MouseButton.LeftButton: - self.clicked.emit() - return True # Issue event handled - super(ClickableGraphicsView, self).mousePressEvent(event) + Args: + QtWidgets (QtWidgets.QWidget): Parent widget + """ -class JobStatusWidget(QtWidgets.QWidget): print_start: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( str, name="print_start" ) @@ -61,62 +57,90 @@ class JobStatusWidget(QtWidgets.QWidget): def __init__(self, parent) -> None: super().__init__(parent) - - self.cancel_print_dialog = dialogPage.DialogPage(self) + self.thumbnail_graphics = [] self._setupUI() + self.cancel_print_dialog = dialogPage.DialogPage(self) 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) - - 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) + @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() - def eventFilter(self, source, event): - """Re-implemented method, filter events""" - 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): - """Show print job fullscreen thumbnail""" - 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): - """Hide print job fullscreen thumbnail""" - 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 + ) # Filter events on this widget, for clicks + 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: @@ -127,23 +151,16 @@ def handleCancel(self) -> None: self.cancel_print_dialog.accepted.connect(self.print_cancel) self.cancel_print_dialog.open() - @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,24 +172,16 @@ 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(f"Unexpected error while posting print job start event: {e}") @QtCore.pyqtSlot(dict, name="on_fileinfo") def on_fileinfo(self, fileinfo: dict) -> None: """Handle received file information/metadata""" 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 - 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: @@ -234,6 +243,8 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None: self.total_layers = "?" self.file_metadata.clear() self.hide_request.emit() + self.thumbnail_view.deleteLater() + self.thumbnail_view_layout.deleteLater() if hasattr(events, str("Print" + value.capitalize())): event_obj = getattr(events, str("Print" + value.capitalize())) @@ -247,7 +258,7 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None: "QApplication.instance expected non None value" ) except Exception as e: - logging.info( + logger.info( f"Unexpected error while posting print job start event: {e}" ) @@ -280,24 +291,19 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None: @QtCore.pyqtSlot(str, list, name="on_gcode_move_update") def on_gcode_move_update(self, field: str, value: list) -> None: """Handle gcode move""" - if isinstance(value, list): - if "gcode_position" in field: # Without offsets - if self._internal_print_status == "printing": - _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) - ), - ) - self.layer_display_button.setText( - f"{int(_current_layer)}" if _current_layer != -1 else "?" - ) + if "gcode_position" in field: # Without offsets + if self._internal_print_status == "printing": + _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) + ), + ) + self.layer_display_button.setText( + 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") @@ -309,72 +315,11 @@ def virtual_sdcard_update(self, field: str, value: float | bool) -> None: 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: - """Re-implemented method, paint widget""" - _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 "progress" == field: + self.printing_progress_bar.setValue(value) + if "file_position" == field: + ... def _setupUI(self) -> None: """Setup widget ui""" @@ -391,73 +336,41 @@ def _setupUI(self) -> None: 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)) @@ -476,19 +389,14 @@ def _setupUI(self) -> None: 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)) @@ -498,108 +406,37 @@ 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) @@ -638,3 +475,39 @@ def _setupUI(self) -> None: 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.setMaximumSize(QtCore.QSize(300, 300)) + 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/utils/blocks_progressbar.py b/BlocksScreen/lib/utils/blocks_progressbar.py index 98f7cc52..a05aafa5 100644 --- a/BlocksScreen/lib/utils/blocks_progressbar.py +++ b/BlocksScreen/lib/utils/blocks_progressbar.py @@ -1,92 +1,192 @@ +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): + def set_padding(self, value) -> None: """Set widget padding""" - self.padding = value + self._padding = value self.update() - def set_pen_width(self, value): + def set_pen_width(self, value) -> None: """Set widget text pen width""" - self.pen_width = value + self._pen_width = value self.update() - def paintEvent(self, event): - """Re-implemented method, paint widget""" - painter = QtGui.QPainter(self) - painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) + def set_inner_pixmap(self, pixmap: QtGui.QPixmap) -> None: + """Set the inner icon pixmap on the progress bar + circumference. + """ + self._pixmap = pixmap + self.update() - self._draw_circular_bar(painter, self.width(), self.height()) + def resizeEvent(self, a0) -> None: + """Re-implemented method, handle widget resize Events + + Currently rescales the set pixmap so it has the optimal + size. + """ + 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, + ) + self.update() + return super().resizeEvent(a0) + + 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 + + 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, + ) - arc1_start = 236 * 16 - arc1_span = -290 * 16 + 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.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 ) painter.drawText(text_rect, QtCore.Qt.AlignmentFlag.AlignCenter, progress_text) - def setValue(self, value): - """Set 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): - """Set bar color""" - 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() From 6fbc375a5b18274fd71c63545e2dce58badff42a Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Fri, 2 Jan 2026 11:50:44 +0000 Subject: [PATCH 18/43] Bugfix uninitilized variable access introduced on #123 (#141) * Del: reference to uninitialized QGraphicsView variable When there is no thumbnail for the current print, we shouldn't referece `self.thumbnail_view` since it hasn't been initialized. When the print stops for whatever reason we want to delete the object, but if it hasn't been initilialized there is nothing to delete. So we must only delete when the print stops if the class has been attributed. * Added clear thumbnail object on print stop * Small refactor, exit method when page is not visible * Calculate and scale thumbnail pixmap on set When setting the pixmap on the progress bar, the image was not scaled and the inner rect was not calculated. This resulted in the pixmap not showing. Now when setting the progressbar thumbnail this is calculated so that the pixmap can be shown in the middle of the progress bar circumference. * Del forgotten print() * Refactor and handle show event Refactored some methods, including accessing values in dicts, by using .get(). Now the slot `on_fileinfo` only runs when the `jobStatusPage` is visible this is because the request for file information is done on `file.py` and the confirmation page. This whould result in the slot triggering multiple times before it was actually necessary and on asking for imformation for all files, while we only want information on one file. Now the request for file information is done when the `jobStatusPage` is actually visible. The `showEvent` method requests the file information when that event is triggered on the class. * Split print state logic into seperate method Split the state logic in a seperate method (`_handle_print_state(state: str))` just so it's more readable than handling all `print_status` object updates in a single method. It was getting to big of a method. * Change print state event dispatch logic --- .../lib/panels/widgets/jobStatusPage.py | 175 ++++++++---------- BlocksScreen/lib/utils/blocks_progressbar.py | 20 +- 2 files changed, 89 insertions(+), 106 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py index 0d7c4e29..9f74ba6e 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -85,6 +85,11 @@ def toggle_thumbnail_expansion(self) -> None: self.headerWidget.show() self.show() + 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, sender_obj: QtCore.QObject, event: events.QEvent) -> bool: """Filter events, @@ -110,9 +115,7 @@ def _load_thumbnails(self, *thumbnails) -> None: logger.debug("Unable to load thumbnails, no thumbnails provided") return self.create_thumbnail_widget() - self.thumbnail_view.installEventFilter( - self - ) # Filter events on this widget, for clicks + self.thumbnail_view.installEventFilter(self) scene = QtWidgets.QGraphicsScene() _biggest_thumb = self.thumbnail_graphics[-1] self.thumbnail_view.setSceneRect( @@ -172,11 +175,13 @@ def on_print_start(self, file: str) -> None: else: raise TypeError("QApplication.instance expected non None value") except Exception as e: - logger.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: """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) @@ -185,24 +190,59 @@ def on_fileinfo(self, fileinfo: dict) -> None: @QtCore.pyqtSlot(name="pause_resume_print") def pause_resume_print(self) -> None: - """Handle pause/resume print job""" - 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: + 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") @@ -216,82 +256,42 @@ 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() - self.thumbnail_view.deleteLater() - self.thumbnail_view_layout.deleteLater() - - 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: - logger.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): if "total_layer" in value.keys(): - self.total_layers = value["total_layer"] + self.total_layers = value.get("total_layer", "?") self.layer_display_button.secondary_text = str(self.total_layers) 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)}") + _current_layer = value.get("current_layer", None) + if _current_layer: + self.layer_display_button.setText(f"{int(_current_layer)}") 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: """Handle gcode move""" - if "gcode_position" in field: # Without offsets + if not self.isVisible(): + return + if "gcode_position" in field: if self._internal_print_status == "printing": _current_layer = calculate_current_layer( z_position=value[2], @@ -314,12 +314,10 @@ def virtual_sdcard_update(self, field: str, value: float | bool) -> None: 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): - ... + if not self.isVisible(): + return if "progress" == field: self.printing_progress_bar.setValue(value) - if "file_position" == field: - ... def _setupUI(self) -> None: """Setup widget ui""" @@ -330,8 +328,6 @@ 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)) @@ -369,7 +365,6 @@ def _setupUI(self) -> None: 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)) @@ -383,7 +378,6 @@ 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) @@ -395,7 +389,6 @@ def _setupUI(self) -> None: 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) @@ -430,45 +423,34 @@ def _setupUI(self) -> None: 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) 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) - - # -----------------------------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, @@ -480,7 +462,6 @@ 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.setMaximumSize(QtCore.QSize(300, 300)) self.thumbnail_view.setAttribute( QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True ) diff --git a/BlocksScreen/lib/utils/blocks_progressbar.py b/BlocksScreen/lib/utils/blocks_progressbar.py index a05aafa5..414097bb 100644 --- a/BlocksScreen/lib/utils/blocks_progressbar.py +++ b/BlocksScreen/lib/utils/blocks_progressbar.py @@ -40,27 +40,29 @@ def set_pen_width(self, value) -> None: self._pen_width = value self.update() + 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.update() + self._scale_pixmap() def resizeEvent(self, a0) -> None: - """Re-implemented method, handle widget resize Events + """Reimplemented method, handle widget resize Events Currently rescales the set pixmap so it has the optimal size. """ - 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, - ) + self._scale_pixmap() self.update() - return super().resizeEvent(a0) def sizeHint(self) -> QtCore.QSize: """Re-implemented method, preferable widget size""" From de9fe96183bb0418fb72a47b80cb6ce519c7d56b Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Fri, 2 Jan 2026 11:57:27 +0000 Subject: [PATCH 19/43] Refactor `SensorPanel`: replace `QListWidgetItem` with `EntryListModel` (#125) * refactor: migrated sensorsPanel.py sensor list from a QtWidgets.QListWidgetItem to a ListModelView Arq with some bugfixes Signed-off-by: Guilherme Costa * sensors: resolve bugs and some cleanup Signed-off-by: Guilherme Costa * sensors: add cutter sensor handling and visual update for list item Signed-off-by: Guilherme Costa * code cleanup and formatting Signed-off-by: Guilherme Costa * sensorwidget bugfix Signed-off-by: Guilherme Costa * sensorsPanel.py: ensure first item is checked on startup sensorWidget.py: lock toggle_button until action succeeds and replace repaint() with update() for proper refresh Signed-off-by: Guilherme Costa * sensorsPanel.py: reformat code to be complient with ruff guidelines Signed-off-by: Guilherme Costa --------- Signed-off-by: Guilherme Costa Co-authored-by: Guilherme Costa Co-authored-by: HugoCLSC --- .../lib/panels/widgets/sensorWidget.py | 213 ++++++--- .../lib/panels/widgets/sensorsPanel.py | 426 ++++++++++++------ 2 files changed, 428 insertions(+), 211 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/sensorWidget.py b/BlocksScreen/lib/panels/widgets/sensorWidget.py index 5223ac83..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 @@ -30,8 +31,12 @@ class SensorState(enum.IntEnum): 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 @@ -40,18 +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 + SensorWidget.FilamentState.PRESENT ) - self.sensor_state: SensorWidget.SensorState = SensorWidget.SensorState.OFF + 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" @@ -60,6 +65,7 @@ def __init__(self, parent, sensor_name: str): ":/filament_related/media/btn_icons/filament_sensor_off.svg" ) self._setupUI() + self.toggle_button.stateChange.connect(self.toggle_sensor_state) @property def type(self) -> SensorType: @@ -90,24 +96,40 @@ 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): - """Change filament sensor state""" - 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: """Re-implemented method, paint widget""" - # 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 - # ) - style_painter = QtWidgets.QStylePainter(self) style_painter.setRenderHint(style_painter.RenderHint.Antialiasing, True) style_painter.setRenderHint( @@ -116,79 +138,128 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: 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: - """Toggle filament sensor""" - 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, + ) + + _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, ) - return str( - f"SET_FILAMENT_SENSOR SENSOR={self.text} ENABLE={not self.sensor_state.value}" + + 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): _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()) + 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) - 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) - 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_name_ = QtWidgets.QLabel(parent=self) + size_policy.setHeightForWidth( + self._text_label_name_.sizePolicy().hasHeightForWidth() + ) + 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.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_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 51a5b2c1..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 @@ -15,80 +17,85 @@ class SensorsWindow(QtWidgets.QWidget): 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.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 - """ + """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"), + 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: - """Handle filament state chage""" - if sensor_name in self.sensor_list: - _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: - """Handle filament sensor clicked""" - _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 @@ -96,132 +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 _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.setObjectName("fs_back_button") - self.fs_header_layout.addWidget( - self.fs_back_button, - 0, + 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.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)) + brush.setStyle(QtCore.Qt.BrushStyle.NoBrush) + palette.setBrush( + QtGui.QPalette.ColorGroup.Active, + QtGui.QPalette.ColorRole.Base, + brush, ) - sizePolicy.setHorizontalStretch(1) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth( - self.fs_sensors_list.sizePolicy().hasHeightForWidth() + 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, ) - 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) - 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) - self.fs_sensors_list.setItemAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter - ) - 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, 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.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, ) - - 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()) + 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) - 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.main_content_layout.addWidget(self.sensor_buttons_frame, 0) - self.fs_sensors_list.addItem(self.item) - self.fs_sensors_list.setItemWidget(self.item, self.label) + 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) From 4d973b6819a5fae2d4f97d6747cca8402e7edee9 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Fri, 2 Jan 2026 12:02:10 +0000 Subject: [PATCH 20/43] Refactor `filesPage.py`: Changed Files List `QtWidgets.QListWidgetItem` to our custom `EntryListModel` (#126) * FilesPage: Refactor almost complete missing passing the directory by itemclick helper_methods: updated the get_file_loc method to return always the filename instead of the full path Signed-off-by: Guilherme Costa * filesPage.py: refactor concluded, scrollbar bugfix Signed-off-by: Guilherme Costa * helper_method.py: Change naming of a method from get_file_loc to get_file_name Signed-off-by: Guilherme Costa * filesPage.py: code cleanup, small docstring generation and add missing commented lines Signed-off-by: Guilherme Costa * filesPage.py: remove unused lines of code Signed-off-by: Guilherme Costa * filesPage.py - fix rebase conflits Signed-off-by: Guilherme Costa * formatting fix Signed-off-by: Guilherme Costa * filesPage.py: Add sync timeout and change click behavior in files page Set file list to refresh after 1.5s to improve UI responsiveness. Clicks are now handled in the _on_item_selected slot instead of inline callbacks to separate logic and unify behavior. --------- Signed-off-by: Guilherme Costa Signed-off-by: gmmcosta15 Co-authored-by: Guilherme Costa Co-authored-by: HugoCLSC --- BlocksScreen/helper_methods.py | 29 +++ BlocksScreen/lib/panels/widgets/filesPage.py | 250 ++++++++++--------- 2 files changed, 165 insertions(+), 114 deletions(-) diff --git a/BlocksScreen/helper_methods.py b/BlocksScreen/helper_methods.py index 0dd2b2db..8de5fc13 100644 --- a/BlocksScreen/helper_methods.py +++ b/BlocksScreen/helper_methods.py @@ -324,3 +324,32 @@ def check_file_on_path( """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_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: +# hash = hashlib.sha256() +# hash.update(data.encode()) +# hash.digest() +# return hash + + +def digest_hash() -> None: ... diff --git a/BlocksScreen/lib/panels/widgets/filesPage.py b/BlocksScreen/lib/panels/widgets/filesPage.py index fbfb3377..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") @@ -35,7 +36,9 @@ class FilesPage(QtWidgets.QWidget): 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) @@ -50,6 +53,33 @@ def __init__(self, parent) -> None: ) 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""" @@ -76,7 +106,7 @@ def on_directories(self, directories_data: list) -> None: @QtCore.pyqtSlot(dict, name="on-fileinfo") def on_fileinfo(self, filedata: dict) -> None: - """Handle receive file information/metadata""" + """Method called per file to contruct file entry to the list""" if not filedata or not self.isVisible(): return filename = filedata.get("filename", "") @@ -104,53 +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() @@ -161,51 +191,58 @@ def _build_file_list(self) -> None: 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 @@ -215,61 +252,28 @@ 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: + """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()) @@ -327,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;") @@ -335,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( @@ -364,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" + ), + } From d013f128761f04017e93debb8de711caafc1c1a2 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Fri, 2 Jan 2026 12:08:42 +0000 Subject: [PATCH 21/43] Work group button refactor (#137) * Refactor: optimized group button and renamed to check button * Refactor: updated button import to the newer file name and class * Refactor: ran ruff formatter --------- Co-authored-by: Roberto --- .../lib/panels/widgets/babystepPage.py | 10 +- .../lib/panels/widgets/probeHelperPage.py | 22 ++- .../lib/ui/controlStackedWidget_ui.py | 26 ++-- .../lib/ui/utilitiesStackedWidget_ui.py | 24 +-- BlocksScreen/lib/utils/check_button.py | 87 +++++++++++ BlocksScreen/lib/utils/group_button.py | 146 ------------------ 6 files changed, 133 insertions(+), 182 deletions(-) create mode 100644 BlocksScreen/lib/utils/check_button.py delete mode 100644 BlocksScreen/lib/utils/group_button.py diff --git a/BlocksScreen/lib/panels/widgets/babystepPage.py b/BlocksScreen/lib/panels/widgets/babystepPage.py index b0fdfdca..6505c0aa 100644 --- a/BlocksScreen/lib/panels/widgets/babystepPage.py +++ b/BlocksScreen/lib/panels/widgets/babystepPage.py @@ -3,7 +3,7 @@ from lib.utils.blocks_button import BlocksCustomButton 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 PyQt6 import QtCore, QtGui, QtWidgets @@ -212,7 +212,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)) @@ -237,7 +237,7 @@ def setupUI(self): # 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)) @@ -261,7 +261,7 @@ def setupUI(self): ) # 0.05mm button - self.bbp_nozzle_offset_05 = GroupButton( + self.bbp_nozzle_offset_05 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box ) self.bbp_nozzle_offset_05.setMinimumSize(QtCore.QSize(100, 70)) @@ -285,7 +285,7 @@ def setupUI(self): ) # 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)) diff --git a/BlocksScreen/lib/panels/widgets/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/probeHelperPage.py index 5eeb3a3d..cc0f1be8 100644 --- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py +++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py @@ -4,7 +4,7 @@ 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 @@ -709,7 +709,9 @@ def _setupUi(self) -> None: self.bbp_offset_steps_buttons.setObjectName("bbp_offset_steps_buttons") # 0.1mm button - self.move_option_1 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) + self.move_option_1 = BlocksCustomCheckButton( + parent=self.bbp_offset_steps_buttons_group_box + ) self.move_option_1.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_1.setMaximumSize(QtCore.QSize(100, 60)) self.move_option_1.setText("0.01 mm") @@ -730,7 +732,9 @@ def _setupUi(self) -> None: ) # 0.01mm button - self.move_option_2 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) + self.move_option_2 = BlocksCustomCheckButton( + parent=self.bbp_offset_steps_buttons_group_box + ) self.move_option_2.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_2.setMaximumSize( QtCore.QSize(100, 60) @@ -752,7 +756,9 @@ def _setupUi(self) -> None: ) # 0.05mm button - self.move_option_3 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) + self.move_option_3 = BlocksCustomCheckButton( + parent=self.bbp_offset_steps_buttons_group_box + ) self.move_option_3.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_3.setMaximumSize( QtCore.QSize(100, 60) @@ -774,7 +780,9 @@ def _setupUi(self) -> None: ) # 0.025mm button - self.move_option_4 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) + self.move_option_4 = BlocksCustomCheckButton( + parent=self.bbp_offset_steps_buttons_group_box + ) self.move_option_4.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_4.setMaximumSize( QtCore.QSize(100, 60) @@ -796,7 +804,9 @@ def _setupUi(self) -> None: ) # 0.01mm button - self.move_option_5 = GroupButton(parent=self.bbp_offset_steps_buttons_group_box) + self.move_option_5 = BlocksCustomCheckButton( + parent=self.bbp_offset_steps_buttons_group_box + ) self.move_option_5.setMinimumSize(QtCore.QSize(100, 60)) self.move_option_5.setMaximumSize( QtCore.QSize(100, 60) diff --git a/BlocksScreen/lib/ui/controlStackedWidget_ui.py b/BlocksScreen/lib/ui/controlStackedWidget_ui.py index 88817b36..d76bb627 100644 --- a/BlocksScreen/lib/ui/controlStackedWidget_ui.py +++ b/BlocksScreen/lib/ui/controlStackedWidget_ui.py @@ -453,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) @@ -521,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) @@ -586,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) @@ -677,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) @@ -745,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) @@ -810,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) @@ -1196,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)) @@ -1239,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)) @@ -1279,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)) @@ -1326,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)) @@ -1369,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)) @@ -1409,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)) @@ -2190,5 +2190,5 @@ def retranslateUi(self, controlStackedWidget): 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/utilitiesStackedWidget_ui.py b/BlocksScreen/lib/ui/utilitiesStackedWidget_ui.py index 987ed2f8..bb2ca93e 100644 --- a/BlocksScreen/lib/ui/utilitiesStackedWidget_ui.py +++ b/BlocksScreen/lib/ui/utilitiesStackedWidget_ui.py @@ -821,7 +821,7 @@ def setupUi(self, utilitiesStackedWidget): 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) + self.btn2 = BlocksCustomCheckButton(parent=self.input_shaper_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -851,7 +851,7 @@ def setupUi(self, 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) + self.btn3 = BlocksCustomCheckButton(parent=self.input_shaper_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -879,7 +879,7 @@ def setupUi(self, utilitiesStackedWidget): 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) + self.btn5 = BlocksCustomCheckButton(parent=self.input_shaper_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -907,7 +907,7 @@ def setupUi(self, utilitiesStackedWidget): 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) + self.btn1 = BlocksCustomCheckButton(parent=self.input_shaper_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -936,7 +936,7 @@ def setupUi(self, utilitiesStackedWidget): 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) + self.btn4 = BlocksCustomCheckButton(parent=self.input_shaper_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -1000,7 +1000,7 @@ def setupUi(self, utilitiesStackedWidget): 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.am_zv = BlocksCustomCheckButton(parent=self.is_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -1027,7 +1027,7 @@ def setupUi(self, 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_ei = BlocksCustomCheckButton(parent=self.is_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -1052,7 +1052,7 @@ def setupUi(self, utilitiesStackedWidget): 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_mzv = BlocksCustomCheckButton(parent=self.is_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -1077,7 +1077,7 @@ def setupUi(self, utilitiesStackedWidget): 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 = BlocksCustomCheckButton(parent=self.is_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -1102,7 +1102,7 @@ def setupUi(self, utilitiesStackedWidget): 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_user_input = BlocksCustomCheckButton(parent=self.is_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -1128,7 +1128,7 @@ def setupUi(self, utilitiesStackedWidget): 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 = BlocksCustomCheckButton(parent=self.is_page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -1553,6 +1553,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/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/group_button.py b/BlocksScreen/lib/utils/group_button.py deleted file mode 100644 index 79f4251e..00000000 --- a/BlocksScreen/lib/utils/group_button.py +++ /dev/null @@ -1,146 +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): - """Widget name""" - return self._name - - @name.setter - def name(self, new_name) -> None: - self._name = new_name - self.setObjectName(new_name) - - def text(self) -> str | None: - """Widget text""" - return self._text - - def setText(self, text: str) -> None: - """Set widget text""" - self._text = text - self.update() # Force button update - return - - def setPixmap(self, pixmap: QtGui.QPixmap) -> None: - """Set widget pixmap""" - self.icon_pixmap = pixmap - self.repaint() - - def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]): - """Re-implemented method, paint widget""" - 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): - """Re-implemented method, set widget properties""" - if name == "name": - self._name = name - elif name == "text_color": - self.text_color = QtGui.QColor(value) - # return super().setProperty(name, value) - - def event(self, e: QtCore.QEvent) -> bool: - """Re-implemented method, filter events""" - 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) From db29de9c488b96f23ef40e81937f96e2548eeaab Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Fri, 2 Jan 2026 12:10:27 +0000 Subject: [PATCH 22/43] Bugfix `tunePage`: Add clickability and distinct icons to controllable fans (#138) * update fan icons and show only user-controllable fans Signed-off-by: Guilherme Costa * tunePage.py: improve icon management and add regex to display the correct icon for each fan type * networkWindow.py: reorganize imports and refactor icons management condition * Test signed commit --------- Signed-off-by: Guilherme Costa Co-authored-by: Guilherme Costa Co-authored-by: Hugo Costa --- BlocksScreen/lib/panels/widgets/tunePage.py | 64 +- .../lib/ui/resources/icon_resources.qrc | 1 + .../lib/ui/resources/icon_resources_rc.py | 963 ++++++++++-------- 3 files changed, 599 insertions(+), 429 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/tunePage.py b/BlocksScreen/lib/panels/widgets/tunePage.py index cb9a00c8..0fc8faf7 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): @@ -110,18 +111,26 @@ 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].lower() 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: + 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: { @@ -130,26 +139,17 @@ 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) _display_button = self.tune_display_buttons.get(name) if not _display_button: @@ -436,6 +436,12 @@ def _setupUI(self) -> None: self.tune_content.setContentsMargins(2, 0, 2, 0) self.setLayout(self.tune_content) self.setContentsMargins(2, 2, 2, 2) + + 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): diff --git a/BlocksScreen/lib/ui/resources/icon_resources.qrc b/BlocksScreen/lib/ui/resources/icon_resources.qrc index 3022fd4d..a3bf6d57 100644 --- a/BlocksScreen/lib/ui/resources/icon_resources.qrc +++ b/BlocksScreen/lib/ui/resources/icon_resources.qrc @@ -36,6 +36,7 @@ media/btn_icons/fan.svg media/btn_icons/fan_cage.svg + media/btn_icons/blower.svg media/btn_icons/standart_temperature.svg diff --git a/BlocksScreen/lib/ui/resources/icon_resources_rc.py b/BlocksScreen/lib/ui/resources/icon_resources_rc.py index 14285978..1346a1f1 100644 --- a/BlocksScreen/lib/ui/resources/icon_resources_rc.py +++ b/BlocksScreen/lib/ui/resources/icon_resources_rc.py @@ -11621,6 +11621,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\ @@ -25442,6 +25602,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 +25855,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\ @@ -25876,15 +26036,15 @@ 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\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x95\ +\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x92\ +\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x86\ +\x00\x00\x00\x46\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7b\ +\x00\x00\x00\x5a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x77\ +\x00\x00\x00\x6c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x71\ +\x00\x00\x00\x7e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x63\ +\x00\x00\x00\x90\x00\x02\x00\x00\x00\x01\x00\x00\x00\x59\ +\x00\x00\x00\xa2\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4a\ \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\ @@ -25945,148 +26105,149 @@ \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\x01\x98\x00\x02\x00\x00\x00\x03\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\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4b\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x4c\ +\x00\x00\x08\xd8\x00\x00\x00\x00\x00\x01\x00\x02\xd9\x51\ +\x00\x00\x09\x00\x00\x00\x00\x00\x00\x01\x00\x02\xde\x4c\ +\x00\x00\x09\x38\x00\x00\x00\x00\x00\x01\x00\x02\xe7\x20\ +\x00\x00\x09\x70\x00\x00\x00\x00\x00\x01\x00\x02\xef\xc4\ +\x00\x00\x09\xa0\x00\x00\x00\x00\x00\x01\x00\x02\xf7\x63\ +\x00\x00\x09\xc4\x00\x00\x00\x00\x00\x01\x00\x02\xff\x3f\ +\x00\x00\x09\xe8\x00\x00\x00\x00\x00\x01\x00\x03\x06\xf5\ +\x00\x00\x0a\x1c\x00\x00\x00\x00\x00\x01\x00\x03\x0f\x03\ +\x00\x00\x0a\x50\x00\x00\x00\x00\x00\x01\x00\x03\x16\xe5\ +\x00\x00\x0a\x84\x00\x00\x00\x00\x00\x01\x00\x03\x1e\xaf\ +\x00\x00\x0a\xbe\x00\x00\x00\x00\x00\x01\x00\x03\x26\x16\ +\x00\x00\x0a\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x29\xd3\ +\x00\x00\x0b\x10\x00\x00\x00\x00\x00\x01\x00\x03\x2d\xac\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x5a\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x08\x00\x00\x00\x5b\ +\x00\x00\x0b\x36\x00\x00\x00\x00\x00\x01\x00\x03\x33\xb5\ +\x00\x00\x0b\x62\x00\x00\x00\x00\x00\x01\x00\x03\x56\x39\ +\x00\x00\x0b\x90\x00\x00\x00\x00\x00\x01\x00\x03\x5c\x26\ +\x00\x00\x0b\xba\x00\x00\x00\x00\x00\x01\x00\x03\x5e\x46\ +\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x66\xde\ +\x00\x00\x0b\xfc\x00\x00\x00\x00\x00\x01\x00\x03\x76\x27\ +\x00\x00\x0c\x28\x00\x00\x00\x00\x00\x01\x00\x03\x7c\xa5\ +\x00\x00\x0c\x50\x00\x00\x00\x00\x00\x01\x00\x03\x87\x1f\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x64\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x65\ +\x00\x00\x0c\x86\x00\x00\x00\x00\x00\x01\x00\x03\x90\x66\ +\x00\x00\x0c\xb4\x00\x00\x00\x00\x00\x01\x00\x03\x93\x0d\ +\x00\x00\x0c\xe2\x00\x01\x00\x00\x00\x01\x00\x03\x9f\x8a\ +\x00\x00\x0d\x0e\x00\x00\x00\x00\x00\x01\x00\x03\xcd\x09\ +\x00\x00\x0d\x2e\x00\x00\x00\x00\x00\x01\x00\x03\xd1\xc0\ +\x00\x00\x0d\x60\x00\x01\x00\x00\x00\x01\x00\x04\x2a\xb9\ +\x00\x00\x0d\x92\x00\x00\x00\x00\x00\x01\x00\x04\x5f\x53\ +\x00\x00\x0d\xac\x00\x00\x00\x00\x00\x01\x00\x04\x64\x9d\ +\x00\x00\x0d\xc6\x00\x00\x00\x00\x00\x01\x00\x04\x6a\x2c\ +\x00\x00\x0d\xe0\x00\x00\x00\x00\x00\x01\x00\x04\x6f\x95\ +\x00\x00\x0d\xf8\x00\x00\x00\x00\x00\x01\x00\x04\x7b\x73\ +\x00\x00\x0e\x16\x00\x00\x00\x00\x00\x01\x00\x04\x81\x77\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x72\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x73\ +\x00\x00\x0e\x3e\x00\x00\x00\x00\x00\x01\x00\x04\x86\xac\ +\x00\x00\x0e\x52\x00\x00\x00\x00\x00\x01\x00\x04\x8c\xa9\ +\x00\x00\x0e\x64\x00\x00\x00\x00\x00\x01\x00\x04\x8e\x2f\ +\x00\x00\x0e\x76\x00\x00\x00\x00\x00\x01\x00\x04\x94\x29\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x78\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x79\ +\x00\x00\x0e\x8a\x00\x00\x00\x00\x00\x01\x00\x04\x96\x7f\ +\x00\x00\x0e\xb6\x00\x00\x00\x00\x00\x01\x00\x04\x9d\x66\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7c\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x09\x00\x00\x00\x7d\ +\x00\x00\x0e\xda\x00\x00\x00\x00\x00\x01\x00\x04\xa6\xca\ +\x00\x00\x0e\xfa\x00\x01\x00\x00\x00\x01\x00\x04\xa9\x76\ +\x00\x00\x0f\x1e\x00\x00\x00\x00\x00\x01\x00\x04\xb4\xc7\ +\x00\x00\x0f\x40\x00\x00\x00\x00\x00\x01\x00\x04\xbc\x6c\ +\x00\x00\x0f\x5c\x00\x00\x00\x00\x00\x01\x00\x04\xcd\x6c\ +\x00\x00\x0f\x80\x00\x00\x00\x00\x00\x01\x00\x04\xd6\xed\ +\x00\x00\x0f\xa0\x00\x00\x00\x00\x00\x01\x00\x04\xdc\x8a\ +\x00\x00\x0f\xc8\x00\x00\x00\x00\x00\x01\x00\x04\xe6\x53\ +\x00\x00\x0f\xe8\x00\x00\x00\x00\x00\x01\x00\x04\xea\x36\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x87\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x88\ +\x00\x00\x10\x04\x00\x00\x00\x00\x00\x01\x00\x04\xf0\x71\ +\x00\x00\x10\x34\x00\x00\x00\x00\x00\x01\x00\x04\xfa\x07\ +\x00\x00\x10\x64\x00\x00\x00\x00\x00\x01\x00\x05\x05\xe3\ +\x00\x00\x10\x88\x00\x00\x00\x00\x00\x01\x00\x05\x0c\x27\ +\x00\x00\x10\xb2\x00\x00\x00\x00\x00\x01\x00\x05\x13\xb0\ +\x00\x00\x10\xde\x00\x00\x00\x00\x00\x01\x00\x05\x1a\x0e\ +\x00\x00\x11\x14\x00\x00\x00\x00\x00\x01\x00\x05\x21\xfd\ +\x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x2f\xa0\ +\x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x05\x34\xd5\ +\x00\x00\x11\x32\x00\x00\x00\x00\x00\x01\x00\x05\x3e\xaa\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x93\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x01\x00\x00\x00\x94\ +\x00\x00\x11\x5a\x00\x00\x00\x00\x00\x01\x00\x05\x46\x74\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x96\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x28\x00\x00\x00\x97\ +\x00\x00\x11\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x4b\x3a\ +\x00\x00\x11\x90\x00\x00\x00\x00\x00\x01\x00\x05\x52\xee\ +\x00\x00\x11\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x55\x13\ +\x00\x00\x11\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x56\x93\ +\x00\x00\x11\xfc\x00\x00\x00\x00\x00\x01\x00\x05\x5e\x40\ +\x00\x00\x12\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x63\x15\ +\x00\x00\x12\x32\x00\x00\x00\x00\x00\x01\x00\x05\x64\x05\ +\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x66\xe9\ +\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x6d\x20\ +\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x82\x2d\ +\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x87\x1d\ +\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x8a\x1c\ +\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x90\x26\ +\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x93\x75\ +\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x9c\xf6\ +\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\xa2\xed\ +\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xfe\ +\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xc1\ +\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xb2\x75\ +\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xb5\xc3\ +\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xb9\x51\ +\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xbd\xf2\ +\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xc7\xc4\ +\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x0e\ +\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xd3\x15\ +\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xd3\xf9\ +\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x42\ +\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xdc\xf2\ +\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xe0\x36\ +\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xe1\x62\ +\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xe7\x23\ +\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x45\ +\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xee\x38\ +\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x3c\ +\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xf2\x5d\ +\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xf5\x31\ +\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xfd\x9d\ +\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x06\x05\x5d\ +\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x0a\x78\ +\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x0b\xcb\ " qt_resource_struct_v2 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x0f\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\x94\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x95\ \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\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x92\ \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\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x86\ \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\x46\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7b\ \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\x5a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x77\ \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\x6c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x71\ \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\x7e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x63\ \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\x90\x00\x02\x00\x00\x00\x01\x00\x00\x00\x59\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\xa2\x00\x02\x00\x00\x00\x01\x00\x00\x00\x49\ +\x00\x00\x00\xa2\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4a\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\xc0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x45\ \x00\x00\x00\x00\x00\x00\x00\x00\ @@ -26105,75 +26266,75 @@ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x12\ \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\x9a\x72\xe1\x94\x53\ \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\x9a\x72\xe1\x94\x4f\ \x00\x00\x01\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x09\x52\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x02\x20\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x35\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x02\x48\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x18\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x02\x70\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x06\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x02\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x14\x42\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ \x00\x00\x02\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xf7\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ \x00\x00\x02\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x22\x41\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ \x00\x00\x03\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x26\x90\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x03\x36\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6e\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x03\x52\x00\x00\x00\x00\x00\x01\x00\x00\x30\xca\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \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\x9a\x72\xe1\x94\x4f\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x21\ \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\x01\x9a\x72\xe1\x94\x57\ \x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x3c\xcf\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x03\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x43\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ \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\x9a\x72\xe1\x94\x53\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x26\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x27\ \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\x01\x9a\x72\xe1\x94\x53\ \x00\x00\x04\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x45\x3c\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x04\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x54\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ \x00\x00\x04\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x56\x16\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ \x00\x00\x04\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x58\x86\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ \x00\x00\x04\xea\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x1e\ -\x00\x00\x01\x98\xe1\xb8\x63\x0a\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ \x00\x00\x05\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x62\x7a\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ \x00\x00\x05\x48\x00\x00\x00\x00\x00\x01\x00\x00\x69\x07\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x05\x64\x00\x00\x00\x00\x00\x01\x00\x00\x6c\x03\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ \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\x9a\x72\xe1\x94\x53\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x32\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x33\ \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\x01\x9a\x72\xe1\x94\x57\ \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\x9a\x72\xe1\x94\x4f\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x02\x00\x00\x00\x36\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x06\x00\x00\x00\x3f\ @@ -26181,273 +26342,275 @@ \x00\x00\x05\xe4\x00\x02\x00\x00\x00\x07\x00\x00\x00\x38\ \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\x01\x9a\x72\xe1\x95\x8f\ \x00\x00\x06\x2a\x00\x00\x00\x00\x00\x01\x00\x02\x49\xaa\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ +\x00\x00\x01\x9a\x72\xe1\x95\x93\ \x00\x00\x06\x5e\x00\x00\x00\x00\x00\x01\x00\x02\x53\xd3\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ +\x00\x00\x01\x9a\x72\xe1\x95\x8f\ \x00\x00\x06\x98\x00\x00\x00\x00\x00\x01\x00\x02\x5e\x2b\ -\x00\x00\x01\x98\xe1\xb8\x63\x9a\ +\x00\x00\x01\x9a\x72\xe1\x95\x93\ \x00\x00\x06\xcc\x00\x00\x00\x00\x00\x01\x00\x02\x68\x43\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ +\x00\x00\x01\x9a\x72\xe1\x95\x8f\ \x00\x00\x07\x02\x00\x00\x00\x00\x00\x01\x00\x02\x72\x6c\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ +\x00\x00\x01\x9a\x72\xe1\x95\x93\ \x00\x00\x07\x3a\x00\x00\x00\x00\x00\x01\x00\x02\x7c\x82\ -\x00\x00\x01\x98\xe1\xb8\x63\x96\ +\x00\x00\x01\x9a\x72\xe1\x95\x93\ \x00\x00\x07\x70\x00\x00\x00\x00\x00\x01\x00\x02\x86\xe8\ -\x00\x00\x01\x98\xe1\xb8\x63\x06\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ \x00\x00\x07\x98\x00\x00\x00\x00\x00\x01\x00\x02\x91\x17\ -\x00\x00\x01\x98\xe1\xb8\x63\x0e\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ \x00\x00\x07\xc4\x00\x00\x00\x00\x00\x01\x00\x02\x9b\x5e\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x08\x00\x00\x00\x00\x00\x00\x01\x00\x02\x9d\x5f\ -\x00\x00\x01\x98\xe1\xb8\x62\xfe\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \x00\x00\x08\x2c\x00\x00\x00\x00\x00\x01\x00\x02\xb8\xa4\ -\x00\x00\x01\x98\xe1\xb8\x63\x02\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ \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\x9a\x72\xe1\x94\x4f\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x46\ \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\x98\x00\x02\x00\x00\x00\x03\x00\x00\x00\x47\ \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\x01\x9a\x72\xe1\x94\x4f\ \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\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x02\xcf\x7c\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x88\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\x0d\x00\x00\x00\x4b\ +\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x4c\ \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\x08\xd8\x00\x00\x00\x00\x00\x01\x00\x02\xd9\x51\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x09\x00\x00\x00\x00\x00\x00\x01\x00\x02\xde\x4c\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x09\x38\x00\x00\x00\x00\x00\x01\x00\x02\xe7\x20\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x09\x70\x00\x00\x00\x00\x00\x01\x00\x02\xef\xc4\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x09\xa0\x00\x00\x00\x00\x00\x01\x00\x02\xf7\x63\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x09\xc4\x00\x00\x00\x00\x00\x01\x00\x02\xff\x3f\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x09\xe8\x00\x00\x00\x00\x00\x01\x00\x03\x06\xf5\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0a\x1c\x00\x00\x00\x00\x00\x01\x00\x03\x0f\x03\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0a\x50\x00\x00\x00\x00\x00\x01\x00\x03\x16\xe5\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0a\x84\x00\x00\x00\x00\x00\x01\x00\x03\x1e\xaf\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0a\xbe\x00\x00\x00\x00\x00\x01\x00\x03\x26\x16\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0a\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x29\xd3\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0b\x10\x00\x00\x00\x00\x00\x01\x00\x03\x2d\xac\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x5a\ \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\x98\x00\x02\x00\x00\x00\x08\x00\x00\x00\x5b\ \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\x36\x00\x00\x00\x00\x00\x01\x00\x03\x33\xb5\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0b\x62\x00\x00\x00\x00\x00\x01\x00\x03\x56\x39\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0b\x90\x00\x00\x00\x00\x00\x01\x00\x03\x5c\x26\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0b\xba\x00\x00\x00\x00\x00\x01\x00\x03\x5e\x46\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x66\xde\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0b\xfc\x00\x00\x00\x00\x00\x01\x00\x03\x76\x27\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0c\x28\x00\x00\x00\x00\x00\x01\x00\x03\x7c\xa5\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x0c\x50\x00\x00\x00\x00\x00\x01\x00\x03\x87\x1f\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x64\ \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\x98\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x65\ \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\x86\x00\x00\x00\x00\x00\x01\x00\x03\x90\x66\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x0c\xb4\x00\x00\x00\x00\x00\x01\x00\x03\x93\x0d\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0c\xe2\x00\x01\x00\x00\x00\x01\x00\x03\x9f\x8a\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0d\x0e\x00\x00\x00\x00\x00\x01\x00\x03\xcd\x09\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x0d\x2e\x00\x00\x00\x00\x00\x01\x00\x03\xd1\xc0\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x0d\x60\x00\x01\x00\x00\x00\x01\x00\x04\x2a\xb9\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x0d\x92\x00\x00\x00\x00\x00\x01\x00\x04\x5f\x53\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0d\xac\x00\x00\x00\x00\x00\x01\x00\x04\x64\x9d\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0d\xc6\x00\x00\x00\x00\x00\x01\x00\x04\x6a\x2c\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0d\xe0\x00\x00\x00\x00\x00\x01\x00\x04\x6f\x95\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0d\xf8\x00\x00\x00\x00\x00\x01\x00\x04\x7b\x73\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0e\x16\x00\x00\x00\x00\x00\x01\x00\x04\x81\x77\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x72\ \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\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x73\ \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\x3e\x00\x00\x00\x00\x00\x01\x00\x04\x86\xac\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0e\x52\x00\x00\x00\x00\x00\x01\x00\x04\x8c\xa9\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0e\x64\x00\x00\x00\x00\x00\x01\x00\x04\x8e\x2f\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0e\x76\x00\x00\x00\x00\x00\x01\x00\x04\x94\x29\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x78\ \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\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x79\ \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\x0e\x8a\x00\x00\x00\x00\x00\x01\x00\x04\x96\x7f\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x0e\xb6\x00\x00\x00\x00\x00\x01\x00\x04\x9d\x66\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7c\ \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\x98\x00\x02\x00\x00\x00\x09\x00\x00\x00\x7d\ \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\x0e\xda\x00\x00\x00\x00\x00\x01\x00\x04\xa6\xca\ +\x00\x00\x01\x9b\x13\x3e\xbb\x62\ +\x00\x00\x0e\xfa\x00\x01\x00\x00\x00\x01\x00\x04\xa9\x76\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x0f\x1e\x00\x00\x00\x00\x00\x01\x00\x04\xb4\xc7\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x0f\x40\x00\x00\x00\x00\x00\x01\x00\x04\xbc\x6c\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x0f\x5c\x00\x00\x00\x00\x00\x01\x00\x04\xcd\x6c\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x0f\x80\x00\x00\x00\x00\x00\x01\x00\x04\xd6\xed\ +\x00\x00\x01\x9b\x13\x3e\xbb\x62\ +\x00\x00\x0f\xa0\x00\x00\x00\x00\x00\x01\x00\x04\xdc\x8a\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x0f\xc8\x00\x00\x00\x00\x00\x01\x00\x04\xe6\x53\ +\x00\x00\x01\x9b\x13\x3e\xbb\x62\ +\x00\x00\x0f\xe8\x00\x00\x00\x00\x00\x01\x00\x04\xea\x36\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x87\ \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\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x88\ \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\x04\x00\x00\x00\x00\x00\x01\x00\x04\xf0\x71\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x10\x34\x00\x00\x00\x00\x00\x01\x00\x04\xfa\x07\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x10\x64\x00\x00\x00\x00\x00\x01\x00\x05\x05\xe3\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x10\x88\x00\x00\x00\x00\x00\x01\x00\x05\x0c\x27\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x10\xb2\x00\x00\x00\x00\x00\x01\x00\x05\x13\xb0\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x10\xde\x00\x00\x00\x00\x00\x01\x00\x05\x1a\x0e\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x11\x14\x00\x00\x00\x00\x00\x01\x00\x05\x21\xfd\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x2f\xa0\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x05\x34\xd5\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x11\x32\x00\x00\x00\x00\x00\x01\x00\x05\x3e\xaa\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x93\ \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\x98\x00\x02\x00\x00\x00\x01\x00\x00\x00\x94\ \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\x5a\x00\x00\x00\x00\x00\x01\x00\x05\x46\x74\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x96\ \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\x98\x00\x02\x00\x00\x00\x28\x00\x00\x00\x97\ \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\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x4b\x3a\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x11\x90\x00\x00\x00\x00\x00\x01\x00\x05\x52\xee\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x11\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x55\x13\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x11\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x56\x93\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x11\xfc\x00\x00\x00\x00\x00\x01\x00\x05\x5e\x40\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x12\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x63\x15\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x12\x32\x00\x00\x00\x00\x00\x01\x00\x05\x64\x05\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x66\xe9\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x6d\x20\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x82\x2d\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x87\x1d\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x8a\x1c\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x90\x26\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x93\x75\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x9c\xf6\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\xa2\xed\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xfe\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xc1\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xb2\x75\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xb5\xc3\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xb9\x51\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xbd\xf2\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xc7\xc4\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x0e\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xd3\x15\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xd3\xf9\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x42\ +\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xdc\xf2\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xe0\x36\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xe1\x62\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xe7\x23\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x45\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xee\x38\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x3c\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xf2\x5d\ +\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xf5\x31\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xfd\x9d\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x06\x05\x5d\ +\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x0a\x78\ +\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x0b\xcb\ +\x00\x00\x01\x9a\x72\xe1\x94\x4b\ " qt_version = [int(v) for v in QtCore.qVersion().split('.')] From c3bfa568648ce68b75836e466151f92e4886339a Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Fri, 2 Jan 2026 12:23:30 +0000 Subject: [PATCH 23/43] Work display info UI (#140) * Bugfix: fixed total layer/current layer fallback * Add: added font size and family * bugfix: where the fallback was allways active * Refactor: ran ruff formatter --------- Signed-off-by: Hugo Costa Co-authored-by: Roberto Co-authored-by: Hugo Costa --- BlocksScreen/helper_methods.py | 5 +- .../lib/panels/widgets/jobStatusPage.py | 56 +++++++++++++------ BlocksScreen/lib/utils/display_button.py | 8 +++ 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/BlocksScreen/helper_methods.py b/BlocksScreen/helper_methods.py index 8de5fc13..dafabd96 100644 --- a/BlocksScreen/helper_methods.py +++ b/BlocksScreen/helper_methods.py @@ -254,7 +254,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) diff --git a/BlocksScreen/lib/panels/widgets/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py index 9f74ba6e..d161fa22 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -58,6 +58,7 @@ class JobStatusWidget(QtWidgets.QWidget): def __init__(self, parent) -> None: super().__init__(parent) self.thumbnail_graphics = [] + self.layer_fallback = False self._setupUI() self.cancel_print_dialog = dialogPage.DialogPage(self) self.tune_menu_btn.clicked.connect(self.tune_clicked.emit) @@ -180,10 +181,8 @@ def on_print_start(self, file: str) -> None: @QtCore.pyqtSlot(dict, name="on_fileinfo") def on_fileinfo(self, fileinfo: dict) -> None: """Handle received file information/metadata""" - if not self.isVisible(): - return - self.total_layers = str(fileinfo.get("layer_count", "?")) - self.layer_display_button.setText("?") + 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", [])) @@ -269,13 +268,23 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None: if not self.isVisible(): return if isinstance(value, dict): + self.layer_fallback = False if "total_layer" in value.keys(): - self.total_layers = value.get("total_layer", "?") - self.layer_display_button.secondary_text = str(self.total_layers) + self.total_layers = value["total_layer"] + 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(): - _current_layer = value.get("current_layer", None) - if _current_layer: + if value["current_layer"] is not None: + _current_layer = value["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: _time = estimate_print_time(int(value)) @@ -293,17 +302,28 @@ def on_gcode_move_update(self, field: str, value: list) -> None: return if "gcode_position" in field: if self._internal_print_status == "printing": - _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( + 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) - ), - ) - self.layer_display_button.setText( - f"{int(_current_layer)}" if _current_layer != -1 else "?" - ) + ) + _current_layer = calculate_current_layer( + z_position=value[2], + 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 "---" + ) @QtCore.pyqtSlot(str, float, name="virtual_sdcard_update") @QtCore.pyqtSlot(str, bool, name="virtual_sdcard_update") diff --git a/BlocksScreen/lib/utils/display_button.py b/BlocksScreen/lib/utils/display_button.py index 1f798dfe..5bda40aa 100644 --- a/BlocksScreen/lib/utils/display_button.py +++ b/BlocksScreen/lib/utils/display_button.py @@ -174,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 @@ -240,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 From 11c85729d63deb869be00ac4728110b9ec577143 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Mon, 5 Jan 2026 17:17:39 +0000 Subject: [PATCH 24/43] Work input shapper rework (#134) * ADD: added input shaper page * bugfix: button blocking clicks * ADD: added input shapper logic * ADD: added input shapper page to .ui * Add: basepopup.py Refactor: loadwidget to have similar logic to loadpage Removed: dialog page and loadPage * Refactor: change popup to Basepopup or/with loadwidget * Rev: removed misstype --------- Co-authored-by: Roberto --- BlocksScreen/lib/panels/controlTab.py | 35 +- BlocksScreen/lib/panels/filamentTab.py | 16 +- BlocksScreen/lib/panels/printTab.py | 24 +- BlocksScreen/lib/panels/utilitiesTab.py | 280 +++-- BlocksScreen/lib/panels/widgets/basePopup.py | 256 +++++ BlocksScreen/lib/panels/widgets/dialogPage.py | 167 --- .../lib/panels/widgets/inputshaperPage.py | 459 +++++++++ .../lib/panels/widgets/jobStatusPage.py | 4 +- BlocksScreen/lib/panels/widgets/loadPage.py | 180 ---- BlocksScreen/lib/panels/widgets/loadWidget.py | 145 ++- .../lib/panels/widgets/optionCardWidget.py | 3 + .../lib/panels/widgets/probeHelperPage.py | 12 +- BlocksScreen/lib/panels/widgets/updatePage.py | 16 +- BlocksScreen/lib/ui/utilitiesStackedWidget.ui | 968 +----------------- .../lib/ui/utilitiesStackedWidget_ui.py | 659 +++--------- 15 files changed, 1278 insertions(+), 1946 deletions(-) create mode 100644 BlocksScreen/lib/panels/widgets/basePopup.py delete mode 100644 BlocksScreen/lib/panels/widgets/dialogPage.py create mode 100644 BlocksScreen/lib/panels/widgets/inputshaperPage.py delete mode 100644 BlocksScreen/lib/panels/widgets/loadPage.py diff --git a/BlocksScreen/lib/panels/controlTab.py b/BlocksScreen/lib/panels/controlTab.py index 99f8a3c5..c17cb176 100644 --- a/BlocksScreen/lib/panels/controlTab.py +++ b/BlocksScreen/lib/panels/controlTab.py @@ -4,7 +4,8 @@ from functools import partial import re from lib.moonrakerComm import MoonWebSocket -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.numpadPage import CustomNumpad from lib.panels.widgets.printcorePage import SwapPrintcorePage from lib.panels.widgets.probeHelperPage import ProbeHelper @@ -79,8 +80,12 @@ 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) @@ -415,17 +420,17 @@ def handle_printcoreupdate(self, value: dict): 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") + self.loadwidget.set_status_message("Unloading print core") if value["swapping"] == "cleaning": - self.loadpage.set_status_message("Cleaning print core") + self.loadwidget.set_status_message("Cleaning print core") def _handle_gcode_response(self, messages: list): """Handle gcode response for Z-tilt adjustment""" @@ -448,21 +453,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") @@ -479,8 +486,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""" @@ -652,7 +659,7 @@ def on_toolhead_update(self, field: str, values: list) -> None: 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 + 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 4ab358fa..1271375e 100644 --- a/BlocksScreen/lib/panels/filamentTab.py +++ b/BlocksScreen/lib/panels/filamentTab.py @@ -6,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 @@ -41,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 = {} @@ -129,16 +133,16 @@ def on_extruder_update( 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) - self.loadscreen.set_status_message( + 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) - self.loadscreen.set_status_message("Heating up \n Please wait") + self.loadwidget.set_status_message("Heating up \n Please wait") @QtCore.pyqtSlot(bool, name="on_load_filament") def on_load_filament(self, status: bool): diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py index 65f0030d..104a765a 100644 --- a/BlocksScreen/lib/panels/printTab.py +++ b/BlocksScreen/lib/panels/printTab.py @@ -14,8 +14,8 @@ from lib.panels.widgets.slider_selector_page import SliderPage 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 lib.panels.widgets.loadWidget import LoadingOverlayWidget +from lib.panels.widgets.basePopup import BasePopup from configfile import BlocksScreenConfig, get_configparser from PyQt6 import QtCore, QtGui, QtWidgets @@ -85,14 +85,18 @@ def __init__( 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.confirmPage_widget = ConfirmWidget(self) self.addWidget(self.confirmPage_widget) @@ -295,18 +299,18 @@ def on_slidePage_request( @QtCore.pyqtSlot(str, name="delete_file") def delete_file(self, filename: str, directory: str = "gcodes") -> None: """Handle Delete file signal, shows confirmation dialog""" - self.dialogPage.set_message("Are you sure you want to delete this file?") - self.dialogPage.accepted.connect( + 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.dialogPage.open() + self.BasePopup.open() 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.dialogPage.disconnect() + self.BasePopup.disconnect() def paintEvent(self, a0: QtGui.QPaintEvent) -> None: """Widget painting""" @@ -341,7 +345,7 @@ def handle_cancel_print(self) -> None: self.on_cancel_print.emit() self.loadscreen.show() self.loadscreen.setModal(True) - self.loadscreen.set_status_message("Cancelling print...\nPlease wait") + 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 diff --git a/BlocksScreen/lib/panels/utilitiesTab.py b/BlocksScreen/lib/panels/utilitiesTab.py index 4797b2ff..1c9d0fa2 100644 --- a/BlocksScreen/lib/panels/utilitiesTab.py +++ b/BlocksScreen/lib/panels/utilitiesTab.py @@ -1,11 +1,9 @@ -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 @@ -14,6 +12,13 @@ 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: @@ -41,8 +46,6 @@ def get_gcode(self, name: str) -> str: class Process(Enum): - """Printer Process""" - FAN = auto() AXIS = auto() BED_HEATER = auto() @@ -113,25 +116,34 @@ 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) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.loadPage.add_widget(self.loadwidget) self.update_page = UpdatePage(self) self.addWidget(self.update_page) - self.panel.utilities_input_shaper_btn.hide() + self.is_page = InputShaperPage(self) + self.addWidget(self.is_page) + + self.dialog_page = BasePopup(self, dialog=True, floating=True) + self.addWidget(self.dialog_page) + # --- 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) @@ -145,7 +157,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) @@ -168,20 +179,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 --- @@ -193,6 +190,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 ) @@ -220,6 +218,7 @@ def __init__( self.update_page.request_refresh_update[str].connect( self.ws.api.refresh_update_status ) + self.printer.gcode_response.connect(self.handle_gcode_response) 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) @@ -234,6 +233,156 @@ def __init__( QtGui.QPixmap(":/system/media/btn_icons/update-software-icon.svg") ) + 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.automatic_is.continue_clicked.connect( + lambda: self.handle_is("SHAPER_CALIBRATE") + ) + + 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.manual_is.continue_clicked.connect(lambda: self.handle_is("")) + + self.is_types: dict = {} + self.is_aut_types: dict = {} + + 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 on_dialog_button_clicked(self, button_name: str) -> None: + print(button_name) + """Handle dialog button clicks""" + if button_name == "Confirm": + self.handle_is("SHAPER_CALIBRATE AXIS=Y") + elif button_name == "Cancel": + self.handle_is("SHAPER_CALIBRATE AXIS=X") + + 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) + if gcode == "": + print("manual Input Shaper Selected") + 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""" @@ -287,21 +436,6 @@ def on_gcode_move_update(self, name: str, value: list) -> None: 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]): - """Handle numpad change""" - 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 @@ -512,74 +646,6 @@ def save_led_state(self): led_state: LedState = self.objects["leds"][self.current_object] self.run_gcode_signal.emit(led_state.get_gcode(self.current_object)) - def run_resonance_test(self, axis: str) -> None: - """Perform Input Shaper Measure resonances test""" - 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: - ... - return results - - def apply_input_shaper_selection(self) -> None: - """Apply input shaper results""" - 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 @@ -617,7 +683,7 @@ def troubleshoot_request(self) -> None: def show_waiting_page(self, page_to_go_to: int, label: str, time_ms: int): """Show placeholder page""" - self.loadPage.label.setText(label) + self.loadwidget.set_status_message(label) self.loadPage.show() QtCore.QTimer.singleShot(time_ms, lambda: self.change_page(page_to_go_to)) diff --git a/BlocksScreen/lib/panels/widgets/basePopup.py b/BlocksScreen/lib/panels/widgets/basePopup.py new file mode 100644 index 00000000..e9ebe5d3 --- /dev/null +++ b/BlocksScreen/lib/panels/widgets/basePopup.py @@ -0,0 +1,256 @@ +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.Popup | QtCore.Qt.WindowType.FramelessWindowHint + ) + 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) + 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 + + self.confirm_button.clicked.connect(self.accept) + self.cancel_button.clicked.connect(self.reject) + + 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._update_button_style() diff --git a/BlocksScreen/lib/panels/widgets/dialogPage.py b/BlocksScreen/lib/panels/widgets/dialogPage.py deleted file mode 100644 index 45d067a4..00000000 --- a/BlocksScreen/lib/panels/widgets/dialogPage.py +++ /dev/null @@ -1,167 +0,0 @@ -import typing - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class DialogPage(QtWidgets.QDialog): - """Simple confirmation dialog 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, - ) -> None: - super().__init__(parent) - self._setupUI() - self.setWindowFlags( - QtCore.Qt.WindowType.Popup | QtCore.Qt.WindowType.FramelessWindowHint - ) - self.setAttribute( - QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True - ) # Make background transparent - self.setWindowModality( # Force window modality to block input to other windows - QtCore.Qt.WindowModality.WindowModal - ) - self.confirm_button.clicked.connect(self.accept) - self.cancel_button.clicked.connect(self.reject) - self.setModal(True) - self.update() - - def set_message(self, message: str) -> None: - """Set dialog text message""" - self.label.setText(message) - - 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() - 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) - 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 resizeEvent(self, event: QtGui.QResizeEvent) -> None: - """Re-implemented method, handle resize event""" - super().resizeEvent(event) - main_window = self._get_mainWindow_widget() - if main_window is None: - return - width = int(main_window.width() * self.x_offset) - height = int(main_window.height() * self.y_offset) - label_x = (self.width() - width) // 2 - label_y = int(height / 4) - 20 # Move the label to the top (adjust as needed) - self.label.setGeometry(label_x, -label_y, width, height) - 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 open(self): - """Re-implemented method, open widget""" - self._geometry_calc() - return super().open() - - def show(self) -> None: - """Re-implemented method, show widget""" - self._geometry_calc() - return super().show() - - def paintEvent(self, event: QtGui.QPaintEvent) -> None: - """Re-implemented method, paint widget""" - 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)) - ) # Semi-transparent dark gray - border_color = QtGui.QColor(128, 128, 128) # Gray color - 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.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) - 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.cancel_button.setFont(button_font) - 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, - ) diff --git a/BlocksScreen/lib/panels/widgets/inputshaperPage.py b/BlocksScreen/lib/panels/widgets/inputshaperPage.py new file mode 100644 index 00000000..d96f543a --- /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 Acceleration:") + 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 d161fa22..bbce76a3 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -2,7 +2,7 @@ import typing 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 @@ -60,7 +60,7 @@ def __init__(self, parent) -> None: self.thumbnail_graphics = [] self.layer_fallback = False self._setupUI() - self.cancel_print_dialog = dialogPage.DialogPage(self) + 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) diff --git a/BlocksScreen/lib/panels/widgets/loadPage.py b/BlocksScreen/lib/panels/widgets/loadPage.py deleted file mode 100644 index f5fd7963..00000000 --- a/BlocksScreen/lib/panels/widgets/loadPage.py +++ /dev/null @@ -1,180 +0,0 @@ -import enum -from configfile import BlocksScreenConfig, get_configparser -from PyQt6 import QtCore, QtGui, QtWidgets - - -class LoadScreen(QtWidgets.QDialog): - class AnimationGIF(enum.Enum): - """Animation type""" - - 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: - """Set widget status message""" - self.label.setText(message) - - 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 - 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: - """Re-implemented method, close widget""" - 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: - """Re-implemented method, size hint""" - 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 paintEvent(self, a0: QtGui.QPaintEvent) -> None: - """Re-implemented method, paint widget""" - 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: - """Re-implemented method, handle widget resize event""" - 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: - """Re-implemented method, show widget""" - 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 9a5f3d68..c23d4a5d 100644 --- a/BlocksScreen/lib/panels/widgets/loadWidget.py +++ b/BlocksScreen/lib/panels/widgets/loadWidget.py @@ -1,10 +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" + def __init__( self, parent: QtWidgets.QWidget, + initial_anim_type: AnimationGIF = AnimationGIF.PLACEHOLDER, ) -> None: super().__init__(parent) @@ -17,12 +31,66 @@ def __init__( 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) @@ -32,6 +100,12 @@ def close(self) -> bool: 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,55 +122,64 @@ 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: - """Re-implemented method, handle resize event""" - 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.gifshow.setGeometry(margin, margin, gifshow_width, gifshow_height) 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) + + gifshow_x = (self.width() - size) // 2 + gifshow_y = (gifshow_max_height - size) // 2 + + self.gifshow.setGeometry(gifshow_x, gifshow_y, size, size) def show(self) -> None: """Re-implemented method, show widget""" - self.timer.start() self.repaint() return super().show() 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() diff --git a/BlocksScreen/lib/panels/widgets/optionCardWidget.py b/BlocksScreen/lib/panels/widgets/optionCardWidget.py index 8c79ee68..6fbfe20d 100644 --- a/BlocksScreen/lib/panels/widgets/optionCardWidget.py +++ b/BlocksScreen/lib/panels/widgets/optionCardWidget.py @@ -38,6 +38,9 @@ def __init__( 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) diff --git a/BlocksScreen/lib/panels/widgets/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/probeHelperPage.py index cc0f1be8..6a632f4d 100644 --- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py +++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py @@ -7,7 +7,8 @@ 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): @@ -48,7 +49,12 @@ class ProbeHelper(QtWidgets.QWidget): def __init__(self, parent: QtWidgets.QWidget) -> None: super().__init__(parent) - self.Loadscreen = LoadScreen(self) + + self.Loadscreen = BasePopup(self) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.PLACEHOLDER + ) + self.Loadscreen.add_widget(self.loadwidget) self.setObjectName("probe_offset_page") self._setupUi() self.inductive_icon = QtGui.QPixmap( @@ -393,7 +399,7 @@ def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None: 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") diff --git a/BlocksScreen/lib/panels/widgets/updatePage.py b/BlocksScreen/lib/panels/widgets/updatePage.py index 534bbabe..5ec9cebd 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() @@ -93,7 +97,7 @@ 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) @@ -151,10 +155,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) 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 bb2ca93e..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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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 = BlocksCustomCheckButton(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")) From 5339d514b92533699271693c1eaefbb6defaf31a Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Mon, 12 Jan 2026 17:43:56 +0000 Subject: [PATCH 25/43] Work connnectivity update page (#139) * ADD: added update page to connection page * ADD: added reload button to updatepage Refactor : evertime it reload shows loadwidget Refactor: info frame layout * Refactor: when update page gets refreshed * Added: icon_button pressed state Refactor: resized icon buttons on connectionwindow * Rev: removed updatepage instances * Fix: fixed loadwidget not hiding * add: added update page instance * Refactor: ran ruff formatter * refactor: removed unused import * Refactor: ran ruff formatter --------- Co-authored-by: Roberto --- BlocksScreen/lib/moonrakerComm.py | 15 +- BlocksScreen/lib/panels/mainWindow.py | 43 +- BlocksScreen/lib/panels/utilitiesTab.py | 39 +- .../lib/panels/widgets/connectionPage.py | 61 +- BlocksScreen/lib/panels/widgets/updatePage.py | 88 +- BlocksScreen/lib/ui/connectionWindow.ui | 1145 +++++++++-------- BlocksScreen/lib/ui/connectionWindow_ui.py | 268 ++-- BlocksScreen/lib/utils/icon_button.py | 78 +- 8 files changed, 952 insertions(+), 785 deletions(-) diff --git a/BlocksScreen/lib/moonrakerComm.py b/BlocksScreen/lib/moonrakerComm.py index 78fba08e..ba298ba7 100644 --- a/BlocksScreen/lib/moonrakerComm.py +++ b/BlocksScreen/lib/moonrakerComm.py @@ -734,13 +734,16 @@ def update_status(self, refresh: bool = False) -> bool: @QtCore.pyqtSlot(name="update-refresh") @QtCore.pyqtSlot(str, name="update-refresh") - def refresh_update_status(self, name: str = "") -> bool: + def refresh_update_status(self, name: str = None) -> bool: """Refresh packages state""" - if not isinstance(name, str) or not name: - return False - return self._ws.send_request( - method="machine.update.refresh", params={"name": name} - ) + 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: diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index 18549a39..3c65bd7b 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 * @@ -58,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" ) @@ -77,6 +79,8 @@ 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 @@ -152,7 +156,23 @@ 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"): @@ -160,6 +180,27 @@ def __init__(self): 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): """Slot for cancel print signal""" diff --git a/BlocksScreen/lib/panels/utilitiesTab.py b/BlocksScreen/lib/panels/utilitiesTab.py index 1c9d0fa2..a9b6652f 100644 --- a/BlocksScreen/lib/panels/utilitiesTab.py +++ b/BlocksScreen/lib/panels/utilitiesTab.py @@ -5,7 +5,6 @@ from lib.moonrakerComm import MoonWebSocket 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 @@ -87,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: @@ -126,8 +129,9 @@ def __init__( ) self.loadPage.add_widget(self.loadwidget) - self.update_page = UpdatePage(self) - self.addWidget(self.update_page) + self.panel.update_btn.clicked.connect( + lambda: self.show_update_page[bool].emit(False) + ) self.is_page = InputShaperPage(self) self.addWidget(self.is_page) @@ -142,14 +146,12 @@ def __init__( 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 ) @@ -202,33 +204,6 @@ 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.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 - ) - self.update_page.request_recover_repo[str, bool].connect( - self.ws.api.recover_corrupt_repo - ) - self.update_page.request_refresh_update.connect( - self.ws.api.refresh_update_status - ) - self.update_page.request_refresh_update[str].connect( - self.ws.api.refresh_update_status - ) - self.printer.gcode_response.connect(self.handle_gcode_response) - 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.panel.update_btn.setPixmap( QtGui.QPixmap(":/system/media/btn_icons/update-software-icon.svg") ) diff --git a/BlocksScreen/lib/panels/widgets/connectionPage.py b/BlocksScreen/lib/panels/widgets/connectionPage.py index 0b7e054d..1e9c32d1 100644 --- a/BlocksScreen/lib/panels/widgets/connectionPage.py +++ b/BlocksScreen/lib/panels/widgets/connectionPage.py @@ -13,11 +13,18 @@ class ConnectionPage(QtWidgets.QFrame): 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") + 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" @@ -55,9 +62,16 @@ def show_panel(self, reason: str | None = None): self.text_update() return False + 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") @@ -69,6 +83,7 @@ def on_klippy_connection(self, connected: bool): @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(): @@ -87,7 +102,6 @@ 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") @@ -98,21 +112,22 @@ def on_websocket_connecting(self, attempt: int): @QtCore.pyqtSlot(name="on_websocket_connection_achieved") def on_websocket_connection_achieved(self): """Handle websocket connected state""" + self.dot_timer.stop() self.panel.connectionTextBox.setText("Moonraker Connected\n Klippy not ready") - self.hide() - @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") @@ -125,41 +140,34 @@ 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 - - if text == 1: - if self.dot_timer.isActive(): - self.dot_timer.stop() - return - self.dot_timer.start() + return + self.dot_count = 0 - 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: + """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): @@ -171,16 +179,19 @@ def webhook_update(self, state: str, message: str): 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/updatePage.py b/BlocksScreen/lib/panels/widgets/updatePage.py index 5ec9cebd..4857f92e 100644 --- a/BlocksScreen/lib/panels/widgets/updatePage.py +++ b/BlocksScreen/lib/panels/widgets/updatePage.py @@ -82,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 @@ -90,7 +93,7 @@ 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: @@ -101,6 +104,14 @@ def handle_ongoing_update(self) -> None: 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) @@ -116,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: @@ -169,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() @@ -194,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: @@ -228,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. @@ -248,6 +274,7 @@ def handle_update_message(self, message: dict) -> 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 @@ -285,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)) @@ -304,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() @@ -476,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) @@ -560,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) @@ -570,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)) @@ -583,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/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/utils/icon_button.py b/BlocksScreen/lib/utils/icon_button.py index a60a7f1d..160f1f80 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,6 +13,7 @@ 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): @@ -42,6 +43,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) @@ -49,38 +54,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( @@ -99,9 +102,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(), @@ -112,12 +115,9 @@ 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() From 0e7a3e7881d98688ea12c156756e1e8f03959167 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Mon, 12 Jan 2026 17:53:33 +0000 Subject: [PATCH 26/43] Bugfix/tab unlocking (#147) * bugfix: fixed event config * Rev: removed on_cancel_print from handle cancel print --------- Co-authored-by: Roberto --- BlocksScreen/lib/panels/mainWindow.py | 9 ++++----- BlocksScreen/lib/panels/printTab.py | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index 3c65bd7b..f6fbc2f9 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -661,7 +661,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() @@ -682,10 +681,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/printTab.py b/BlocksScreen/lib/panels/printTab.py index 104a765a..2a885362 100644 --- a/BlocksScreen/lib/panels/printTab.py +++ b/BlocksScreen/lib/panels/printTab.py @@ -342,7 +342,6 @@ def setProperty(self, name: str, value: typing.Any) -> bool: 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.setModal(True) self.loadwidget.set_status_message("Cancelling print...\nPlease wait") From 4ece7042a8edab1c7cba339a39462206efa928d0 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Mon, 12 Jan 2026 18:00:53 +0000 Subject: [PATCH 27/43] jobStatusPage: only load filedata when printer is printing (#150) Authored-by: Guilherme Costa --- BlocksScreen/lib/panels/widgets/jobStatusPage.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/BlocksScreen/lib/panels/widgets/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py index bbce76a3..f19b5269 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -1,5 +1,6 @@ import logging import typing + import events from helper_methods import calculate_current_layer, estimate_print_time from lib.panels.widgets.basePopup import BasePopup @@ -181,6 +182,8 @@ def on_print_start(self, file: str) -> None: @QtCore.pyqtSlot(dict, name="on_fileinfo") def on_fileinfo(self, fileinfo: dict) -> None: """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) From fa53e8c85ec9557c4c5270930fb7f83116549198 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Wed, 14 Jan 2026 10:25:35 +0000 Subject: [PATCH 28/43] Bugfix/inputshaper page (#148) * bugfix: multiple connects signals * bugifx: loadcreen having button * bugfix: fixed input shaper after merge * Bugfix: being able to click behind the popup * Refactor: ran ruff formatter --------- Authored-by: Roberto --- BlocksScreen/lib/panels/utilitiesTab.py | 20 +++++++++---------- BlocksScreen/lib/panels/widgets/basePopup.py | 13 ++++++------ .../lib/panels/widgets/inputshaperPage.py | 2 +- .../lib/panels/widgets/probeHelperPage.py | 2 +- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/BlocksScreen/lib/panels/utilitiesTab.py b/BlocksScreen/lib/panels/utilitiesTab.py index a9b6652f..37fa6f61 100644 --- a/BlocksScreen/lib/panels/utilitiesTab.py +++ b/BlocksScreen/lib/panels/utilitiesTab.py @@ -123,7 +123,7 @@ def __init__( # --- UI Setup --- self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.loadPage = BasePopup(self) + self.loadPage = BasePopup(self, dialog=False) self.loadwidget = LoadingOverlayWidget( self, LoadingOverlayWidget.AnimationGIF.DEFAULT ) @@ -208,6 +208,7 @@ def __init__( QtGui.QPixmap(":/system/media/btn_icons/update-software-icon.svg") ) + # ---- Input Shaper ---- self.automatic_is = OptionCard( self, "Automatic\nInput Shaper", @@ -236,6 +237,12 @@ def __init__( self.is_types: dict = {} self.is_aut_types: dict = {} + self.dialog_page.accepted.connect( + lambda: self.handle_is("SHAPER_CALIBRATE AXIS=Y") + ) + self.dialog_page.rejected.connect( + lambda: self.handle_is("SHAPER_CALIBRATE AXIS=X") + ) self.is_page.action_btn.clicked.connect( lambda: self.change_page(self.indexOf(self.panel.input_shaper_page)) @@ -324,21 +331,12 @@ def handle_gcode_response(self, data: list[str]) -> None: self.loadPage.hide() return - def on_dialog_button_clicked(self, button_name: str) -> None: - print(button_name) - """Handle dialog button clicks""" - if button_name == "Confirm": - self.handle_is("SHAPER_CALIBRATE AXIS=Y") - elif button_name == "Cancel": - self.handle_is("SHAPER_CALIBRATE AXIS=X") - 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) - if gcode == "": - print("manual Input Shaper Selected") + elif gcode == "": self.dialog_page.confirm_background_color("#dfdfdf") self.dialog_page.cancel_background_color("#dfdfdf") self.dialog_page.cancel_font_color("#000000") diff --git a/BlocksScreen/lib/panels/widgets/basePopup.py b/BlocksScreen/lib/panels/widgets/basePopup.py index e9ebe5d3..a9a4d188 100644 --- a/BlocksScreen/lib/panels/widgets/basePopup.py +++ b/BlocksScreen/lib/panels/widgets/basePopup.py @@ -26,13 +26,13 @@ def __init__( dialog: bool = True, ) -> None: super().__init__(parent) - self.setWindowFlags( - QtCore.Qt.WindowType.Popup | QtCore.Qt.WindowType.FramelessWindowHint + 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" @@ -45,6 +45,7 @@ def __init__( if floating: self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True) + self.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) else: self.setStyleSheet( """ @@ -59,9 +60,6 @@ def _update_button_style(self) -> None: if not self.dialog: return - self.confirm_button.clicked.connect(self.accept) - self.cancel_button.clicked.connect(self.reject) - if not self.floating: self.confirm_button.setStyleSheet( f""" @@ -252,5 +250,6 @@ def setupUI(self) -> None: 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/inputshaperPage.py b/BlocksScreen/lib/panels/widgets/inputshaperPage.py index d96f543a..539dcaf4 100644 --- a/BlocksScreen/lib/panels/widgets/inputshaperPage.py +++ b/BlocksScreen/lib/panels/widgets/inputshaperPage.py @@ -398,7 +398,7 @@ def _setupUI(self) -> None: 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 Acceleration:") + 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) diff --git a/BlocksScreen/lib/panels/widgets/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/probeHelperPage.py index 6a632f4d..a3dea377 100644 --- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py +++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py @@ -50,7 +50,7 @@ class ProbeHelper(QtWidgets.QWidget): def __init__(self, parent: QtWidgets.QWidget) -> None: super().__init__(parent) - self.Loadscreen = BasePopup(self) + self.Loadscreen = BasePopup(self, dialog=False) self.loadwidget = LoadingOverlayWidget( self, LoadingOverlayWidget.AnimationGIF.PLACEHOLDER ) From 1465ea1cd34514ce490ca84b6e3308da9f034ed0 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Wed, 14 Jan 2026 10:27:06 +0000 Subject: [PATCH 29/43] work popup features (#144) * Fix: fixed wrong if check for popups * feat: first version of userinput for popup * feat: added new error and info icons * feat: added ClearPixmap to icon_button * Refactor: finished popup userInput feat * Refactor: formated _handle_error_message message ignored unknow type popup * Refactor: popups only shows error messages and added popup whitelist * Refactor:run ruff formater * Rev: removed prints * refactor: removed bare except * bugfix: fixed wordwrap * Refactor: ran ruff formatter --------- Authored-by: Roberto --- BlocksScreen/lib/panels/mainWindow.py | 33 +- .../lib/panels/widgets/popupDialogWidget.py | 114 ++- .../lib/ui/resources/icon_resources.qrc | 4 +- .../lib/ui/resources/icon_resources_rc.py | 751 ++++++++---------- .../ui/resources/media/btn_icons/error.svg | 60 +- .../lib/ui/resources/media/btn_icons/info.svg | 14 +- BlocksScreen/lib/utils/icon_button.py | 5 + 7 files changed, 476 insertions(+), 505 deletions(-) diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index f6fbc2f9..63c53fa7 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -547,15 +547,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 @@ -564,15 +562,17 @@ 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 == "!!": + popupWhitelist = ["filament runout", "no filament"] + if _message.lower() in popupWhitelist or _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)) + self.popup.new_message( + message_type=_msg_type, + message=str(_message), + userInput=True, + ) @api_handler def _handle_error_message(self, method, data, metadata) -> None: @@ -581,17 +581,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, diff --git a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py index f878f612..69deec51 100644 --- a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py +++ b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py @@ -2,9 +2,7 @@ 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): @@ -25,9 +23,10 @@ class ColorCode(enum.Enum): def __init__(self, parent) -> None: super().__init__(parent) - 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) @@ -48,20 +47,32 @@ def __init__(self, parent) -> None: 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.slide_in_animation.finished.connect(self.on_slide_in_finished) + + self.SingleTime = QtCore.QTimer(self) + self.SingleTime.setInterval(5000) + self.SingleTime.setSingleShot(True) + self.SingleTime.timeout.connect(self._add_popup) + 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): """Handle slide out animation finished""" - self.close() + 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: @@ -73,7 +84,13 @@ def _calculate_target_geometry(self) -> QtCore.QRect: parent_rect = main_window.geometry() width = int(parent_rect.width() * 0.85) - height = min(self.text_label.rect().height(), self.icon_label.rect().height()) + 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 @@ -89,20 +106,19 @@ def updateMask(self) -> None: 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: - """Set popup timeout""" - 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 = "", - timeout: int = 0, + timeout: int = 6000, + userInput: bool = False, ): """Create new popup message @@ -110,17 +126,29 @@ def new_message( 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_ """ self.messages.append( - {"message": message, "type": message_type, "timeout": timeout} + { + "message": message, + "type": message_type, + "timeout": timeout, + "userInput": userInput, + } ) 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 + if ( self.messages and self.slide_in_animation.state() @@ -131,7 +159,17 @@ def _add_popup(self) -> None: 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) @@ -139,19 +177,31 @@ def _add_popup(self) -> None: self.icon_label.setPixmap(self.warning_icon) 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: @@ -183,22 +233,18 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: painter.drawRoundedRect(self.rect(), 10, 10) def _setupUI(self) -> None: - self.vertical_layout = QtWidgets.QVBoxLayout(self) - self.horizontal_layout = QtWidgets.QHBoxLayout() + 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") @@ -209,9 +255,9 @@ def _setupUI(self) -> None: self.text_label.setPalette(palette) self.text_label.setFont(font) - self.spacer = QtWidgets.QSpacerItem(60, 60) + self.actionbtn = IconButton(self) + self.actionbtn.setMaximumSize(QtCore.QSize(60, 60)) - self.horizontal_layout.addWidget(self.text_label, 1) - self.horizontal_layout.addItem(self.spacer) - - self.vertical_layout.addLayout(self.horizontal_layout) + 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/ui/resources/icon_resources.qrc b/BlocksScreen/lib/ui/resources/icon_resources.qrc index a3bf6d57..8338a1da 100644 --- a/BlocksScreen/lib/ui/resources/icon_resources.qrc +++ b/BlocksScreen/lib/ui/resources/icon_resources.qrc @@ -89,9 +89,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 1346a1f1..6481444f 100644 --- a/BlocksScreen/lib/ui/resources/icon_resources_rc.py +++ b/BlocksScreen/lib/ui/resources/icon_resources_rc.py @@ -22339,54 +22339,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\ @@ -23113,160 +23134,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\ @@ -26193,39 +26128,39 @@ \x00\x00\x11\xfc\x00\x00\x00\x00\x00\x01\x00\x05\x5e\x40\ \x00\x00\x12\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x63\x15\ \x00\x00\x12\x32\x00\x00\x00\x00\x00\x01\x00\x05\x64\x05\ -\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x66\xe9\ -\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x6d\x20\ -\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x82\x2d\ -\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x87\x1d\ -\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x8a\x1c\ -\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x90\x26\ -\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x93\x75\ -\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x9c\xf6\ -\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\xa2\xed\ -\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xfe\ -\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xc1\ -\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xb2\x75\ -\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xb5\xc3\ -\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xb9\x51\ -\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xbd\xf2\ -\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xc7\xc4\ -\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x0e\ -\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xd3\x15\ -\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xd3\xf9\ -\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x42\ -\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xdc\xf2\ -\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xe0\x36\ -\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xe1\x62\ -\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xe7\x23\ -\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x45\ -\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xee\x38\ -\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x3c\ -\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xf2\x5d\ -\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xf5\x31\ -\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xfd\x9d\ -\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x06\x05\x5d\ -\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x0a\x78\ -\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x0b\xcb\ +\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x68\x2e\ +\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x6e\x65\ +\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x83\x72\ +\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x88\x62\ +\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x8b\x61\ +\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x91\x6b\ +\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x94\xba\ +\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x98\xdb\ +\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\x9e\xd2\ +\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xa0\xe3\ +\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xa6\ +\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xae\x5a\ +\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xb1\xa8\ +\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xb5\x36\ +\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xb9\xd7\ +\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xc3\xa9\ +\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xc8\xf3\ +\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xce\xfa\ +\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xcf\xde\ +\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xd2\x27\ +\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd8\xd7\ +\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xdc\x1b\ +\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xdd\x47\ +\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xe3\x08\ +\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xe4\x2a\ +\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xea\x1d\ +\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xed\x21\ +\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xee\x42\ +\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x16\ +\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xf9\x82\ +\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x06\x01\x42\ +\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x06\x5d\ +\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x07\xb0\ " qt_resource_struct_v2 = b"\ @@ -26266,75 +26201,75 @@ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x12\ \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\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x08\x66\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x01\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x09\x52\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x02\x20\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x35\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x02\x48\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x18\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x02\x70\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x06\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x02\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x14\x42\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x02\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xf7\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x02\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x22\x41\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ \x00\x00\x03\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x26\x90\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x03\x36\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6e\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x03\x52\x00\x00\x00\x00\x00\x01\x00\x00\x30\xca\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x03\x7a\x00\x00\x00\x00\x00\x01\x00\x00\x32\xb1\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x21\ \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\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x3c\xcf\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x03\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x43\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x03\xfe\x00\x00\x00\x00\x00\x01\x00\x00\x41\xbf\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x26\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x27\ \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\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x04\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x45\x3c\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x04\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x54\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x04\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x56\x16\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x04\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x58\x86\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x04\xea\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x1e\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x05\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x62\x7a\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x05\x48\x00\x00\x00\x00\x00\x01\x00\x00\x69\x07\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x05\x64\x00\x00\x00\x00\x00\x01\x00\x00\x6c\x03\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x05\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x6d\x01\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x32\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x33\ \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\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x05\xd0\x00\x00\x00\x00\x00\x01\x00\x02\x3a\xc0\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x02\x00\x00\x00\x36\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x06\x00\x00\x00\x3f\ @@ -26342,275 +26277,275 @@ \x00\x00\x05\xe4\x00\x02\x00\x00\x00\x07\x00\x00\x00\x38\ \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\x9a\x72\xe1\x95\x8f\ +\x00\x00\x01\x9b\x09\x08\xfa\x11\ \x00\x00\x06\x2a\x00\x00\x00\x00\x00\x01\x00\x02\x49\xaa\ -\x00\x00\x01\x9a\x72\xe1\x95\x93\ +\x00\x00\x01\x9b\x09\x08\xfa\x11\ \x00\x00\x06\x5e\x00\x00\x00\x00\x00\x01\x00\x02\x53\xd3\ -\x00\x00\x01\x9a\x72\xe1\x95\x8f\ +\x00\x00\x01\x9b\x09\x08\xfa\x11\ \x00\x00\x06\x98\x00\x00\x00\x00\x00\x01\x00\x02\x5e\x2b\ -\x00\x00\x01\x9a\x72\xe1\x95\x93\ +\x00\x00\x01\x9b\x09\x08\xfa\x11\ \x00\x00\x06\xcc\x00\x00\x00\x00\x00\x01\x00\x02\x68\x43\ -\x00\x00\x01\x9a\x72\xe1\x95\x8f\ +\x00\x00\x01\x9b\x09\x08\xfa\x11\ \x00\x00\x07\x02\x00\x00\x00\x00\x00\x01\x00\x02\x72\x6c\ -\x00\x00\x01\x9a\x72\xe1\x95\x93\ +\x00\x00\x01\x9b\x09\x08\xfa\x11\ \x00\x00\x07\x3a\x00\x00\x00\x00\x00\x01\x00\x02\x7c\x82\ -\x00\x00\x01\x9a\x72\xe1\x95\x93\ +\x00\x00\x01\x9b\x09\x08\xfa\x11\ \x00\x00\x07\x70\x00\x00\x00\x00\x00\x01\x00\x02\x86\xe8\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x07\x98\x00\x00\x00\x00\x00\x01\x00\x02\x91\x17\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x07\xc4\x00\x00\x00\x00\x00\x01\x00\x02\x9b\x5e\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x08\x00\x00\x00\x00\x00\x00\x01\x00\x02\x9d\x5f\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x08\x2c\x00\x00\x00\x00\x00\x01\x00\x02\xb8\xa4\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x08\x58\x00\x00\x00\x00\x00\x01\x00\x02\xbd\xed\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x46\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x03\x00\x00\x00\x47\ \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\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x02\xca\x47\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x02\xcf\x7c\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x01\x88\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\x0d\x00\x00\x00\x4c\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x08\xd8\x00\x00\x00\x00\x00\x01\x00\x02\xd9\x51\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x09\x00\x00\x00\x00\x00\x00\x01\x00\x02\xde\x4c\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x09\x38\x00\x00\x00\x00\x00\x01\x00\x02\xe7\x20\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x09\x70\x00\x00\x00\x00\x00\x01\x00\x02\xef\xc4\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x09\xa0\x00\x00\x00\x00\x00\x01\x00\x02\xf7\x63\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x09\xc4\x00\x00\x00\x00\x00\x01\x00\x02\xff\x3f\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x09\xe8\x00\x00\x00\x00\x00\x01\x00\x03\x06\xf5\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0a\x1c\x00\x00\x00\x00\x00\x01\x00\x03\x0f\x03\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0a\x50\x00\x00\x00\x00\x00\x01\x00\x03\x16\xe5\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0a\x84\x00\x00\x00\x00\x00\x01\x00\x03\x1e\xaf\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0a\xbe\x00\x00\x00\x00\x00\x01\x00\x03\x26\x16\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x0a\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x29\xd3\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x0b\x10\x00\x00\x00\x00\x00\x01\x00\x03\x2d\xac\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x5a\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x08\x00\x00\x00\x5b\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x0b\x36\x00\x00\x00\x00\x00\x01\x00\x03\x33\xb5\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x0b\x62\x00\x00\x00\x00\x00\x01\x00\x03\x56\x39\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x0b\x90\x00\x00\x00\x00\x00\x01\x00\x03\x5c\x26\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x0b\xba\x00\x00\x00\x00\x00\x01\x00\x03\x5e\x46\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x66\xde\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x0b\xfc\x00\x00\x00\x00\x00\x01\x00\x03\x76\x27\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x0c\x28\x00\x00\x00\x00\x00\x01\x00\x03\x7c\xa5\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x0c\x50\x00\x00\x00\x00\x00\x01\x00\x03\x87\x1f\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x64\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x65\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x0c\x86\x00\x00\x00\x00\x00\x01\x00\x03\x90\x66\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ \x00\x00\x0c\xb4\x00\x00\x00\x00\x00\x01\x00\x03\x93\x0d\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x0c\xe2\x00\x01\x00\x00\x00\x01\x00\x03\x9f\x8a\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x0d\x0e\x00\x00\x00\x00\x00\x01\x00\x03\xcd\x09\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ \x00\x00\x0d\x2e\x00\x00\x00\x00\x00\x01\x00\x03\xd1\xc0\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ \x00\x00\x0d\x60\x00\x01\x00\x00\x00\x01\x00\x04\x2a\xb9\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ \x00\x00\x0d\x92\x00\x00\x00\x00\x00\x01\x00\x04\x5f\x53\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0d\xac\x00\x00\x00\x00\x00\x01\x00\x04\x64\x9d\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0d\xc6\x00\x00\x00\x00\x00\x01\x00\x04\x6a\x2c\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0d\xe0\x00\x00\x00\x00\x00\x01\x00\x04\x6f\x95\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x0d\xf8\x00\x00\x00\x00\x00\x01\x00\x04\x7b\x73\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0e\x16\x00\x00\x00\x00\x00\x01\x00\x04\x81\x77\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x72\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x73\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x0e\x3e\x00\x00\x00\x00\x00\x01\x00\x04\x86\xac\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0e\x52\x00\x00\x00\x00\x00\x01\x00\x04\x8c\xa9\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0e\x64\x00\x00\x00\x00\x00\x01\x00\x04\x8e\x2f\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0e\x76\x00\x00\x00\x00\x00\x01\x00\x04\x94\x29\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x78\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x79\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x0e\x8a\x00\x00\x00\x00\x00\x01\x00\x04\x96\x7f\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x0e\xb6\x00\x00\x00\x00\x00\x01\x00\x04\x9d\x66\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7c\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x09\x00\x00\x00\x7d\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x0e\xda\x00\x00\x00\x00\x00\x01\x00\x04\xa6\xca\ -\x00\x00\x01\x9b\x13\x3e\xbb\x62\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ \x00\x00\x0e\xfa\x00\x01\x00\x00\x00\x01\x00\x04\xa9\x76\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x0f\x1e\x00\x00\x00\x00\x00\x01\x00\x04\xb4\xc7\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x0f\x40\x00\x00\x00\x00\x00\x01\x00\x04\xbc\x6c\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x0f\x5c\x00\x00\x00\x00\x00\x01\x00\x04\xcd\x6c\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x0f\x80\x00\x00\x00\x00\x00\x01\x00\x04\xd6\xed\ -\x00\x00\x01\x9b\x13\x3e\xbb\x62\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ \x00\x00\x0f\xa0\x00\x00\x00\x00\x00\x01\x00\x04\xdc\x8a\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x0f\xc8\x00\x00\x00\x00\x00\x01\x00\x04\xe6\x53\ -\x00\x00\x01\x9b\x13\x3e\xbb\x62\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ \x00\x00\x0f\xe8\x00\x00\x00\x00\x00\x01\x00\x04\xea\x36\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x87\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x88\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x10\x04\x00\x00\x00\x00\x00\x01\x00\x04\xf0\x71\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x10\x34\x00\x00\x00\x00\x00\x01\x00\x04\xfa\x07\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x10\x64\x00\x00\x00\x00\x00\x01\x00\x05\x05\xe3\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x10\x88\x00\x00\x00\x00\x00\x01\x00\x05\x0c\x27\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x10\xb2\x00\x00\x00\x00\x00\x01\x00\x05\x13\xb0\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x10\xde\x00\x00\x00\x00\x00\x01\x00\x05\x1a\x0e\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x11\x14\x00\x00\x00\x00\x00\x01\x00\x05\x21\xfd\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x2f\xa0\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x05\x34\xd5\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x11\x32\x00\x00\x00\x00\x00\x01\x00\x05\x3e\xaa\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x93\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x01\x00\x00\x00\x94\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x11\x5a\x00\x00\x00\x00\x00\x01\x00\x05\x46\x74\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ \x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x96\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x98\x00\x02\x00\x00\x00\x28\x00\x00\x00\x97\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x11\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x4b\x3a\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ \x00\x00\x11\x90\x00\x00\x00\x00\x00\x01\x00\x05\x52\xee\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x11\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x55\x13\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x11\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x56\x93\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x11\xfc\x00\x00\x00\x00\x00\x01\x00\x05\x5e\x40\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ \x00\x00\x12\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x63\x15\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ \x00\x00\x12\x32\x00\x00\x00\x00\x00\x01\x00\x05\x64\x05\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ -\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x66\xe9\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ -\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x6d\x20\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x82\x2d\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ -\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x87\x1d\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ -\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x8a\x1c\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x90\x26\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ -\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x93\x75\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ -\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x9c\xf6\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ -\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\xa2\xed\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xfe\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ -\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xc1\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ -\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xb2\x75\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xb5\xc3\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xb9\x51\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ -\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xbd\xf2\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xc7\xc4\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ -\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x0e\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xd3\x15\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xd3\xf9\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ -\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x42\ -\x00\x00\x01\x9a\x72\xe1\x94\x5b\ -\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xdc\xf2\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xe0\x36\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ -\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xe1\x62\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ -\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xe7\x23\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x45\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xee\x38\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ -\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x3c\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xf2\x5d\ -\x00\x00\x01\x9a\x72\xe1\x94\x57\ -\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xf5\x31\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ -\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xfd\x9d\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ -\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x06\x05\x5d\ -\x00\x00\x01\x9a\x72\xe1\x94\x53\ -\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x0a\x78\ -\x00\x00\x01\x9a\x72\xe1\x94\x4f\ -\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x0b\xcb\ -\x00\x00\x01\x9a\x72\xe1\x94\x4b\ +\x00\x00\x01\x9b\x8e\xa8\x75\xac\ +\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x68\x2e\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ +\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x6e\x65\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x83\x72\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ +\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x88\x62\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ +\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x8b\x61\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x91\x6b\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ +\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x94\xba\ +\x00\x00\x01\x9b\x8e\xa8\x75\x96\ +\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x98\xdb\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ +\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\x9e\xd2\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xa0\xe3\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ +\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xa6\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ +\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xae\x5a\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xb1\xa8\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xb5\x36\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ +\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xb9\xd7\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xc3\xa9\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ +\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xc8\xf3\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xce\xfa\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xcf\xde\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ +\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xd2\x27\ +\x00\x00\x01\x9b\x09\x08\xf9\x59\ +\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd8\xd7\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xdc\x1b\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ +\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xdd\x47\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ +\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xe3\x08\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xe4\x2a\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xea\x1d\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ +\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xed\x21\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xee\x42\ +\x00\x00\x01\x9b\x09\x08\xf9\x55\ +\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x16\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ +\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xf9\x82\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ +\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x06\x01\x42\ +\x00\x00\x01\x9b\x09\x08\xf9\x51\ +\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x06\x5d\ +\x00\x00\x01\x9b\x09\x08\xf9\x4d\ +\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x07\xb0\ +\x00\x00\x01\x9b\x09\x08\xf9\x49\ " 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/utils/icon_button.py b/BlocksScreen/lib/utils/icon_button.py index 160f1f80..3880d285 100644 --- a/BlocksScreen/lib/utils/icon_button.py +++ b/BlocksScreen/lib/utils/icon_button.py @@ -29,6 +29,11 @@ def setPixmap(self, pixmap: QtGui.QPixmap) -> None: 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 From dd13e39e76f4bd455f94bad39e2a71712fc763f1 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Wed, 14 Jan 2026 11:13:09 +0000 Subject: [PATCH 30/43] =?UTF-8?q?Improvement/Apply=20Z=E2=80=91offset=20ch?= =?UTF-8?q?anges=20immediately,=20with=20an=20option=20to=20save=20permane?= =?UTF-8?q?ntly=20(#149)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * babystepPage: make buttons update instantly and respond to all Z-offset changes * babystepPage and printTab: bugfix showing the current saved z-offset * printTab: change save message * add missing icons from merge --------- Co-authored-by: Guilherme Costa --- BlocksScreen/lib/panels/printTab.py | 108 +- .../lib/panels/widgets/babystepPage.py | 97 +- .../lib/panels/widgets/jobStatusPage.py | 5 + .../lib/ui/resources/icon_resources.qrc | 4 + .../lib/ui/resources/icon_resources_rc.py | 1153 +++++++++-------- .../media/btn_icons/move_nozzle_away.svg | 15 + .../media/btn_icons/move_nozzle_close.svg | 12 + 7 files changed, 793 insertions(+), 601 deletions(-) create mode 100644 BlocksScreen/lib/ui/resources/media/btn_icons/move_nozzle_away.svg create mode 100644 BlocksScreen/lib/ui/resources/media/btn_icons/move_nozzle_close.svg diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py index 2a885362..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.loadWidget import LoadingOverlayWidget -from lib.panels.widgets.basePopup import BasePopup -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: @@ -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, @@ -97,6 +103,7 @@ def __init__( self.addWidget(self.filesPage_widget) self.BasePopup = BasePopup(self) + self.BasePopup_z_offset = BasePopup(self, floating=True) self.confirmPage_widget = ConfirmWidget(self) self.addWidget(self.confirmPage_widget) @@ -137,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( @@ -148,6 +154,7 @@ def __init__( 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 ) @@ -176,6 +183,7 @@ def __init__( 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) @@ -203,6 +211,7 @@ 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 @@ -245,6 +254,8 @@ def __init__( 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.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") @@ -305,6 +316,38 @@ def delete_file(self, filename: str, directory: str = "gcodes") -> None: ) 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) @@ -312,19 +355,6 @@ def _on_delete_file_confirmed(self, filename: str, directory: str) -> None: self.filesPage_widget.reset_dir() self.BasePopup.disconnect() - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: - """Widget painting""" - 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) - def setProperty(self, name: str, value: typing.Any) -> bool: """Intercept the set property method @@ -359,6 +389,21 @@ 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") @@ -423,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)) diff --git a/BlocksScreen/lib/panels/widgets/babystepPage.py b/BlocksScreen/lib/panels/widgets/babystepPage.py index 6505c0aa..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.icon_button import IconButton from lib.utils.check_button import BlocksCustomCheckButton +from lib.utils.icon_button import IconButton from PyQt6 import QtCore, QtGui, QtWidgets @@ -33,8 +32,18 @@ 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.save_value) + @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: @@ -42,9 +51,9 @@ def on_move_nozzle_close(self) -> None: 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: @@ -52,9 +61,9 @@ def on_move_nozzle_away(self) -> None: 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: @@ -67,18 +76,11 @@ 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 save_value(self): - """Save new z offset value""" - 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()) - def on_gcode_move_update(self, name: str, value: list) -> None: """Handle gcode move updates""" if not value: @@ -87,8 +89,6 @@ def on_gcode_move_update(self, name: str, value: list) -> None: 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") def setupUI(self): """Setup babystep page ui""" @@ -143,16 +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, @@ -234,6 +224,30 @@ def setupUI(self): 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 @@ -260,30 +274,6 @@ def setupUI(self): 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, - ) - # 0.025mm button self.bbp_nozzle_offset_025 = BlocksCustomCheckButton( parent=self.bbp_offset_steps_buttons_group_box @@ -351,9 +341,8 @@ 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 === @@ -399,7 +388,7 @@ def setupUI(self): 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) @@ -416,7 +405,7 @@ def setupUI(self): 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/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py index f19b5269..bd5bbb8c 100644 --- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py +++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py @@ -35,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" ) @@ -225,6 +228,8 @@ def _handle_print_state(self, state: str) -> None: 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 = "?" diff --git a/BlocksScreen/lib/ui/resources/icon_resources.qrc b/BlocksScreen/lib/ui/resources/icon_resources.qrc index 8338a1da..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 diff --git a/BlocksScreen/lib/ui/resources/icon_resources_rc.py b/BlocksScreen/lib/ui/resources/icon_resources_rc.py index 6481444f..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\ @@ -25305,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\ @@ -25460,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\ @@ -25970,582 +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\x95\ -\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x92\ -\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x86\ -\x00\x00\x00\x46\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7b\ -\x00\x00\x00\x5a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x77\ -\x00\x00\x00\x6c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x71\ -\x00\x00\x00\x7e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x63\ -\x00\x00\x00\x90\x00\x02\x00\x00\x00\x01\x00\x00\x00\x59\ -\x00\x00\x00\xa2\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4a\ -\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\x03\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\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x02\xcf\x7c\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4b\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x4c\ -\x00\x00\x08\xd8\x00\x00\x00\x00\x00\x01\x00\x02\xd9\x51\ -\x00\x00\x09\x00\x00\x00\x00\x00\x00\x01\x00\x02\xde\x4c\ -\x00\x00\x09\x38\x00\x00\x00\x00\x00\x01\x00\x02\xe7\x20\ -\x00\x00\x09\x70\x00\x00\x00\x00\x00\x01\x00\x02\xef\xc4\ -\x00\x00\x09\xa0\x00\x00\x00\x00\x00\x01\x00\x02\xf7\x63\ -\x00\x00\x09\xc4\x00\x00\x00\x00\x00\x01\x00\x02\xff\x3f\ -\x00\x00\x09\xe8\x00\x00\x00\x00\x00\x01\x00\x03\x06\xf5\ -\x00\x00\x0a\x1c\x00\x00\x00\x00\x00\x01\x00\x03\x0f\x03\ -\x00\x00\x0a\x50\x00\x00\x00\x00\x00\x01\x00\x03\x16\xe5\ -\x00\x00\x0a\x84\x00\x00\x00\x00\x00\x01\x00\x03\x1e\xaf\ -\x00\x00\x0a\xbe\x00\x00\x00\x00\x00\x01\x00\x03\x26\x16\ -\x00\x00\x0a\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x29\xd3\ -\x00\x00\x0b\x10\x00\x00\x00\x00\x00\x01\x00\x03\x2d\xac\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x5a\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x08\x00\x00\x00\x5b\ -\x00\x00\x0b\x36\x00\x00\x00\x00\x00\x01\x00\x03\x33\xb5\ -\x00\x00\x0b\x62\x00\x00\x00\x00\x00\x01\x00\x03\x56\x39\ -\x00\x00\x0b\x90\x00\x00\x00\x00\x00\x01\x00\x03\x5c\x26\ -\x00\x00\x0b\xba\x00\x00\x00\x00\x00\x01\x00\x03\x5e\x46\ -\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x66\xde\ -\x00\x00\x0b\xfc\x00\x00\x00\x00\x00\x01\x00\x03\x76\x27\ -\x00\x00\x0c\x28\x00\x00\x00\x00\x00\x01\x00\x03\x7c\xa5\ -\x00\x00\x0c\x50\x00\x00\x00\x00\x00\x01\x00\x03\x87\x1f\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x64\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x65\ -\x00\x00\x0c\x86\x00\x00\x00\x00\x00\x01\x00\x03\x90\x66\ -\x00\x00\x0c\xb4\x00\x00\x00\x00\x00\x01\x00\x03\x93\x0d\ -\x00\x00\x0c\xe2\x00\x01\x00\x00\x00\x01\x00\x03\x9f\x8a\ -\x00\x00\x0d\x0e\x00\x00\x00\x00\x00\x01\x00\x03\xcd\x09\ -\x00\x00\x0d\x2e\x00\x00\x00\x00\x00\x01\x00\x03\xd1\xc0\ -\x00\x00\x0d\x60\x00\x01\x00\x00\x00\x01\x00\x04\x2a\xb9\ -\x00\x00\x0d\x92\x00\x00\x00\x00\x00\x01\x00\x04\x5f\x53\ -\x00\x00\x0d\xac\x00\x00\x00\x00\x00\x01\x00\x04\x64\x9d\ -\x00\x00\x0d\xc6\x00\x00\x00\x00\x00\x01\x00\x04\x6a\x2c\ -\x00\x00\x0d\xe0\x00\x00\x00\x00\x00\x01\x00\x04\x6f\x95\ -\x00\x00\x0d\xf8\x00\x00\x00\x00\x00\x01\x00\x04\x7b\x73\ -\x00\x00\x0e\x16\x00\x00\x00\x00\x00\x01\x00\x04\x81\x77\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x72\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x04\x00\x00\x00\x73\ -\x00\x00\x0e\x3e\x00\x00\x00\x00\x00\x01\x00\x04\x86\xac\ -\x00\x00\x0e\x52\x00\x00\x00\x00\x00\x01\x00\x04\x8c\xa9\ -\x00\x00\x0e\x64\x00\x00\x00\x00\x00\x01\x00\x04\x8e\x2f\ -\x00\x00\x0e\x76\x00\x00\x00\x00\x00\x01\x00\x04\x94\x29\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x78\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x02\x00\x00\x00\x79\ -\x00\x00\x0e\x8a\x00\x00\x00\x00\x00\x01\x00\x04\x96\x7f\ -\x00\x00\x0e\xb6\x00\x00\x00\x00\x00\x01\x00\x04\x9d\x66\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7c\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x09\x00\x00\x00\x7d\ -\x00\x00\x0e\xda\x00\x00\x00\x00\x00\x01\x00\x04\xa6\xca\ -\x00\x00\x0e\xfa\x00\x01\x00\x00\x00\x01\x00\x04\xa9\x76\ -\x00\x00\x0f\x1e\x00\x00\x00\x00\x00\x01\x00\x04\xb4\xc7\ -\x00\x00\x0f\x40\x00\x00\x00\x00\x00\x01\x00\x04\xbc\x6c\ -\x00\x00\x0f\x5c\x00\x00\x00\x00\x00\x01\x00\x04\xcd\x6c\ -\x00\x00\x0f\x80\x00\x00\x00\x00\x00\x01\x00\x04\xd6\xed\ -\x00\x00\x0f\xa0\x00\x00\x00\x00\x00\x01\x00\x04\xdc\x8a\ -\x00\x00\x0f\xc8\x00\x00\x00\x00\x00\x01\x00\x04\xe6\x53\ -\x00\x00\x0f\xe8\x00\x00\x00\x00\x00\x01\x00\x04\xea\x36\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x87\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x88\ -\x00\x00\x10\x04\x00\x00\x00\x00\x00\x01\x00\x04\xf0\x71\ -\x00\x00\x10\x34\x00\x00\x00\x00\x00\x01\x00\x04\xfa\x07\ -\x00\x00\x10\x64\x00\x00\x00\x00\x00\x01\x00\x05\x05\xe3\ -\x00\x00\x10\x88\x00\x00\x00\x00\x00\x01\x00\x05\x0c\x27\ -\x00\x00\x10\xb2\x00\x00\x00\x00\x00\x01\x00\x05\x13\xb0\ -\x00\x00\x10\xde\x00\x00\x00\x00\x00\x01\x00\x05\x1a\x0e\ -\x00\x00\x11\x14\x00\x00\x00\x00\x00\x01\x00\x05\x21\xfd\ -\x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x2f\xa0\ -\x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x05\x34\xd5\ -\x00\x00\x11\x32\x00\x00\x00\x00\x00\x01\x00\x05\x3e\xaa\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x93\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x01\x00\x00\x00\x94\ -\x00\x00\x11\x5a\x00\x00\x00\x00\x00\x01\x00\x05\x46\x74\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x96\ -\x00\x00\x01\x98\x00\x02\x00\x00\x00\x28\x00\x00\x00\x97\ -\x00\x00\x11\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x4b\x3a\ -\x00\x00\x11\x90\x00\x00\x00\x00\x00\x01\x00\x05\x52\xee\ -\x00\x00\x11\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x55\x13\ -\x00\x00\x11\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x56\x93\ -\x00\x00\x11\xfc\x00\x00\x00\x00\x00\x01\x00\x05\x5e\x40\ -\x00\x00\x12\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x63\x15\ -\x00\x00\x12\x32\x00\x00\x00\x00\x00\x01\x00\x05\x64\x05\ -\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x68\x2e\ -\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x6e\x65\ -\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x83\x72\ -\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x88\x62\ -\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x8b\x61\ -\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x91\x6b\ -\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x94\xba\ -\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x98\xdb\ -\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\x9e\xd2\ -\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xa0\xe3\ -\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xa6\ -\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xae\x5a\ -\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xb1\xa8\ -\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xb5\x36\ -\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xb9\xd7\ -\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xc3\xa9\ -\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xc8\xf3\ -\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xce\xfa\ -\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xcf\xde\ -\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xd2\x27\ -\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd8\xd7\ -\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xdc\x1b\ -\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xdd\x47\ -\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xe3\x08\ -\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xe4\x2a\ -\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xea\x1d\ -\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xed\x21\ -\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xee\x42\ -\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x16\ -\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xf9\x82\ -\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x06\x01\x42\ -\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x06\x5d\ -\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x07\xb0\ +\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\x95\ +\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\x92\ +\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\x86\ +\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\x7b\ +\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\x77\ +\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\x71\ +\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\x63\ +\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\x59\ +\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\x4a\ +\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\x9b\x09\x08\xf9\x51\ -\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x08\x66\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x01\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x09\x52\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x02\x20\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x35\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x02\x48\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x18\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x02\x70\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x06\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x02\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x14\x42\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x02\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xf7\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x02\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x22\x41\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x03\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x26\x90\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x03\x36\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6e\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x03\x52\x00\x00\x00\x00\x00\x01\x00\x00\x30\xca\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x03\x7a\x00\x00\x00\x00\x00\x01\x00\x00\x32\xb1\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\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\x9b\x09\x08\xf9\x55\ -\x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x3c\xcf\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x03\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x43\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x03\xfe\x00\x00\x00\x00\x00\x01\x00\x00\x41\xbf\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\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\x9b\x09\x08\xf9\x51\ -\x00\x00\x04\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x45\x3c\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x04\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x54\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x04\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x56\x16\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x04\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x58\x86\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x04\xea\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x1e\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x05\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x62\x7a\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x05\x48\x00\x00\x00\x00\x00\x01\x00\x00\x69\x07\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x05\x64\x00\x00\x00\x00\x00\x01\x00\x00\x6c\x03\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x05\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x6d\x01\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\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\x9b\x09\x08\xf9\x55\ -\x00\x00\x05\xd0\x00\x00\x00\x00\x00\x01\x00\x02\x3a\xc0\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\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\x9b\x09\x08\xfa\x11\ -\x00\x00\x06\x2a\x00\x00\x00\x00\x00\x01\x00\x02\x49\xaa\ -\x00\x00\x01\x9b\x09\x08\xfa\x11\ -\x00\x00\x06\x5e\x00\x00\x00\x00\x00\x01\x00\x02\x53\xd3\ -\x00\x00\x01\x9b\x09\x08\xfa\x11\ -\x00\x00\x06\x98\x00\x00\x00\x00\x00\x01\x00\x02\x5e\x2b\ -\x00\x00\x01\x9b\x09\x08\xfa\x11\ -\x00\x00\x06\xcc\x00\x00\x00\x00\x00\x01\x00\x02\x68\x43\ -\x00\x00\x01\x9b\x09\x08\xfa\x11\ -\x00\x00\x07\x02\x00\x00\x00\x00\x00\x01\x00\x02\x72\x6c\ -\x00\x00\x01\x9b\x09\x08\xfa\x11\ -\x00\x00\x07\x3a\x00\x00\x00\x00\x00\x01\x00\x02\x7c\x82\ -\x00\x00\x01\x9b\x09\x08\xfa\x11\ -\x00\x00\x07\x70\x00\x00\x00\x00\x00\x01\x00\x02\x86\xe8\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x07\x98\x00\x00\x00\x00\x00\x01\x00\x02\x91\x17\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x07\xc4\x00\x00\x00\x00\x00\x01\x00\x02\x9b\x5e\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x08\x00\x00\x00\x00\x00\x00\x01\x00\x02\x9d\x5f\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x08\x2c\x00\x00\x00\x00\x00\x01\x00\x02\xb8\xa4\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x08\x58\x00\x00\x00\x00\x00\x01\x00\x02\xbd\xed\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\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\x03\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\x9b\x09\x08\xf9\x4d\ -\x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x02\xca\x47\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x02\xcf\x7c\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4b\ +\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\x4c\ +\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\xd8\x00\x00\x00\x00\x00\x01\x00\x02\xd9\x51\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x09\x00\x00\x00\x00\x00\x00\x01\x00\x02\xde\x4c\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x09\x38\x00\x00\x00\x00\x00\x01\x00\x02\xe7\x20\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x09\x70\x00\x00\x00\x00\x00\x01\x00\x02\xef\xc4\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x09\xa0\x00\x00\x00\x00\x00\x01\x00\x02\xf7\x63\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x09\xc4\x00\x00\x00\x00\x00\x01\x00\x02\xff\x3f\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x09\xe8\x00\x00\x00\x00\x00\x01\x00\x03\x06\xf5\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0a\x1c\x00\x00\x00\x00\x00\x01\x00\x03\x0f\x03\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0a\x50\x00\x00\x00\x00\x00\x01\x00\x03\x16\xe5\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0a\x84\x00\x00\x00\x00\x00\x01\x00\x03\x1e\xaf\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0a\xbe\x00\x00\x00\x00\x00\x01\x00\x03\x26\x16\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x0a\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x29\xd3\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x0b\x10\x00\x00\x00\x00\x00\x01\x00\x03\x2d\xac\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x5a\ +\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\x5b\ +\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\x36\x00\x00\x00\x00\x00\x01\x00\x03\x33\xb5\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x0b\x62\x00\x00\x00\x00\x00\x01\x00\x03\x56\x39\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x0b\x90\x00\x00\x00\x00\x00\x01\x00\x03\x5c\x26\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x0b\xba\x00\x00\x00\x00\x00\x01\x00\x03\x5e\x46\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03\x66\xde\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x0b\xfc\x00\x00\x00\x00\x00\x01\x00\x03\x76\x27\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x0c\x28\x00\x00\x00\x00\x00\x01\x00\x03\x7c\xa5\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x0c\x50\x00\x00\x00\x00\x00\x01\x00\x03\x87\x1f\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x64\ +\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\x65\ +\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\x86\x00\x00\x00\x00\x00\x01\x00\x03\x90\x66\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x0c\xb4\x00\x00\x00\x00\x00\x01\x00\x03\x93\x0d\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x0c\xe2\x00\x01\x00\x00\x00\x01\x00\x03\x9f\x8a\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x0d\x0e\x00\x00\x00\x00\x00\x01\x00\x03\xcd\x09\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x0d\x2e\x00\x00\x00\x00\x00\x01\x00\x03\xd1\xc0\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x0d\x60\x00\x01\x00\x00\x00\x01\x00\x04\x2a\xb9\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x0d\x92\x00\x00\x00\x00\x00\x01\x00\x04\x5f\x53\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0d\xac\x00\x00\x00\x00\x00\x01\x00\x04\x64\x9d\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0d\xc6\x00\x00\x00\x00\x00\x01\x00\x04\x6a\x2c\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0d\xe0\x00\x00\x00\x00\x00\x01\x00\x04\x6f\x95\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x0d\xf8\x00\x00\x00\x00\x00\x01\x00\x04\x7b\x73\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0e\x16\x00\x00\x00\x00\x00\x01\x00\x04\x81\x77\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x72\ +\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\x73\ +\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\x3e\x00\x00\x00\x00\x00\x01\x00\x04\x86\xac\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0e\x52\x00\x00\x00\x00\x00\x01\x00\x04\x8c\xa9\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0e\x64\x00\x00\x00\x00\x00\x01\x00\x04\x8e\x2f\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0e\x76\x00\x00\x00\x00\x00\x01\x00\x04\x94\x29\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x78\ +\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\x79\ +\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\x8a\x00\x00\x00\x00\x00\x01\x00\x04\x96\x7f\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x0e\xb6\x00\x00\x00\x00\x00\x01\x00\x04\x9d\x66\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7c\ +\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\x7d\ +\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\xda\x00\x00\x00\x00\x00\x01\x00\x04\xa6\xca\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x0e\xfa\x00\x01\x00\x00\x00\x01\x00\x04\xa9\x76\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x0f\x1e\x00\x00\x00\x00\x00\x01\x00\x04\xb4\xc7\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x0f\x40\x00\x00\x00\x00\x00\x01\x00\x04\xbc\x6c\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x0f\x5c\x00\x00\x00\x00\x00\x01\x00\x04\xcd\x6c\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x0f\x80\x00\x00\x00\x00\x00\x01\x00\x04\xd6\xed\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x0f\xa0\x00\x00\x00\x00\x00\x01\x00\x04\xdc\x8a\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x0f\xc8\x00\x00\x00\x00\x00\x01\x00\x04\xe6\x53\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x0f\xe8\x00\x00\x00\x00\x00\x01\x00\x04\xea\x36\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x87\ +\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\x88\ +\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x8d\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x10\x04\x00\x00\x00\x00\x00\x01\x00\x04\xf0\x71\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x10\x34\x00\x00\x00\x00\x00\x01\x00\x04\xfa\x07\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x10\x64\x00\x00\x00\x00\x00\x01\x00\x05\x05\xe3\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x10\x88\x00\x00\x00\x00\x00\x01\x00\x05\x0c\x27\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x10\xb2\x00\x00\x00\x00\x00\x01\x00\x05\x13\xb0\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x10\xde\x00\x00\x00\x00\x00\x01\x00\x05\x1a\x0e\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x11\x14\x00\x00\x00\x00\x00\x01\x00\x05\x21\xfd\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x08\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x2f\xa0\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x08\xbe\x00\x00\x00\x00\x00\x01\x00\x05\x34\xd5\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x11\x32\x00\x00\x00\x00\x00\x01\x00\x05\x3e\xaa\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x93\ +\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\x94\ +\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\x46\x74\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x01\x88\x00\x02\x00\x00\x00\x01\x00\x00\x00\x96\ +\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\x97\ +\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\x4b\x3a\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x11\x90\x00\x00\x00\x00\x00\x01\x00\x05\x52\xee\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x11\xa8\x00\x00\x00\x00\x00\x01\x00\x05\x55\x13\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x11\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x56\x93\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x11\xfc\x00\x00\x00\x00\x00\x01\x00\x05\x5e\x40\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x12\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x63\x15\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x12\x32\x00\x00\x00\x00\x00\x01\x00\x05\x64\x05\ -\x00\x00\x01\x9b\x8e\xa8\x75\xac\ -\x00\x00\x12\x48\x00\x00\x00\x00\x00\x01\x00\x05\x68\x2e\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x12\x6e\x00\x00\x00\x00\x00\x01\x00\x05\x6e\x65\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x05\x83\x72\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x12\xaa\x00\x00\x00\x00\x00\x01\x00\x05\x88\x62\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x01\x00\x05\x8b\x61\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x12\xd6\x00\x00\x00\x00\x00\x01\x00\x05\x91\x6b\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x05\x94\xba\ -\x00\x00\x01\x9b\x8e\xa8\x75\x96\ -\x00\x00\x13\x20\x00\x00\x00\x00\x00\x01\x00\x05\x98\xdb\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x05\x9e\xd2\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x13\x4a\x00\x00\x00\x00\x00\x01\x00\x05\xa0\xe3\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x13\x62\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xa6\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ -\x00\x00\x13\x88\x00\x00\x00\x00\x00\x01\x00\x05\xae\x5a\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x13\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xb1\xa8\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x13\xd4\x00\x00\x00\x00\x00\x01\x00\x05\xb5\x36\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x13\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xb9\xd7\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x14\x0e\x00\x00\x00\x00\x00\x01\x00\x05\xc3\xa9\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x14\x3a\x00\x00\x00\x00\x00\x01\x00\x05\xc8\xf3\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x14\x62\x00\x00\x00\x00\x00\x01\x00\x05\xce\xfa\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x14\x78\x00\x00\x00\x00\x00\x01\x00\x05\xcf\xde\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x05\xd2\x27\ -\x00\x00\x01\x9b\x09\x08\xf9\x59\ -\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd8\xd7\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x14\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xdc\x1b\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x14\xee\x00\x00\x00\x00\x00\x01\x00\x05\xdd\x47\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x05\xe3\x08\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x15\x2a\x00\x00\x00\x00\x00\x01\x00\x05\xe4\x2a\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x15\x48\x00\x00\x00\x00\x00\x01\x00\x05\xea\x1d\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x15\x68\x00\x00\x00\x00\x00\x01\x00\x05\xed\x21\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x15\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xee\x42\ -\x00\x00\x01\x9b\x09\x08\xf9\x55\ -\x00\x00\x15\xaa\x00\x00\x00\x00\x00\x01\x00\x05\xf1\x16\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x15\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xf9\x82\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x06\x01\x42\ -\x00\x00\x01\x9b\x09\x08\xf9\x51\ -\x00\x00\x16\x20\x00\x00\x00\x00\x00\x01\x00\x06\x06\x5d\ -\x00\x00\x01\x9b\x09\x08\xf9\x4d\ -\x00\x00\x16\x48\x00\x00\x00\x00\x00\x01\x00\x06\x07\xb0\ -\x00\x00\x01\x9b\x09\x08\xf9\x49\ +\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/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 From 95d956f5eb96518119c70b51ac3a0a0e58241c11 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Wed, 14 Jan 2026 11:38:41 +0000 Subject: [PATCH 31/43] Work network priority (#122) * ADD: added priority to 'get_saved_networks' * ADD: added priority buttons ADD: added details page to saved network Refacotr: refactor how edit on saved is made * ADD: added option to set an Tittle * UPD: changed some text * Refactor: Ran Ruff formatter * Refactor: some butttons text and group name --------- Co-authored-by: Roberto Co-authored-by: Hugo Costa --- BlocksScreen/lib/network.py | 3 + BlocksScreen/lib/panels/networkWindow.py | 60 +- BlocksScreen/lib/ui/wifiConnectivityWindow.ui | 1170 +++++++++++------ .../lib/ui/wifiConnectivityWindow_ui.py | 515 +++++--- BlocksScreen/lib/utils/blocks_frame.py | 95 +- 5 files changed, 1233 insertions(+), 610 deletions(-) diff --git a/BlocksScreen/lib/network.py b/BlocksScreen/lib/network.py index 312b60e8..51b87074 100644 --- a/BlocksScreen/lib/network.py +++ b/BlocksScreen/lib/network.py @@ -731,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" diff --git a/BlocksScreen/lib/panels/networkWindow.py b/BlocksScreen/lib/panels/networkWindow.py index 57617efe..125d07ba 100644 --- a/BlocksScreen/lib/panels/networkWindow.py +++ b/BlocksScreen/lib/panels/networkWindow.py @@ -223,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(), @@ -316,8 +316,23 @@ def __init__(self, parent: typing.Optional[QtWidgets.QWidget], /) -> None: QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg") ) - self.panel.network_activate_btn.clicked.connect(self.saved_wifi_option_selected) - self.panel.network_delete_btn.clicked.connect(self.saved_wifi_option_selected) + 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() + ) + self.panel.network_delete_btn.clicked.connect( + lambda: self.saved_wifi_option_selected() + ) self.network_list_worker.build() self.request_network_scan.emit() @@ -801,10 +816,16 @@ def update_network( 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)) @@ -838,14 +859,35 @@ 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.hig_priorrity_btn.setChecked(True) + elif priority == 20: + self.panel.low_priorrity_btn.setChecked(True) + else: + self.panel.med_priorrity_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/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..1af9f761 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 = GroupButton(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 = GroupButton(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 = GroupButton(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.group_button import GroupButton from lib.utils.icon_button import IconButton diff --git a/BlocksScreen/lib/utils/blocks_frame.py b/BlocksScreen/lib/utils/blocks_frame.py index 6783ad8a..7de7514e 100644 --- a/BlocksScreen/lib/utils/blocks_frame.py +++ b/BlocksScreen/lib/utils/blocks_frame.py @@ -1,29 +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): - """Get widget frame radius""" - 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): - """Re-implemented method, paint widget""" - 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.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")) From 102a7466217387e3d227a65b7773105ee0acc581 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Wed, 14 Jan 2026 11:42:56 +0000 Subject: [PATCH 32/43] Bugfix: fixed loadwidget default being placeholder (gif) (#145) Co-authored-by: Roberto Co-authored-by: Hugo Costa --- BlocksScreen/lib/panels/widgets/loadWidget.py | 2 +- BlocksScreen/lib/panels/widgets/probeHelperPage.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/loadWidget.py b/BlocksScreen/lib/panels/widgets/loadWidget.py index c23d4a5d..1119bd14 100644 --- a/BlocksScreen/lib/panels/widgets/loadWidget.py +++ b/BlocksScreen/lib/panels/widgets/loadWidget.py @@ -18,7 +18,7 @@ class AnimationGIF(enum.Enum): def __init__( self, parent: QtWidgets.QWidget, - initial_anim_type: AnimationGIF = AnimationGIF.PLACEHOLDER, + initial_anim_type: AnimationGIF = AnimationGIF.DEFAULT, ) -> None: super().__init__(parent) diff --git a/BlocksScreen/lib/panels/widgets/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/probeHelperPage.py index a3dea377..77e17d11 100644 --- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py +++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py @@ -52,7 +52,7 @@ def __init__(self, parent: QtWidgets.QWidget) -> None: self.Loadscreen = BasePopup(self, dialog=False) self.loadwidget = LoadingOverlayWidget( - self, LoadingOverlayWidget.AnimationGIF.PLACEHOLDER + self, LoadingOverlayWidget.AnimationGIF.DEFAULT ) self.Loadscreen.add_widget(self.loadwidget) self.setObjectName("probe_offset_page") From a9a486bad7832299656f3c6f1b119f5bd7fb74f6 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Wed, 14 Jan 2026 13:35:59 +0000 Subject: [PATCH 33/43] Bugfix/after merge fix (#151) * bugfix: fixed wrong imports * bugfix: wrong button name --------- Co-authored-by: Roberto --- BlocksScreen/lib/panels/networkWindow.py | 7 +++---- BlocksScreen/lib/ui/wifiConnectivityWindow_ui.py | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/BlocksScreen/lib/panels/networkWindow.py b/BlocksScreen/lib/panels/networkWindow.py index 125d07ba..c15b4f75 100644 --- a/BlocksScreen/lib/panels/networkWindow.py +++ b/BlocksScreen/lib/panels/networkWindow.py @@ -874,12 +874,11 @@ def handle_button_click(self, ssid: str): priority = entry.get("priority") if priority == 90: - self.panel.hig_priorrity_btn.setChecked(True) + self.panel.high_priority_btn.setChecked(True) elif priority == 20: - self.panel.low_priorrity_btn.setChecked(True) + self.panel.low_priority_btn.setChecked(True) else: - self.panel.med_priorrity_btn.setChecked(True) - + 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.setDisabled(False) diff --git a/BlocksScreen/lib/ui/wifiConnectivityWindow_ui.py b/BlocksScreen/lib/ui/wifiConnectivityWindow_ui.py index 1af9f761..e66053a9 100644 --- a/BlocksScreen/lib/ui/wifiConnectivityWindow_ui.py +++ b/BlocksScreen/lib/ui/wifiConnectivityWindow_ui.py @@ -781,7 +781,7 @@ def setupUi(self, wifi_stacked_page): self.verticalLayout_17.addItem(spacerItem8) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.low_priority_btn = GroupButton(parent=self.frame_12) + 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) @@ -793,7 +793,7 @@ def setupUi(self, 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 = GroupButton(parent=self.frame_12) + 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) @@ -804,7 +804,7 @@ def setupUi(self, wifi_stacked_page): 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 = GroupButton(parent=self.frame_12) + 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) @@ -1227,5 +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.group_button import GroupButton +from lib.utils.check_button import BlocksCustomCheckButton from lib.utils.icon_button import IconButton From 1a3f83e9c2f66158336cca7746e6d208b1dd6e19 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Wed, 14 Jan 2026 17:52:51 +0000 Subject: [PATCH 34/43] Fans controlling UI wasnt working (#153) Co-authored-by: Guilherme Costa --- BlocksScreen/helper_methods.py | 3 +- BlocksScreen/lib/panels/controlTab.py | 67 ++++++++++----------- BlocksScreen/lib/panels/widgets/tunePage.py | 5 +- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/BlocksScreen/helper_methods.py b/BlocksScreen/helper_methods.py index dafabd96..b4dafc0f 100644 --- a/BlocksScreen/helper_methods.py +++ b/BlocksScreen/helper_methods.py @@ -7,14 +7,13 @@ 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") diff --git a/BlocksScreen/lib/panels/controlTab.py b/BlocksScreen/lib/panels/controlTab.py index c17cb176..5d1b9b80 100644 --- a/BlocksScreen/lib/panels/controlTab.py +++ b/BlocksScreen/lib/panels/controlTab.py @@ -1,24 +1,23 @@ 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.loadWidget import LoadingOverlayWidget 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 PyQt6 import QtCore, QtGui, QtWidgets - -from lib.panels.widgets.popupDialogWidget import Popup from lib.utils.display_button import DisplayButton -from lib.panels.widgets.slider_selector_page import SliderPage - -from lib.panels.widgets.optionCardWidget import OptionCard -from helper_methods import normalize +from PyQt6 import QtCore, QtGui, QtWidgets class ControlTab(QtWidgets.QStackedWidget): @@ -274,6 +273,12 @@ def __init__( ) ) + 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) @@ -306,23 +311,25 @@ def on_fan_object_update( if "speed" not in field: return - if name == "fan_generic Auxiliary_Cooling_Fans": - name = "Auxiliary\ncooling fans" - elif name == "fan_generic CHAMBER_EXHAUST": - name = "Exhaust Fan" - elif name == "fan_generic Part_Cooling_Fan": - name = "Cooling fan" - else: - name = name.removeprefix("fan_generic") - fan_card = self.tune_display_buttons.get(name) + 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 - if fan_card is None: - icon_path = ( - ":/temperature_related/media/btn_icons/blower.svg" - if "blower" in name.lower() - else ":/temperature_related/media/btn_icons/fan.svg" - ) - icon = QtGui.QPixmap(icon_path) + 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)) @@ -379,20 +386,12 @@ 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 == "Auxiliary\ncooling fans": - name = "Auxiliary_Cooling_Fans" - elif name == "Exhaust Fan": - name = "CHAMBER_EXHAUST" - elif name == "Cooling fan": - name = "Part_Cooling_Fan" - else: - ... 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 diff --git a/BlocksScreen/lib/panels/widgets/tunePage.py b/BlocksScreen/lib/panels/widgets/tunePage.py index 0fc8faf7..09b5868d 100644 --- a/BlocksScreen/lib/panels/widgets/tunePage.py +++ b/BlocksScreen/lib/panels/widgets/tunePage.py @@ -95,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 @@ -113,7 +114,7 @@ def on_fan_object_update( """ fields = name.split() first_field = fields[0] - second_field = fields[1].lower() if len(fields) > 1 else None + second_field = fields[1] if len(fields) > 1 else None if "speed" in field: if not self.tune_display_buttons.get(name, None) and first_field in ( "fan", @@ -126,6 +127,8 @@ def on_fan_object_update( _new_display_button.setParent(self) _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): From 92e7e54bfde28cc8e07c5fe7f6b7c8241753eb5c Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Wed, 14 Jan 2026 17:54:53 +0000 Subject: [PATCH 35/43] Bugfix/update page & Popup logic (#154) * add: added popup cap * bugfix: made update page hide * bugfix: fixed broken popup logic --------- Co-authored-by: Roberto --- BlocksScreen/lib/panels/mainWindow.py | 16 +++++++++------- .../lib/panels/widgets/popupDialogWidget.py | 3 +++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index 63c53fa7..3ea87f52 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -308,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) @@ -566,13 +567,14 @@ def _handle_notify_gcode_response_message(self, method, data, metadata) -> None: return _gcode_msg_type, _message = str(_gcode_response[0]).split(" ", maxsplit=1) popupWhitelist = ["filament runout", "no filament"] - if _message.lower() in popupWhitelist or _gcode_msg_type == "!!": - _msg_type = Popup.MessageType.ERROR - self.popup.new_message( - message_type=_msg_type, - message=str(_message), - userInput=True, - ) + 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: diff --git a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py index 69deec51..3696c90e 100644 --- a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py +++ b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py @@ -131,6 +131,9 @@ def new_message( Returns: _type_: _description_ """ + if len(self.messages) == 4: + return + self.messages.append( { "message": message, From ca26de51b6d8fa390768fb7f0d851375febfb49f Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Thu, 15 Jan 2026 10:53:11 +0000 Subject: [PATCH 36/43] bugfix: ipv4 ip command error fix (#155) Co-authored-by: Guilherme Costa --- BlocksScreen/lib/panels/networkWindow.py | 39 ++++++++++-------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/BlocksScreen/lib/panels/networkWindow.py b/BlocksScreen/lib/panels/networkWindow.py index c15b4f75..f9636a82 100644 --- a/BlocksScreen/lib/panels/networkWindow.py +++ b/BlocksScreen/lib/panels/networkWindow.py @@ -1,13 +1,13 @@ import logging -import typing import subprocess # nosec: B404 +import typing 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") @@ -630,22 +630,7 @@ def get_hotspot_ip_via_shell(self): Returns: The IP address string (e.g., '10.42.0.1') or None if not found. """ - command = [ - "ip", - "a", - "show", - "wlan0", - " |", - "grep", - " 'inet '", - "|", - "awk", - " '{{print $2}}'", - "|", - "cut", - "-d/", - "-f1", - ] + command = ["ip", "-4", "addr", "show", "wlan0"] try: result = subprocess.run( # nosec: B603 command, @@ -654,9 +639,6 @@ def get_hotspot_ip_via_shell(self): check=True, timeout=5, ) - ip_addr = result.stdout.strip() - if ip_addr and len(ip_addr.split(".")) == 4: - return ip_addr except subprocess.CalledProcessError as e: logging.error( "Caught exception (exit code %d) failed to run command: %s \nStderr: %s", @@ -664,10 +646,21 @@ def get_hotspot_ip_via_shell(self): command, e.stderr.strip(), ) - raise + return "" + except FileNotFoundError: + logging.error("Command not found") + return "" except subprocess.TimeoutExpired as e: logging.error("Caught exception, failed to run command %s", e) - raise + 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""" From 0dd5d86bb49bfaa118558ff952b45a3931a9cdbf Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Fri, 16 Jan 2026 15:49:57 +0000 Subject: [PATCH 37/43] bugfix: fans_widget on tunepage are stacked (#156) Co-authored-by: Guilherme Costa --- BlocksScreen/lib/panels/widgets/tunePage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/BlocksScreen/lib/panels/widgets/tunePage.py b/BlocksScreen/lib/panels/widgets/tunePage.py index 09b5868d..301c5cd1 100644 --- a/BlocksScreen/lib/panels/widgets/tunePage.py +++ b/BlocksScreen/lib/panels/widgets/tunePage.py @@ -115,6 +115,7 @@ def on_fan_object_update( 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 if "speed" in field: if not self.tune_display_buttons.get(name, None) and first_field in ( "fan", From a5f360043f29be7c88d05f60cfac96be7dda0350 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Mon, 19 Jan 2026 10:25:30 +0000 Subject: [PATCH 38/43] bugfix ztilt loadscreen (#158) * Add z_tilt object update handler and corresponding signal * bugfix: fixed ztilt hiding on wrong moment * Refactor: ran ruff formatter --------- Co-authored-by: HugoCLSC Co-authored-by: Roberto --- BlocksScreen/lib/panels/controlTab.py | 12 +++++++----- BlocksScreen/lib/panels/mainWindow.py | 6 ++++++ BlocksScreen/lib/printer.py | 8 +++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/BlocksScreen/lib/panels/controlTab.py b/BlocksScreen/lib/panels/controlTab.py index 5d1b9b80..a957bea0 100644 --- a/BlocksScreen/lib/panels/controlTab.py +++ b/BlocksScreen/lib/panels/controlTab.py @@ -287,7 +287,7 @@ def __init__( self.ws.klippy_state_signal.connect(self.probe_helper_page.on_klippy_status) self.printer.on_printcore_update.connect(self.handle_printcoreupdate) self.printer.gcode_response.connect(self._handle_gcode_response) - + self.printer.z_tilt_update.connect(self._handle_z_tilt_object_update) # self.panel.cp_printer_settings_btn.hide() self.panel.temperature_cooldown_btn.hide() self.panel.cooldown_btn.hide() @@ -296,6 +296,12 @@ def __init__( 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) + def _handle_z_tilt_object_update(self, value, state): + if state: + self.ztilt_state = state + if self.loadscreen.isVisible(): + self.loadscreen.hide() + @QtCore.pyqtSlot(str, str, float, name="on_fan_update") @QtCore.pyqtSlot(str, str, int, name="on_fan_update") def on_fan_object_update( @@ -455,10 +461,6 @@ def _handle_gcode_response(self, messages: list): self.loadscreen.hide() return - if probed_range < tolerance: - self.loadscreen.hide() - return - self.loadwidget.set_status_message( f"Retries: {retries_done}/{retries_total} | Range: {probed_range:.6f} | Tolerance: {tolerance:.6f}" ) diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index 3ea87f52..66627b5e 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -575,6 +575,9 @@ def _handle_notify_gcode_response_message(self, method, data, metadata) -> None: message=str(_message), userInput=True, ) + if not self.controlPanel.ztilt_state: + if self.controlPanel.loadscreen.isVisible(): + self.controlPanel.loadscreen.hide() @api_handler def _handle_error_message(self, method, data, metadata) -> None: @@ -596,6 +599,9 @@ def _handle_error_message(self, method, data, metadata) -> None: message=str(text), userInput=True, ) + if not self.controlPanel.ztilt_state: + if self.controlPanel.loadscreen.isVisible(): + self.controlPanel.loadscreen.hide() @api_handler def _handle_notify_cpu_throttled_message(self, method, data, metadata) -> None: diff --git a/BlocksScreen/lib/printer.py b/BlocksScreen/lib/printer.py index 52706fd5..5889c19d 100644 --- a/BlocksScreen/lib/printer.py +++ b/BlocksScreen/lib/printer.py @@ -76,7 +76,9 @@ class Printer(QtCore.QObject): configfile_update: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( dict, name="configfile_update" ) - + z_tilt_update: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( + str, bool, name="z_tilt_update" + ) config_subscription: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( [dict], [list], @@ -466,6 +468,10 @@ def _heater_fan_object_updated(self, value: dict, fan_name: str = "") -> None: _names = ["heater_fan", fan_name] # object_name = " ".join(_names) + def _z_tilt_object_updated(self, value: dict, name: str = "") -> None: + if value["applied"]: + self.z_tilt_update[str, bool].emit("applied", value["applied"]) + def _idle_timeout_object_updated( self, value: dict, name: str = "idle_timeout" ) -> None: From a17c8eb3de0ec9e4c253f2b60a4eee21be834ea0 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Mon, 19 Jan 2026 19:41:37 +0000 Subject: [PATCH 39/43] bugfix: inputshaper load not hiding (#161) Co-authored-by: Roberto --- BlocksScreen/lib/panels/utilitiesTab.py | 1 + 1 file changed, 1 insertion(+) diff --git a/BlocksScreen/lib/panels/utilitiesTab.py b/BlocksScreen/lib/panels/utilitiesTab.py index 37fa6f61..c6453eae 100644 --- a/BlocksScreen/lib/panels/utilitiesTab.py +++ b/BlocksScreen/lib/panels/utilitiesTab.py @@ -199,6 +199,7 @@ def __init__( self.subscribe_config[list, "PyQt_PyObject"].connect( self.printer.on_subscribe_config ) + self.printer.gcode_response.connect(self.handle_gcode_response) # --- Initialize Printer Communication --- self.printer.printer_config.connect(self.on_printer_config_received) From 14a60849af1aec01ba716587082926868d9ee1c7 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Tue, 20 Jan 2026 13:24:26 +0000 Subject: [PATCH 40/43] bugfix/popup show right arrow (#163) Co-authored-by: Guilherme Costa --- BlocksScreen/lib/panels/widgets/popupDialogWidget.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py index 3696c90e..d565bd3a 100644 --- a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py +++ b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py @@ -1,8 +1,9 @@ import enum from collections import deque from typing import Deque -from PyQt6 import QtCore, QtGui, QtWidgets + from lib.utils.icon_button import IconButton +from PyQt6 import QtCore, QtGui, QtWidgets class Popup(QtWidgets.QDialog): @@ -188,12 +189,9 @@ def _add_popup(self) -> None: self.slide_in_animation.setEndValue(end_rect) self.slide_out_animation.setStartValue(end_rect) self.slide_out_animation.setEndValue(start_rect) - if not self.userInput: - self.actionbtn.clearPixmap() - else: - self.actionbtn.setPixmap( - QtGui.QPixmap(":/arrow_icons/media/btn_icons/right_arrow.svg") - ) + 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( From d0144bf2bf3145a47b4c2ac1289882ffd2f92e89 Mon Sep 17 00:00:00 2001 From: Guilherme Costa Date: Tue, 20 Jan 2026 13:27:26 +0000 Subject: [PATCH 41/43] swap lower and raise nozzle icons (#164) Co-authored-by: Guilherme Costa --- .../lib/panels/widgets/probeHelperPage.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/BlocksScreen/lib/panels/widgets/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/probeHelperPage.py index 77e17d11..9cf25e59 100644 --- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py +++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py @@ -1,14 +1,13 @@ import typing +from lib.panels.widgets.basePopup import BasePopup +from lib.panels.widgets.loadWidget import LoadingOverlayWidget from lib.panels.widgets.optionCardWidget import OptionCard -from PyQt6 import QtCore, QtGui, QtWidgets +from lib.utils.blocks_button import BlocksCustomButton from lib.utils.blocks_label import BlocksLabel -from lib.utils.icon_button import IconButton from lib.utils.check_button import BlocksCustomCheckButton -from lib.utils.blocks_button import BlocksCustomButton - -from lib.panels.widgets.loadWidget import LoadingOverlayWidget -from lib.panels.widgets.basePopup import BasePopup +from lib.utils.icon_button import IconButton +from PyQt6 import QtCore, QtGui, QtWidgets class ProbeHelper(QtWidgets.QWidget): @@ -890,7 +889,7 @@ def _setupUi(self) -> None: font.setPointSize(14) self.current_offset_info.setFont(font) self.current_offset_info.setStyleSheet("background: transparent; color: white;") - self.current_offset_info.setText("Z:0mm") + self.current_offset_info.setText("Z:0.000mm") self.current_offset_info.setPixmap( QtGui.QPixmap(":/graphics/media/btn_icons/z_offset_adjust.svg") ) @@ -917,7 +916,7 @@ def _setupUi(self) -> None: self.mb_lower_nozzle.setText("") self.mb_lower_nozzle.setFlat(True) self.mb_lower_nozzle.setPixmap( - QtGui.QPixmap(":/arrow_icons/media/btn_icons/up_arrow.svg") + QtGui.QPixmap(":/baby_step/media/btn_icons/move_nozzle_close.svg") ) self.mb_lower_nozzle.setObjectName("bbp_away_from_bed") self.bbp_option_button_group = QtWidgets.QButtonGroup(self) @@ -936,7 +935,7 @@ def _setupUi(self) -> None: self.mb_raise_nozzle.setText("") self.mb_raise_nozzle.setFlat(True) self.mb_raise_nozzle.setPixmap( - QtGui.QPixmap(":/arrow_icons/media/btn_icons/down_arrow.svg") + QtGui.QPixmap(":/baby_step/media/btn_icons/move_nozzle_away.svg") ) self.mb_raise_nozzle.setObjectName("bbp_close_to_bed") self.bbp_option_button_group.addButton(self.mb_raise_nozzle) From d62879fe45c97e9f546f90564667e7d3448e3f32 Mon Sep 17 00:00:00 2001 From: Roberto Martins Date: Tue, 20 Jan 2026 13:33:11 +0000 Subject: [PATCH 42/43] Refactor loadscreen on the project (#165) * Refactor: added single instance of loadScreen on all project bugfix fixed conenctionpage below load page * Refactor: ran ruff formatter --------- Signed-off-by: Hugo Costa Co-authored-by: Roberto --- BlocksScreen/lib/panels/controlTab.py | 41 +++++-------- BlocksScreen/lib/panels/filamentTab.py | 57 +++++++++---------- BlocksScreen/lib/panels/mainWindow.py | 47 ++++++++++++--- BlocksScreen/lib/panels/printTab.py | 12 +--- BlocksScreen/lib/panels/utilitiesTab.py | 21 +++---- .../lib/panels/widgets/connectionPage.py | 2 + .../lib/panels/widgets/inputshaperPage.py | 15 +---- .../lib/panels/widgets/probeHelperPage.py | 13 ++--- BlocksScreen/lib/panels/widgets/updatePage.py | 19 ++----- 9 files changed, 106 insertions(+), 121 deletions(-) diff --git a/BlocksScreen/lib/panels/controlTab.py b/BlocksScreen/lib/panels/controlTab.py index a957bea0..d5b5d6f5 100644 --- a/BlocksScreen/lib/panels/controlTab.py +++ b/BlocksScreen/lib/panels/controlTab.py @@ -6,8 +6,6 @@ from helper_methods import normalize from lib.moonrakerComm import MoonWebSocket -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 @@ -47,6 +45,7 @@ class ControlTab(QtWidgets.QStackedWidget): request_file_info: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( str, name="request-file-info" ) + call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") tune_display_buttons: dict = {} card_options: dict = {} @@ -70,6 +69,7 @@ def __init__( self.extruder_info: dict = {} self.bed_info: dict = {} self.toolhead_info: dict = {} + self.ztilt_state = False self.extrude_length: int = 10 self.extrude_feedrate: int = 2 self.extrude_page_message: str = "" @@ -77,15 +77,10 @@ def __init__( self.move_speed: float = 25.0 self.probe_helper_page = ProbeHelper(self) self.addWidget(self.probe_helper_page) + self.probe_helper_page.call_load_panel.connect(self.call_load_panel) self.printcores_page = SwapPrintcorePage(self) self.addWidget(self.printcores_page) - 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) @@ -299,8 +294,7 @@ def __init__( def _handle_z_tilt_object_update(self, value, state): if state: self.ztilt_state = state - if self.loadscreen.isVisible(): - self.loadscreen.hide() + self.call_load_panel.emit(False, "") @QtCore.pyqtSlot(str, str, float, name="on_fan_update") @QtCore.pyqtSlot(str, str, int, name="on_fan_update") @@ -425,17 +419,16 @@ def handle_printcoreupdate(self, value: dict): return if value["swapping"] == "in_pos": - self.loadscreen.hide() + self.call_load_panel.emit(False, "") 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.loadwidget.set_status_message("Unloading print core") - + self.call_load_panel.emit(True, "Unloading print core") if value["swapping"] == "cleaning": - self.loadwidget.set_status_message("Cleaning print core") + self.call_load_panel.emit(True, "Cleaning print core") def _handle_gcode_response(self, messages: list): """Handle gcode response for Z-tilt adjustment""" @@ -458,20 +451,17 @@ def _handle_gcode_response(self, messages: list): probed_range = float(match.group(3)) tolerance = float(match.group(4)) if retries_done == retries_total: - self.loadscreen.hide() + self.call_load_panel.emit(False, "") return - - self.loadwidget.set_status_message( - f"Retries: {retries_done}/{retries_total} | Range: {probed_range:.6f} | Tolerance: {tolerance:.6f}" + self.call_load_panel.emit( + True, + f"Retries: {retries_done}/{retries_total} | Range: {probed_range:.6f} | Tolerance: {tolerance:.6f}", ) def handle_ztilt(self): """Handle Z-Tilt Adjustment""" - self.loadscreen.show() - self.loadwidget.set_status_message( - "Please wait, performing Z-axis calibration." - ) - self.run_gcode_signal.emit("G28\nM400\nZ_TILT_ADJUST") + self.call_load_panel.emit(True, "Please wait, performing Z-axis calibration.") + self.run_gcode_signal.emit("Z_TILT_ADJUST") @QtCore.pyqtSlot(str, name="on-klippy-status") def on_klippy_status(self, state: str): @@ -487,8 +477,7 @@ def on_klippy_status(self, state: str): def show_swapcore(self): """Show swap printcore""" self.run_gcode_signal.emit("CHANGE_PRINTCORES") - self.loadscreen.show() - self.loadwidget.set_status_message("Preparing to swap print core") + self.call_load_panel.emit(True, "Preparing to swap print core") def handle_swapcore(self): """Handle swap printcore routine finish""" @@ -660,7 +649,7 @@ def on_toolhead_update(self, field: str, values: list) -> None: 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.loadscreen.hide + self.call_load_panel.emit(False, "") 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 1271375e..4b368bd8 100644 --- a/BlocksScreen/lib/panels/filamentTab.py +++ b/BlocksScreen/lib/panels/filamentTab.py @@ -6,8 +6,6 @@ from lib.filament import Filament from lib.ui.filamentStackedWidget_ui import Ui_filamentStackedWidget -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 @@ -19,6 +17,7 @@ class FilamentTab(QtWidgets.QStackedWidget): request_change_page = QtCore.pyqtSignal(int, int, name="request_change_page") request_toolhead_count = QtCore.pyqtSignal(int, name="toolhead_number_received") run_gcode = QtCore.pyqtSignal(str, name="run_gcode") + call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") class FilamentTypes(enum.Enum): PLA = Filament(name="PLA", temperature=220) @@ -42,11 +41,6 @@ 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 = 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 = {} @@ -130,19 +124,25 @@ def on_extruder_update( """Handle extruder update""" if not self.isVisible: return - - if self.target_temp != 0: - if self.current_temp == self.target_temp: - self.loadwidget.set_status_message("Extruder heated up \n Please wait") - return - if field == "temperature": - 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) - self.loadwidget.set_status_message("Heating up \n Please wait") + if not self.loadignore or not self.unloadignore: + if self.target_temp != 0: + if self.current_temp == self.target_temp: + if self.isVisible: + self.call_load_panel.emit( + True, "Extruder heated up \n Please wait" + ) + return + if field == "temperature": + self.current_temp = round(new_value, 0) + if self.isVisible: + self.call_load_panel.emit( + True, + f"Heating up ({new_value}/{self.target_temp}) \n Please wait", + ) + if field == "target": + self.target_temp = round(new_value, 0) + if self.isVisible: + self.call_load_panel.emit(True, "Heating up \n Please wait") @QtCore.pyqtSlot(bool, name="on_load_filament") def on_load_filament(self, status: bool): @@ -150,14 +150,13 @@ def on_load_filament(self, status: bool): if self.loadignore: self.loadignore = False return - if not self.isVisible: return if status: - self.loadscreen.show() + self.call_load_panel.emit(True, "Loading Filament") else: self.target_temp = 0 - self.loadscreen.hide() + self.call_load_panel.emit(False, "") self._filament_state = self.FilamentStates.LOADED self.handle_filament_state() @@ -167,14 +166,12 @@ def on_unload_filament(self, status: bool): if self.unloadignore: self.unloadignore = False return - if not self.isVisible: return - if status: - self.loadscreen.show() + self.call_load_panel.emit(True, "Unloading Filament") else: - self.loadscreen.hide() + self.call_load_panel.emit(False, "") self.target_temp = 0 self._filament_state = self.FilamentStates.UNLOADED self.handle_filament_state() @@ -197,7 +194,8 @@ def load_filament(self, toolhead: int = 0, temp: int = 220) -> None: message="Filament is already loaded.", ) return - self.loadscreen.show() + self.loadignore = False + self.call_load_panel.emit(True, "Loading Filament") self.run_gcode.emit(f"LOAD_FILAMENT TOOLHEAD=load_toolhead TEMPERATURE={temp}") @QtCore.pyqtSlot(str, int, name="unload_filament") @@ -220,7 +218,8 @@ def unload_filament(self, toolhead: int = 0, temp: int = 220) -> None: return self.find_routine_objects() - self.loadscreen.show() + self.unload_filament = False + self.call_load_panel.emit(True, "Unloading Filament") self.run_gcode.emit(f"UNLOAD_FILAMENT TEMPERATURE={temp}") def handle_filament_state(self): diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index 66627b5e..32355803 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -17,6 +17,8 @@ from lib.printer import Printer from lib.ui.mainWindow_ui import Ui_MainWindow # With header from lib.panels.widgets.updatePage import UpdatePage +from lib.panels.widgets.basePopup import BasePopup +from lib.panels.widgets.loadWidget import LoadingOverlayWidget # from lib.ui.mainWindow_v2_ui import Ui_MainWindow # No header from lib.ui.resources.background_resources_rc import * @@ -63,6 +65,7 @@ class MainWindow(QtWidgets.QMainWindow): on_update_message: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( dict, name="on-update-message" ) + call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") def __init__(self): super(MainWindow, self).__init__() @@ -140,6 +143,7 @@ def __init__(self): slot=self.mc.restart_klipper_service ) self.conn_window.reboot_clicked.connect(slot=self.mc.machine_restart) + self.printer_object_report_signal.connect( self.printer.on_object_report_received ) @@ -175,11 +179,46 @@ def __init__(self): 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" + + self.controlPanel.call_load_panel.connect(self.show_LoadScreen) + self.filamentPanel.call_load_panel.connect(self.show_LoadScreen) + self.printPanel.call_load_panel.connect(self.show_LoadScreen) + self.utilitiesPanel.call_load_panel.connect(self.show_LoadScreen) + self.conn_window.call_load_panel.connect(self.show_LoadScreen) + + self.loadscreen = BasePopup(self, floating=False, dialog=False) + self.loadwidget = LoadingOverlayWidget( + self, LoadingOverlayWidget.AnimationGIF.DEFAULT + ) + self.loadscreen.add_widget(self.loadwidget) if self.config.has_section("server"): # @ Start websocket connection with moonraker self.bo_ws_startup.emit() self.reset_tab_indexes() + @QtCore.pyqtSlot(bool, str, name="show-load-page") + def show_LoadScreen(self, show: bool = True, msg: str = ""): + _sender = self.sender() + + if _sender == self.filamentPanel: + if not self.filamentPanel.isVisible(): + return + if _sender == self.controlPanel: + if not self.controlPanel.isVisible(): + return + if _sender == self.printPanel: + if not self.printPanel.isVisible(): + return + if _sender == self.utilitiesPanel: + if not self.utilitiesPanel.isVisible(): + return + + self.loadwidget.set_status_message(msg) + if show: + self.loadscreen.show() + else: + self.loadscreen.hide() + @QtCore.pyqtSlot(bool, name="show-update-page") def show_update_page(self, fullscreen: bool): """Slot for displaying update Panel""" @@ -365,7 +404,7 @@ def global_change_page(self, tab_index: int, panel_index: int) -> None: "Panel page index expected type int, %s", str(type(panel_index)) ) - self.printPanel.loadscreen.hide() + self.show_LoadScreen(False) current_page = [ self.ui.main_content_widget.currentIndex(), self.current_panel_index(), @@ -575,9 +614,6 @@ def _handle_notify_gcode_response_message(self, method, data, metadata) -> None: message=str(_message), userInput=True, ) - if not self.controlPanel.ztilt_state: - if self.controlPanel.loadscreen.isVisible(): - self.controlPanel.loadscreen.hide() @api_handler def _handle_error_message(self, method, data, metadata) -> None: @@ -599,9 +635,6 @@ def _handle_error_message(self, method, data, metadata) -> None: message=str(text), userInput=True, ) - if not self.controlPanel.ztilt_state: - if self.controlPanel.loadscreen.isVisible(): - self.controlPanel.loadscreen.hide() @api_handler def _handle_notify_cpu_throttled_message(self, method, data, metadata) -> None: diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py index 6331585a..7431938e 100644 --- a/BlocksScreen/lib/panels/printTab.py +++ b/BlocksScreen/lib/panels/printTab.py @@ -11,7 +11,6 @@ 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.panels.widgets.slider_selector_page import SliderPage @@ -63,6 +62,7 @@ class PrintTab(QtWidgets.QStackedWidget): on_cancel_print: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( name="on_cancel_print" ) + call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") _z_offset: float = 0.0 _active_z_offset: float = 0.0 @@ -92,12 +92,6 @@ def __init__( self.numpadPage.request_back.connect(self.back_button) self.addWidget(self.numpadPage) - 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) @@ -372,9 +366,7 @@ def setProperty(self, name: str, value: typing.Any) -> bool: def handle_cancel_print(self) -> None: """Handles the print cancel action""" self.ws.api.cancel_print() - self.loadscreen.show() - self.loadscreen.setModal(True) - self.loadwidget.set_status_message("Cancelling print...\nPlease wait") + self.call_load_panel.emit(True, "Cancelling print...\nPlease wait") def change_page(self, index: int) -> None: """Requests a page change page to the global manager diff --git a/BlocksScreen/lib/panels/utilitiesTab.py b/BlocksScreen/lib/panels/utilitiesTab.py index c6453eae..6cff5f27 100644 --- a/BlocksScreen/lib/panels/utilitiesTab.py +++ b/BlocksScreen/lib/panels/utilitiesTab.py @@ -14,7 +14,6 @@ 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 @@ -89,6 +88,7 @@ class UtilitiesTab(QtWidgets.QStackedWidget): show_update_page: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( bool, name="show-update-page" ) + call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") def __init__( self, parent: QtWidgets.QWidget, ws: MoonWebSocket, printer: Printer @@ -123,17 +123,12 @@ def __init__( # --- UI Setup --- self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - 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.is_page.call_load_panel.connect(self.call_load_panel) self.addWidget(self.is_page) self.dialog_page = BasePopup(self, dialog=True, floating=True) @@ -310,7 +305,7 @@ def handle_gcode_response(self, data: list[str]) -> None: 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.call_load_panel.emit(False, "") self.aut = False return return @@ -329,7 +324,7 @@ def handle_gcode_response(self, data: list[str]) -> None: self.is_page.add_type_entry(key) self.is_page.build_model_list() - self.loadPage.hide() + self.call_load_panel.emit(False, "") return def handle_is(self, gcode: str) -> None: @@ -354,8 +349,7 @@ def handle_is(self, gcode: str) -> None: 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() + self.call_load_panel.emit(True, "Running Input Shaper...") @QtCore.pyqtSlot(list, name="on_object_list") def on_object_list(self, object_list: list) -> None: @@ -657,8 +651,7 @@ def troubleshoot_request(self) -> None: def show_waiting_page(self, page_to_go_to: int, label: str, time_ms: int): """Show placeholder page""" - self.loadwidget.set_status_message(label) - self.loadPage.show() + self.call_load_panel.emit(True, label) QtCore.QTimer.singleShot(time_ms, lambda: self.change_page(page_to_go_to)) def _connect_page_change(self, button: QtWidgets.QWidget, page: QtWidgets.QWidget): @@ -667,7 +660,7 @@ def _connect_page_change(self, button: QtWidgets.QWidget, page: QtWidgets.QWidge def change_page(self, index: int): """Request change page by index""" - self.loadPage.hide() + self.call_load_panel.emit(False, "") self.troubleshoot_page.hide() if index < self.count(): self.request_change_page.emit(3, index) diff --git a/BlocksScreen/lib/panels/widgets/connectionPage.py b/BlocksScreen/lib/panels/widgets/connectionPage.py index 1e9c32d1..9403d290 100644 --- a/BlocksScreen/lib/panels/widgets/connectionPage.py +++ b/BlocksScreen/lib/panels/widgets/connectionPage.py @@ -14,6 +14,7 @@ class ConnectionPage(QtWidgets.QFrame): restart_klipper_clicked = QtCore.pyqtSignal(name="restart_klipper_clicked") firmware_restart_clicked = QtCore.pyqtSignal(name="firmware_restart_clicked") update_button_clicked = QtCore.pyqtSignal(bool, name="show-update-page") + call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") def __init__(self, parent: QtWidgets.QWidget, ws: MoonWebSocket, /): super().__init__(parent) @@ -65,6 +66,7 @@ def show_panel(self, reason: str | None = None): def showEvent(self, a0: QtCore.QEvent | None): """Handle show event""" self.ws.api.refresh_update_status() + self.call_load_panel.emit(False, "") return super().showEvent(a0) @QtCore.pyqtSlot(bool, name="on_klippy_connected") diff --git a/BlocksScreen/lib/panels/widgets/inputshaperPage.py b/BlocksScreen/lib/panels/widgets/inputshaperPage.py index 539dcaf4..ead82cfb 100644 --- a/BlocksScreen/lib/panels/widgets/inputshaperPage.py +++ b/BlocksScreen/lib/panels/widgets/inputshaperPage.py @@ -1,5 +1,3 @@ -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 @@ -18,6 +16,7 @@ class InputShaperPage(QtWidgets.QWidget): run_gcode_signal: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( str, name="run-gcode" ) + call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") def __init__(self, parent=None) -> None: if parent: @@ -28,12 +27,6 @@ def __init__(self, parent=None) -> None: 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() @@ -50,8 +43,7 @@ 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.call_load_panel.emit(False, "Updating...") self.repeated_request_status.stop() self.build_model_list() @@ -59,8 +51,7 @@ 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.call_load_panel.emit(True, "Updating...") self.repeated_request_status.start(2000) def reset_view_model(self) -> None: diff --git a/BlocksScreen/lib/panels/widgets/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/probeHelperPage.py index 9cf25e59..a105037b 100644 --- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py +++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py @@ -10,6 +10,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets + class ProbeHelper(QtWidgets.QWidget): request_back: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( name="request_back" @@ -35,6 +36,7 @@ class ProbeHelper(QtWidgets.QWidget): request_page_view: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( name="request_page_view" ) + call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") distances = ["0.01", ".025", "0.1", "0.5", "1"] _calibration_commands: list = [] @@ -49,11 +51,6 @@ class ProbeHelper(QtWidgets.QWidget): def __init__(self, parent: QtWidgets.QWidget) -> None: super().__init__(parent) - 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.inductive_icon = QtGui.QPixmap( @@ -397,9 +394,7 @@ def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None: for i in self.card_options.values(): i.setDisabled(True) - self.Loadscreen.show() - self.loadwidget.set_status_message("Homing Axes...") - + self.call_load_panel.emit(True, "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) @@ -533,7 +528,7 @@ def _toggle_tool_buttons(self, state: bool) -> None: if state: for i in self.card_options.values(): i.setDisabled(False) - self.Loadscreen.hide() + self.call_load_panel.emit(False, "") self.po_back_button.setEnabled(False) self.po_back_button.hide() self.po_header_title.setEnabled(False) diff --git a/BlocksScreen/lib/panels/widgets/updatePage.py b/BlocksScreen/lib/panels/widgets/updatePage.py index 4857f92e..b91e41d3 100644 --- a/BlocksScreen/lib/panels/widgets/updatePage.py +++ b/BlocksScreen/lib/panels/widgets/updatePage.py @@ -1,7 +1,6 @@ import copy import typing -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 @@ -52,6 +51,7 @@ class UpdatePage(QtWidgets.QWidget): update_available: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal( bool, name="update-available" ) + call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel") def __init__(self, parent=None) -> None: if parent: @@ -62,11 +62,6 @@ def __init__(self, parent=None) -> None: self.cli_tracking = {} self.selected_item: ListItem | None = None self.ongoing_update: bool = False - 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() @@ -90,8 +85,7 @@ 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.call_load_panel.emit(False, "") self.repeated_request_status.stop() self.on_request_reload() self.build_model_list() @@ -100,8 +94,7 @@ 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.call_load_panel.emit(True, "Updating...") self.repeated_request_status.start(2000) def on_request_reload(self, service: str | None = None) -> None: @@ -166,12 +159,10 @@ def on_update_clicked(self) -> None: self.request_update_moonraker.emit() else: self.request_update_client.emit(cli_name) - - self.loadwidget.set_status_message(f"Updating {cli_name}") + self.call_load_panel.emit(True, f"Updating {cli_name}") else: self.request_recover_repo[str, bool].emit(cli_name, True) - self.loadwidget.set_status_message(f"Recovering {cli_name}") - self.load_popup.show() + self.call_load_panel.emit(True, f"Recovering {cli_name}") self.request_update_status.emit(False) @QtCore.pyqtSlot(ListItem, name="on-item-clicked") From 5a6732cc80d15d1995acf6264edc58d4c73b55a4 Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Wed, 21 Jan 2026 10:43:38 +0000 Subject: [PATCH 43/43] Deleted Unused `ztilt_state` variable from control tab (#166) The variable `ztilt_state` was left behing during another PR, it should have been deleted. Now it is --- BlocksScreen/lib/panels/controlTab.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/BlocksScreen/lib/panels/controlTab.py b/BlocksScreen/lib/panels/controlTab.py index d5b5d6f5..1c8eb30b 100644 --- a/BlocksScreen/lib/panels/controlTab.py +++ b/BlocksScreen/lib/panels/controlTab.py @@ -69,7 +69,6 @@ def __init__( self.extruder_info: dict = {} self.bed_info: dict = {} self.toolhead_info: dict = {} - self.ztilt_state = False self.extrude_length: int = 10 self.extrude_feedrate: int = 2 self.extrude_page_message: str = "" @@ -293,7 +292,6 @@ def __init__( def _handle_z_tilt_object_update(self, value, state): if state: - self.ztilt_state = state self.call_load_panel.emit(False, "") @QtCore.pyqtSlot(str, str, float, name="on_fan_update")