pax_global_header00006660000000000000000000000064147035077400014521gustar00rootroot0000000000000052 comment=a7142a09aede82a4fb02f3d011c64c095b4c9eb3 urwid-2.6.16/000077500000000000000000000000001470350774000127475ustar00rootroot00000000000000urwid-2.6.16/.coveralls.yml000066400000000000000000000001071470350774000155400ustar00rootroot00000000000000service_name: travis-pro repo_token: lNMvPpeiiKc20j7oGjScYtGKUfVseah6S urwid-2.6.16/.devcontainer/000077500000000000000000000000001470350774000155065ustar00rootroot00000000000000urwid-2.6.16/.devcontainer/dev.dockerfile000066400000000000000000000005071470350774000203170ustar00rootroot00000000000000FROM python:3.12 RUN apt-get update -y \ && apt-get upgrade -y \ && apt-get install -y python3-gi gobject-introspection libgirepository1.0-dev \ && apt-get clean RUN mkdir -p ~/.virtualenvs \ && pip3 install virtualenv \ && /usr/local/bin/virtualenv -p /usr/local/bin/python3 --download ~/.virtualenvs/urwidurwid-2.6.16/.devcontainer/devcontainer.json000066400000000000000000000022311470350774000210600ustar00rootroot00000000000000{ "build": { "dockerfile": "dev.dockerfile" }, "features": { "ghcr.io/devcontainers/features/github-cli": {} }, "customizations": { // Configure properties specific to VS Code. "vscode": { // Set *default* container specific settings.json values on container create. "settings": { "python.venvPath": "~/.virtualenvs", "python.terminal.activateEnvInCurrentTerminal": true, "python.defaultInterpreterPath": "~/.virtualenvs/urwid/bin/python", "python.testing.unittestEnabled": true }, "extensions": [ "ms-python.python", "ms-python.debugpy", "ms-python.vscode-pylance", "ms-python.isort", "ms-python.black-formatter", "charliermarsh.ruff", "ms-azuretools.vscode-docker", "GitHub.vscode-github-actions" ] } }, "name": "urwid-dev", "containerUser": "root", "init": true, "postCreateCommand": "~/.virtualenvs/urwid/bin/pip3 install isort black ruff pylint refurb mypy", "postStartCommand": "~/.virtualenvs/urwid/bin/pip3 install -r test_requirements.txt PyGObject && ~/.virtualenvs/urwid/bin/pip3 install -U -e ." }urwid-2.6.16/.dockerignore000077700000000000000000000000001470350774000174062.gitignoreustar00rootroot00000000000000urwid-2.6.16/.editorconfig000066400000000000000000000005161470350774000154260ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf indent_style = space insert_final_newline = true trim_trailing_whitespace = true [*.{py,pyi,pyx,pxd}] indent_size = 4 max_line_length = 120 [*.{yml,yaml,json,ini,toml}] indent_size = 2 max_line_length = 120 [*.{rst,md}] indent_size = 2 [{Makefile,Makefile.in}] indent_style = tab urwid-2.6.16/.github/000077500000000000000000000000001470350774000143075ustar00rootroot00000000000000urwid-2.6.16/.github/CODE_OF_CONDUCT.md000066400000000000000000000125611470350774000171130ustar00rootroot00000000000000 # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations urwid-2.6.16/.github/ISSUE_TEMPLATE/000077500000000000000000000000001470350774000164725ustar00rootroot00000000000000urwid-2.6.16/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000005621470350774000211670ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: bug assignees: '' --- ##### Description: [...] ##### Affected versions (if applicable) - [ ] `master` branch (specify commit) - [ ] Latest stable version from `pypi` - [ ] Other (specify source) ##### Steps to reproduce (if applicable) [...] ##### Expected/actual outcome [...] urwid-2.6.16/.github/ISSUE_TEMPLATE/docs.md000066400000000000000000000004161470350774000177450ustar00rootroot00000000000000--- name: Documentation issue about: Documentation related issue or request title: "[docs]" labels: docs assignees: '' --- ##### Description: [...] ##### Affected versions (if applicable) - [ ] `master` branch (specify commit) - [ ] Latest stable version from `pypi` urwid-2.6.16/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011401470350774000222130ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: "[Feature]" labels: Feature assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here.urwid-2.6.16/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000004321470350774000206620ustar00rootroot00000000000000--- name: Question about: Project related question title: "[Question]" labels: question assignees: '' --- ##### Description: [...] ##### Affected versions (if applicable) - [ ] `master` branch (specify commit) - [ ] Latest stable version from `pypi` - [ ] Other (specify source) urwid-2.6.16/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000010461470350774000201110ustar00rootroot00000000000000##### Checklist - [ ] I've ensured that similar functionality has not already been implemented - [ ] I've ensured that similar functionality has not earlier been proposed and declined - [ ] I've branched off the `master` branch - [ ] I've merged fresh upstream into my branch recently - [ ] I've ran `tox` successfully in local environment - [ ] I've included docstrings and/or documentation and/or examples for my code (if this is a new feature) ##### Description: *(P. S. If this pull requests fixes an existing issue, please specify which one.)* urwid-2.6.16/.github/dependabot.yml000066400000000000000000000011251470350774000171360ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" - package-ecosystem: pip directory: "/" schedule: interval: weeklyurwid-2.6.16/.github/labeler.yml000066400000000000000000000021261470350774000164410ustar00rootroot00000000000000"CI/CD": - all: - changed-files: - any-glob-to-any-file: - ".github/workflows/**" - "tox.ini" - ".yamllint.yml" "Maintenance": - all: - changed-files: - any-glob-to-any-file: - ".github/*" - ".github/ISSUE_TEMPLATE/*" "tests": - all: - changed-files: - any-glob-to-any-file: - "tests/**" "docs": - any: - changed-files: - any-glob-to-any-file: - "docs/**" - "examples/**" - "README.rst" "Widget": - any: - changed-files: - any-glob-to-any-file: - "urwid/widget/**" - "urwid/numedit.py" - "urwid/vterm.py" "Display": - any: - changed-files: - any-glob-to-any-file: - "urwid/display/**" "Event loop": - any: - changed-files: - any-glob-to-any-file: - "urwid/event_loop/**" "Canvas": - any: - changed-files: - any-glob-to-any-file: - "urwid/canvas.py" urwid-2.6.16/.github/release.yml000066400000000000000000000012141470350774000164500ustar00rootroot00000000000000changelog: exclude: labels: - "CI/CD" - "Maintenance" - "in progress" - "tests" authors: - "dependabot[bot]" - "dependabot" categories: - title: Breaking Changes âš  labels: - "Breaking" - title: New features 🗹 labels: - "Feature" - title: Deprecations âš¡ labels: - "Deprecation" - title: Bug fixes 🕷 labels: - "bug" - "regression" - title: Documentation 🕮 labels: - "docs" - title: Refactoring 🛠 labels: - "refactoring" - title: Other Changes labels: - "*"urwid-2.6.16/.github/workflows/000077500000000000000000000000001470350774000163445ustar00rootroot00000000000000urwid-2.6.16/.github/workflows/documentation.yml000066400000000000000000000027051470350774000217440ustar00rootroot00000000000000name: Documentation on: push: branches-ignore: - 'dependabot/**' - 'gh-pages' tags: - "[0-9]+.[0-9]+.[0-9]+" # Canonical versioned tags - "[0-9]+.[0-9]+.[0-9]+.post[0-9]+" # Canonical rebuild tags pull_request: branches-ignore: - 'dependabot/**' - 'gh-pages' paths: - "docs/**" - "README.rst" - "urwid/**" - "setup.py" - "pyproject.toml" - ".github/workflows/documentation.yml" jobs: Build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # need for setuptools_scm - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install Ubuntu dependencies run: | sudo apt-get -qq update sudo apt-get install -y python3-gi gobject-introspection libgirepository1.0-dev - name: Install python dependencies run: | pip install -U -r test_requirements.txt PyGObject pip install -U sphinx sphinx-github-changelog - name: Install main package run: pip install -e . - name: Build documentation env: SPHINX_GITHUB_CHANGELOG_TOKEN: ${{ secrets.URWID_SPHINX_RO_TOKEN }} run: sphinx-build docs build/documentation - uses: actions/upload-artifact@v4 with: path: build/documentation name: documentationurwid-2.6.16/.github/workflows/isolated_static_check.yml000066400000000000000000000047621470350774000234100ustar00rootroot00000000000000name: Isolated Static check on: pull_request: paths: - "ruff-requirements.txt" - "black-requirements.txt" - "isort-requirements.txt" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: Isort: name: Validate import sorting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install --upgrade -r isort-requirements.txt - name: Check imports with isort run: | isort --check --diff . Black: name: Validate black formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install --upgrade -r black-requirements.txt - name: Check code style with black run: | black --check . Ruff: name: Check with Ruff runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install --upgrade -r ruff-requirements.txt - name: Lint with ruff run: | ruff check --output-format github . PyLint: name: Check with pylint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Ubuntu dependencies run: | sudo apt-get -qq update sudo apt-get install -y python3-gi gobject-introspection libgirepository1.0-dev - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r test_requirements.txt PyGObject pip install --upgrade -r pylint-requirements.txt - name: Install package for test run: pip install -e . - name: Lint with pylint run: pylint --output-format=github urwid urwid-2.6.16/.github/workflows/labels.yml000066400000000000000000000003471470350774000203350ustar00rootroot00000000000000name: Set PR labels on: pull_request_target: {} jobs: triage: permissions: contents: read pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 with: dot: trueurwid-2.6.16/.github/workflows/pythonpackage.yml000066400000000000000000000162521470350774000217320ustar00rootroot00000000000000name: Python package on: push: branches-ignore: - 'dependabot/**' - 'gh-pages' tags: - "[0-9]+.[0-9]+.[0-9]+" # Canonical versioned tags - "[0-9]+.[0-9]+.[0-9]+.post[0-9]+" # Canonical rebuild tags pull_request: paths: - "urwid/**" - "source/**" - "setup.py" - "pyproject.toml" - "MANIFEST.in" - "README.rst" - "COPYING" - "tests/**" - "examples/**" - ".coveralls.yml" - ".github/workflows/pythonpackage.yml" concurrency: group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" cancel-in-progress: true jobs: Isort: name: Validate import sorting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install --upgrade -r isort-requirements.txt - name: Check imports with isort run: | isort --check --diff . Black: name: Validate black formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install --upgrade -r black-requirements.txt - name: Check code style with black run: | black --check . Ruff: name: Check with Ruff runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install --upgrade -r ruff-requirements.txt - name: Lint with ruff run: | ruff check --output-format github . PyLint: name: Check with pylint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Ubuntu dependencies run: | sudo apt-get -qq update sudo apt-get install -y python3-gi gobject-introspection libgirepository1.0-dev - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r test_requirements.txt PyGObject pip install --upgrade -r pylint-requirements.txt - name: Install package for test run: pip install -e . - name: Lint with pylint run: pylint --output-format=github urwid Test: needs: [ Isort, Black, Ruff, PyLint ] runs-on: ${{ matrix.os }} strategy: max-parallel: 6 matrix: os: [ "ubuntu-latest", "windows-latest" ] # , "macos-latest" # enable macOS only if OS specific branch will be added python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13" ] exclude: # Not supported by external requirements - os: "windows-latest" python-version: "3.13" steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # need for setuptools_scm - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true cache: 'pip' - name: Install Ubuntu dependencies if: runner.os == 'Linux' run: | sudo apt-get -qq update sudo apt-get install -y python3-gi gobject-introspection libgirepository1.0-dev - name: Install Mac OS X dependencies if: runner.os == 'macOS' run: | brew install gtk+3 gobject-introspection gtk-mac-integration - name: Install dependencies run: | python -m pip install --upgrade pip wheel pip install -r test_requirements.txt - name: Install PyGObject if: runner.os == 'Linux' || runner.os == 'macOS' run: pip install PyGObject - name: Install package for test run: pip install -e . - name: Test run: | coverage run -m unittest discover -s tests -v coverage report coverage xml - name: Coveralls Parallel uses: coverallsapp/github-action@v2 with: flag-name: run-${{ matrix.python-version }}-${{ matrix.os }}" parallel: true file: coverage.xml UploadCoverage: name: Upload coverage to Coveralls needs: [ Test ] if: ${{ always() }} runs-on: ubuntu-latest steps: - name: Coveralls Finished uses: coverallsapp/github-action@v2 with: parallel-finished: true build: name: Build distribution needs: [ Test, Isort, Black, Ruff, PyLint ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # need for setuptools_scm - uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install dependencies run: pip install -U build - name: Build dist run: python -m build - uses: actions/upload-artifact@v4 with: path: dist/*.tar.gz name: built-sdist retention-days: 3 - uses: actions/upload-artifact@v4 with: path: dist/*.whl name: built-bdist retention-days: 3 Metadata: name: Validate metadata runs-on: ubuntu-latest needs: [ build ] steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install --upgrade twine - uses: actions/download-artifact@v4 with: # unpacks default artifact into dist/ # if `name: wheels` is omitted, the action will create extra parent dir pattern: built-* merge-multiple: true path: dist - name: Validate metadata run: | twine check dist/* upload_pypi: needs: [ build, Metadata ] # upload to PyPI on every tag if: github.event_name == 'push' && github.ref_type == 'tag' && github.repository == 'urwid/urwid' # alternatively, to publish when a GitHub Release is created, use the following rule: # if: github.event_name == 'release' && github.event.action == 'published' runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/urwid permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - uses: actions/download-artifact@v4 with: pattern: built-* merge-multiple: true path: dist - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 urwid-2.6.16/.github/workflows/yamllint.yml000066400000000000000000000007331470350774000207230ustar00rootroot00000000000000name: Lint YAML on: pull_request: paths: - "**.yml" - "**.yaml" jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip yamllint - name: Lint YAML files run: yamllint . -f github urwid-2.6.16/.gitignore000066400000000000000000000175601470350774000147500ustar00rootroot00000000000000### NotepadPP template # Notepad++ backups # *.bak ### VisualStudioCode template .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json !.vscode/*.code-snippets # Local History for Visual Studio Code .history/ # Built Visual Studio Code Extensions *.vsix ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf # AWS User-specific .idea/**/aws.xml # Generated files .idea/**/contentModel.xml # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml # Gradle .idea/**/gradle.xml .idea/**/libraries # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using # auto-import. # .idea/artifacts # .idea/compiler.xml # .idea/jarRepositories.xml # .idea/modules.xml # .idea/*.iml # .idea/modules # *.iml # *.ipr # CMake cmake-build-*/ # Mongo Explorer plugin .idea/**/mongoSettings.xml # File-based project format *.iws # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # SonarLint plugin .idea/sonarlint/ # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Editor-based Rest Client .idea/httpRequests # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser ### Linux template *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### VirtualEnv template # Virtualenv # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ .Python [Bb]in [Ii]nclude [Ll]ib [Ll]ib64 [Ll]ocal [Ss]cripts pyvenv.cfg .venv pip-selfcheck.json ### Emacs template # -*- mode: gitignore; -*- *~ \#*\# /.emacs.desktop /.emacs.desktop.lock *.elc auto-save-list tramp .\#* # Org-mode .org-id-locations *_archive # flymake-mode *_flymake.* # eshell files /eshell/history /eshell/lastdir # elpa packages /elpa/ # reftex files *.rel # AUCTeX auto folder /auto/ # cask packages .cask/ dist/ # Flycheck flycheck_*.el # server auth directory /server/ # projectiles files .projectile # directory configuration .dir-locals.el # network security /network-security.data ### Vim template # Swap [._]*.s[a-v][a-z] !*.svg # comment out if you don't need vector files [._]*.sw[a-p] [._]s[a-rt-v][a-z] [._]ss[a-gi-z] [._]sw[a-p] # Session Session.vim Sessionx.vim # Temporary .netrwhist *~ # Auto-generated tag files tags # Persistent undo [._]*.un~ ### Windows template # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk ### macOS template # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/#use-with-ide .pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ ### SublimeText template # Cache files for Sublime Text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache # Workspace files are user-specific *.sublime-workspace # Project files should be checked into the repository, unless a significant # proportion of contributors will probably not be using Sublime Text # *.sublime-project # SFTP configuration file sftp-config.json sftp-config-alt*.json # Package control specific files Package Control.last-run Package Control.ca-list Package Control.ca-bundle Package Control.system-ca-bundle Package Control.cache/ Package Control.ca-certs/ Package Control.merged-ca-bundle Package Control.user-ca-bundle oscrypto-ca-bundle.crt bh_unicode_properties.cache # Sublime-github package stores a github token in this file # https://packagecontrol.io/packages/sublime-github GitHub.sublime-settings ### GPG template secring.* # Generated version /urwid/version.py # Built packages /wheelhouse ### Documentation build /buildurwid-2.6.16/.pre-commit-config.yaml000066400000000000000000000021011470350774000172220ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - id: mixed-line-ending - repo: https://github.com/pycqa/isort rev: 5.13.2 hooks: - id: isort name: isort (python) - id: isort name: isort (cython) types: [cython] - id: isort name: isort (pyi) types: [pyi] - repo: https://github.com/psf/black rev: 24.10.0 hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.6.9 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix ] - repo: https://github.com/adrienverge/yamllint.git rev: v1.35.1 hooks: - id: yamllint - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 # Use the ref you want to point at hooks: - id: python-check-blanket-noqa - id: python-check-blanket-type-ignore - id: rst-directive-colons - id: rst-inline-touching-normal urwid-2.6.16/.yamllint.yml000066400000000000000000000013541470350774000154040ustar00rootroot00000000000000--- yaml-files: - '*.yaml' - '*.yml' - '.yamllint' rules: line-length: max: 120 allow-non-breakable-inline-mappings: true level: warning indentation: indent-sequences: consistent brackets: max-spaces-inside: 1 level: warning document-end: disable document-start: disable truthy: disable braces: enable colons: level: warning commas: level: warning comments: level: warning comments-indentation: level: warning empty-lines: enable empty-values: disable float-values: disable hyphens: enable key-duplicates: enable key-ordering: disable new-line-at-end-of-file: disable new-lines: enable octal-values: disable quoted-strings: disable trailing-spaces: enable urwid-2.6.16/COPYING000066400000000000000000000635021470350774000140100ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! urwid-2.6.16/MANIFEST.in000066400000000000000000000003631470350774000145070ustar00rootroot00000000000000recursive-include examples *.py *.tac recursive-include docs *.rst *.py *.py.xdotool *.png *.html *.sh Makefile include COPYING CHANGELOG README.rst include requirements.txt classifiers.txt include urwid/display/_web.js urwid/display/_web.css urwid-2.6.16/README.rst000066400000000000000000000206701470350774000144430ustar00rootroot00000000000000Urwid ===== |pypi| |docs| |gitter| |ci| |pre-commit| |coveralls| About ===== Urwid is a console user interface library for Python on Linux, OSX, Cygwin or other unix-like OS and partially supports Windows OS (see below). It includes many features useful for text console application developers including: - Applications resize quickly and smoothly - Automatic, programmable text alignment and wrapping - Simple markup for setting text attributes within blocks of text - Powerful list box with programmable content for scrolling all widget types - Your choice of event loops: Twisted, Glib, Tornado, asyncio, trio, ZeroMQ or select-based loop - Pre-built widgets include edit boxes, buttons, check boxes and radio buttons - Display modules include raw, curses, and experimental LCD and web displays - Support for UTF-8, simple 8-bit and CJK encodings - 24-bit (true color), 256 color, and 88 color mode support - Compatible with Python 3.7+ and PyPy Home Page: http://urwid.org/ Installation ============ To install using pip .. code:: bash pip install urwid For advanced functionality extra requirements need to be installed. Example for ZeroMQ event loop and LCD display: .. code:: bash pip install urwid[serial,zmq] Alternatively if you are on Debian or Ubuntu .. code:: bash apt-get install python3-urwid Windows support notes ===================== * Not supported: 1. Terminal widget and all related render API (TermCanvas, TermCharset, TermModes, TermScroller) 2. Any file descriptors except sockets (Windows OS limitation) 3. ZMQEventLoop. * Special requirements: 1. Extra libraries required for curses display support: .. code-block:: bash pip install urwid[curses] * CursesDisplay incorrectly handles mouse input in case of fast actions. * Only UTF-8 mode is supported. Testing ======= To run tests locally, install & run `tox`. You must have appropriate Python versions installed to run `tox` for each of them. To test code in all Python versions: .. code:: bash tox # Test all versions specified in tox.ini: tox -e py39 # Test Python 3.9 only tox -e py39,py10,pypy3 # Test Python 3.9, Python 3.10 & pypy3 Supported Python versions ========================= - 3.7 - 3.8 - 3.9 - 3.10 - 3.11 - 3.12 - 3.13 - pypy3 Authors ======= Creator ------- `wardi `_ Maintainers ----------- `and3rson `_, `tonycpsu `_, `ulidtko `_, `penguinolog `_ Contributors ------------ `1in7billion `_, `abadger `_, `agrenott `_, `akorb `_, `alethiophile `_, `aleufroy `_, `alobbs `_, `amjltc295 `_, `and-semakin `_, `andrewshadura `_, `andy-z `_, `anttin2020 `_, `Apteryks `_, `Arfrever `_, `AutoAwesome `_, `belak `_, `berney `_, `bk2204 `_, `BkPHcgQL3V `_, `bwesterb `_, `carlos-jenkins `_, `Certseeds `_, `Chipsterjulien `_, `chrisspen `_, `cltrudeau `_, `Codeberg-AsGithubAlternative-buhtz `_, `cortesi `_, `d0c-s4vage `_, `derdon `_, `dholth `_, `dimays `_, `dlo `_, `dnaeon `_, `doddo `_, `douglas-larocca `_, `drestebon `_, `dsotr `_, `dwf `_, `EdwardBetts `_, `elenril `_, `EnricoBilla `_, `extempore `_, `fabiand `_, `floppym `_, `flowblok `_, `fmoreau `_, `goncalopp `_, `Gordin `_, `GregIngelmo `_, `grzaks `_, `gurupras `_, `HarveyHunt `_, `Hoolean `_, `hukka `_, `hydratim `_, `ids1024 `_, `imrek `_, `isovector `_, `itaisod `_, `ixxra `_, `jeblair `_, `johndeaton `_, `jonblack `_, `jspricke `_, `kedder `_, `Kelketek `_, `KennethNielsen `_, `kesipyc `_, `kkrolczyk `_, `Kwpolska `_, `Lahorde `_, `laike9m `_, `larsks `_, `lfam `_, `lgbaldoni `_, `lighth7015 `_, `livibetter `_, `Lothiraldan `_, `Mad-ness `_, `madebr `_, `magniff `_, `marlox-ouda `_, `mattymo `_, `mdtrooper `_, `mgk `_, `mimi1vx `_, `mobyte0 `_, `MonAaraj `_, `MonthlyPython `_, `mountainstorm `_, `mselee `_, `mwhudson `_, `naquad `_, `nchavez324 `_, `neumond `_, `nolash `_, `ntamas `_, `nyov `_, `ocarneiro `_, `okayzed `_, `pquentin `_, `rbanffy `_, `ReddyKilowatt `_, `regebro `_, `renegarcia `_, `rianhunter `_, `roburban `_, `RRMoelker `_, `rwarren `_, `scopatz `_, `seanhussey `_, `seonon `_, `shadedKE `_, `sithglan `_, `Sjc1000 `_, `sporkexec `_, `squrky `_, `ssbr `_, `techdragon `_, `thehunmonkgroup `_, `thisch `_, `thornycrackers `_, `TomasTomecek `_, `tompickering `_, `tony `_, `ttanner `_, `tu500 `_, `uSpike `_, `vega0 `_, `vit1251 `_, `waveform80 `_, `Wesmania `_, `xandfury `_, `xndcn `_, `zhongshangwu `_, `zrax `_ .. |pypi| image:: https://img.shields.io/pypi/v/urwid :alt: current version on PyPi :target: https://pypi.python.org/pypi/urwid .. |docs| image:: https://github.com/urwid/urwid/actions/workflows/documentation.yml/badge.svg?branch=master :alt: Documentation Status :target: https://urwid.org .. |gitter| image:: https://img.shields.io/gitter/room/urwid/community :alt: Gitter :target: https://gitter.im/urwid/community .. |ci| image:: https://github.com/urwid/urwid/actions/workflows/pythonpackage.yml/badge.svg?branch=master :target: https://github.com/urwid/urwid/actions :alt: CI status .. |pre-commit| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit :target: https://github.com/pre-commit/pre-commit :alt: pre-commit .. |coveralls| image:: https://coveralls.io/repos/github/urwid/urwid/badge.svg :alt: test coverage :target: https://coveralls.io/github/urwid/urwid urwid-2.6.16/black-requirements.txt000066400000000000000000000000171470350774000173030ustar00rootroot00000000000000black==24.10.0 urwid-2.6.16/classifiers.txt000066400000000000000000000016211470350774000160170ustar00rootroot00000000000000Development Status :: 5 - Production/Stable Environment :: Console Environment :: Console :: Curses Intended Audience :: Developers License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Operating System :: POSIX Operating System :: Unix Operating System :: MacOS :: MacOS X Operating System :: Microsoft :: Windows Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Widget Sets Programming Language :: Python :: 3 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy urwid-2.6.16/docs/000077500000000000000000000000001470350774000136775ustar00rootroot00000000000000urwid-2.6.16/docs/Makefile000066400000000000000000000005151470350774000153400ustar00rootroot00000000000000 PYTHON_SOURCE=$(shell echo */*.py.xdotool) TARGETS=${PYTHON_SOURCE:.py.xdotool=1.png} TOOLS=tools/compile_pngs.sh tools/screenshots.sh .PHONY: all all: ${TARGETS} ${TARGETS}: %1.png: %.py %.py.xdotool ${TOOLS} echo zzz $< tools/compile_pngs.sh $< apt-get-dependencies: sudo apt-get install xvfb rxvt-unicode-256color xdotool urwid-2.6.16/docs/changelog.rst000066400000000000000000001215741470350774000163720ustar00rootroot00000000000000 Changelog --------- .. changelog:: :changelog-url: https://urwid.org/changelog.html :github: https://github.com/urwid/urwid/releases/ :pypi: https://pypi.org/project/urwid/ Urwid 2.1.2 =========== 2020-09-26 * Add pack method to LineBox. Fixes: #346 (by Miguel de Dios) * Add a test to check the linebox.pack is good. (by Miguel de Dios) * Add bin/release.sh script to partially automate releases. (by Tony Cebzanov) * Add workaround for #386 (by Tony Cebzanov) * Fix curses_display python3 ord() (by Ya-Liang Chang (Allen)) * Fix bumping to dev version in release.sh script (by Tony Cebzanov) * Fix focus_end on a collapsed tree (by Anonymous Maarten) * Fix crash with "ellipsis" clipping for py2 tour.py works with py2 now Typo in tour.py (by akorb) * Ignore resetting to invalid locale (Closes: #377) (by Jochen Sprickerhof) * Use ord2 for python2/3 compatibility (by Ya-Liang Chang (Allen)) Urwid 2.1.1 =========== 2020-07-26 * Add TrioEventLoop.run_async(), removed nursery constructor arg (#392) (by Tamás Nepusz) * Add py38 to Travis tests (by Andrey Semakin) * Add popular IDEs folders to .gitignore (by Andrey Semakin) * Add wrap_around kwarg to SimpleListWalkers (by Krzysztof Królczyk) * Change documentation on Terminal (by James Johnson) * Remove debug documentation change test (by James Johnson) * Remove support for py34 (by Andrey Semakin) * Remove invalid escape sequence (by Andrey Lebedev) * Fix GridFlow keypress handling when v_sep is 0 (by Aurelien Grenotton) * Fix Terminal in ListBox (#382) (by James Johnson) * Fix Crash on `fg`, SIGCONT (after Ctrl-Z, SIGSTOP, SIGTSTP) (by goncalopp) * Fix 256-color mode on some terminals. Addresses #404. (by Tony Cebzanov) * vterm: reduce __init__ boilerplate (by max ulidtko) * vterm: errno 5 is not EOF. (by max ulidtko) * Terminal: use UTF-8 by default. (by max ulidtko) * Instance of Terminal has no __super attr -- thanks pylint! (by max ulidtko) * Do not call wait_readable with a closed fd in TrioEventLoop (by Michael Hudson-Doyle) * Make options a static method where applicable (by Philip Matura) * Set up Travis to run py38, speed up build (by Andrey Semakin) * Use comparison with a string instead of "is" test with a literal (by Andrej Shadura) Urwid 2.1.0 =========== 2019-11-13 * Add support for Python 3.7 and 3.8, drop support for Python 3.3 * Add 24-bit (true color) support. (by Tony Cebzanov) * Add TrioEventLoop (by Tamas Nepusz) * Add support for input encoding in Terminal widget (by Tamas Nepusz) * Add ability to specify LineBox title attribute (by Tom Pickering) * Add custom checkbox symbol (by Krzysztof Królczyk) * Add installation instruction to README (by Patryk NiedźwiedziÅ„ski) * Remove PollingListWalker class (by Heiko Noordhof) * Change SelectableIcon default cursor_position to 0. (by Werner Beroux) * Extended numerical editing: integers and floats (by hootnot) * Re-raise coroutine exceptions in AsyncioEventLoop properly (by nocarryr) * Fixed locale issue (by Andrew Dunai) * Gate SIGWINCH behind GLib 2.54+ (by Nick Chavez) * Remove method Text._calc_line_translation() (by rndusr) * Fix colon in HalfBlock5x4Font (by Alex Ozer) * Don't use deprecated inspect.getargspec() with python3 (by rndusr) * Fix issue "Non-integer division in bargraph when using set_bar_width(1)" (by Carlos Jenkins) * Fix misleading indentation in Screen._stop() (by Akos Kiss) * Fix crash on click-Esc & Esc-click (by Maxim Ivanov) * Use 'TimerHandle.cancelled()' if available (by Mohamed Seleem) * Break rather than raising exception on shard calculation bug. (by Tony Cebzanov) * Increase _idle_emulation_delay. (by Tony Cebzanov) * Fix EOF detection for the Terminal widget on Python 3 (by Tamas Nepusz) * Fix the asyncio example, and make the raw Screen work without real files (by Eevee) * Unbreak python ./examples/treesample HOME END keys. (by Dimitri John Ledkov) * Urwid.util: Fix bug in rle_append_beginning_modify (by BkPHcgQL3V) * Fix AttributeError on mouse click (by mbarkhau) * Fix ProgressBar smoothing on Python 3.x (by Tamas Nepusz) * Fix asyncio event loop test on py3.4 (by Maxim Ivanov) * Handle case where MainLoop._topmost_widget does not implement mouse_event (by Rasmus Bondesson) * Implement `ellipsis` wrapping mode for StandardTextLayout (by Philip Matura) * Fix .pack call in Columns.column_widths (by Philip Matura) * Use ._selectable member for Edit widget (by Philip Matura) * Fix use of ignore_focus, for widgets inheriting from Text (by Philip Matura) * Remove some special handling for TreeListBox (by Philip Matura) * Make Columns and Pile selectable when any child widget is (by Philip Matura) * Implement get_cursor_coords for Frame widget (by Philip Matura) * Fix Frame mouse_event when footer is trimmed (by Philip Matura) * Fix Python 3.8 SyntaxWarning: 'str' object is not callable (by Anders Kaseorg) * README: Use SVG build status badge (by Olle Jonsson) Urwid 2.0.1 =========== 2018-01-21 * #275: Late fix for proper exception reraising from within main loop (by Andrew Dunai & Adam Sampson) Urwid 2.0.0 =========== 2018-01-17 * Full Python 2.x/3.x support (by Andrew Dunai) * Proper handling & customization of OS signals by GLib event loop (by Federico T) * vterm: Fix handling of NUL characters (by aszlig) * Add 256-color support for fbterm (by Benjamin Yates) * Italics support (by Ian D. Scott) * Store envron's TERM value as a Screen attribute (by Benjamin Yates) * Replaced hashbangs to use proper Python binary (by Douglas La Rocca) * Post-change signal for Edit, CheckBox and RadioButton widgets (by Toshio Kuratomi) * ListBox.body update (by Random User) * SimpleListWalker is now default when setting ListBox.body (by Random User) * #246, #234: SelectEventLoop alarm improvements (by Dave Jones) * #211: Title align & borderless sides for LineBox (by Toshio Kuratomi) * Support for 'home' and 'end' keys in ListBox (by Random User) * Various code cleanups (by Jordan Speicher, Marin Atanasov Nikolov) * CI fixes (by Marlox, Ian Ward, Anatoly Techtonik, Tony Cebzanov & OndÅ™ej Súkup) * Example fixes (by Kenneth Nielsen) * Documentation fixes (by anatoly techtonik, Marcin Kurczewski, mobyte0, Christian Geier & xndcn) * Code cleanup & typo fixes (by Jakub Wilk & Boris Feld) * Integration of tox for easier Python cross-version testing (by Andrew Dunai) * Test fixes (by Michael Hudson-Doyle, Mike Gilbert & Andrew Dunai) * Correct error messages in Decoration (by Marcin Kurczewski) * #141: Fix for StandardTextLayout.calculate_text_segments (by Grzegorz Aksamit) * #221: Fix for raw display should release file descriptors (by Alain Leufroy) * #261: Fix issues with unicode characters in ProgressBar (by Andrew Dunai) * Fix for 'page up' and 'page down' in ListBox when having focusable children (by Random User) * Fixes for examples compatibility with Python 3 (by Lars Kellogg-Stedman) * Fix default screen size on raw display (by Andreas Klöckner) * Fix underlining for padded text (by Random User) * Fix for terminal widget crash with Python 3 (by Sjc1000) * Fix for string formatting error (by Jakub Wilk) * Fix for iterator in WidgetContainerListContentsMixin (by Marlox) * Fix for missing `modified` signal in SimpleFocusListWalker (by Michael Hansen) * Dropped Python 3.2 support * Test coverage is now collected Urwid 1.3.1 =========== 2015-11-01 * Fix for screen not getting reset on exception regression (by Rian Hunter) * AttrSpec objects are now comparable (by Random User) * MonitoredList now has a clear method if list has a clear method (by neumond) * Fix for BarGraph hlines sort order (by Heiko Noordhof) * Fix for final output not appearing on exit with some terminals now that extra newline was removed (by Jared Winborne) * Fix for a resizing bug in raw_display (by Esteban null) Urwid 1.3.0 =========== 2014-10-17 * New AsyncioEventLoop for Python 3.4, Python 3.x with asyncio package or Python 2 with trollius package (by Alex Munroe, Jonas Wielicki, with earlier work by Kelketek Rritaa) * Screen classes now call back to MainLoop using event loop alarms instead of passing timeout values to MainLoop (by Alex Munroe) * Add support for bright backgrounds on linux console (by Russell Warren) * Allow custom sorting of MonitoredList (by Tony Cebzanov) * Fix support for negative indexes with MonitoredFocusList (by Heiko Noordhof) * Documentation fixes (by Ismail, Matthew Mosesohn) * SelectableIcon using cursor_position=0 by default instead of 1. Urwid 1.2.2 =========== 2014-10-05 * Fix for a serious raw_display performance regression (by Anton Khirnov) * Fix for high color palette detection (by extempo) * Small changes to enable windows support (by Jeanpierre Devin) Urwid 1.2.1 =========== 2014-04-04 * Fix false failures of event loop tests * Remove extra newline generated on exit of raw_display * Documentation fixes (by Paul Ivanov) Urwid 1.2.0 =========== 2014-02-09 * Add support for PyPy, drop support for Python 2.4, 2.5 * Signals now support using weakly referenced arguments to help avoid leaking objects when a signal consumer is no longer referenced (by Matthijs Kooijman) * Add TornadoEventLoop class (by Alexander Glyzov) * Update GlibEventLoop to use python-gi for Python3 compatibility (by Israel Garcia) * Automate testing with Python 2.6, 2.7, 3.2, 3.3 and PyPy using travis-ci * New container method get_focus_widgets() (by Matthijs Kooijman) * Add support for double and triple click mouse events (by Igor KotrasiÅ„ski) * Allow disabling and re-enabling of mouse tracking (by Jim Garrison) * Create section in docs for example program screenshots generated as images like the tutorial examples * Add suggested basic color combination images to manual * Fall back to 80x24 if screen size detection fails * Fix screen.stop(), screen.start() disabling mouse events * Fix to make GridFlow v_sep argument behave as documented * Fix for registering high palette entries in the form "hX" where X > 15 so that basic colors are applied in 88-color mode * Fix for raw_display clear-right escape not working with standout attribute on some terminals * Fix for Terminal widget select loop: retry when interrupted Urwid 1.1.2 =========== 2013-12-30 * Move to urwid.org and use sphinx docs for generating whole site, move changelog to docs/changelog.rst * Fix encoding exceptions when unicode used on non-UTF-8 terminal * Fix for suspend and resume applications with ^Z * Fix for tmux and screen missing colors on right bug * Fix Pile zero-weighted items and mouse_event when empty * Fix Terminal select() not retrying when interrupted by signal * Fix for Padding.align and width change not invalidating Urwid 1.1.1 =========== 2012-11-15 * Fix for Pile not changing focus on mouse events * Fix for Overlay.get_cursor_coords() Urwid 1.1.0 =========== 2012-10-23 * New common container API: focus, focus_position, contents, options(), get_focus_path(), set_focus_path(), __getitem__, __iter__(), __reversed__() implemented across all included container widgets A full description doesn't fit here, see the Container Widgets section in the manual for details * New Sphinx-based documentation now included in source: Tutorial rewritten, manual revised and new reference based on updated docstrings (by Marco Giusti, Patrick Totzke) * New list walker SimpleFocusListWalker like SimpleListWalker but updates focus position as items are inserted or removed * New decoration widget WidgetDisable to disable interaction with the widgets it wraps * SelectableIcon selectable text widget used by button widgets is now documented (available since 0.9.9) * Columns widget now tries to keep column in focus visible, hiding columns on the left when necessary * Padding widget now defaults to ('relative', 100) instead of 'pack' so that left and right parameters are more useful and more child widgets are supported * New list walker "API Version 2" that is simpler for many list walker uses; "API Version 1" will still continue to be supported * List walkers may now allow iteration from the absolute top or bottom of the list if they provide a positions() method * raw_display now erases to the end of the line with EL escape sequence to improve copy+paste behavior for some terminals * Filler now has top and bottom parameters like Padding's left and right parameters and accepts 'pack' instead of None as a height value for widgets that calculate their own number of rows * Pile and Columns now accepts 'pack' instead of 'flow' for widgets that calculate their own number of rows or columns * Pile and Columns now accept 'given' instead of 'fixed' for cases where the number of rows or columns are specified by the container options * Pile and Columns widgets now accept any iterable to their __init__() methods * Widget now has a default focus_position property that raises an IndexError when read to be consistent with new common container API * GridFlow now supports multiple cell widths within the same widget * BoxWidget, FlowWidget and FixedWidget are deprecated, instead use the sizing() function or _sizing attribute to specify the supported sizing modes for your custom widgets * Some new shift+arrow and numpad input sequences from RXVT and xterm are now recognized * Fix for alarms when used with a screen event loop (e.g. curses_display) * Fix for raw_display when terminal width is 1 column * Fixes for a Columns.get_cursor_coords() regression and a SelectableIcon.get_cursor_coords() bug * Fixes for incorrect handling of box columns in a number of Columns methods when that column is selectable * Fix for Terminal widget input handling with Python 3 Urwid 1.0.3 =========== 2012-11-15 * Fix for alarms when used with a screen event loop (e.g. curses_display) * Fix for Overlay.get_cursor_coords() Urwid 1.0.2 =========== 2012-07-13 * Fix for bug when entering Unicode text into Edit widget with bytes caption * Fix a regression when not running in UTF-8 mode * Fix for a MainLoop.remove_watch_pipe() bug * Fix for a bug when packing empty Edit widgets * Fix for a ListBox "contents too long" error with very large Edit widgets * Prevent ListBoxes from selecting 0-height selectable widgets when moving up or down * Fix a number of bugs caused by 0-height widgets in a ListBox Urwid 1.0.1 =========== 2011-11-28 * Fix for Terminal widget in BSD/OSX * Fix for a Filler mouse_event() position bug * Fix support for mouse positions up to x=255, y=255 * Fixes for a number of string encoding issues under Python 3 * Fix for a LineBox border __init__() parameters * Fix input of UTF-8 in tour.py example by converting captions to unicode * Fix tutorial examples' use of TextCanvas and switch to using unicode literals * Prevent raw_display from calling tcseattr() or tcgetattr() on non-ttys * Disable curses_display external event loop support: screen resizing and gpm events are not properly supported * Mark PollingListWalker as deprecated Urwid 1.0.0 =========== 2011-09-22 * New support for Python 3.2 from the same 2.x code base, requires distribute instead of setuptools (by Kirk McDonald, Wendell, Marien Zwart) everything except TwistedEventLoop and GLibEventLoop is supported * New experimental Terminal widget with xterm emulation and terminal.py example program (by aszlig) * Edit widget now supports a mask (for passwords), has an insert_text_result() method for full-field validation and normalizes input text to Unicode or bytes based on the caption type used * New TreeWidget, TreeNode, ParentNode, TreeWalker and TreeListBox classes for lazy expanding/collapsing tree views factored out of browse.py example program, with new treesample.py example program (by Rob Lanphier) * MainLoop now calls draw_screen() just before going idle, so extra calls to draw_screen() in user code may now be removed * New MainLoop.watch_pipe() method for subprocess or threaded communication with the process/thread updating the UI, and new subproc.py example demonstrating its use * New PopUpLauncher and PopUpTarget widgets and MainLoop option for creating pop-ups and drop-downs, and new pop_up.py example program * New twisted_serve_ssh.py example (by Ali Afshar) that serves multiple displays over ssh from the same application using Twisted and the TwistedEventLoop * ListBox now includes a get_cursor_coords() method, allowing nested ListBox widgets * Columns widget contents may now be marked to always be treated as flow widgets for mixing flow and box widgets more easily * New lcd_display module with support for CF635 USB LCD panel and lcd_cf635.py example program with menus, slider controls and a custom font * Shared command_map instance is now stored as Widget._command_map class attribute and may be overridden in subclasses or individual widgets for more control over special keystrokes * Overlay widget parameters may now be adjusted after creation with set_overlay_parameters() method * New WidgetPlaceholder widget useful for swapping widgets without having to manipulate a container widget's contents * LineBox widgets may now include title text * ProgressBar text content and alignment may now be overridden * Use reactor.stop() in TwistedEventLoop and document that Twisted's reactor is not designed to be stopped then restarted * curses_display now supports AttrSpec and external event loops (Twisted or GLib) just like raw_display * raw_display and curses_display now support the IBMPC character set (currently only used by Terminal widget) * Fix for a gpm_mev bug preventing user input when on the console * Fix for leaks of None objects in str_util extension * Fix for WidgetWrap and AttrMap not working with fixed widgets * Fix for a lock up when attempting to wrap text containing wide characters into a single character column Urwid 0.9.9.2 ============= 2011-07-13 * Fix for an Overlay get_cursor_coords(), and Text top-widget bug * Fix for a Padding rows() bug when used with width=PACK * Fix for a bug with large flow widgets used in an Overlay * Fix for a gpm_mev bug * Fix for Pile and GraphVScale when rendered with no contents * Fix for a Python 2.3 incompatibility (0.9.9 is the last release to claim support Python 2.3) Urwid 0.9.9.1 ============= 2010-01-25 * Fix for ListBox snapping to selectable widgets taller than the ListBox itself * raw_display switching to alternate buffer now works properly with Terminal.app * Fix for BoxAdapter backwards incompatibility introduced in 0.9.9 * Fix for a doctest failure under powerpc * Fix for systems with gpm_mev installed but not running gpm Urwid 0.9.9 =========== 2009-11-15 * New support for 256 and 88 color terminals with raw_display and html_fragment display modules * New palette_test example program to demonstrate high color modes * New AttrSpec class for specifying specific colors instead of using attributes defined in the screen's palette * New MainLoop class ties together widgets, user input, screen display and one of a number of new event loops, removing the need for tedious, error-prone boilerplate code * New GLibEventLoop allows running Urwid applications with GLib (makes D-Bus integration easier) * New TwistedEventLoop allows running Urwid with a Twisted reactor * Added new docstrings and doctests to many widget classes * New AttrMap widget supports mapping any attribute to any other attribute, replaces AttrWrap widget * New WidgetDecoration base class for AttrMap, BoxAdapter, Padding, Filler and LineBox widgets creates a common method for accessing and updating their contained widgets * New left and right values may be specified in Padding widgets * New command_map for specifying which keys cause actions such as clicking Button widgets and scrolling ListBox widgets * New tty_signal_keys() method of raw_display.Screen and curses_display.Screen allows changing or disabling the keys used to send signals to the application * Added helpful __repr__ for many widget classes * Updated all example programs to use MainLoop class * Updated tutorial with MainLoop usage and improved examples * Renamed WidgetWrap.w to _w, indicating its intended use as a way to implement a widget with other widgets, not necessarily as a container for other widgets * Replaced all tabs with 4 spaces, code is now more aerodynamic (and PEP 8 compliant) * Added saving of stdin and stdout in raw_display module allowing the originals to be redirected * Updated BigText widget's HalfBlock5x4Font * Fixed graph example CPU usage when animation is stopped * Fixed a memory leak related to objects listening for signals * Fixed a Popen3 deprecation warning Urwid 0.9.8.4 ============= 2009-03-13 * Fixed incompatibilities with Python 2.6 (by Friedrich Weber) * Fixed a SimpleListWalker with emptied list bug (found by Walter Mundt) * Fixed a curses_display stop()/start() bug (found by Christian Scharkus) * Fixed an is_wide_character() segfault on bad input data bug (by Andrew Psaltis) * Fixed a CanvasCache with render() used in both a widget and its superclass bug (found by Andrew Psaltis) * Fixed a ListBox.ends_visible() on empty list bug (found by Marc Hartstein) * Fixed a tutorial example bug (found by Kurtis D. Rader) * Fixed an Overlay.keypress() bug (found by Andreas Klöckner) * Fixed setuptools configuration (by Andreas Klöckner) Urwid 0.9.8.3 ============= 2008-07-14 * Fixed a canvas cache memory leak affecting 0.9.8, 0.9.8.1 and 0.9.8.2 (found by John Goodfellow) * Fixed a canvas fill_attr() bug (found by Joern Koerner) Urwid 0.9.8.2 ============= 2008-05-19 * Fixed incompatibilities with Python 2.3 * Fixed Pile cursor pref_col bug, WidgetWrap rows caching bug, Button mouse_event with no callback bug, Filler body bug triggered by the tutorial and a LineBox lline parameter typo. Urwid 0.9.8.1 ============= 2007-06-21 * Fixed a Filler render() bug, a raw_display start()/stop() bug and a number of problems triggered by very small terminal window sizes. Urwid 0.9.8 =========== 2007-03-23 * Rendering is now significantly faster. * New Widget base class for all widgets. It includes automatic caching of rows() and render() methods. It also adds a new __super attribute for accessing methods in superclasses. Widgets must now call self._invalidate() to notify the cache when their content has changed. To disable caching in a widget set the class variable no_cache to a list that includes the string "render". * Canvas classes have been reorganized: Canvas has been renamed to TextCanvas and Canvas is now the base class for all canvases. New canvas classes include BlankCanvas, SolidCanvas and CompositeCanvas. * External event loops may now be used with the raw_display module. The new methods get_input_descriptors() and get_input_nonblocking() should be used instead of get_input() to allow input processing without blocking. * The Columns, Pile and ListBox widgets now choose their first selectable child widget as the focus widget by default. * New ListWalker base class for list walker classes. * New Signals class that will be used to improve the existing event callbacks. Currently it is used for ListWalker objects to notify their ListBox when their content has changed. * SimpleListWalker now behaves as a list and supports all list operations. This class now detects when changes are made to the list and notifies the ListBox object. New code should use this class to wrap lists of widgets before passing them to the ListBox constructor. * New PollingListWalker class is now the default list walker that is used when passing a simple list to the ListBox constructor. This class is intended for backwards compatibility only. When this class is used the ListBox object is unable to cache its render() method. * The curses_display module can now draw in the lower-right corner of the screen. * All display modules now have start() and stop() methods that may be used instead of calling run_wrapper(). * The raw_display module now uses an alternate buffer so that the original screen can be restored on exit. The old behaviour is available by setting the alternate_buffer parameter of start() or run_wrapper() to False. * Many internal string processing functions have been rewritten in C to improve their performance. * Compatible with Python >= 2.2. Python 2.1 is no longer supported. Urwid 0.9.7.2 ============= 2007-01-03 * Improved performance in UTF-8 mode when ASCII text is used. * Fixed a UTF-8 input bug. * Added a clear() function to the display modules to force the screen to be repainted on the next draw_screen() call. Urwid 0.9.7.1 ============= 2006-10-03 * Fixed bugs in Padding and Overlay widgets introduced in 0.9.7. Urwid 0.9.7 =========== 2006-10-01 * Added initial support for fixed widgets - widgets that have a fixed size on screen. Fixed widgets expect a size parameter equal to (). Fixed widgets must implement the pack(..) function to return their size. * New BigText class that draws text with fonts made of grids of character cells. BigText is a fixed widget and doesn't do any alignment or wrapping. It is intended for banners and number readouts that need to stand out on the screen. Fonts: Thin3x3Font, Thin4x3Font, Thin6x6Font (full ascii) UTF-8 only fonts: HalfBlock5x4Font, HalfBlock6x5Font, HalfBlockHeavy6x5Font, HalfBlock7x7Font (full ascii) New function get_all_fonts() may be used to get a list of the available fonts. * New example program bigtext.py demonstrates use of BigText. * Padding class now has a clipping mode that pads or clips fixed widgets to make them behave as flow widgets. * Overlay class can now accept a fixed widget as the widget to display "on top". * New Canvas functions: pad_trim() and pad_trim_left_right(). * Fixed a bug in Filler.get_cursor_coords() that causes a crash if the contained widget's get_cursor_coords() function returns None. * Fixed a bug in Text.pack() that caused an infinite loop when the text contained a newline. This function is not currently used by Urwid. * Edit.__init__() now calls set_edit_text() to initialize its text. * Overlay.calculate_padding_filler() and Padding.padding_values() now include focus parameters. Urwid 0.9.6 =========== 2006-08-22 * Fixed Unicode conversion and locale issues when using Urwid with Python < 2.4. The graph.py example program should now work properly with older versions of Python. * The docgen_tutorial.py script can now write out the tutorial example programs as individual files. * Updated reference documentation table of contents to show which widgets are flow and/or box widgets. * Columns.set_focus(..) will now accept an integer or a widget as its parameter. * Added detection for rxvt's HOME and END escape sequences. * Added support for setuptools (improved distutils). Urwid 0.9.5 =========== 2006-06-14 * Some Unicode characters are now converted to use the G1 alternate character set with DEC special and line drawing characters. These Unicode characters should now "just work" in almost all terminals and encodings. When Urwid is run with the UTF-8 encoding the characters are left as UTF-8 and not converted. The characters converted are: \u00A3 (£), \u00B0 (°), \u00B1 (±), \u00B7 (·), \u03C0 (Ï€), \u2260 (≠), \u2264 (≤), \u2265 (≥), \u23ba (⎺), \u23bb (⎻), \u23bc (⎼), \u23bd (⎽), \u2500 (─), \u2502 (│), \u250c (┌), \u2510 (â”), \u2514 (â””), \u2518 (┘), \u251c (├), \u2524 (┤), \u252c (┬), \u2534 (â”´), \u253c (┼), \u2592 (â–’), \u25c6 (â—†) * New SolidFill class for filling an area with a single character. * New LineBox class for wrapping widgets in a box made of line- drawing characters. May be used as a box widget or a flow widget. * New example program graph.py demonstrates use of BarGraph, LineBox, ProgressBar and SolidFill. * Pile class may now be used as a box widget and contain a mix of box and flow widgets. * Columns class may now contain a mix of box and flow widgets. The box widgets will take their height from the maximum height of the flow widgets. * Improved the smoothness of resizing with raw_display module. The module will now try to stop updating the screen when a resize event occurs during the update. * The Edit and IntEdit classes now use their set_edit_text() and set_edit_pos() functions when handling keypresses, so those functions may be overridden to catch text modification. * The set_state() functions in the CheckBox and RadioButton classes now have a do_callback parameter that determines if the callback function registered will be called. * Fixed a newly introduced incompatibility with python < 2.3. * Fixed a missing symbol in curses_display when python is linked against libcurses. * Fixed mouse handling bugs in the Frame and Overlay classes. * Fixed a Padding bug when the left or right has no padding. Urwid 0.9.4 =========== 2006-05-30 * Enabled mouse handling across the Urwid library. Added a new mouse_event() method to the Widget interface definition and to the following widgets: Edit, CheckBox, RadioButton, Button, GridFlow, Padding, Filler, Overlay, Frame, Pile, Columns, BoxAdapter and ListBox. Updated example programs browse.py, calc.py, dialog.py, edit.py and tour.py to support mouse input. * Released the files used to generate the reference and tutorial documentation: docgen_reference.py, docgen_tutorial.py and tmpl_tutorial.html. The "docgen" scripts write the documentation to stdout. docgen_tutorial.py requires the Templayer HTML templating library to run: http://excess.org/templayer/ * Improved Widget and List Walker interface documentation. * Fixed a bug in the handling of invalid UTF-8 data. All invalid characters are now replaced with '?' characters when displayed. Urwid 0.9.3 =========== 2006-05-14 * Improved mouse reporting. The raw_display module now detects gpm mouse events by reading /usr/bin/mev output. The curses_display module already supports gpm directly. Mouse drag events are now reported by raw_display in terminals that provide button event tracking and on the console with gpm. Note that gpm may report coordinates off the screen if the user drags the mouse off the edge. Button release events now report which button was released if that information is available, currently only on the console with gpm. * Added display of raw keycodes to the input_test.py example program. * Fixed a text layout bug affecting clipped text with blank lines, and another related to wrapped text starting with a space character. * Fixed a Frame.keypress() bug that caused it to call keypress on unselectable widgets. Urwid 0.9.2 =========== 2006-03-18 * Preliminary mouse support was added to the raw_display and curses_display modules. A new Screen.set_mouse_tracking() method was added to enable mouse tracking. Mouse events are returned alongside keystrokes from the Screen.get_input() method. The widget interface does not yet include mouse handling. This will be addressed in the next release. * A new convenience function is_mouse_event() was added to help in separating mouse events from keystrokes. * Added a new example program input_test.py. This program displays the keyboard and mouse input it receives. It may be run as a CGI script or from the command line. On the command line it defaults to using the curses_display module, use input_test.py raw to use the raw_display module instead. * Fixed an Edit.render() bug that caused it to render the cursor in a different location than that reported by Edit.get_cursor_coords() in some circumstances. * Fixed a bug preventing use of UTF-8 characters with Divider widgets. Urwid 0.9.1 =========== 2006-03-06 * BarGraph and ProgressBar can now display data more accurately by using the UTF-8 vertical and horizontal eighth characters. This behavior will be enabled when the UTF-8 encoding is detected and "smoothed" attributes are passed to the BarGraph or ProgressBar constructors. * New get_encoding_mode() function to determine how Urwid will treat raw string data. * New raw_display.signal_init() and raw_display.signal_restore() methods that may be overridden by threaded applications that need to call signal.signal() from their main thread. * Fixed a bug that prevented the use of UTF-8 strings in text markup. * Removed some forgotten asserts that broke 8-bit and CJK input. Urwid 0.9.0 =========== 2006-02-18 * New support for UTF-8 encoding including input, display and editing of narrow and wide (CJK) characters. Preliminary combining (zero-width) character support is included, but full support will require terminal behavior detection. Right-to-Left input and display are not implemented. * New raw_display module that handles console display without relying on external libraries. This module was written as a work around for the lack of UTF-8 support in the standard version of ncurses. Eliminates "dead corner" in the bottom right of the screen. Avoids use of bold text in xterm and gnome-terminal for improved text legibility. * Fixed Overlay bug related to UTF-8 handling. * Fixed Edit.move_cursor_to_coords(..) bug related to wide characters in UTF-8 encoding. Urwid 0.9.0-pre3 ================ 2006-02-13 * Fixed Canvas attribute padding bug related to -pre1 changes. Urwid 0.9.0-pre2 ================ 2006-02-10 * Replaced the custom align and wrap modes in example program calc.py with a new layout class. * Fixed Overlay class call to Canvas.overlay() broken by -pre1 changes. * Fixed Padding bug related to Canvas -pre1 changes. Urwid 0.9.0-pre1 ================ 2006-02-08 * New support for UTF-8 encoding. Unicode strings may be used and will be converted to the current encoding when output. Regular strings in the current encoding may still be used. PLEASE NOTE: There are issues related to displaying UTF-8 characters with the curses_display module that have not yet been resolved. * New set_encoding() function replaces util.set_double_byte_encoding(). * New supports_unicode() function to query if unicode strings with characters outside the ascii range may be used with the current encoding. * New TextLayout and StandardTextLayout classes to perform text wrapping and alignment. Text widgets now have a layout parameter to allow use of custom TextLayout objects. * New layout structure replaces line translation structure. Layout structure now allows arbitrary reordering/positioning of text segments, inclusion of UTF-8 characters and insertion of text not found in the original text string. * Removed util.register_align_mode() and util.register_wrap_mode(). Their functionality has been replaced by the new layout classes. Urwid 0.8.10 ============ 2005-11-27 * Expanded tutorial to cover advanced ListBox usage, custom widget classes and the Pile, BoxAdapter, Columns, GridFlow and Overlay classes. * Added escape sequence for "shift tab" to curses_display. * Added ListBox.set_focus_valign() to allow positioning of the focus widget within the ListBox. * Added WidgetWrap class for extending existing widgets without inheriting their complete namespace. * Fixed web_display/mozilla breakage from 0.8.9. Fixed crash on invalid locale setting. Fixed ListBox slide-back bug. Fixed improper space trimming in calculate_alignment(). Fixed browse.py example program rows bug. Fixed sum definition, use of long ints for python2.1. Fixed warnings with python2.1. Fixed Padding.get_pref_col() bug. Fixed Overlay splitting CJK characters bug. Urwid 0.8.9 =========== 2005-10-21 * New Overlay class for drawing widgets that obscure parts of other widgets. May be used for drop down menus, combo boxes, overlapping "windows", caption text etc. * New BarGraph, GraphVScale and ProgressBar classes for graphical display of data in Urwid applications. * New method for configuring keyboard input timeouts and delays: curses_display.Screen.set_input_timeouts(). * Fixed a ListBox.set_focus() bug. Urwid 0.8.8 =========== 2005-06-13 * New web_display module that emulates a console display within a web browser window. Application must be run as a CGI script under Apache. Supports font/window resizing, keepalive for long-lived connections, limiting maximum concurrent connections, polling and connected update methods. Tested with Mozilla Firefox and Internet Explorer. * New BoxAdapter class for using box widgets in places that usually expect flow widgets. * New curses_display input handling with better ESC key detection and broader escape code support. * Shortened resize timeout on gradual resize to improve responsiveness. Urwid 0.8.7 =========== 2005-05-21 * New widget classes: Button, RadioButton, CheckBox. * New layout widget classes: Padding, GridFlow. * New dialog.py example program that behaves like dialog(1) command. * Pile widgets now support selectable items, focus changing with up and down keys and setting the cursor position. * Frame widgets now support selectable items in the header and footer. * Columns widgets now support fixed width and relative width columns, a minimum width for all columns, selectable items within columns containing flow widgets (already supported for box widgets), focus changing with left and right keys and setting the cursor position. * Filler widgets may now wrap box widgets and have more alignment options. * Updated tour.py example program to show new widget types and features. * Avoid hogging cpu on gradual window resize and fix for slow resize with cygwin's broken curses implementation. * Fixed minor CJK problem and curs_set() crash under MacOSX and Cygwin. * Fixed crash when deleting cells in calc.py example program. Urwid 0.8.6 =========== 2005-01-03 * Improved support for CJK double-byte encodings: BIG5, UHC, GBK, GB2312, CN-GB, EUC-KR, EUC-CN, EUC-JP (JISX 0208 only) and EUC-TW (CNS 11643 plain 1 only) * Added support for ncurses' use_default_colors() function to curses_display module (Python >= 2.4). register_palette() and register_palette_entry() now accept "default" as foreground and/or background. If the terminal's default attributes cannot be detected black on light gray will be used to accommodate terminals with always-black cursors. "default" is now the default for text with no attributes. This means that areas with no attributes will change from light grey on black (curses default) to black on light gray or the terminal's default. * Modified examples to not use black as background of Edit widgets. * Fixed curses_display curs_set() call so that cursor is hidden when widget in focus has no cursor position. Urwid 0.8.5 =========== 2004-12-15 * New tutorial covering basic operation of: curses_display.Screen, Canvas, Text, FlowWidget, Filler, BoxWidget, AttrWrap, Edit, ListBox and Frame classes * New widget class: Filler * New ListBox functions: get_focus(), set_focus() * Debian packages for Python 2.4. * Fixed curses_display bug affecting text with no attributes. Urwid 0.8.4 =========== 2004-11-20 * Improved support for Cyrillic and other simple 8-bit encodings. * Added new functions to simplify taking screenshots: html_fragment.screenshot_init() and html_fragment.screenshot_collect() * Improved urwid/curses_display.py input debugging * Fixed cursor in screenshots of CJK text. Fixed "end" key in Edit boxes with CJK text. Urwid 0.8.3 =========== 2004-11-15 * Added support for CJK double-byte encodings. Word wrapping mode "space" will wrap on edges of double width characters. Wrapping and clipping will not split double width characters. curses_display.Screen.get_input() may now return double width characters. Text and Edit classes will work with a mix of regular and double width characters. * Use new method Edit.set_edit_text() instead of Edit.update_text(). * Minor improvements to edit.py example program. Urwid 0.8.2 =========== 2004-11-08 * Re-released under GNU Lesser General Public License. Urwid 0.8.1 =========== 2004-10-29 * Added support for monochrome terminals. see curses_display.Screen.register_palette_entry() and example programs. set TERM=xterm-mono to test programs in monochrome mode. * Added unit testing code test_urwid.py to the examples. * Can now run urwid/curses_display.py to test your terminal's input and colour rendering. * Fixed an OSX browse.py compatibility issue. Added some OSX keycodes. Urwid 0.8.0 =========== 2004-10-17 * Initial Release urwid-2.6.16/docs/conf.py000066400000000000000000000250651470350774000152060ustar00rootroot00000000000000# # Urwid documentation build configuration file, created by # sphinx-quickstart on Wed Nov 30 20:10:17 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import annotations import ast import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.todo", "sphinx.ext.coverage", "sphinx.ext.githubpages", "sphinx_github_changelog", ] sphinx_github_changelog_token = os.environ.get("SPHINX_GITHUB_CHANGELOG_TOKEN") # Add any paths that contain templates here, relative to this directory. templates_path = ["tools/templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "Urwid" copyright = "2014, Ian Ward et al" # noqa: A001 # sphinx magic # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. FILE_PATH = os.path.dirname(__file__) VERSION_MODULE = os.path.abspath(os.path.join(FILE_PATH, "../urwid/version.py")) VERSION_VARS = {} with open(VERSION_MODULE, encoding="utf-8") as f: parsed = ast.parse(f.read()) for node in parsed.body: if not isinstance(node, (ast.Assign, ast.AnnAssign)): # Not variable define continue if node.value is None: # Not a proper assign continue try: value = ast.literal_eval(node.value) except ValueError: continue if isinstance(node, ast.Assign): VERSION_VARS.update( {tgt.id: value for tgt in node.targets if isinstance(tgt, ast.Name) and isinstance(tgt.ctx, ast.Store)} ) else: VERSION_VARS[node.target.id] = value # The short X.Y version. version = ".".join(str(x) for x in VERSION_VARS["__version_tuple__"][:2]) # The full version, including alpha/beta/rc tags. release = VERSION_VARS["__version__"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # Generate CNAME html_baseurl = "https://urwid.org" # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "default" html_style = None # make readthedocs really use the default theme # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "sidebarbgcolor": "#263193", "sidebarbtncolor": "#263193", "footerbgcolor": "#181035", "relbarbgcolor": "#181035", "sidebarlinkcolor": "#aabee8", "linkcolor": "#263193", "visitedlinkcolor": "#263193", "headtextcolor": "#181035", "headlinkcolor": "#181035", "collapsiblesidebar": False, } # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = f"Urwid {release}" # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = "urwid-logo.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["tools/static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = {"index": ["indexsidebar.html"]} # Additional templates that should be rendered to pages, maps page names to # template names. html_additional_pages = { "index": "indexcontent.html", } # If false, no module index is generated. html_domain_indices = False # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "Urwiddoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ("index", "Urwid.tex", "Urwid Documentation", "Ian Ward", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [("index", "urwid", "Urwid Documentation", ["Ian Ward"], 1)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ("index", "Urwid", "Urwid Documentation", "Ian Ward", "Urwid", "One line description of project.", "Miscellaneous"), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = "Urwid" epub_author = "Ian Ward" epub_publisher = "Ian Ward" epub_copyright = "2014, Ian Ward et al" # The language of the text. It defaults to the language option # or en if the language is not set. # epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. # epub_identifier = '' # A unique identification for the text. # epub_uid = '' # A tuple containing the cover image and cover page html template filenames. # epub_cover = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_post_files = [] # A list of files that should not be packed into the epub file. # epub_exclude_files = [] # The depth of the table of contents in toc.ncx. # epub_tocdepth = 3 # Allow duplicate toc entries. # epub_tocdup = True autoclass_content = "both" autodoc_member_order = "alphabetical" autodoc_default_options = {"members": True} urwid-2.6.16/docs/examples/000077500000000000000000000000001470350774000155155ustar00rootroot00000000000000urwid-2.6.16/docs/examples/bigtext.py000077700000000000000000000000001470350774000240142../../examples/bigtext.pyustar00rootroot00000000000000urwid-2.6.16/docs/examples/bigtext.py.xdotool000066400000000000000000000002371470350774000212260ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 39 21 key --window $RXVTWINDOWID Down Down Down Down Down Down Down Down Return key --window $RXVTWINDOWID Up Up Up Return urwid-2.6.16/docs/examples/bigtext1.png000066400000000000000000000063301470350774000177540ustar00rootroot00000000000000‰PNG  IHDR_;ˆþŸþgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEÍÿÿÿåååÍõÅó€bKGDÿ-Þ ;IDATxÚí –£:†“„s70Çò¦ì ôñeÿk°%[6l Õ#ººš˜×ñ#ÿˆG?2\< þ‡L¿‡!Ž?žÓ§WóZŸÃažy«ÏMm?ž/6ux,æÛŒÃáο†Và{Xc¸xŠÓóµü| CÀó»rx±Ï-“žaUCh{µ¦±×‘OÛz ÒçëYh8|‘0éõxècËüÏsÞþ¼®4³_ŸçõÄ?óè€Ë Ï×°Ððc_ÃŒ1f#<"¾8® €Åzh~\ÿ¶x }Öáà‰À~µ,w¤–9‹ÀŒ¡<§J„[4¼üLÚx<-C-Â%ð¬aŽ…~`!UÔ0SÂ'=Òð€‹cË,ŒRô m5ü àµoJâA;9ûO/vÐ…ÿ qÑWÌB+ ’X‹ðæAw 7ìÍÀC¾G7v÷EÝ÷ýŸ=6bø‘úî>\/­ €%ô݇ä_!tå„oÄ”#IbxÃ~"ÀÑ2à{+"™ ü{oI$³Á²Äƒœcf†ö¶š è=ä@©âβX•Š ÿƒú]ÃX€X€X€X€øÖÀöw ÿ`````n~X€X€X€X€X€X€X€ïü3,†lÂ?´@mâêRµIK1àÕy"ðΗýg1²³Tˈ=0 ° ð•ÀV€X€ø¶Àë–ϱ5n2ŠÇ€ûí¥mˆð';+À,ÀüðY~Øžü#À,À|нì­^†I? úÓPóô“væùF}øç…X€X€X€X€X€X€X€?<^:ü;oäÌþ¹ÿžìÆÑ±å‡§Tl™þUJ7¯×©Oxõ¢±ö¡Fü%`wS`=íB?âw¥R,©Åù=G}šŸôA-~Déi=nž„3_ì¦: ©‹Jöv¤ šêxèSKøâÓNëq—a[ŒyÌ9ð¨uì["ðøà± ì `5Ý1 §–y$žZ¾<2mŒ‹‡W‹ðx)ð4fJ`ÀnŒË°¿T©á+€ýÑí7«}NP,Kø½Ztž%¦±e–p!ÌY‚–º¸apûyw%C p£ùé²ðùnM^ý$À,À,À,À,Àü›€­5Êœ½^;¯õ`P Ö€áðzÍIë©«_l­ Ú©âˆÂ‘>`¿B¾ž©é$`6 Ž˜¹¦²ÔÒ®a¿4„5Çõœl"°JÀ*mȬë|=ÂP~ñs5læ#;¨m‰À–²H·$rà®Å›€#¬ Œp yø¤ §´6[¶´!HÀæ`8OÃÔqÌ’’ÐAG÷!I¤5«3³„ú]ƒÿR`=^5ü¹ ˜˜õùÀY²ÂÜàŸ2Àve}{n ãŠW¿•RùÈ>0@=)‘É‚s€W¯;¸jé~¸bßONJÐ;ÀzìÏŸÔÓ¡«D`ï*a`0;¹wÃ1‰Íš®vcg#l-"ý¼¯9&Íxù¸1]økÆ>¹Vð)’(®Ùµõ6àà*!¸Ê<Ÿ%$ZÛé ;]ÏAŒá‘4Ìpö‡÷ê3`Ë!Ó°êÔðZZ«Ý0PÜ3ÒRºÊ.ŒñY"¶œ7}× avd}EŠÈùOF™K¢Å%°'ƒ\8--ñS0ØÌõn£†è‹ZºüÉ },ÂápbH€‰Œ€ãçxÖd Hk¶Á­EàødÐ4SÖ)¼:žåmtTL¥ÞÂHŠ ÝV“€ù©D¸ÞqdOM ‚ž´RižhAÏìšáû•Ÿóïd~Ì_éÖÄ^Fàp‰ í£¡L‘å`èÂf<›RßYö2γ•%0ÙyìšcªÞƒ`J6Ť³ì%ïævì%óÙ+´2` Ò„ ~ã,{Ù<íÏ2°ÛÀ±[œcÚìe´r~˜¶è5ñ‰‘Ч@*+àÀ¾˜ÜÜd/©sÞôÃÜv„U©áŠZmÀMö2<®¼à ‰PA]×0¬I"œª¨Fà{©é—Þ.Ž÷}`:è¶²Ä!{ÜÜf]0ÞMkæ½´Ö`/É`îtñ Ë;‹5‡Ôqàaú”þŽcÇ^*Éf]â×uÍb~øm`ª _а¡N˜¨€e#tBµ E˜¦Ø¿¼È¥;$Êw­Ü–™oÔ÷Z»æùkÀ§í¥é!·öw›k€/Ô0ª—ƒÇ+-À>*Ô:°)Y¢Ç^Ž.Í£2`³ L_¾ šVN·—yµ¢¢ásO´—5`ßq¯va.>à$”Bì ýå´Ôùö2öNU:›*½±\†™±òR•J¯¹>Üo/>õÞÊËÚ`MYVÌ‘vÕÖºì%Vbu®Š$‰8Ü9ckÀÊî±—®úÔCµzI>-Â]öÒ~µ‚4œÀ­j¸³zyÀ^ºª$b*Xf æ<“Ég‚­ÕË7쥪f‰µxÊ5ë>ô6ÕË£À_skùýuÅÍvb//VÔ5ßQÃ5•Ø”îâ]ÜçW/«“ªÀ¦8ÞUxEõò`<}½Â^V±¿˜«%¼² üQCà=x`u”Kì¥Ë/–Í3OÀøÄXE•ŸÌó@ª»RdUŒ0´÷ÛËÙ)EÇ€•á‚nj¥b Äòå!à~{Iœ#3°M"æ‘OöÒ¯Ky¥Ô-—D^n…ì¶ÝÅ£(רKÍ}f©a°1’††UY6gÍýö;ͲD’„¥–¬zi”ᷦ`£àÛnmÒ‡<›ÿ+•õÑ·vã)iãsÀú7‡Î«í­ßVøØŽz+ÈŸVû•ÜNzç±ÿºo?¶s³<¼ÿØŽtÍ,ÀïëS¬Ãç€/¾Põ¥×Ñ|ùõe¯Ú+Ósמ¸ Xå#$ÿûäØþ‡] ì²[¸ª/&JWÀïÌ~+/&JWÀ¿­á:ðò=?t<»dIÔßÊX©Ãßç «ký}൴öþ{/?ìä½—ç‹[Càÿ+ØÃØ(tÈÆIEND®B`‚urwid-2.6.16/docs/examples/bigtext2.png000066400000000000000000000065621470350774000177640ustar00rootroot00000000000000‰PNG  IHDR_;ˆþŸþgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEÍÿÿÿÍåååÍÑé bKGDÿ-Þ ÒIDATxÚíK’㪆í˜Ñó[=?b7ÎüÖ ö¿•Ë+!AHY²å>¨ÝÕz}N¥2¥„úvÓÉsúkúÉXlßîæ·GóVïl7Ýë}eWkÀ·ûƒÌe·Ùr«vØm@ûƒµØl‰áT`c§ûc øþ`Ìã¹CÉĶǸÃÝoŠù¾‡÷–é1ÇÝÌ0[CGÂelÛÅÍÿ}<˜=æf«7Ózì±°Ù׃¡Iï{áÃþ‹øY£¦=öŸ»Ý¿ÝVZØmÇ-ó¸‡?¶ÉÂ:ìþ`3¾mû°#Ì…“Æ-œŒ®¶Ï`¶Ü…[&lÎ}ÛÌð˜5Zx8A`·Y;R"0a¨Û°S±p‹oß“oÜn3 ûV³ðml}Øáøs¡88!µUôaâ ·0ëÆÐ‡YX=ôXÇ(}WÄ]¾!ð’…W]â†Ù“»ßä¤óqÊÜÎ]ðxàê¾çî÷n?,®úˆQ"€à½K,Yxõ¤Û‘ {#0ËèÊá>)}_øÞ##6€o)çŒéêÓù®uðGB_}JúðáÃAWLx‡MQ1¢K°'ä÷+ õ!¾¶G$1þ^Û%’Ø QâÊFNŠÑA{YŸ@}è4$ÃPqe·Xt•1ý'þYÓðÀxà<€ðn¾ðÀxà<€ðÀxàë±Ù”Íø W¨Í\\«6ke-¼¸LÞø²Íkµ4øŽeðÀøL`>€ðÀ—^–|nнp“PÜÜ//yƒ…_™8øÀx?|”æ à<€ð!ò²·zég}ÍôéWCÍÓÍÚXæõá¯gVÀxà<€ðÀxà<€_<}Öô÷çËi’¤C»Ÿ¾Gj{Ì¿Z«æíJý*à‰O“šuÖ~©¿ X^X™CèîPjM,œz¤k(ku³<úö¸†Vf;ÒÎÂ…O–f§M*£';–è8WRÓ§ÿÅÍGMtS'O~_ùŽ('ž”*€]Ož^ÈÈ)¬Y`ŽÀw X< Çù0&ë€.xR‡³{—K¤-ë#£„þ¬i(°ú´r«J7}ÕñÀY° ±ÁM.dß,Ëú¶íõmz/Lj­óÆ60@=(¡È‚c€ï;Ȯҽ®È÷€“'¨ `5õ8Ïß)ÓU€ªëR€UrÏû;b¡»ÞìÉ4ZX@ˆøy8ÞsL>ãÜGNéÆ_pÈÉ%°Î€q‰ù]ro½ Ø«Jðª2Û«„DË;µa'°ì9é<’Ô’‚Î~óžmaÌ)0d>¬;}x)¬Õ(žiö¡ ¨J.„pQ"ölRÁËžÀùSSó?çõè‹ÅŽ]À§ùp \‡Ññ»mÔ&Ø'/ã7j——9°ƒ¶© @„o@MX8{å¥Tòrì¨r`†€ªÿÌ€›å¥ê•—¹»ë ï@|$»‚VÚ€çòRNªS^æÀ›1ôh^/XXð6๼tÏžuÉË p4£Æ~vÞ¢½|ã©Ñ \ÓªSüÌ‚÷&ö> ……ÓiXxÄ^`nîön`1< i¢¥wËË}¦.!0Èœ8]#ú·®7ÊKÜ'/+FsqØùeìŸ{†à\^NS¯¼¬¦­†Ô,Þ›šû§-àË©5ø4`þ§ŸçÃ)¯ùB 4,]÷u,]ó ð÷Á7£„lñØ,$ .Oõ$xœÈäÿd~+òø±©‡Ufì<æªJsØq …OÍ$ðŠ „s` ð,ÓµoÖ^0dbÁ¦Ê'YØùO¦ØDõŠ£\Œ rÄêLr±rnÑõ°ß«3°öxö!4$ !©5hðRWF…1qa»[wqx[ì2ˆÐÑC¹;¡†¡MÀOz>2HÉrœù²Á‡Éuw)$§ŸŒ2w‰=ì=Œ ’þ²´¿5ÏTï:pÀuQËI— Rû,ìO'‚!!pü=^5 (ÂoPk8Ž 2 E`•Ì«âUÞJâÀb*fðdA|¬&ÓR±p=qd#ƒL€À‘V:-%è‘©Þ_ù9øJâGü‘jmÈËìoqù(0Rdqà $¼1"O¡ï(y—Y‹!Øjœšc¨šABHŬ£ä%Msò’hȬA ­°4!¼Þ8J^6›ãYv8¦E3 ÑÜ /£”[ÑøGç£Ï_éx ¤° ìŠÉ ÀMò“óª6pëÖ¥G`_Ôjn’—~¸òнKø ê²Ã’KøKÝÜ /þPkÀÅù¾ Œ'ÝZ”Ø%/½š[­ æÀ›aM<Öä% ÌÄOº§ô'Ž y©ÑIVë—š‡øÀOcUPhxƒ L´¡ó•:„ Q”Ú®šcŽŽ·¥…ÅT‘E2«`j-£ŠŸÚ¬tစ虮xoL˜ôõ—Ù‚‡Ê,o——°ÜV«À<1Ç¢¥EØT"J7sC³ 8¾º„¼F¥—™dwÄœòT¾ ©JþéKí0uZÈ*®÷·€Cì³·î;ûô@Æ;Žªôatà¡´³ ,ø^ààñ@……ØVQ÷P…`΀EæÁ°ÄbuªXÏÞk”}T~‚Õ^p„À@ÔZ8+&Gï–s—˜Ï,<Õ,,jQ»ë â²nxI^ê…“®âÃäµ]3à¢øœ4ð‚:Ô.4bŠ~`÷ò"™ž(ß}´ðXf¼RßkMÍökÀ«å¥è!jíÏçŸèÃÁ{)x¼ÓäW|ÈœÎ(Ñ#/'™–ѰXÆä—¨†•Ãåe^­¨øð±ÀÊ˰KBÇ»]!ƒø›ÏaVp…˜ ݈r\ëxyó£Ô•ÄÁS¥7–ËB2\@^ªÒIà5ׇûå¥ £~È8 ¸¬­aV”õaMiWm­K^†J¬Ê½"¹D쟜á5`Íw÷ÈKYõ€¡Z½D>ÌÂ]òRN+z·}8»€[ôáÎêåy)«.CÁ!/u5J,M@C®XÖ¡—©^î~›ZËŸ¯+¶òò$`©ùŠ>\óžÂ]|ŠûøêeõfRXtǧ Ϩ^ž._Ï—Uà/lµ„V¶>#*pøAxø¬Žrм”ùÍ2»°#¦€k¬üdšRÝ-«£…¡¸_^Z¥5&T‚J|¨‹Ë—»€ûå%rNDTÀ `¡áÝjÍøÇ›ÿkåèËËé°ñ:`õ‰À>y=õ·¼X‡a;ú)#¿Xo*¹œK¨a;þ8 ¯Û¹XÞ¶3Ró¾ð??f²*ù÷ÏÁÓÿNf?ö÷÷¯6 _†ú·wÓ7cì—o3 ÀXÙx°Ù½Ž&fˆJɾ…ù±ñN`ÃPÎ üóó+o¼ ØbþfÎþfÖ9B­m–ýþÅÞìÃØü(€ÝqŸ@ÎìŸovEàüóȿٯô®åsàYÏÕNº°³ð€ka­vÑÌúðÛÃZ™8§É ÄžŒù(ñöÄñq©Ù†¿üIjí¼:¤2õ:à‡öÄÆ›€›Ÿ½¬>Íx°¢‰Uà•ÿÙ\ͯî¹9>MSõ¡ýWëÞ›ãÓDþïë÷¹DÏÐ7øY½Xw 퉬ê]ÀC{ZøÀÏßOÑD¾/qt={‰ã÷_<^«þ±Àÿùgs%D¾ ÀIEND®B`‚urwid-2.6.16/docs/examples/bigtext3.png000066400000000000000000000067341470350774000177660ustar00rootroot00000000000000‰PNG  IHDR_;ˆþŸþgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEÍÿÿÿåååÍÍ´¿ï%bKGDÿ-Þ ÷gLÍý–‹ؾè÷•ƒmÉ–›@BzÍdº‰ù÷!„ôCúvëÃɃpþ5þ"ŒßîÓ·GõZïb7Ýê}eSkÀ·ûƒM·Ù|«vØm@ûCÔgØb‰áTàÉN÷Çðý!„Çs‡R<˜…mËäw¿*áÛÞ[n¬eyܧ ÓÚÈ‘hÛâ6qóÿaù´ÖÛ4öØcái[A&½?î™ûñ“7ÁMZ쯻ݾ]WœÙ­ÇÍó¸ã?;*pqˆ™ß¶}Ø1æÌ‡ÙÈ ÏÁÇõ ˜­‡6áæÁÕ¹½ À‚ŽY¥…€#$»Õ²Ø[lÉ€C ؆‚…k|x ø}ãv›YØ·ˆ’…os`ëÃÇŸ íÀè„ÜVÁ‡™'ÜpÒM \[¬cä>L Ò&Їo¼dáU—¸ÑAöäîÛƒt>Ž“Q…Û¸ ZÜ·ÜýÖíG„E!J ­Ð»Ä’…WOºÙ°5‹ôˆ®î“Ò÷õï-2bøsN®>œïZ'$ôÕ‡¨>4Å„wØ”#¹„xB~¿ÂÀA2àk{DøÿÚ.Å‹W6rTŒ˜ ÚËúéC§!…Š+»Å¢«ôáß8ÈÏþÓ;pîÀ¸wàÜ;pîÀ¸wàÜ;pþ·ÛN™¿†ë <ÿëÀ¸wàÜ;pîÀ—vCyÙtÒ|mIËoZ`¡¥àtmÉÃÌ+xóyDŤt§ÊK-%Žß{=Ÿ§¼xÍÈ0ãüky£¸wàüðFN©YêI`±8:pîÀø@à+Þ \›j’Ë“‰cèÀ¸wàZà¼r°ýk9ƒ¬$Ž…ýÝØÄïBîÀ¸wàü–a%7­ ¸wà¼<~Öð÷ç›q4¬AºŸ¾ÅHZ¦ßRªêõù*à‘£š5–¾”ˆßl. ¬¦CèFÜ¡”’Y8¶7¢¬Õ§ùÉ?¨ÅH5­ÇØI4óIÀfÚ¨!“šàÉ· ùM5Üô±ÅïøôQ#_ÕIÀ£ßVº!ÎÉ€G¥2`×€Ç×E`“KktÃ|4¶Ø‘ØMz ðÈ|cœYØ·˜’…ÇS§!óaæ ØŒaÖâ–Ê}ø `w.»u+$‹îhû•F‰il%Œ 6JĥޮšÌuVþÑÀM÷làþðTîÀ¸wàÜ;pîÀŸ< Zê£×;صž RÂ0ì^¯>h=e`ùÀà ½oŽÀÔaDâH°[!_ÏÔtðà74ŽhÛ~ÓZÔRïÃniðkë9 X`eÜ^öóe C¾ãÇú°¶g¶÷6;€Š"Í.‘7-^ ha ¹…ƒyôÀ9†5 <ð@‚¬Ÿ†ã|˜‡u —:©ñìÞåqÍòÈ(!?kèÀ ¬>­Üªb§¯:8 VÜàB »€M^ß¶­~œ÷…)e:² PJ$²ààÅ~ÓTºŸ€ òýàè jX-ÎówÌt¨*Ø©J°Î˜¬’{Ø÷ˆáHhVÔØŒÉ4XXJDú<ú£Ï8÷1cìø«ÆœœËø—˜wì²¾õ:`¯*Á«Êl¯"íШ MËIç‘´æ–ÔLp¶›÷l 3àCâòч—ÂZé†ìž‘*` PUúp¡µ‹¡å(à)E0¼äœŸšš»ø9ø¨{?_ ¬7pìth>͇#Hà&]öøHi€}ò2ìQ½¼L´Mm÷€O-záì•—F5ÊË9°£J5Šþ3®–—ªU^¦>ì®7¼Kó‘ì Z`¨žËK3ªFy™koFl‘C¼`a=ÔÏ奻÷¬I^€ƒ%µ°óéåÛGªKZB5ŠŸYp¼‰½Cfáxf±XÒ­›;½Û"¬f†ç!M—¢ôny¹ÏÂÜ%t™#§ þ-‹À•òÒ·ÉË‚Ñ\v~ÚçžÀ¡8•—ãØ*/‹i«"5ë÷¦æöa ørj > xøiÀçùpÌk¾P$ €JW0ø:–,ùø~ðÍ(a*äc86Ëi—§P=¢Ä˜ü¿Äou?6õ°JŒÆ\U]vœZ`íS3 ¼•p l¤<ËtuÀ›µW†D,X8\ùD ;ÿI›.^q”€³'ƒ\±:“]lœ[t}ö[u–ÏþÒZB”Áà@ ¢Zƒ*odáÉ |†"ÌlgrË.>Þ–û£ Ç@CðÐÁP ÃP'àG92H™ü!¸iL…³ë ÁU¤ˆœÊÔ%jô°÷ödñ—¥90~«†!Q½ëÀ(!袚“.}2Hí³°?` #àð=\5iÈÂÚP¡Öpx2hš)«h^®òVS);€‰¤Ûj"0?  —GòdÐ èI+ç ôÈÔ ï¯ü|%ñ£¤Zëò2û..”š"E7¼@¢Ž1ŒxC }GÉË0ÏZ”À` ¤q05‡P <ƒ`HÖÙ¤£ä%Osò’iÈd„Z0PiB{½q”¼¬žŽgnØuà§Aëzà y¤ÜЦ-: xþÂH†K %p`WL®®’—”œWõð·na™ûpöE­:à*yéW^1°w _A]öaXr ©"++䥢j 8;ß·é¤[‹»ä¥Ws«uÁx3¬éçÂZ…¼$¹‘8ÂI—&Žk1qàièsJ{âØ—’œdµ.ñq©¹‹ŸOþï÷4ØÖ?ßÿÜv±¡—° ,¾-쟯_GkJÂ|D¦!+ŽH :+ Ì€'ÐßõŸ Ü Ówdÿî‡ÿ"„ÀÂN“~åmxF>¥Iñµ.O ˜0°Û;"\k8¶‰Và<[p`¬Ì–€}ýÚÆÏð×:ð™CÑRc6–ˆbg.ަÀv³„ócÁ€ÝQŒ&ZØû†ýŽÈâKÐR©K°LçKí0w^ÈÊ®÷ ÀÓl]Òn<øíËO´¿ùka»,L–v–õ°]}@ˆÌÂöãfàÇ_¸oâ{ X'þ€žà€µfÞªS[À3—ør ©K°O÷NÌ€©µpR0ŒŽ¾ \>éâ)¶\‚,ü]²°.E 쮃²Ëº:àYX.äO:æÃa ôápV|€Ð5Ö.$aêUàbâ˜ì%¾ƒ[„“P” ‰‚¢D ¼Rß«MÍv7àe©™ |-Äuâçþ¿øù8µöf=¬Ï>ñ ½—ƒ‡ž`_%ú:°)‹ÀËÅ@Å)U¼4Mz «e†yd¬)ù¥Ša¥¾ziÆäýrjée•ªT8d—HǯT/3ॷk–ûÉØ%-Cofßù €“ÐB*tO”ÓRUÀáÍx앃*¾+/¹áÊ„ò‹a'q´p¬ô†r&3=hHKU2 ¼êú0Æ3{©#³³¯á ºþÎRÿRNSÎkkT€Õy}X2EÚT[Ù&3 ðÄIž£JϤD—È€ý3C X»åìµ™ÉGìhS|ê¡X½$>ÌÂfîs`£ø¤ ï­ N.à}¸±z)Nº‚Ÿú .BÁ9Öÿ­Ï^L{À¯ <{1Qì·/¹DþžêOºì¯ Üú'/¯¬ÔûÏ{ïåËMïå1À]­uà÷á:Çeéëo®¾÷²¨å^—éTkçøXË/néÇXuy°líGö·¯ßçõãøð³z+°lè;˜„Æ×7uŽ£÷ª?ß9£‰y_âhº÷’žß p—— üÛ}ä|ÎñŸIEND®B`‚urwid-2.6.16/docs/examples/browse.py000077500000000000000000000002201470350774000173650ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import annotations import os import real_browse os.chdir("/usr/share/doc/python3") real_browse.main() urwid-2.6.16/docs/examples/browse.py.xdotool000066400000000000000000000001641470350774000210600ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 79 19 key --window $RXVTWINDOWID Down plus Down space Down space Down Down Down urwid-2.6.16/docs/examples/browse1.png000066400000000000000000000061031470350774000176050ustar00rootroot00000000000000‰PNG  IHDRÇ•×ÉPLTEåååÍÍÍÿÿÿÿÿã›2îbKGDhÙQ pHYs  ÒÝ~ü ÊIDATxÚí[z£0…U÷»4Ù@wV0ù²<°¿xÿ[t/À$âˆS޹lÿ®¥c•ƒÁ`0 ƒÁ`0 ƒÁ~¿©]ö1Ú?Ûd/ds^IJ·ódéùä7@> ²”‚F’2.DÈB‡‹½—V@f5WÙ/0O! 8ónÈÇäïŸƒé ‡“zÈȲýüúTzA©¯¯°«YÖÿ'dßÍ¢‚W‡R8ƒ4@~÷ß¹¦'2 ÷ÙÆd¥É~r#È:»øTæª÷nÙ*¿«.جäw@n™§pã/…ذ ¹‚^ú_­.C6®.ÚòB–øÚ´=d€fÿò©íp )´[Bvãµ¹.ä1i~3K ×>\@n Y“O,“@þHÃ…DvÑ23@d@äa€Ü äµA÷¦g8{nš·äÈô¤|„7Êõ6×…üσ¬ ʤ¦£õ Wtæ̳ù-öCЂ—½›ÃõA— ëÒHéËP]ùä¸ÅS [,@©&u¬Â×OQWêGe©“È^)2 cbÚQ„Ì Ó·„‹eÈJ% ò+C¶1ÙNðDÈK1Y(_ên‹[9ùƒeʨœRX€R!‡ìB¤ ’AV&ß0J'”kCVüaþlªt ç!ËbȽ—½·ðäå.äÒ‹è$.´„ äŽ Ë­O’óp…/µ›Cžoç]xr3È/ že*lšq&0H5çI¾£áz.*•CͰ ÑMG¤dbz&ÅÙ;ÿI «Ëq¯Žrhȶy 9ª=震+)¬RÈ=Å’ÊeŲ̀Èqä§ SÈ]u+C^&1 %ËÜ(É“¸'+ÑÓ7Ou!¡`Ùbl¦>!»£”Ì}i³ ç®ΩžéDc“8ÄìÂ%²Ù‚ìb«o?Û²ðŠú>ùC–3½Snã·«HÑ2 ;‚ìe̫ϩ|Š'[ÈÈ€üê“ÛH;²%ȘcGCÚþ 1âꥴýäØevexòaȉ@ä¡’bš' 7€¬ù,ÈNóäyL–‰'«ùª˜dfT§ŒýW«–3²‹:FÉsæK[ÈOðä¤×!q3¨p€ ÈÌì÷žÄ‚¥û ›a(åt0aUÒ?™rcÈétp–¢ÿ$dáйÓRôód@n9™¢z2r{È2Þu½ÇRô—€†nRŸ¥è? yÂÖ2fô=2 ·Ÿre_~ŠÎL@^1Ú3{äŽ6€ È- œ¢Ó Û]´›JöJRç±):ÉžôÁû©d¯:L‹öOÑ©!w<šCVêùì‘2ŸAî¡Þä% +Χºh7•ì5cò¡):ý\¾MO•ìõ²‹StÆA¡Aíª’½â€cStÎKªû)²®*ÚB&@.öäƒStÎã¦ö…rïJ€üÌh— Gë»O‚|Pê¾çŠÜMÙ{7%ï/!u:µ¢'ûy?9ùs?"uÚ­RM!÷2ãËk¨pÝ™CîÄ‘_râÉÒϼ ÈÁJ®Ü„\`̵9zò1©3å2÷] ²‹i¸Xs½ò»QW)óËH€¼É“kÞ¨¯²w¨p€ ÈÑDH¤ûS'w€ä߯ H»ŒüwúŠ(ÌéD!‡kâEgF­ YúYà„½@€,¹d ·i š@fw,Rlê-@Þj.&O!³PÌÅOïÕâ*i•<9j>²TO»dä²p¦ÛKÃ.w_3È¥åÔ“UÆ“g·Üä"Èé…§p2&u*Äd©³e@.‡ì#À¤3"¸™ÜÑÔƒ2 È€ ÈKöDêTˆ*­K€\r^êäª)+uš®GèŒ_âBÀÏ’:E”:ÐA€\èÉ%R' ï R' ï…\ uº˜|ƧI"JáärÈËRgš]èzkÍÞùCÝêïÈ—„Œpä«BÎŒÙì ânj¸§DL.4ZéÀÍo:Wäh¶È; K@®yVG¦\ ïB+Ý|v˜–ŒQì[ÏëÖÓÉñ )_r:AD ìL¨‰.$ÓXA3È’!´ ìÏ"×3Uº9™xCŽIEwæ;qäÚ7§¹/CVI¸@ûÈ”+–Ã…É(`ß|‘¯R—¾]E…3Ù…ö²3Õ|Y„Új@î2 ص}Œö¯#€ ȉQ²Æ¾þ @®yÖGcâÛµïÒ·ò[ dä#Ã@MjÒYq:yÜ€|²¤ÜücN)J& %@ÞcÇ>ï¿dâcá¦7äÝž¼9\öºQ!^2s_xò!ÈägÝ”éHo6ª÷Bþ`Ù…›u“ÝWˆ’Q€|²;Ï•!ž™¹=dy¥1š?È=B¦d-'u^1xŸ.ur äR' ï€\&u†‘^¹Dꌕ쀼ÁÊ¥ÎôþD—R˜Ï•:ÃÈO@®™q$Å+7†·B.‘:C%; oƒ¼Kꌃ9/- uþÈ:O€ ©³-d ÷™’µ…Qb¶`VŸC>:ªç«êäEÈERçØi±µíáþDnKßÝ“³¥N_Þï…'”ä‰í•:i^ ÈÏ=¹LêÌB6‘÷AfØh 2Á“Ÿ@.“: K@ÎBÞ#u¦7gg }ÏÞ ©ó×@†ÔydHm!ùGÈ”¬ù„%fÕ /«pW¿*ž"uòAÈ[¤Nyñìã©S^<>Eê¼zÄ8Eêä† +AÞ u^k\}MÈ%Rç¥î*Ò2ÂÁI!užRg[È0@î2%k9©e¿‡ oSá¹ò Ÿ¹LêDû>È%R' Ø‹¬\êDûO.‘:QÀ^2e!›­¨­.‚\"u¢€½ò.©ìû ópkRç !u> ƒõaÃ0èß% ìÏ}üy<w³6.Œ›Æ•ñáav™6vóÝ,¹5ÝX·ñ+Km²'ôGÜŽZ½æ¬ú5›g'ÔOžK$»tCÿNùÉB“Aܾ3õÆ ä…íO ›Wh^çý ‡là<웸Né±Ô&wÂÉá³£žBñ‹²ù7~xÉ÷1šwÊŸ$¶òn¼Y ‡ »7n™Ü@ûÏ¿Í<ä•6É Ÿä¨)äÇÈÞ]³ïìFLjÜnú÷møúÿ¾oƒ]a¿[½n¶Ø]zmpG™Ã÷Cv»V!¯´Y%š|B> qäµlš#ÈÇD“Ol ä Ïö.N'ö. oã]ð&]¾N“­‘7µ±šUZ@Þdk.È€<*l$6á|Ûõ%wþ]2  dÆŒ"œº¡á ò"œ5!ÛfrˆpêöŸ ·á| dÚ o!.T²p ®8™ïI¹”hã»;…§]ÚA“Ëjr² un&ÂYY““'ë5‡:7ᬬÉаrãæ’@FÏÖïèŸ y#d›#7~__rÅ2¥‹€ ÈŸY13Y‰8iŽÉ¸¥Ç©J¡- Ñ[žŠ!“]©GªJ@®Y2 d%„äúšC–ä! Ôd—Ǥñ»S™‚Ü\’ð:šŒ¤’€Üй€T† ä6 ç³™3E†DF¯ßòú—E Õ<ä´ òg@ö¯ át­‡ÓMŸê o! ZE“)šŸÈgïL&Y6¹ãÌú-DAkAv$Õ8d­äÅ­•Г‰ï»@.9ìi²䂿‚#œJ ‹íDAkÅ.‚¹‚³w Ž‚²w±•((šÕ€ ȧ!_ µå?@þNÈê _.—óx•tÓYòl9äHf òÌ?²¶çsg8º¤\áÌ`ͦî¹ÛÕ;y©&wÜ:|gýbu!@>8àg³—C6lyQä —…|±†ár¹r=Èq C>¶¬Éæ1wð€¼P´/qÖB»Î\°Ý8Ä­¿q6•¹°–oM^ ù È/ÆíÂ(d„{´äç¥þІïýÇ\MË=ù;!›Žš$Ek&•ënaºn IÏÏö»wÖ€¬»¬Hr=‚lº×}HòoÜó“Â^öת5ÄLª2e!S¹áNq!«²éû È• Kî>KYMVÜû ç’RMV Yæ5Y5ß½³–wa ù>œÖ\[i×á“BÏ϶»wÖJÅà…¦'°ÞÄ@l@nò£þF»# È€hé¼ cÞföN4«!¯„üço'` È­BžrÌL£dpnÖä ÈôÀû¥Ñ•rºÎv!ÿȯƒL¶ ☒;sÊ8Âi›ÄÉ~OJ»› Ÿ$y rè¨I>ÔÙ­¡8ÂI½|2@”ÈG} ¶¹ÓÛ_ü '"Ÿ=’yƒ&ò3æb²RÑ@äφlm²$NÑ™ûÖ‰26Y¸}-d È9È#ïBéàfÆ eÈÕ™dYõ¿uädÿžMÍÝÈyÈr6däÙš<þ=‡{Ø…š!€Üdùì ¼Ó`þåç0äY‡ëã¦4¹d‡g¥ØŒæHUѬ©²‰p 7’= ‡š!í¢©¡$e SÏ$îÌuïLâ™äãr±V‡p¨Šºsr9D{<ÒG)¬RÈ-Ù’Âe›1Yoòeo¢"°ß)È^KýÐ_YFžÅ“mõ¦ÜR¢¥ožÊB¦^N¥z-6ÁÁL…Ýü^Š-s[±Œ‚Þ÷á 5Å °©xÄà]ø:2O•Ø.×jVÓàOOÚ€»+òCöë~º§w{CÚ…d@~|p÷¹o?mÈhro$5 ò§Bö-߫ӿê$—D‹‡«ò ¡8ziÓFRh2‡áꀼr b¨¤¢˜' W€¬ùU]ÌK@Žm²L49äØ*æ Þ…éÕ™äêôÃÕ¹”ÝP‰e -Û‰z% ¿@““VGë¹,ÞÈ_ Ù~ïI‘q t;òj1 ¥ìw&lo”ôÛ!“"@® 9êÞ¦šŠþNÈ‚r£CÑ?L“¹ äÐQëj2r}È’ç€ks(úG@ö]7©Í¡èï„Üã © y ƒßÈ€ ÈÏrb[>Eg¦ O-ɹ  r È+StJžŠ=‹63’½P¨s]ŠNâÝ“6x;#Ù‹vÓ¢å):Õ(äŒGuÈJ=Î)C×ñ䯛|dÅS±§!»fF²—´É«Rtr® ®ÓÒHörÞÅŠ¡Sh<'{;#Ù v X—¢s8¤ºAÖEƒö3! ÏÖä•):‡v©}!€Ü:䵡N@~$´( GÓ›_ye¨SpËÅr7ÃÞ›òþ¡Nm£ Éœ÷“ûšP§]+Ur+_># GAcÈ(òAN4Yræe@ö²2Ô醛3Ì"Rm@š¼.Ôé÷rž‡û®ÞEß\L©ÞüÙˆ¨)—ùcB€ü”&—œ¨­aïˆÂ2 Þî£N<;@òá·v¡ÎEBü¾"ò9È›bÿLÜhfÔÂ%gv.Yr1Èä§i êAŽf,RQê-@~VœMîCŽLqüd­[VH“Ċ!KÕÓd¿ÉC&@žg.|º=1Ùoróš @ž Yö5Ye4y0å Ï‚œ>øbN§Ny›,µ· Èó!³è5F¤ n&#šñ € d@†ô!C mÈõzÕ¯s*Ïß·î÷~¿ßÌRWèVu Ý¿»ÙdêØÕ7SrKº²®Ã cu²ä=n#{M¿_sTýžÍÙÃõ)ü¹D²IWä+æ«\Åþ'Y¯Ì@Yÿ²y‡æ}þÖïñî2dçn/âæ9¥7b¬NÝ{=„,Â)dóÓݼd‹»æJã“„Z×¼A×UÝ…[&· ýáËÌCž¨“ÐóIöêC¾/€Ìêš…|‹®4(Fà¶×¯ûëÐQøÙ_m¡ƒÝ½x¶zÙ¬±›ôÒÕíev_Ùmš„¦ÉŽç³…)Œ>)Ç Û ¿4ù÷šœ…0¯É·ÚŲ¿éÞ…s€­!ÞìZ{°ï™ ³Ñ©ý•÷ÊÉÝ¿!’‘ÇÕß1Q`êhy_b¢Nî€ý{4Ü+‚¬–¸pSG\8ë+8xo—¬gaÿL¥ŸøÁgKŽ®õ.Fhç½;È™¶C 0m2Üý[­ó¨12rä Èî¡w<·1rsdfK/'2»–žØ“D-©wäÙ2»¡Èóe½V»¹øö”zGŽå½°ëÌ)z^IEND®B`‚urwid-2.6.16/docs/examples/edit.py000077500000000000000000000002751470350774000170230ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import annotations import os import sys import real_edit real_edit.EditDisplay(os.path.join(os.path.dirname(sys.argv[0]), "edit_text.txt")).main() urwid-2.6.16/docs/examples/edit.py.xdotool000066400000000000000000000002011470350774000204740ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 39 20 key --window $RXVTWINDOWID Return Return a d d i n g space s o m e space t e x t space urwid-2.6.16/docs/examples/edit1.png000066400000000000000000000054571470350774000172440ustar00rootroot00000000000000‰PNG  IHDR_,–-­ÝgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEåååÍÍÍÿÿÿÿÿ2Yž^bKGDøoéÇ IDATxÚí vëª@¥ˆ¼Õtî ÎÊüÇôb@?ÀvâÆIÜ+ß{ZŠùl ea0ér(€˜ òÏÛ˜ák@bnJžCD|iÈz¢ÿÈ×Ή±¦%NCTO—c D 9u©=¡•p¦¢RL=Á \¾}Eóe¹‹JÈi´ÝÚo׉ùŠo1è8gp°àÚ±å¤6 9k•§©»Ö(xŒ®1*a.ðVçJ’¦Æˆt2ÜþËeJ»£©|E| Aš’30Iã²ðsð¨‹J¨F™N„^ý€ë†Óá\•“pÒ&E–°T^%Jv§ÀU#qD•#07™é"a–6ÍQ% P/Pj”AÃ)— :,§è'ÉEq§«Ùç3œ¦•p¬âje”àBDÜ*ÃáB€‰Y²Ø2Ìu”h:UH¨ZÇÅ€S‰"+²£Y2ÚÎjIBîûk„k—Š'rÑñ D &ßén™¤Ëëp`%Ì—¯5ÉQ.Rô$­Ô(€ £"iT…g•PQ4£¢Œg¼ÓÎZ;gð0ž¥Ë1p’Áí4>—Jš-'‘0œM%(€Öa:™ÓÉÆáSÀü`ñ´Ýîü ŽÃÆ’A²š\üøÌÀb°¸*v>ö6<(î2öÐɃ{ ¬J\¶ÀI$ . 6Í.`嬹“ˆ{GÊWé‰Ë—Z<½(Ž‘4€­f0 Xœ¢Uh .5o ö… -©„xº¬ð7:Œµ)VÚ 8M 62·‰Ñ_ q°Ó¦-:MÃ仡êlnݘ$îïô¬Ñ(×.m mùâl¿«8Þ{­Vrít’]Ô&5îQ‰¾ï‹_V†#õóµ3«¢…´c‚^K§Ì{$¬ÚÔ”Of–›¡Õœ‚Å‘DÜêOýþø8|ž#€øocÓsuZc9c?ž®äj²Ãòi\ˆ©ãpÜUÙWmfi©?· ¬ÓÛcà؇ÀÔ¡) ;`ä\¸‚ÉWEËÀäóÿ,ð2›5&*y&O'䜸Xî 1Éæb`iöù&â&4Ibr퀪¥®zʨæImJuÆ™’ƠĨÕUESÚ”øË1(‰ÑŠ˜ì$/j ± )µþÔN`a`ér‘™/µï'0pŸ¸¶å”YRî’·Æl' \pL•ÄVnÍ)öæ¤U`Jc Ó½À©“06KÖMÊÜͨ›E¤k$î”`å ½OõZÑ”LÍåÔƒ!¢°åd{œ1j+¯ßh|'ÏÁ±üD¤¹ÀŽð}ŒÐj>X*ŸÜÝÉ¥ñòÜ+·¯Û›o[`?ü—€ë#ÿ8]Ûa° ø>2ÿ†Äa‹,ã øAaˆÕ#§'“s`y>Y%0-˜y}Åèž8Îq pÖ„|B6åR1ÕÎDàË)–g~y“mÍ|óÊž˜åñüw¶vˆ³ÄÔlË7xgT46© @™q 0ÒØÛ™d Îpµâ½}xp±ÓÈ–F±3Å¢ó ¸‘0t¦òÓ€ùiFÍB~$Nµô¼.¡ñë´Ê_xp¢kS²/Ev£2zm9ìÈOÍÃ3ÐK€‹#+Akœ|®#€8€ø`xÑñ,`zÑ{û¼Já÷À¯’0þg%ÀÀü×÷¿¹M++-^pæ+¤†ñCòÀiÅqy0ðì$ ÌÎvwñ²”4eMwõ ®(ÓÁy61‡ÁÎÝcYœZ=1$‰7¼SÏvŠ€Iß?«n)t×2Nï.š0{¬€W)/gÛqË~<8±J 8¡V%Ü÷¿wé0Ên(^‡Igøß\T"¯®G^FnF‰¼ ½ì[aG‰7ot uðŸNÀÀ˜öúWߌ{“¾ ˜v§ à§kD“8€8€ÿ«À9F¶ö]#úaÀ~{@YIé\@Ÿ¬Û¡)ãMÀÅ9EÅiY-ƒ‚Ï!´ÀÔ.>XÞAËNKÌõ¶À6†Ì¢RÔÍ*6àû+I7þK8©‡-U§Pz9ðÜÍÅÁ:ö¶3ßï®»ì%ì׺½ Øèp ‹£„ƒÄ¿¸¾Ç\– £„®¹Éç+pÝžòÅ£Ä]ãðã‰8€8€?8)GŸÎ·v>ïåî#€8€8€8€ø3€cáÉÁ>Ýâ©SÀÀ§Ææ÷0µ[SéhOãó»YËFeuꊦtÏ" wòÒh7áu`”Ý‘{`| 0n´°ž÷ã–µ9èæ¶ú}‰ôýuàfh²­Qý$]«0m98ÉÊ7{HmùTÏ!—† Àà¿Ø}ËS ì–ˆtßEt:ŒØWm8SÒ¯î4g%†!lÝ^°)g Lüá͘FÀÔ™àËÀæ‹@Ðì0¸&áu`~µ n?Ä—4±o8\‘ðP%è7*‘ê²êÞâ Añp¸XÉoæ³4nÀP‡…z°Ñ¸löçÄ}§Ø|i‘ßA¶€×FŸûï_˜î›¦¡åìÃaøAàÆüð=ÑøÁ'¥yðK‚--z÷cà ¼ƒŽ¥eàÇz˜î¶YÑv»-í|·Œ£²}ÏoöfÇu˜gcœ?š‚i`à¡,Poe¹=¸“Ë–0µÉì…£o¤{€åëÓ@Zb L¼˜¶ÜŽ:`rKƒ½éJ5ÀÀÖ¸+§Ä`âÕ´ \ÌKÔO6·—ž/zC%º4¨_…F'_‰kÅü[ƺ ;t:Lc-«/ÜIà¹ÀiÅ„F´Z“ð‚JŒ$Ì_‹²c“~‡†Àä(¼g‡,b“}C‡TBÓ°æâà+ëæÛÞ‹UÂØxÖ¼ÔÌ»vï%ô%_}­Tó-í2ÞäòYñÞ»Çãã0®Ã[õ0øHÛŸŒÝû€G7ܺ½<å ô$àsƒ/‹xeoÖ&÷í–ÓºNPµˆN,îÙjàX=~t`Y u`<°XQ¸ûk£/æ ý‡Ÿ,ÁüóÃ5¢þôãÌÀç88€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€8€ÿ ð×¹Žÿøòóó38q‹n¿.·Ðf!ß×ëõßüo+á-Íõ–ºK÷óð××d#&ÿûâ~-Ï×;j»–ä—O¾%þþ÷kà›^Ì9._¢SÑ‹ËíT>=Õ4?Óð÷U´iÒ/Vἄ/?Ót™k’*îÎÿO5¤µfíºÔËÏ'F’øžÕ÷ú¯ îR¥5åÀd[pVaÕá[XªxøriTâ"µ ð4þg( ¦V­„§rQ»€Ë˜0ÝüµÌ*QplS{þ p.ó> ¯tº«v i²i¹Óýx²L:—Ò.[ÀßN‡gý[•ð:|yX:uî±rã(£ÄS;þšJ\ý(1MR²–N—Ó¸*î~ã1=œ#€þ?˜/ÍëÕscLIEND®B`‚urwid-2.6.16/docs/examples/edit2.png000066400000000000000000000055521470350774000172410ustar00rootroot00000000000000‰PNG  IHDR_,–-­ÝgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEåååÍÍÍÿÿÿÿÿº¤UHbKGDøoéÇ ÊIDATxÚí š¤( €É ’ì×Õ'°oÐ_ÝÿL[yj=­²'ÙÙ)Q~cS ùóTÌ{c)Ž|3›<"0!Qþ7qä I™­¼ìÂz$@µûËFsñ53q)sÖK6¼lÀe£”SjE*y.(e£¦bÞó© ÿøêkbΦù €ä›BÍE´÷ f-`1à¹L,)’ 3BÝÌ»òVÃT5¬:Nl7ôRXÕ¢O)©£œ'ã1 ÿ±Ài À™ÄåÂˈŠÈ–ˆ®hÞ ¶Á™˜äNå¯Ë}ÏÀr•PެÚU`Ì'rÖ[Ê[Hê·½I”ÃI0 %cw=0çïL"9 §NÃ|rߥh­ä/ÁØpëq@ì'!ë®—0¶a®yM¹V›ªçBçP+ôÈ] ;ÍÀÈΪÎÎA¼±†õ¶ç9´Õµ^";¾[µšJPKD)PÈI´¢w³½/`-xÅ-ßîý­†‹ô%‰§’s‹}WEÛ‚Ö L3|.ð~-][6Ñ]+Z yh[ÂA´±!rMB k½Žû€hdÓHä–Y›)ÓÚ£çdIÙÇ=H¦a4ua ÇÇÀ sžšRoHSñz sÎ&! êE1§^G?¸îÔb둘ŸTO5Œl€þvÀ^-HìÂJkÐj¸NÕ4攀…a†¨^¢©tªjD~l¨œD`í,jd'&ö)XFõÒ]âxÜó.ÀâÍtC8ÈxtîAœ­e/?²&úDMÈ~Xúô>ù Ó£‡ÍyêN(ÇZÄM}¥=sýåó£Di €u¤ [à$.ž6Ï]¢œµQ@j€G]¥ÚÚâéEqŠä!Ôè`ù¯4}dÓE¥¦§¬7 íä5ÀGlk9rZòÓsšltn3£¿à"ë½è­ :KÃä«¡½?­ö´²íÛ}ŽPß{%y鈻 E«6£Réäp1›Ôœð>ÑÖ}‰Í4^¨ßðõMìFf:c¾GÃjMÍù5ÞÔÛZÓ«Ú ºˆ%±0õ.=$$ä(‚MÍ]/ÚûÓkÊA´¼WR îŠÄÁµÕ4Dè÷mc[^MñÀ8†ÍñØ#… Åbó,ƒO)qX´ž%%*yËŒK–; <¾ŸÃ⻬G1°Üö¹áv¤”%)¹tÙ j¥¬ºË˜Ä<ÂɃ¥Á””3î‡2ŒŸÛm°ûr”¦h d†Öë™e¸¸êR‹áwÝ ¬) ¬)ÝQúA=mÜgîÝyؘ€mLÊp²Àåæßœ]f«·f—{ Ë[° i¬a¸8uÆcIÃZb).)¹!¯›E¤j$®”dõ µO6êµ¢915Ÿ§¦ Q…?O£"—’–Û Îç]ëZ»üD¤G‘õð}œË(ͧŒÎMK ú¶pûZ°ÑIŠaë¿,¥Ö,ìkm» _Gæßžä¯’âOÈ;…!VŒœÌ‘ƒÈóéÈ a^_0úOñàl ¹Båê;Qg"qŠå”ȳ¼:šŠÏWî‰Yöç*Ö4ô6Ó° ê[–M0&Œ.ÜZi1Ÿ G¦4öq&˜€s\£x¾¸DƒiK£y[9F]n4L]¨üToÆ‘^‰‚ø‘D85ÒsN›#O¹^‰3K]Ëÿ—÷-EwOÁŒÎá¬å•²ÐQç—=+Pœ‡„„„„Ü.´“<‹v8ã=ÞKÃøÏj8€8€ø¯ßÿÆ5¬Ìx!ðBgÞ¸@ø`?$_œV:._ œ'~ÊÆüèLfJxée)yê,ÑÒ3¸bL/®t˜t¾-–ùõ(»@Æ–Q†œ7ߪ|>°3LúþYí–Bw-àôNàb 垣µá8ÇŽ[ñã‹›J'Ôª†ûú÷.Î#ëÈ–1²aÐþ7›¹äö5Æ}€qãû#æõ¸enº±­~]"}ExãC@®LùqTéÈz‰ö!§7ß6_ÛῈ[-à5¹¾ýÂtu3µtøÐ czу&àCÀï|Rž?*ø©*ÀeÛº:À{‰,ôÁVe¸˜LuÛ,hû¾-­|›ŠG—èÇᯙ?š‚ià¡LPŽ2ÝžÜÎåHÚlöÂÑߤk€õ(Ð3¶ÀÀ“iKsÔƒ›ìCWp è &ŽÆÝyôËÏõÕmà^Ê\[¿ ¦Ú¹a]Q8؈b8¹Í·>ýw€±.ÂNE bãX€Ùx.pZ ¡­† Ö4¼`# ó×¢¬oÒïÐÑØÇº>À³. 8dß°á“Ð<\ ÊÕ6°æ˜u6UÃØx6¼Ô;˜Wí¾ßKèK¾úZ© °´Ê¸?*é2¦Æ\ÓzÜî‡qÝo¶É_wô ÀØýã>àQÛWõ ô$àƒ ˆuemÖh¿Ýr àÏ· Ô¦ýÀÒ=[¼kÂMàá€ñ`ÀEá£ßsݯ֕_ö~:øç[D§é ¹M¾Ž%ÿøôóó3ØqI.?§ËÖæI¾ÏçóïüÿVÆKžó%w—ïç௯É&Lþ÷ä~–€g‚ó¥Ë_ì§O¾dþþ}øbó§/±©ØÅé²+ïžjžŸi øû,Ö4é OÖ༆O?ÓtšK’"®Î¦º¥¥fë:ÕËÏ;FšøžÍ÷ü[wªÚšòÆdïàlÂj× °q#ðéÔ˜ÄIJàiük(¦V­†§rQwŸ0]üµÌ&Qpì­ö6üp>çu^©tg­@r˦åJ÷0ðd™Ô —³¶€¿ Ïö9¶&ámøt°Tê\c¥á(^bN©Í$ÎÞKL“œÙK¥Ëy\W¿Q¦›à—ÿD#ëâuú}IEND®B`‚urwid-2.6.16/docs/examples/edit_text.txt000066400000000000000000000013331470350774000202470ustar00rootroot00000000000000æ›¿æ´¼æ¸Žæºæ½ºç€šç¯çƒ«è™«è°ƒè¾¾é€¯é˜é†‹é•¿é—«é˜šé¡ºé©¼é«“ 공곽껫ë“뇽늙등ë—뛴룸많맹뫘볶 ტექსტების დáƒáƒ›áƒ£áƒ¨áƒáƒ•ებáƒáƒ¡áƒ დრმრáƒáƒ•áƒáƒšáƒ”ნáƒáƒ•áƒáƒœ კáƒáƒ›áƒžáƒ˜áƒ£áƒ¢áƒ”რულ სისტემებში. â•”â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•— â•‘ • “smart quotes†║ â•‘ ╭─────────╮ â•‘ â•‘ • euro: │ € 14.95 │ â•‘ â•‘ ╰─────────╯ â•‘ ╚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• Math: ∮ Eâ‹…da = Q, n → ∞, ∑ f(i) = ∠g(i), ∀x∈â„: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β) urwid-2.6.16/docs/examples/graph.py000077700000000000000000000000001470350774000231022../../examples/graph.pyustar00rootroot00000000000000urwid-2.6.16/docs/examples/graph.py.xdotool000066400000000000000000000001331470350774000206540ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 79 24 key --window $RXVTWINDOWID Down Down Down Return urwid-2.6.16/docs/examples/graph1.png000066400000000000000000000044251470350774000174120ustar00rootroot00000000000000‰PNG  IHDRÇhIzK»PLTEÿÍÍåååÍÿÿÿÍΪt bKGDøoéÇ pHYs  ÒÝ~ü™IDATxÚí]rÛ6FÓé’éÀ¼o –WpŃö¿„H‚¿¦LT„sk'm9ÉéÉÇË+Ñøö—­oßm±È¶èV?Ü’E®Å÷oø†É˜Ì“1“Y`2&³ÀdLÆd˜ŒÉ•›<üRPéj+. “°¶â2˜ŒÉ&cre&k*M홬00I5{&9dLÆdL¦0“k6y ùuúÕðRå!„­¸rBÈ[qdª‰Ö˜üÌ1y”“sC¶%7ëÑ:,09 dû@åˆu LÎÉj€Üœ€ŒÉ‡!_ ““BS“Éä¼õ Șüä†î"+ä3…É…!cr1“ë|³õîß²Ö¿UÇÅíöñûݾµå ×7 ÷ý÷íã—…íy|´íoLN Ù²õ{•Û[©+7ÙAnoí{ ™LN Ù2}ÿh].‡¼hCf$Uºr“[Ò!w™ÜÚ_åƒ\¥ÉÈAf ¹ãŒÉ9⢃ÜZóA®²»˜œød{“C^´pö 5Ù&'¼~1ÒZÎî?LNù‚ËêúLv€ý;³‹œÝá’ÿÜQçý`™ ä{™2/_…lúãå ÿ¹qñUȲÿ@¿BbrfÈ&‡Ô•›<5Å Wgòd÷IR'·é>Eø&§‚ìˆ:ºr#î³d,˜|²åÛ!!ËÄ1¹[ ë5d×sLN{â ËðÃ1y½…2Y†ðH ™>¹»qÝ…]¿þa˜œü²úœº˜|h@”r}&_™Qg–bÔ¹žÉ!ÓÂùQçRq“2-ÜÖN`röQ§Ád|Ôé°ÆNi09ñ¨³óy:|ÃdxÔB#dL¾„Ýs"Ä““Ÿø"“ï˜|òF —2}ru yè.R>3¨3K1ê,PŒ:‹CfÔI\¼F&ÓÂmŒ:S>‘J ·1…“¼‚HguJ^ §³:Ï…6&Ï nŒ:O=}ÉsSu€¼>ê”wLÎ=ê<×FÓ'¯µp ÈòŽÉŒ:Ÿ2£Î‹£Îâu¯‘É´p£Nn1K yûÞjZ¸ü7°§›ÂqY½9ê”wLNyóU<ý”ÔI&ëü£Nž~Òù_ՉɺĨ“s:¹ý dF )FÅ!?ݨóçƒõ5ÈÅê©ãâ!?]\`21“1“1“ŒÉ˜ŒÉ@ÆdL2&_a2‰ ââÅ 7ýB/>¤19d¥vÑcrÈÍ:Ïs1y&²}k­DcáÑ[h÷î·PZcòiÈÊ!êÞTã6[÷¦|5Lþ²Ø‡¬…ŒÉ³ÂæÁ²ŠS1ù3“&—€ÜetƒÉé!wM…_¸ÖYû6Ãw˜Ìì¢NȘ\Ìäº Ë£ñàïE\\¹Æ¸ãOÛüÌ ²ì°ù¿Hbò²¸ÈÙÍ#2ZD·oÄ6Ñ“#Èæþ9d3,?‡ì÷ Æä²Œˆšñ3áöIn`!G·L…ÛІGb09‚<þ £Š~ãõÉ#d9~±a'q³ø‡ 1yr÷[Èòäp§ªÿG!×™É2Î`C^ ¾sŽrr³6&ïød¿ñúç'>9®åÁ_¥}ò´…3ãÆë#ÃíNÆ /÷~ WiŸ<¹éw`—ùpûbdÑ]„ïü²{1R©ÉÌ.˜]ù«&×¹ÐßBU—@®-.ûüý ?kÝbr&Èm@ÛþêqcrjÈo=Û“1yOä>%ÞL Lþdý È˜\2&‚Üj29÷‰Ï.δp˜¼«òoôɹ/F,á3qÉY “‹CÆäb&€|”Éùçɽt¶@\Ô ¹X\`2&c2&“1“ŒÉ˜ dLÆdL2&?³É@&.ˆ c2&c21“Ÿ²Î_ªz“ (­ª7ùȘŒÉ˜ dLÆä M2q‘r¿‡E£U ÈuÆ…3¯ÃeZ@ïùGÔša;—F-ybò1ÈVMôãòOL~Ș˜<…¬VMçâ“çèS‘ÉY.F|\„Q§Z 619åeõƒ§9L~ Ș\Ìd '-ââÈÄ&c21“1ù¥!ëü…É…ªn“/€ŒÉ˜ŒÉ@ÆdL®Ðd Ä1“1ȘŒÉ@ÆdLÆd c2&c2&cò ˜\ä{™2UÇÅ%k‹ {@úãB„…‘‰ KL6áð°êÁ¤Ó“evÈö‹Un²‰ts4lnH—)ÓÚÔn²\@î¥ÍeY¹ÉW@®ÎdS²©¾»èO|&dY}w1´pr 9fCŸ_Œ!¥¿*1 » IŸ —­˜]ô%JA®Øäœe:/c’òS=3äÏŒ“1ȘŒÉ˜ dLÆdLÆdLÆd c2&We2‰ âȘŒÉ˜ dLÆd c2&c21“1“1ùL^B¦ ÄU .(LÆd “1¹“5•¦öL¦’Õ¶ÉTzȘŒÉ˜La2&WhòÙ-þ¦ÇM\³¸p‹ ’¸fqɘŒÉ&c2&S˜ŒÉ&c2&S˜ŒÉuš<Ì"Ëb,r,¾c2&c2 LÆdLfɘÌ“1“Y`2&W¾ø¼•æ8ÏQ³IEND®B`‚urwid-2.6.16/docs/examples/graph2.png000066400000000000000000000046001470350774000174060ustar00rootroot00000000000000‰PNG  IHDRÇhIzK»PLTEÿÍÍåååÍÍÿÿÿ†ˆIbKGDaf¸} pHYs  ÒÝ~ü IDATxÚíknÜ6FSt ºŠ(øY­ Æxµÿ%”õ ž–G$% Ï…ã2ãÌ >=ùtuG2üaêÇOS,¢-šÕ/»dkñó¾a2&³ÀdLÆd˜ŒÉ,0“1™&cræ&÷¿T¸Z‹ Ȭµ¸€ &c2…ɘœ™É%¦¶LV¤Š-“ 2&c2&S˜ŒÉ9›<‡ü:ýjû ©ôû¿ÂZ\9 䵸²UxkL¾2dLÞ¥D‰É±!›…õ”e»Àä(Í•%Ö,09B&«rq2&ï†<¼&…,Æ&“Éq!— còW º‹¨&'†ŒÉÉLÎr•¦tÖqa¾ÿŽC:ȹÅEUÉD·BbrdÈ:†Ô™›<ÕÉ ggòd E¥É“#_É2dL6 ÙB–˜ ²Y™¸ˆ“›”˜òr 'MfhS˜rs2¢…”•¬$&‡|Âiu~&'©ÜM>òÑQçß¿vŸ‹ûA¾á¨ó~» LŽ ù£ÎÛA¾ã¨ó% WÍɈ¶r»™§p L¹u6§Õm‚ÈJ´Ó LÙŽ:¥›'·e`Ș\ S8n„!5˜¼pàë!ËöÓÁY}òB ×g²lÃ#(dúäaÔ)„îfžÝÅ!Èk§ÕAOûr7y­"BÎÏä 3êŒRŒ:—39"dZ87êœ+®19äµ)œÀä§!ïujL ÙB±Xý §Ô˜²»ªS7Ã8ø†É!!7WujoR2&WaûžH?áÄäà>Ïä “B^iá¢B¦OnGí=#}wòFÌ."@NRŒ:“CÎmÔù0õn~D­ÿ²Ž‹ÇããóÝ‚~Ôé ç÷ýóññÛÀv<>êúó1GþQbò³ [¹ãZw㪩ž¹É²øîC^(óÇ>>1ùIȆ黉‚÷G±vá`ö“—á ggrízä>“ëOóˆ—áâ"K“GýX®½”8"2&Oâb ¹®»¼8ÔâÑ]Œ|S“$2ÝÅj W»¾Í?bòs—OFLJ8ÈæwÝ£GÎFr?ã;á´:?“-`÷‹ÙELÈöᔓ:÷Nbÿ ùÄyr‚¸¸ äW¾ªó2ßH_^xË[Ì. yýÞêû½[}Èûo`?’'™›¼÷ªÎc7‘`òÄÔµ«:&‡ƒ¼rU'™òòUwüQ ?ðÍ/8Ääà-Ü ²Ää€W®ê¼å ì—üÊWu^r’ÊÝä ¯:÷¾Ú?7ƒœ¬öÄB^‹ c2&“1“ŒÉ˜ŒÉ˜ŒÉ˜ dLÆä¬L2qA\¼ä¢[”³/•˜²R›è19äb™ç1Ș<Ù|…yRa¡( »(í/÷ˆ° U–˜|²²ˆšU¸‹–[óQîy5Lþ²Ø†¬¾ “'„y²‹ŠC1ù+“&§€ÜdtÉá!7M…[ØÖ¹tm†ë.0ù‚§Õ˜œ2&'39/ÈrïߥÆÞ'hââ4È9Æ…î|ZÿFõ²ìž°ú¿Hbò²ì¹ÈÉÍ#Ò[x×oÄÖÞ“=Ⱥú²A–_Cv{crYzDõp‹™°û¤z7°½[¦ÚÛÐúg ¢1Ùƒ<ü ƒŠnãõÑd9ÙúÄõì‚ÄäÈÍOlé!ËÛ;UÝKì…œg&K?ƒµg¨y9.tû“sÜ/!G7kcòÖOv¯}à“ÃZî<ðeÚ'[8=l¼>0\o᤿pro·p™öÉ£“‘nvÙ ×OFfÝEû“_6OF25y_1»¸dLNfr>Ë4¥²Ž‹S çß{îÛïö¿eYcr$Èu‹¶þÝáÆäÐß:¶!cò–È]J¼}3-0ù Èå7EÆäô1yäº$“cøÌâH ‡É›*÷iñFŸ rņð‘¸Àä(…ÉÉ!cr2“/y÷ >ùÄKg/¯ùrqÉ@ÆdLÆdLÆdL2&c2&“1Ș|†Éw„¼÷5q‘äûÆ&“1“ŒÉ; '¹ w“(­²7ùȘŒÉ˜ dLÆä M2qr·‡EQªóŒ %&^ï ˆvç–ÂßK¤è·s)Ôœ'&ïƒ ¹9oöFj2"“Ãø”k3 Œ LCV‹&‹cqÉSÈí¨S‘ÉQNF\\´£N5lbòAÈcââ`aò 19™É@ZÄÅ ‰ LÆd c2&còKCæv~Ÿ„ŒÉ˜ŒÉ@ÆdLÎÐd Ä1“1ȘŒÉ@ÆdLÆd c2&c2&cò ˜œä*Mé¬ãâȹŅy@ºÇ…hZ&,1Y·÷«L81YF‡l^,s“µ§›¥arCÚì™Ö:w“å róPØ\–™›|äìLÖ ë컋îÀ§ãA–Ùw} 'ÇÃaÖôÉþɈRº³°»ôɾpÑŠÙEW"äŒMŽYš«:ygä!óÎ&c21“1ȘŒÉ@ÆdLÆd c2&c2£N₸2&c2&“1ȘŒÉ˜ dLÆdLÆdL~ “ç©qA%ˆ “1™ÂdLÎÃä’ S[&SÁjÝd*àIN—[ÅZbMÉ»z^^òÒ<@IryJ°y’£Ë =þ&Mu–g´Ü[)¤¬dP5%Ø8ÉþpÜ›4jª³F²äjýÁYÅÁ (Ù“›«LLˆí†Lu–ÓEñ‰ä³­$$ÿó’O~|7ßO~óCƶ¥§†»»SszwgUßÝŠO§ôöôN†níÐm•äûû3svoýÝE²ó|?¼—¡¡µ1”}\Ï}Õw—¿ø–È'Ñ%;¤™^ý<§n&îj•iËÿXê»ÿ¦$’½gí‘ mÈ?H(3›óÑȪ=ˆdçù|ôð =2d÷±CÒ¨“üÝó‰x–¨VÉßkÃÛ†ñ Wñ|ë6l·~j´çô¶26ï] ‹ç¡ñ‡gj[Ög²Ï™ C$+Ãxà‚V<§nCûÒLrRˆö(ŒÛ=á¶xvÆÙî9ãmÓ£=ã‘‹añü`F!¤uà\µŸËn£q®nOL|âµ’Oä[Qz—wɦZò™ó& ‡!HË’‡ïJNœdÑ™òt±†dã%ÛFÛ;vv3ɽ:ÉçΪx}™HòC,ùa Éfmɧw.U„8vâmïÝ*ÉšÄ\–Tò½M C7|ŸKÖ«%'—*B/K u’û}M!ŽcÉ2´BòùÈ¥ 32EÉ’)FvxôÉ’o]$›B$‡¯•’M9’ÛïDòƒ‹d“G²ÍÉNêƒÉB}…äïÉ–Õ’ï\â½õ™yYrmN¶ö¼ä8'û!S|æv®ÎÉÖ¤—\—“MMN–€u’—r²ö®ˆd‰Òs/9ÏÉfÉRK¨Ô?œï’}uq$û¸=uCïTà ÙG©6Báaî³Â#ÔuÕE$ÇÕ…þ¥NZY]øH.Tîß9öë"ÙWArV]ø]^]l]àí„á/?J{‹Ç4\iÓ$÷þ É5—»Šã~o‹G¯¸ô8~óåqõ>ã_²rí‹_µ{-mOždåÚ—OÛáôYV®}õ\Ñ®ií#¿Îdõâ6f}ÓùoY¹öõïJþòSL¯Þç¢$y¼†äË’äÉêoSóJ\•$Ocɦ^²)Iïr]’<ß…d³•dóI’ÍÖ’ íÍ$›I~”勦РùñQÚºY%ùb,÷ñÅÅ/›<Æcot|a·änSJ•äˉÜ'——O6yL&ÞòÓÄŽÙn²+;t©öº*I–œp5^]=Ûä1z¯SMS¿\MŸ«$Ϭ×Ùìåef“Ç×Ùì%d’™ŒÎ\ãë¬Jòõ\îóëëß6yÌç!®]ݚ븺ž×K–[&ùËO¹ÕK–@[ŸVö8ZÝ’Ûů‹ºt!ú¬ÃIÉÖ±lËZìíI´?éƒ&5éB4Z«Ó(’Ÿe,Hž–cÚ§ »Ì^f/®F¾¾¨dm¼”"<¤‹¹ÕhÎóH–›²¯õfdH6ê$;¥A²ëy|Oò/Û¶Ë8Žqé×çd±é$'Ù ùÝLUR‰$‹T»Dé"_ÜP­d·!_ܨÛaV>-F’E¡êÌ$» Õ¡yM.Q¥Ÿ"Ù&-$Ûäá$»†—|™å”%Û,²…ä™Í [J¶ØÜ1_-ùg|â[G²Õi*#Ù˜’5ù–$kÊ^ '•eÉÖ¢YɵՅ¤âjÉú¬l-š¢äë¹—:/¼•5ÅÉÈQºÐœ¬±½*’M&yH€¯ÈÉ&“<‰%kŸœ}Àk–®‹d“Ižs² ñ©Fzm$› Ùkvͯ3—®WE²É$ÏMVeøî¹Æud{ "Õ…ÞÚÂâQ« w*,;û‚BN²A²x¹ºÐbaâJŸ..}åàÓ…F²+3Ük¡;?-I¾šú‚BNr¨#´ºÐZcú¬ãËŽg¾ ˜…Z¶òêBõÎfU‘lÃÕÒp’¯ç¿£!5=ÿ­q=¯¯ï7®ûªrÂLê äÉ»µsÄteI¼Š¸¬(‡úJÊçk—ÌÕµÚ¿%y¶ÆµÉG$K¦ØÍsY¾¾Xc¨ÌU¹zX—Ù¬*d¿ÎÞŸÏÐäðN4œ–®ºË=Û°¨lî‘oòõþ%Ä£ä=‹>~É ˆæV«Ûqb»¶Ñêv¥½çÅëÂÜ,fq³xµ’½æÅâõU{th‡ößìòíÏ›•kïßÞ´ñ¶7ÉÜjËXß–ômÌÍëXô_7¯¾×Êw=¶½sÉoò%¤‹oöÏ-³$ÙÆöŽC”ZɯÀ&“l2á;•lÃØJu‹ÞßtÙ³ä®MÛÛJ^ØìP#Y†ö,ùímï’[dk3D²^ÉÉ*Y‡öÉû"ËÉvé~0'» ¬’½MÝð’eèfw’-±dÍÈ{‹ä–/*lºh}´ºX,B$‡Dl³DHêyg’½V»Ê* m!7¯ÁvžUà¯J^,‡Ïa±ˆb÷fA Ôñ?Ãè6Dd´)‡IEND®B`‚urwid-2.6.16/docs/examples/palette_test2.png000066400000000000000000000521711470350774000210100ustar00rootroot00000000000000‰PNG  IHDRÇþGµSäèPLTE‡‡¯¯ÿÿׇ_ÿÿ‡åååÍÍÍÍÍîÍÍÍÿÿÿÿÿ\\ÿÿÿÿÿ_ÿ‡ÿ¯ÿ×ÿ_ÿ×_ׇׯ×××ÿ×ÿÿÿ‡ÿ_×_¯‡¯¯¯×¯ÿ¯¯ÿ‡×_¯‡_‡‡‡¯‡×‡ÿ‡×ÿ¯×_‡___‡_¯_×_ÿ_×ׯ¯‡‡___‡¯×ÿ×ׯ¯‡____‡_¯_×_ÿ_ÿ__ÿ‡_ÿ¯_ÿ×_ÿÿÿ¯×‡¯_‡‡_‡‡‡¯‡×‡ÿ‡ÿ_‡ÿ‡‡ÿ¯‡ÿׇÿÿ_×ÿÿ‡×_¯¯_¯‡¯¯¯×¯ÿ¯ÿ_¯ÿ‡¯ÿ¯¯ÿׯÿÿ‡×ÿ_¯ÿÿ_××_ׇׯ×××ÿ×ÿ_×ÿ‡×ÿ¯×ÿ××ÿÿ¯×ÿ‡¯ÿ_‡ÿÿ_ÿ‡ÿ¯ÿ×ÿÿ_ÿÿ¯××ÿ¯¯ÿ‡‡ÿ__ÿÿ×_ÿׇÿׯÿ××ÿ×ÿׯÿ¯‡ÿ‡_ÿ__×_‡×_¯×_××ÿ¯_ÿ¯‡ÿ¯¯ÿ¯×ÿ¯ÿׇÿ¯_ÿ‡_×__¯_‡¯_¯¯_ׯÿ‡_ÿ‡‡ÿ‡¯ÿ‡×ÿ‡ÿ×_ÿ¯_ׇ_¯__‡_‡‡_¯‡_ׇÿ__ÿ_‡ÿ_¯ÿ_×ÿ_ÿ×_ׯ_¯‡_‡____‡__¯__×_×_¯¯_‡‡__‡‡_‡¯_‡×_‡×‡‡×¯‡×××_‡¯__¯‡_¯¯_¯×_¯×‡¯×¯¯×ׇ¯××__ׇ_ׯ_××_×ׇ×ׯ××ׯ¯×‡‡×ÆÆÆÐÐÐÚÚÚäääîîîׯ‡×¯¯×¯×¯‡×ŠŠŠ”””žžž¨¨¨²²²¼¼¼‡¯¯×‡‡×‡¯×‡×DDDNNNXXXbbblllvvv€€€¯‡¯‡‡‡‡¯‡&&&000:::¯‡‡¯¯‡¯¯¯ÀXüKbKGD ³k=€ pHYs  ÒÝ~üQ*IDATxÚí½y€}ÛU×ù¾¿ÊïñÞKHÌð’Ž€ya "6A"(E (@éÖV:4„4­ŠF[†eˆ  Ä)à•ˆ2D¥„n‡é!b0*Aå_÷ÙÃÚkÚûî[uï­[Ukqξ묳ëW¿WõÉ÷÷Ý{î}àˆˆˆˆˆó°ÑÞ„™Ùnl¡æâßÝùƒ""̱3sòU~ÿØÕ;ÁqÄmæÍtFé_üüo¿Ï1ól2ÝÉž‰P_e¿<"bŽhCÕ눉?}‰fõQu¿Öûòˆˆ=8î$i–`ã‚㈫püÀ¾3e+ø{^A•þó""ŽÏñtš¯Çÿ€.ÇWâ¸üŸ‚ cŽ1á,YáXÙ’`9bd×Èí…Á~…¸åmJèdŸýŠ.ÙZ‰ˆˆˆ¸™³øEDDܸqó£p|¡ªñƒ9JÄÏù¨_ìú_ìóS_ÎúOFÿœKõûÚïp„ïßmŽÿ}/Nø}OÌñÅÎyþìEp|hŒÍÏùTߺ_Ù"Çõÿú¿J÷Ú?O:¡[ôOݺ¸¸§¾*8v¯.†?ÃòÓ½p¼íÏ¿ÇìWp±ó›Þ2Žé_¥‹ö3ësëýPØœ Y ŽÅáwoö3lÃð–üí\¸_LFó'ߎ5£Dþ8îÇûq¼ö3TƒÃ±ùíìɱþÇ÷î¹_ÜAcqyŽï±Ÿ˜âøÞÑ8¦ïz«8¾wXŽïÁ‡àøgëÞ‰8þ½Ï‘ã=öÝöJ‚ã ö¿&_˜Ù»8¾¸˜þ*'ß,¼Ïþ±X‹Šw‹KÈà«‚c÷R­ø¼Ÿóà–’½_áý9 òà8ú¥×òÅ¥ÿŒø yGÜ´ÿ-ÇÁñMù.Z²àøLâ -èÿ?à ‡‹ðǧäøþÆqã¹^GŒãëè{r?^Þ¸•Ï…ãŒ- ¼÷ñ òý-ü_ÔU¿?ÿÃïy 9VO”è™·î9ÀkâxLì¡8¾ÿ„Ñ÷½ñïî3Ý».“oés€Çàø~õ÷s‡ ûcªËñÿð›ÄñäÉžýž Ž7~ïoéãûâ_ÿ–tÄsá~ν9%á_>à˜Œû²bÿäÛ¢Çcí†÷z08îë;—c‰Í}†o!X±îÌé{Ååøþd4ò­ã˜žè_íä8ÌÆÇJ1v÷ïkÍ~Âcó?ƒ=9Ößýöé±¾y‰ç§ÂWL}™‡û‚ãûÖ{è¯:,Ç·ÛWÇWæxaw¬Çd}OÁñò{ì8¾Žws¼Ç¾ùÚ?Þ‹ãû÷§ÿS™p|³üñ>ï«p1</öAhŸÁ߯Fú¾Þ°›Î÷˜%7ÒWD_úD¤/}s4òôGœ†ã'(Ž/Áqp|_õy·óo}p±_„?ޏ5/=ïv¡v'žÛŒ¸«w33ã¹Íˆkçø2ïÃ2G68Ž8GŽwêqÎâ¹Íˆ3çxìã¹Íˆ›Ã±Ñãxn3âæq|Ø÷½‹ˆŽ#"|޳ïGœ=Ç+}xn3âz9޾tÄ­à8""8ŽˆŽ#"Åqøãˆ[ÀñhËŒÍÿßLÙgQxsD%~Âcð>|-~b—û‰®ql?\,ÞxóÊÏ8ñ“ÛëgÊûyþǵùÇo^ãàø€?UÑ—¾˜qzOúðp®¡é}Z^ûˆÇøý~®òùЉmà)xo¼yŒÙÿÞÙOåb,$ãŸì2ÇêÙ xãÍpÌSÍop|yŽ'¾BKo<øvu'?½àxÏŸëÚ:oåíéƒã}A=>ÜOuŸ}·{ "Çœë Kçøã`yùgºÈ±}¿ÍxãÍ+ƒ¬–êíLc¿bŸŸhô¥#nCÄsBÁqDÄ™pü`DÄÍà8"8ŽˆŽ#"‚㈈à8"8ŽˆŽ#"‚㈈à8"8ŽˆŽ#"‚㈈à8"8Žˆ¸¿ÓÕbòG?tè˜|¯‡¯#&ŸGŽ“oúÄ£Åä›>éøÇw‡c™cxHb^ìÃ1<Üp}Ã#“ €+s D™cx"8>'Žq:Ž_ÇÁñ]á8ýc¶ÁœÎ’äB­Ôz›³ÇÛeóX ¤DÑL·hrÿòŽ·É™¸üå…>JŒy*f“½/o‰˜ƒUŽ·¹ÊüUPJ » CÁîâöåKos3²ù« ¾”hšÙœ6P¥&Kos3²ùË ¾”hšÛdšC_ž¼UöãÖw"š;¾-¡9ûp\aäÊJ4‹;=¤nõ‹Wи„ÍâÎÃT„¼¬©B¥‡1çÇ•A®²D³¸ÓçÁãØ@?ã¸ÂÈ5˜hwžÈɆ$m"Ö8® r &šÅbºë=¢_Aò«8†¬ìÏñC’cbð „áø¡}8~XrLŒÆ(ìæøá«süˆäø‘Ʊ^Ìá‘rüDÉ1Á œ†Ãñ÷åøI’cBpœ†ŽŸtŽß郋‡²Zü¸U'_’c0cñðÃF‹%Çm2äWûC‹5ŽÁŒÅ#-ÞÅ1”±XãÂ4-pܾê’ƒ‹'=Éh±Ã1}xr¶CêñCc=Öú½?ÇzüðT%ýFÑ%Ǹ”Cêñ#—ÒãGöäRŸ¸¦Ç¸šCêñ“<=îÖz-¨\Á[ŽËÿ]…c§ í•wq¼·?îFÚ+8 ¾–cñ'ïã»Q†öÊëþø‘=ýq7ÊÐ^Ù÷ÇlÁw)ŽIG¥ûþøHÓ^q ^WدxV¸lدxXs¬ö+¤ Co\è]½§±÷~Å#šãÑ~x"¶;ö߯x¢æx°_ñÄ6—¾j/Žå~Å“`5ûrçìW\ŠãèK»¬ß±¾4NÓ—Æeö•ƒãàxáàøVplzwIUÓã¨Ï Aõ8Æñ;_-&ô““ïõ”ëˆÉßç—-&ßô©G‹É7}Úñ#8ŽïÇ`TÂ$WáD`þ×䨃ñUòº¯~Mƒ¡Wóü×9*Ç`èÁMÀ1q-Oÿ¡×É1 “Ãp 6ŸcX¦¯•ã /Ï1à¥Gæ¸Ñ †ô5pŒ–ão0ÇL?åÚ9Æ/¹ŽÑsœLQsm>ÇÛ¿wÅK ™Šq\íÄI8®&OL_ÇÕE@°{l_±}Óâ$ÐMű9F±xÚõrŒnˆìQñ-¯ÇåÌw‡q}z Oƒ¯Žñœct;ŒîÌ14»×¥ÇGã˜d8ëò |Œ _¯?Vã_aÆSøŠ»Áñ©üñys|Œ÷å¸DpŒãƒ`¼'ÇO =>4ǧØ?çÀña0¾ _ç¾Û¶GQ‚%‡õǧḷ?ŽÝYã¸þ}XrŽ¥8Çõ›^W$úÒÇà8úÒÁqpÇÁñäøÁˆˆ›ÁqDpGDÇÁqDpGDÇÁqDpGDÇÁqDp/Ä3âÑ…xæBž^Fˆ¤f Õ|<£ŒhÉ3X)¡šGË‘Ô,¡šg–Q\JX¹—{Ï4<Õd%¹£žÁ±à˜ÈíŽ fˆÙ¯ðú wãXç 8޾ô àø„ÇÁqpÇóxöBüÒ…x—…x×…x·…øe ñœ…xð–Æc'Œà88ŽÃñö/YKzIrœ *É‚ãt‹ôªà8ÝQI¾§[ŧ9ì¢'œ×4Çd)? ÇÝh/€OVòêSn3ÇÈdzÛX°lãô—¢[3Jˆcäã]ʈ–¼‹àùx×2B$Ä1òñne„Ï1³àôJŽ‘ç”QgæxÌ+®iÎqe·_ÇcVãÌjÃ1Á,1ÌjÃq‡yÌ14¾Çá;È1»+—p&'_Qæøç;‰ã2Çç8ßI—9ïªEÕ_`ã¸Ì·JN6ŽËÁq.°ä9eŽå¸Ú ‡c2y·[½î;8}Nûº6Wâ~£·˜¯xì8¶â,õXÍ9žèq)ô˜“ WÉ×|¬ÇTvõ¸\±d¨Ç”IÑÇ‚ïƒ#.Ù¨?᪃9cf/î„?F#—8nJ½“㯀fŸc¡ÉïºÀñ»9æ‰Ãqhªœ„ãW8~ðÀ?¦9~ì±ÛαòØÓWÇ#_Á9¶¾Â$C_Á9¶¾Âr<ñçÉñƒÌ|ÇWñCŽ/©Çï²[-ÇVq =>%ÇÍÏ8¦9t¡ÇäXí»={ºïfý1í»IŽ1ßw³þ˜öÝš-vü1í»Mü±Úw;¡?ʳúÉqÓc’cÜ~ŽEDõA`úy¦Òö+œ>Hs´_1냴ý §R·)ú~…Ó1û^ähû´a޵·_a}E…‡ÐãÞþÀ­ß¯ˆ¾ôB©ò¥æ\žãFpç\ÑÇÁñ¡Æåæ¬>´yG9~îB¼ûBÜ[ˆ÷Xˆ÷\ˆ÷Zˆ÷^ˆ÷Yˆ#Áqp/sœþåRIÉ8¯Û?o-aUÁqº¥ËqºE §;*±§[;9NsØ…Ïqšc²àø&qŒ|<·ŒIÍ6Nß½ÒÛÉe™cäã^!âùx2¢%R‘÷,#DB#ïUFø£1 A¯àùxŸ2ê,8¾‘¹añÀX\vŽ æNµá˜`Æ{ 9&˜;Õ†ãó˜cH|]Ž;ÂÁñ å¸ì‹sV'_Qæøç;‰ã2Çç¸Þz2Çç¸ÞzÏ2ç=µ(£ú‹íÖ{•9‚ãViÉ{—9‚ã\hIz}Ÿ2Çr\íÅv+à»izL¶ØrÌôXMå=†¯ÇÂÃÓcå1Öc–8z\®˜0õ¸»æàøæqlô¸)µöÝ¥[Ʋ¯ð9–&ÙçXúã«qüÞsŽ›@Ç7cò#l}…ËñN_±¯@óð|ºÜú –8¾BŒto·¯ŽožO8Vë¼}õ3_¡’Ù:c=6ÉL±à+z|C9í»¹‹Súc»ïV.àí»IŽé¾[õÊÞ¾[]âõÄÙw#Ü’á¾1ߎg}è~L¤íWÌú ÙUÀíƒ4§QÖ{˜÷A²«€ÛÉf=ñú b¿"ë±Óá¾"ôøFr}éèKÇÁqDpÇëñ¼…xß…øå ñ~ ñ+âýâW.Äÿ¸°cpÇGã8-ÌyÆyM•ä Áñ¶°oɈãtËM8¯©B X…óš*nÂyMEp©p^S…]ô ç5UTV^Çsàù?¯•J¶qú¾¨ÀÖŒâùøåm¬ ÇÈÇû•^’XÍǯ(#ZÂ+‰Õ|¼á%‰Õ|üÊ2Jk% "‚ãz&Vóñe”Y=dzä¸#l8&˜;Õ†c±¹sL0›¤sL0 Œ·±sL0›¤sÜaæocç¸ÃÜ’Véw„¿­8žÇå©ê!Ëqòe—a¦ÎÛÄq™ãs\o½_™ãrLz\渓—9ï¯EYèq™#8nÒã2Gpœ \ËËñ„Ÿ³Ãã¸+ðû2ž™LK=@3¬gzÜý±£ÇÆ=vüñ@Áý±Ñãꌹ?èqøãÃqhǃÁ8nîb?Ž?eøý±Ã±ñÇÐvÂñÇÄqèÐãsâXù L}…õÇıò0¾S_Aþxä+Xâø ëG¾‚-ÿ_a’™¯|ŽzŒ_1áX­óà$WZçáÊë<“Ìôxç:/8>gŽi¡Ç`žî»yþ˜ï¹‘B—Ýw3W—Üw«ÎxºïÖýñ®}·ðgÆ1ï~ ÑŒç Ž'}¶_!Û ²’ ¸têƒdWy„ö+f}Xç3ÇÑ—Ž¾tpGÇÁqp¼Güª…xþB¬ý ñ«âƒâƒâ×,ć,Ä " Žƒãàx_Ž‘ ]n™ nºÃ.ž¿ò$'çuÛhɲ„óš*6ä8(K8¯©`HŽSAŒ\⼦»¨HŽSAeÈIzlŽQáí·³e§Ï‡Âøù¼´q¼ûIÎÄj>>°5K«ùøÕe„I²#TF´äƒX’XÍÇ—&ÉzŒ|üš2JÑô•Ù´Ì©n×ʇ•9\†¥:oz\æ\c²ŽËœ×¢Ì+QæŽuå7”9‚ã\à—9–cVYyÚså½=W>Éý9eŽå¸Ú l—9–ã~u¶zìrLäV–Ç­¢õFÁ}Ň1ž›FWÜõX^¹´+²=&¼S1ÕãêŒôøi zÌžö\yoÏ•Orê1å=>kŽI—‰ãVQÿZ8Éžõ¸s !Ãè•Ër !Ì>ÇF¯Àñ_ÑzƱyÚså½=W>ÉÝḠ4UŽ›@Sr3}Å„cå+`|˜¯˜øcå+à$Wò˜ú ëG¾S_a9>ˆ¯À VÞÛså“Üg¾‚8¾Ý¾b‡Ãè1Œ¯¯ó.Íñh‡+¯óp =ÞÓW õØr¼ðIî;}Åsnª¯ µïæúc¹÷Ã1-ôÀ*ûí»Aì¹q…¾ä¾›¹ºä¾[¡Ó}7òÇ;÷Ýž¶°ïær<~oÏ•OrþXí»%Ǭ ¢û!Äqßr«û½Òö+D÷]ðÊž}˜>®ÖÁú ÙU`ÞiûÓ>deåiÏ•÷ö\ù$w·¢÷+¼>È à8úÒÑ—Žç+‚ãà88Žƒã«plã… ñâ7-Äâ#â7/ÄG-ÄG/ÄoYˆYˆ]ˆ-ÄÇ-ÄÇ/Ä',ij"8Žƒã#qœþ…¯åÂpŒ<;9NsxÂyM›@r¼UZò‘,á¼nsL¢8Þnµä£XÂyÝæH‚KóºÍéµ¢8ÞæPÆ^9¯ÛÊX…óºU(c¯œ×meŽ·9”õªàx›CK8¯©À.zé|8F> ±òì£ÂÛ9ngçù׳%‰Õ|<¡Œ0IÖcäã#ÛX°$±šßÜF`ãùø¨6Ö,I¬æã£Û(0.g\Áq=«ùø˜6¶Œ‰Õ||l[†^I¬æãEml;³ùø¸6¶ œcäããÛØ2pŽ‘OhcËГ ,ˆ\HŽKéŒ9]Œ9Fæ!ÇhcK:dzJ8ÇcwçØÇx;ÇcwçØÇx;ÇcŽwç£9nxwŽñ±šc|¬æ/Ò7¼;Ç Ü!ÇøøÝãvs &ÃgÊqYðstûE¾“8.sª›€ä¸ÞúMeN5ç˜ô¸Ìá2l9.sæ—9sŽËÎ1é1q\æŽe%q\æŽss\æXŽ?†q\æXŽYåEeŽå˜ñüqeŽå¸”|™c9&£‘8.s,Ç,yV™#8ÎÆö¹ê1çÅ+3=®,=†ÑcpŽÉw`ϵÜüq×cô@ hA¶Ôcí==þ¾s 1ÐcòÇšc¦Çä‰ã滓?sÌ/z‘ÐcòÇ®Yè1ùc0œ!õ˜ü±æØêq¹:kŽI—%Ç/4ÿƽ9ûãÎqÅšq …¯Çqs=±Ck±Ã±t>ÇPvÂ㸹‹&ÐÇÍ]4¦ 㸹 ž…?Fs€Xæ Ž›»hM¶™qÜœDhJFç¤*õMðÄ1ù ‡ãzOû xþ˜|ÅÈÃø ,ø 8‰öðü1´‹øh*|…˱òXð0¾ÆWp«LþXù _ã+ õØñXóþøùŠz £Çß=Öy0ë<,¬ó\ŽÕ: ë<8‰^çíÐã1Çjçr¬Öy.ÇjÇ9†ã+†«uöð7„c¹÷æûc¹÷Ã1°ç¾Û„ãɾ›åØî»Á®ñ̾›Ä—$Zì»I|áî»YŽí¾Ûå8v|…Ï18ƾÓ¾ÛûcÞñ°ýÈ>HÛ¯è}Z‘ÝðåÝZ¦‚…>H3=±},ôAšÁ(ë=¸}f0h¿Âéƒ@¯óœ>ô:¯UTd°Îm˜ýŠ¬Ç¢ w¿B¶Aë<Û9ßýŠèKG_:ž¯Žƒãà88ŽñO\ˆßº¿m!>i!~ûBüŽ…øä…ø ñ) ñâ…øÔ…ø´…øô…ø] ñ»â™ ÇÁñ‘8N«Sñ:àyêUN\pÓ-J 9N7ἦ %`Îkª¸ ç5UÁ¥ÂyMvÑ+œ×TQYy弦ŠÊÎk*¨¬¼r^SÅdšãtÇd)§;:Ë çuûö‹V’§‚F8—·cä#ã«ÎÎ1*¼cÏåL¬æC`\ÏVO¬æã“Ê/I¬æã·—-á•Äj>~Gá%‰Õ||r%ȵ’‘ Áq=«ùø”2ʬž‰Õ|¼¸Œ2CÓcäãSË(³z&fóñieÔYåùøô2ê¬rŒ|ü®2ª¬$X0rÇÈgB5Ï,£¹”ΘcÐŘcèÄámÄoÓÌ&éÌãmìÌ&éw˜9ÆÛØ9î0·¤U:ÇáÆo«tŽ;Âa¼XsÜnü¶Jç¸#<æ¸#<æ¸#<æXbìsÜa>[ŽKW£Û/ê­ßZæTÁ1éq™ÓL\ŽË.Öã2‡ë±å¸Ìqõ˜8.s„· q\æ=ÎÎq™cõ˜]¼¸Ì±zübÆq™cõ˜U>­Ì±z\íÅvëÓË«ÇÕ^`ã¸Ì±zÌ’ß]æŽs±ýÌ2GpÜ*­v®zü‰FÁô¸²Ì9&,õ‚ãæ ØOb<ô˜ü1š?¶zlý±Õcåáé1ùãʲ§ÇÌkŽ™ð’?¦—æIx»?†tÌÌWtW áš¹¯è®˜¿@ø æŠÇL9ÐàôçÌ1éò.Ž}=žq\M²ãg“C Lý±Ã±ôÇ>ÇÙç˜1hÅ1¹bè*ÓŽ?&žøch‡ã¦ÇB þ¸ tMÇ\rËM©g7]¾9¾ÌW`앯€ñ`¾bâÉW( ÏWLü1´‹ød*Y_aðu|…òÇ®¯Pþ˜Uº¯Pþ˜Uº¯Pþ˜|˜¯Pþ˜sL¾Bù㉯«¾¾â¬õØrŒ=¶ë<8¾Bëñ%Öy¸ò:c=žpˆYç9}î+²;}½_áõA Çb›î~…ÓÉ®âÌ9޾tô¥ãùŠà88Žƒãàø°ñ ñ?-Äÿ¼¿g!~ïBü/ ñûâ÷/Äg.Äÿº/YˆÏZˆÿm!>{!>g!]ˆà88ŽÄqZŠ×šhf‘ ^åÌ7Ýበîöå-qœnñdNpš3ãy†§{ìÕ'7Ýk¯qœn±WŸÜto'ÇiNϧ9,ἦ»è%Îëö_¡ÎÎk*¨¤dçÃ1ò‘©UgK2¹ƒxnzŒ|ŒëÙ’Äj>Æ<ÁÆ1ò!0®gK«ùð1.gŽë™X͇ÄXž‰Õ|HŒÙ™õùË31›‰ñKDmãô³ 1þ,VÛ8F>$Æõ¬ID.$Ç¥”PÍÇç´QbœÏ„j>-#DR³3æí¢%ljË1ÚØ‡cÈÇhcKf£Ž3ŽQ“ÇU“§ã3ws\5ùš8“áÇƆc"·#|n—:qüŒcÒã2‡L„Ëq™SMàr\æ47—ã2§špî—9ÕDÈ„8.sª‰è‰à¸Ì©&¢Ù‰ßÏ^ÇeN3 Ž?“q\æTÑì„äù%eN5ÕN Û‹­òYeN5ÕN Û l—9ÕCT;tòeŽÐã\`lN™#8Œ2>Zæ<ªE™Á|¶z,8& n" 9nþXê1ô8_  ­ƒù㑃%#=9ã±ÃãXé1ÇÍK=ç˜ü±Ôc¼ä%JŸaôŒãJ·Ñcxw_ñÙŒçÏç˜é±ðÇL£=æFùL9&]¶z,9–z\ë—åô˜c£ÇWàø÷Í9î=æ˜ t©8K]†Ëq×å1Ç] ¾L—[âøcºjJÍ8–î]—=Žé¬J}|ùcòøc__Áðú xþXù °dä+\Ž•¯À‚¯€ñ0¾‚ë1ùcå+`|Œ¯0zìø L}EÇWúcë+Ð|¦¾Bùã›á+ì:oÂ1‹=6ø:zlðuôZ†=Æ‚cÁWÀñZa|…]çÁø =†ï+„cÁWL8Vë<¹{1ô7‰ãɾùc¹÷f9¦…ž¿ïV5™ql÷ݪ&O÷ݪ&³Äî»I[ wß­j2ãØî» +1ãXè±õU“5Ç0cî«&Ïý1äZo7Çp9í»gºbû m¿¢÷A`ú ÙU`ÞÉ®lãÂéƒdWy$› ðÄöAÚ6EOl$» Á±íƒdWÁ9nNY6?0Xçñæ^b|EÖcÞü€ÐcÚ¯Í ôØöAÌ~ÅçØmŠü*8žõAΗãèKG_:ž¯Žƒãà88Ž/]ˆÏ]ˆ—-Äÿ¾Ÿ·ÿÇBü…øƒ ñ‡âóâ â â/ÄYˆ/Zˆg,DpÇGâ8­NÅë–ÀrŒ<õ*'šYä!ÅËjâpŒ<õ*'šYÈ+Àá"IsŽQFðWÍ,Êz…åeÕÌ¢ŒÀ„c”`Í,Ê|aO†ð¢Œ€ä84¹ÄyÝþK[ÒK’ãT ¬t>#™Zu¶z&’cÏåÌäBr u‘É…äÄs93¹pŒrf`1พ™\(Ž!ÏL.Ç`gÖc4x;Çg&Šc|¾¨er¡8Æ°ÚÆ1¼cÈ$‹ÇÈgB5¤Œ äRJ¨æã‹ÚX°,¡šg”-y+1ÇhxécèÄáX`¼ÇÐÉŒcÔqÆ1j2ã¸jò”cüÁÝWM>cŽ;ÌcŽƬÆ9&˜ñŒså¸tÕ_Êä×r\æTŸã2‡Ü„Ëq™SM„ÇeN5(\+Äq™SMŠׄ8.sª‰@çø0ŽË2èæ¢s\æTÎ1ãùóËœj&Ð9n•”A™SÍ:Ǭò…eNõèø2—ñ‡Ëœê!zÒ9N¾¢ÌzL•Jõ•9œc¥ÇÉW”9ŽÏ×WHŽ›?3Ÿ‹¹W–·¤’mõ¸²Ìü±ÕãÊ2c«Ç•e=®,{“ðV–Ç̓‰ð‚à˜ü1˜>ÇU¡»W–Ç­Òõ¸²ìq¬ô¸]sÜ}…ôÇL£¹[\9öô˜üñ¹rLºÜ9®—ŠãÏ]à8_A ñ¯Ç’ãÏ[à˜Ëðˆã|Eísœ¯º@—Šâ8_–yÚ£ù èÇùŠÚø 4r…@cÂqNª@ý1º.3ŽÁµu݇Ç ¤ð|s|˜¯˜pL¾/Sø:¾c í">JÖWL8&_é]_éY¥û HÌ*ÝW@úcò`¾ÒsŽÉW@ú㉯à[_Á`VÃú <ñ¸i¾âjzü²ÝzíWLú b¿¢Ñ¬ú f¿Â郘ý §bö+šSæ-³_aû þ~EÖã¾å6ܯ`[nÃý fÛ6…Ù¯pú ÙUŽEd°Î³}œí:/úÒÑ—Žç+‚ãà88ŽƒãcÅ“â.Ä[ˆ—/Ä_ˆw^ˆ?±÷âÿ\ˆ‹…xl!þäB|ñB|ÉB|éB<}!‚ãà88>Çiq*^k"€M¥ö VÀ¦’x­‰6•^î°©Ôó^À¦’LÊ«6•d‚œ`SE&åU›J*±§[*ÙrÉqºå%šYÈ$ÍÙÉqš#9N•”Œóºý4dò¥9ἦ‚JJv>#™Zu¶$±šL-;y!±šL­:[’XÍG¦ö唊B]pŽë™XÍG¦¶ž”Ô3±šLm=)AÓcä#S[OJꙘÍG¦·ž=i#…ØšRÒ8F>2´õÔI#Ž‘Ï„j>ç3¡š/)#DR³„j>¾´<ÉÇÈÇÓË‘ÔìŒ9F»h‰Ã±ÀxŽÑ.Z2ãuœqŒšÌ8®š<åb7ÇU“o8ÇDnGØpì`l8&r;ÂçÆqéªÇOö8.s·Çeq¬u9s\æ0n_þòV!ŽËœn%Š›€à¸Ì!ŽyB—9Ä1Oˆã2‡8IÓã2‡8I½uQæÇ"ÉwÇeqì%²Ì©…Ü–Tª¿¸Ì©B%ð/)s&zœ|E™3á8ùŠ2gÂñùúŠ'/èq~©dô˜ûã‘s1éqwÆc=v9Vz,9nþXê±Jª?–z,“r¡õ˜'E›­»“W–YB+=†H*Ç]\9fzL@w£\×y»ôø9&]žpLº\Gc©Ëp9îº<æ˜tc_AºÜÃ1é2K ÍcÒe)Г.w6“.÷Äø Òe“(Ž»Ë@Ñeìà¸éòÌ7¥f7wÁ’ÂóŒã¦Ô·ÂW`ê+¬?ù L}…åø ¾S_Aþx§¯ÀÔWÇ{ù ËñÈW€'ÆWŒü±õrÕw‡|ÅP'[_U_ñÇê+´O8žø ¬úŠ‹Kù ŸcÒcrÄûŠ Çjçr|#}½ìÜwãöÝþØÂ¾ÛËÉeŒ÷ݸ?í»‰d°ï¶/Ç£}7Ã1®ËWMf‰ãÇë¼½8í»gºâôAÐÛ!Ã>ˆ^çy}î+È3«>ˆÞ¯ðú °‰éƒhŽ›Smá:;aÇWd=fmÑ~oƒ 9î[nÃý ÑóÀƒ'Äñ¬ÝÏk£'Äñ¬r¾G_:úÒñ|EpÇÁqp±Bÿ—-Äÿµj!¾|!þôBü™…øŠ…øÊ…xöBL~ÁqpGXŽO{ `S©çµÅqªô¼°©ÔóÇiNÏ{"€MszÞ+šY¨K`ÀqºÃ3Îk*PVâ¼n·ZÒKÁññ9Þý´g]pŽÑô•ÙòÎq=3°èèÂåÄ,$¾Œc4fó Ç¥’Éňcä3¡šñW´RɪùøÊ2¢%_ÉJ Õ|<»5Ë‚ãëã¸ugãËvsŒšÜŽ;†c‚_9äX`ÌjÁñÑ9^xÚóËÊœn%š›à—9ÝJ7¡ÇeŽ@7P“ôúåeŽ@7Г?]æ0+‘Í:ÇÉW”9ÕC Û*•ê¯(sª‡¬'_Qæp=V'_QæL8_qj=öžöÔzÜñ–4,õ¸;c0aÖzÜ^€žh=&%æ“W–…0WŽ•CøãÊ1S`â™üqå˜é±:8¾ŽÇO{vŽI—IœÛeç˜tcLº,ÅYpLºLâ ã+H—Iœ[Eqüg8ÇM—=Žé¬íøcRç¦ÔŒãF.qÜ”:8>2Ç O{:¾ÒÃó0þØúŠ~Qï9¾Ârl}¤?æ¾¢s̬†ë+`ü1÷Êcì+z|z<áxà+Ç_1[çqޱÃWøz<áØê1ƾ‚ðµÖyð}Ep|^c²ï&ýñœcLöݸ?nÉý13Ê.Ç€·Îs|ÅÇjß-8>>Ç»Ÿömµ_Ñ,³hƒèý §âîWd=þrÓú0û¼ 2Ú¯=¾_!õ˜w?0XçÙ>Ì:O´AT$8>5ÇÑ—Ž¾tpÇÁqp¼o|ÕB|õB¼b!þìBœÙ+Ï:¿r!^µ_³_»_·n!þüB^]F´äÕ‚cäãëÛÈâùø†6Ö‚cäãÛèrŒÆ,„Ÿ5Ç»?É=±šL­:[=±šL­:[’ ÍGÆWcä£+ÏÎ1ò‘¡UgË2¹C]%Tó!0~^+•ì´ÌcÁñcƱÀX€Ü9Æ7îæXb|Ã86ŸðçpŒvorŒvчcÐŘcÐŘãŽð˜cCµÃqGø”—n8×cp£ã2Çç¸Þú†2G€KWõÖ7–9‚ãViÉ_(s¸¹€šà 9^ø$÷o*s¾‰É¯Ôäã2‡8þ‹ž—9]Òãzë/—9]Òã|'q\ætt'_Qæ›°'_QæTXŽOî+^MÈxÇWt¶‚¬Ç¼ãaû!P}ñ:¯oNÔý òÅcÞý@£Ï»FŽ£/}uŽ£/ÏWÇÁqpߎ_³¯]ˆo^ˆoYˆ¿¶}!Îì?ºc!þæB|ëB|ÛB|ûBüª…8ø38ŽïÇÛ:³_ŽÓJ0àx»eÅñv«%ßÂÎë6G\ œ×mN¿¨•sç8ýÛ+XA›Jí•'ØTi¯`l*µWŒ8N·Ú+zQrœnµW°ŠfyÚå–†cäC`¬®´ùxmÑ©ÇÈÇ7·Q'Ø8F>¾¥5K«ùøkm—3‹N®à¸žçÇ1ò‘©e'/$Vó‘©e'²#™ZvòB‚6_vJ=F> ±ýü6Á1ò‘¡e'¯dr!9ngË®‰cCuç˜`Æk‡ûocçX`,ðîûocçXbÌñ¾ Œ·ÑáXcìq,0ÞF‡c|ënŽ%Æ>Çøö݃ óé8.=ðæ& 8Î…Äq™ÃõÜhl·¾¹Ìq9&=.s8ÇVËÁq«—9‚ã\8g=.gÆ­Ãq™Ã¹µ—9ŒÛÎ1éq™C 9®·¾­Ì!Ž…ç;‰ã2‡¨ývÃqòeNõp8¾_‘¯8оâµÄ³ðÇzL@Üõxì»  î»s qÖþ`4<ÒãüRÉùch€aý14ÀþFóK%…e£ÇùE‘ݸ²|Fƒ€úcH“,8nî‚ëÁ%xà!Ý< (;qü1ér=ŽI—ëØ“Î1ér=Lº\GcÒå:6®9ǤËuìűÐã¦Ô×á+¾còÊ£ù _'Ѿž?V¾ÍW°åŸöp’ã+°à+Г¡¯óÇ#_†6ƾ‚s<òð|þøÔzlðuôØr ÇW 9Vë<,¬óà$zw#8ö|ÅP'{¾BsìùŠoå ¾¯êñ„ã‰_#ÇVáû Á1æ;¾â[|…À¾¯øâí»qŽGûnûr<Úw3cc\ŠcØ}·“÷AêîD߸ ý §Òw0¶W¼VµA ÛÁB¤oSl¯pû Í`ôýŠÐAo‡ û èípË,Ú ½2ìƒ@nSÔ~¾Uî°õm ðýŠo×»Ì)Ç}Ë­îW°>¢/}éèKÇÁqpÇgÎñã ñ ñº…ø[ ñ·âï,Äw.Ä ý+Ï:êmŸ¼/]ˆÏXˆO\ˆ.ÄSâ¢Ç·’cä!«¸àns§;”€U8¯Û-/á¼n•–ð çu«H‚K…óºUúE¯¬<í¹òÞž‡ú$wlúsÄkM°©$^·ŠãT¯5À¦’xõ8N÷Äk¹§{âµf§áÞÎ1Ù Ú|Œ#¯+#ZÂ+‰Õ|ü­6:Ib5»5«$VóñwÚ(0nzŒÊìwBˆ0zeåiÏ•÷ö<Ô'¹'Vó‘©UgK«ùÈÔª³Õ«ùÈÔª³% Ú|d|ÕÙ9F> ±òì#Zu¶ìš8Æã»9Æw 9&˜ÆÛØ9bÌ83¼9ÇCŒÇãŽ7çxü´çÊ{{ê“ÜŽÑ.ZâpŒv—9F»h‰Ã1èbÌ1èbÌqGø”—n8¹ Áq­|G™ÓÜǵòº2‡ë1^§õ¸Ì™s\æpŽI‰ã2Gp¬+ßYæŽss¼ð´çÊ{{ê“ÜŸ\þâøÉÇeÎK™üZŽËâø3ǤÇeG·_Ô[/,s8ºý"ßI—9]Ãñ©}EeyK*Ù­¢õ8_  î+^G<Üõ˜€V ×cíAþ¸ë±ðÇè®Çh WVžö\yoÏC}’»Öã'/è±ä¸ùc(€Çä¡Öz £Çœc¯lôøL8ÎWÃçÂ]ÀóÇFÇÍ]˜„q ­Å­Â8–î½Â8†°è••§=WÞÛóPŸäÞ%]žpLºÜ9®—cÒe«ÇÄ1éò„cÒeɱð¤ËÄq«œÖWàq…oçXù ¢½Ò}…òÇ,ѾN¢}œDû 4_Á–ÚWÀIVžö\yoÏC}’ûa|¦¾‚üñN_©¯ ŽÏÓWø“£û • ×y¯ó\ŽÕ:ÏåX­óà$zçr¼ð´çÊ{{ê“ܯî+´O8žø ¬úŠž­¯€á†cŽáûŠ×}Å>«uÞ˜c,pŒŽáûŠá{{ê“ÜŽwî»éý·Ç;÷ÝôþÛ'ÂåXî½ùþXí»¼Òö+×;ºû¶qÑ*^ã>H3&QmÙéƒ4ƒQVwèÕ߯h••§=WÞÛóPŸä.Ú ºâôAàôCtºâôAàôCõ˜w:ǨðvŽaôùóÇÈÇw•-ù.–$Vóñ÷Ê“d=F>þ~%ÈhzŒÊl!8FÓcTfóÁq=3° rásŒÆ,8½’cäãm ŽÏc¼~7Ç Ç³Æ˜qL0«„s Ž®È;Çæ–à»5Çæ–´Jç_—ch|ŽÇÇç¸tÌÉMŽë­¿[æ47Î1éq™ÃõØr\æ¸zL—9B[…8.s„çç¸Ìzœ \ˡǹ€š¤×ï+s„çzòŠ2GpÜ*ÅhǧÓãÊò– £ÇùÊøã®ÀÄs%ä»ðj òÇä+¤?Îåæ;°ŒçïF.7LÂû=ŒçïðÇä+¾—ñü½`ÂÌ|Å÷1ž¿è ×c᩟˜ã|Å.‡ƒiñÀ“:·dæÍC¼•«L;þ˜®ÖŽ?Æ‚?6ÃñÇtë+¤»`¶9à;2Çä+ ý1š¯€ñ趸ûcòʳ¤ûŠ‘?ó æZj•î+ m1Kº¯€¶ÅÝ“¯ |Õå^÷ßαòh¾"8¾V=ÖIÖà»{Ç9޹΃·Î3‰³Î³ÛuÖy&qÖy3=Ö2_Ç0Ãp,ôxæ+ÆÖy®¯¨k=cé+àr,}|Г‘¯ùce‹ÃŸŒc±;Ñí«èîØÆÅ¬Òw0ú ÷A²«@߸ðú m¿â»1îƒÐ~Ť"ö+²Û>ˆÙ¯xÅp›"ö+NÏqô¥£/ÇÁqpG\‰coXˆ°ÿp!þÑB|ÿBüß ñâŸ,Ä,ÄW/DàÇÁñ¾#)ÞP“ÇÛ–¸à¦;”`ÇÛœ ÇÈCŠ ÇÈ0ã £'œ×ô';Cp¼ý [ªÁñÑ9F…·s u‘X͇ÀX%‰Õ|ŒëÙê‰Õ|ø7=FeÖáMQ™u8®g»8cvÄ1òñe„Ï1òñÕm¬ ‚ãëäX`¼Çcc´ÿp7ÇøG»9Æ÷ïæ59<Çæ1Çcq8™ãòh¹ AuÓã2§™¸—9ÍD€ë1q\æ4!â¸Ìi&¢» Îq™SMDO„—9™\ždŽk埔9XždŽkòeŽÐã^Ù^ÇeŽRà†öv'8>W–·Äøc©ÇùŠ€nþXê1˜?ùc©Ç` ÈK=9ã-iþXê1Ès,õ¸]½¢õ¸]%ÌWHL2-õXûãÊrp|ZŽó¸Q†Ï1TâqLg¥yÊqQç!Ç9)D9žùcºZà˜'CÌÊŒcp-3ã‘9&_éY¢}QÍí+`ü1Œ¯KPÖ¾Bp\í+8Ç䕯׊ö.Çä+̵D+_' O¯Ç†cÖàÛ9&` ¾cÒct;¡æ$¬¿Öcƒ¯³ÎƒÑc=Æ‚C˰§ÇÁñyp αõU“ws ˜užá˜dcŽXŽ!ü±ñd‹M¢8Æ ¹Öóý±ë+Ÿ€ã¾;Q÷+Þ€q$» °‹–ÈîÄ:¯í`è¦t"û m›B&²Ò·)Zbû b›‚'|§MmSôý §R·)ú~…lƒÈ>HìWœšãèKG_:8Žƒãà88ޏÇ6~p!þéBüÐBüðBü? ñÿ.Ä?[ˆ¾ÿb!¾j!õkþ— ñÆ…xÓBüÈBü«…xl!‚ãà88¶#)~°'šYä¨W9ÓÌB^¥°C$ÛË1D’¦8Ã$–c8ÉnŠ!8Nß]%ùBp¼ýWÈ$_ãÒŽAMG§[íµf–ãt§¾ ¦–ãt£½ÖÌpœoµ×œy£½<¹:ǨðvŽ! Þ8F…·s â¹é1¼p ÎÀbÀ1šƒÁ«8®g»8cvÄ1ò1çùøe„Hˆcäã«ÚÈ“£q\if§Ã1ØÙFÍ1Ø9Òc°³Šc°‘'’c¢·!]“#p¬1ö8†Læã‡vsŒÞÍ1jr=ÌjÃñããq܆Ç1á;ámœpLø.s ßW ê1-¹2Çå2.Çe™hMÞ8.sÈD &à—9d"Àâ¸Ì©&E‚)©z\ædr·Wè$½þ³2'»½ÂIþy™“u“ã2Ç×ã|'q\æøç;‡ä8› 0Ž=Î&Bj±ÄøÅF-Æ›¤­xSqB‹޳‡€âÒos*µÇâ˜È­,oI%äÁM¸?ùã¦Ç•e&Æ Ü€­,‹¤ùã&¼•e–tÜô¸²Ì’&Ì]+ËN¢õ,©,s_aüqeYè1ÍÉÆ9_UþL‹ábüÆ¢¾þøMÒ]^Áýq8ÇhØ–Í*sŽÁŒñi8ÎWP‰âXêq­+ŽHp\ëŠã.Ã(,o‰â˜Û Ç£Ù hƒ¡8î.ƒ%;9Þáa}Es=9´¯p86zÜ9fzŒ78–ÞØçáªÐ†cfëOÁ1ù HÌ’î+0ðÇ`¾¢sÜJ­Ò}…ÃqMº¯°“?&_áp\ïu_1áXù °„8&_aý1q¬|œä(ÃŒŠcf‹ñÆÇøº+;ás<^ç]“Ž­[Ž­›dªÇ?¼[í:ÏrŒKé1´ ;z<áX­óŽÊñ¿Dw#ŽßØ6ÙðÆ7j“,ýñœã·>ÇÿJî±ùû ]¶“úã9ǰëˆìçQÓƒ„YµAªã±úZ¤ùà}èKߨ¾´qN_ÎÂÎ×cLûÒf—ÍéKÃî³™¾4D'/ž¯Žƒãàø–<'(á<'¤Ÿ¦ðžÒ.Â{NH?Má='¤]„÷œØBï8_o¬Ðÿ£ ñÿ-Ä›âÇâÕ q¨ŸÏ/ÄO,Ä[â'â­ ñ¯bòŸÇÁñ™pì=í©™…¼ޱÀ1Þl+.¸é[° ÁqºE zõו~Ë\ŽëœšŽ8Þ¦´W 8ÞnÉDs\¿WM}Žëœvu«9Þý´g&ŽQÎL.vq ¯Ï1ò!0þ1^Ú8F>^]F´äÕÇåX&p9|Ž!^ár “ ôìé1Ôw”cüÐnŽQÇ#r 6*Ž f‰ñõs …õOXŒg1þɯ䯷[žöüÑ2§š ¦JÕã2'#¼½¢&TIɛ˜ ìöŠšðÊ•9ÕMÔ¤aœ ‰ã2‡ë1¸ÑÀA9Î&¸ÜNH_‘Í6pÉV=Î&\‰ñ[ª‰Àœãl&0ç8› i'pW|ÅøiϮǕe!ÆÍ7=®äaÓãJ.æVÑz ækÎ}Å«‰çJv¹: ÇhÔŒ_ˆu^'—2ãѨÅ[˜-Pj‘Ïj‹aü18´ì‚s î† ã»Ä±û´§âøG¡¼±ôÇÐøŽ8~³áøÍ>ÇÙõǤÎ8´¯€ÄcƱôÆð9†k*$Ç-£äŠãÖ¯óžöì¾¢sÜJäÉWtŽ{©$ÝWtŽ[‰8V¾ÌÇä+”?>Çøñ][Ìô˜Æ[F7SŸœqŒ·îæÎÆÅÝÐc“8zlGMâè1Æzlðuôøÿ¸ÜdƒÅ¸ùã9Ç}·í-’hãç¿}—mÄñ¿ö7Ûî˜?6O{:þ¸eßWNâøc“(Ža||_!8>¸?ަâlSø¾Bp ÓQË»±¿õ­Ú]ˆ}·»·›ãÝO{ʦG߯뼾åÆ÷+xÂ7Øø~Od÷bGûN¤ï`c¿?ÎÚ½!"ôÔù ·Ýþ¨ŽÖc´î‡L„+ 6}n"î ÇÑ—ë1¦}iã"œ¾´·°óõÓ¾´qN_Ú"ÏWÇÁqp| žÒOSxÏ ÊF8Ï é§)¼ç„´‹ðž”pžR›'áø…ø©…8ÔonåYç³ÿÿBüÛ…xÍBê¿ým ñÓ ñ3 ñïâí ñïâ?,DpÇ’ãô¯J,ÇéÖI9öÞÛS3‹<õ*'šYä˜qŒ<¼â‚»ý5úÅ18Îÿ4·×œŽë­’8®sXb9®·j:áyØbÌ1ʘÂå¸~¯š–cäã2B$Ä1òñSeÄq9ÞýÞž™\HŽA<—3“ Éq;;ǨðvŽÛÙ9F>Æêêðƒ<±zŒ‚ôT‘¹ƒ>Ç_t¢=Ž ¾ê1D†£pL0wª ÇærlÞÛÓá:q8Fæ!ÇèÂ<äØP}lŽ1ö㟞rŒŸÙÍqGŽñöÝ##Œ¿›c(¬¯ÎqQz_ë­Ÿ*s„÷JIÆñÂ{{þ›2§šøz\æT7©ÇõÖ¿-sª›€ÔãZyM™ÓÜǹpH޳‡0øJ޳™€ÀØpœ=:Æi¶âgŠŸhÔBÁÜ8Î&]ƒAgÑtxÀq6ØÀ­¶âðwøcp_!ý1zrx=¿·gÞʲë›Wr?ÓãJ.óÇ­¢õ8_q q@Žáª0Ä:Ì7{¬9ÆÏt .ø‚—2Ç`âÛ|±öÇè¶˜á ±ÎëNtj r-;&ÇŒ±?>>Çî{{*Žwøc ü±áXè1¾™Ü×°ò‘8ÆÛvsLÙ„cž8ùc‰0Œß..‰6Éc2GôŽ?®÷º¯`0S©$‡÷ã÷Dì¾cL¾Ö×{ÝWÀøcâXù †ïQ9`|LŽ­?îƒö)ðöÇ%hÏâÄOÖy0zL›äˆzl9¶z¸?žbü¶Ž,vèq,‘zl×xÖWŽaú 0ûÇ=–Û‡ßw›ôA²«€ÛÉf=9Ç»ßÛ“·@Ú~ŤBû´Î«ÞiûÔiû¯Ñ»}ãâ8ûªý¡9þiê~´„7D˜ƒ!"z j„¸ë<ÙõPƒë1D¦»U™Þ‰¾ôÙô¥á)°«Ç˜ö¥ú:}iã"œ¾4ÞîY O1íKÏWÇÁqp|îÏ ©‡)Üç„”‰pŸÒ.Â{NP6ÂyNH?Má='¤]„÷œ lÄ9þ ñ³ ñŸâPô¯¼³íâõ ñøBŠZ,ÄÏ-Ä;â?/ÄÏ/Ä/,ÄYˆ™°ÇÁñæ8ý¼F§[nÂyMEp©êiÏ•Or×Ì")ÞP‡cä!Å„cä!«\Çc°;­uÎ.ŽaË1ÊXbÀ1êË>ÇùV{ÍÙÁ8F>ÆÿQ–«ùøÙ2ÂK«ùøOe” ×Ê¡žö\ù$÷L.$ÇP™\HŽaôÞÎ1ÙשÇXÐã]:±Wv1Óã /Ç`#OŽÂ1ب8&˜MÒ9î0sŒ·ñPO{®|’»Ã±ÀxŽñúÝãñóá ¾;|š c¬ÇøùÝãöä‡ôåÇSÝDMƵò³eÎ\ˡǺr¨§=W>Éý e¹ AuÓã2‡Ü„à¸Þz¼Ì!7!8®•ÓrœGL0þ¹â'ˆßÇÛœ \4;ë+²‡ÀÏ×£ð,lÅϱQ‹_sœ=„ƇõÇRaü1¸¯˜øcòÒƒûãÃ<í¹òIî]+Ë[büq#·²¼%ƒéqeyK*Ù­rRŽÁø¬û ¹SŒßQÅ܃I0˜½àþÜ÷‘äB¬Á82ÇÒãjþ þøj»Ÿä®8ÎWàF†ã|Å.=Žó„Á¸FŽGþøx›Üá˜Úå˜Ã‹ãù p\+ÝWLü1ù s-‘?>ÐÓž+ŸäÞ}¤?fI÷þÍW€ù <®ð½ã'çØø 8k¼£­ó_“\rg’C=í¹òI=Ö‰§Ç×Êñϩݷ±?žs\üñŽ}7f†GÿÛdqü_hM`|DŽí-pi_QñÔW\êiÏ•OrwýñÜWÀpŒ?æ_‹?îõxŠñ;²S=–ûǾ[vÅþ±ÝhÃá9æÝÈu^­\¶’]æ}Ë<í¹òIî¼R÷+Þ€q¤íW¼^ï`𠶺_ñ¸ÞÁ8½ƒ!ãý Ì9ÎzŒÿ\_·Œ©Çí¸ûhÝÊd?϶?T$úÒw­/Ɇ›ÔcLûÒpX÷¥{ÆbÿØì²9}iÏSÄóÁqpßøç„v?.Ä}Å;vúŠÙsBÚExÏ é§)¼ç„´‹8<Ç6þëBü·…øÅ…XyÖùÁˆ»ÁqDp,#ý³À“9ÁiÎ%8Æà½=ã_ŽcäC`\Ï–$Vóác\Î_üEäcÎñî÷öŒ_dp| ŽÑÆ–Ì8F÷ãxüÞžñ‹ Ž/ÅqYW¸z\æT¡“ªÇeΆë0YyoÏøEÇ—âXé1\,õ"iþXê1œdå½=ãŠã]þÊ{þxÇÃ÷öŒ_dp|)Ž•¯€ç•¯KÈ+_'YyoÏøEǗ☀5ø:ë¼F­Mº£É°IVÞÛ3~‘Áñ8ÆÜWTMf‰õU“då½=ã_ŽcÞýÀ`Ç›øoà‰íƒd'YyoÏøEÇWä8úÒÁqpÇWâøxô¯<ë¿¶ˆà8"8>Çj—cå½=ã×q½ï~Úså½=ã×qf›.àÊ{{Ư-âZ9^xÚså½=ã×q­/<í¹òÞžñk‹87Žwùcï½=ã×q­/<í¹òÞžñk‹¸VŽžö\yoÏøµEœǘûŠñ{{Ư-âz9Þý´çÊ{{Ư-âL8޾tDpGÇÁqDDpGÇÁqDDpGÇÁqDDpGÇÁqDDpq¹øï+Æê¦Å¼ëIEND®B`‚urwid-2.6.16/docs/examples/pop_up.py000077700000000000000000000000001470350774000235042../../examples/pop_up.pyustar00rootroot00000000000000urwid-2.6.16/docs/examples/pop_up.py.xdotool000066400000000000000000000001131470350774000210530ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 39 18 key --window $RXVTWINDOWID space urwid-2.6.16/docs/examples/pop_up1.png000066400000000000000000000011501470350774000176030ustar00rootroot00000000000000‰PNG  IHDR_ KbKGDÿ‡Ì¿ pHYs  ÒÝ~ü IDATxÚíØmŠÂ0…Ñ»ÿ}v"ö+m…lBëyÄ3¾æI«teAÀ—¯øòå+¾|ùŠ/_ñåËW|ùò_¾|Å—¯øòå+¾|ùŠ/_¾âËW|ùò_¾|Å—/_ñå+¾|ùŠ/_¾âË—¯øò_¾|Å—/_ñåËW|ùŠ/_¾âË—¯øòå+¾|Å—/_ñåËW|ùò_¾âË—¯øòå+¾|ùŠ/_ñåËW|ùò_¾|Å—¯øòå+¾|ùŠ/_¾z®o¦ø^:žï5#W¾Ë&ÞîæfØžÉYýT.¢ó‚ûøß}ÇM¾lè„ïùóá”o^%ÓMGÝñðóaw}«Ú¿ß·Û,·ùÄßœ¿wYmû‘«û‡ù}÷äàþa~¬:þâóÅáxŸùÞÒ×÷gâË—¯øò_¾|Å—/_ñåËW|ùŠ/_¾âË—¯øòå+¾|Å—/_ñåËW|ùò_¾âË—¯øòå+¾|ùŠ/_ñåËW|ùò_¾âË—¯øòå+¾|ùŠ/_ñåËW|ùò_¾|Å—¯øòå+¾|ùŠ/_¾âËW|ùò_¾|Å—/_ñå+¾|ùŠ/_¾âË—¯øò_¾|Å—/_ñåËW|ùŠ/_¾âË—¯øòå+¾|UöŽ …_wbIEND®B`‚urwid-2.6.16/docs/examples/pop_up2.png000066400000000000000000000022131470350774000176050ustar00rootroot00000000000000‰PNG  IHDR_S ü@ PLTEåååÍÿÿÿæïšwbKGD Lò pHYs  ÒÝ~üIDATxÚ혱nÜ0 †íö²gHÞÇyƒ¹¥ïÕ¥²Pî)@?J×@¼4 •¤ì‹ïNwÉ]œ"mø88’ü™¦(ÊTU™L&“Éd2™L&“Éd2™L&“Éd2™L&“Éd2™L&“Éd2™L'k¥¿×UÝTU½ßÝœ ¾Ôßzö[è>GÍqðê\®²ÖM]]4õº¯¯êõNÿ¹.fß߬ªqôåÍjîÙæEà*ƒë«j}»¸Þ åòæbݼÜU³mqvÅ“'Óµïã%Âm¥QqýéâvŒŠËzç/÷­n}7° l`¿Q08´+Ñx1Í~Ýö5žß·ò`x¸¹Ü€!w` ÀDˆç€3û8ÜßÿØÐ~ö|ÑR¢6ºZÈGIý™”Â/BðC—îÛè‰É“'ãÀFÊ3|àw‰à;µ´ßI‹‚}è={.Øav_ ï³+PšP[Ø»= »÷›þlpŠjqRp›vÁmšƒeð3Áâ?þÑbnß±X¦-ƒ»Ä'€£8Vü‡$WêcÈ>Ö“¢úX'žËìfW O|ò‘ZžŸ4*|”`p[˜Çq#QÁ‘£ /ix ëÌ æÿÊÇT|Ë0ã¸Ý‰Û°O¹­ †Á;¡QÔL˜ iø£¬ŒžS¿—%ÈËlÚ†˜s·ðPE0út/ Kî*YÌ™s¤.ØVW0§~Í4›= Õ¤ÆCYœ%ø }hyuSîôÝCÉ’sœ“äp“Çd+o«`ÊI5_Kv 9?õîw éÌÙSÁ)í‚“x u»™ƒSŒs‹ùr“ÅnÏbÝ>ÔfI›n²¸àcyqà}hôq`‹ÙÅ£s6°´ð?êcÊëS¹¥ ûK&„!´aŠ Ùm¢FE/q{ÝDHœ"d»‹ƒdT~Tz8¾¤q?L©Ù§f7<¼Üë€!¾ƒ´™g…\)½Mð<¬–”ïF”OÊ6, –ÈŸ”úḠxü¼“l9ôï,ߨi}¡p#M€A¢ñ5âø_Ê6° là·þ²þü:à¯ü·<˜|7öÓ±›ãÁöÁ>8÷-(vÀ¸SDùÞ§Òiì`,ÕÒÎÝÅ»V4¹D–²b L.W]Z7¶\›s]v¬B)|°d‹»@Zé'—í]ÇõÞtŽÇU»¯û‡C`-:Ýx4Á%¥¦ $‰+p<Ô–¨àG$9ÁÑj}ŒŠYúœRZÒ´uÏ‚¹bFX2»ÍÀm1° là¿6™L&“Éd2™–Ó„[Ì•Ï"IEND®B`‚urwid-2.6.16/docs/examples/real_browse.py000077700000000000000000000000001470350774000245052../../examples/browse.pyustar00rootroot00000000000000urwid-2.6.16/docs/examples/real_edit.py000077700000000000000000000000001470350774000235552../../examples/edit.pyustar00rootroot00000000000000urwid-2.6.16/docs/examples/subproc.py000077700000000000000000000000001470350774000240322../../examples/subproc.pyustar00rootroot00000000000000urwid-2.6.16/docs/examples/subproc.py.xdotool000066400000000000000000000001361470350774000212330ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 39 18 key --window $RXVTWINDOWID a n y t h i n g question urwid-2.6.16/docs/examples/subproc1.png000066400000000000000000000027551470350774000177720ustar00rootroot00000000000000‰PNG  IHDR_ KbKGDÿ‡Ì¿ pHYs  ÒÝ~ü‘IDATxÚíÚQvœ8ÐÚÿ>µ9'žnU%À&v&sßGÒ¡…$.B€:1ä¹Ä+Û(Oú&ÕGñÇùu>ÿÖqzÝ÷¹Aê%•9Ý‹ïú¾?~Ì×Áqæxmy—ÙöJ_×Îs…s§¶½Æ¡­}ÇþĦÂE[—*Üêa¯§Œ‹¾¯OÑœÙ8–™ FÚTžzÇÎõ¼QÆè>ä w݈ӶŠ2Eó5so¯Žß ¾£÷íÿ\ú®¬`íú–:§m]ò‘?9?¼‡@ºZ^[~“ïú«­?ý婇» k¾Ô/úîb<ë{atÜòyr»åÛÜ\¢ß˜Çï¸ë;ݾèÅü›*Hã7&гñ{Ù7ZßÓù·4§¾ë »ù7.οãuo?{~˜û±{"(î³Í–|w®ÆoúêÞóÃþë¶Š2‹ §Ç† Ïù4ÞLÜ*ÏVúeéŸò½õ‚ò¿ö««gQçÅ‹;KÿE_ëg?4¢…/_áË—¯ðåËWøòå+|ù _¾|…/_¾Â—/_áËWøòå+|ùò¾|ù _¾Â—/_áË—¯ðå+|ùò¾|ù _¾|…/_áË—¯ðåËWøòå+|ù _¾|…/_¾Â—/_áËWøòå+|ùò¾|ù _¾Â—/_áË—¯ðåËWøò¾|ù _¾|…/_¾Â—¯ðåËWøòå+|ùò¾|…/_¾Â—/_áË—¯ðå+|ùò¾|ù _¾|…/_áË—¯ðåËWøòå+|ù _¾|…/_¾Â—/_áËWøòå+|ùò¾|…/_¾Â—/_áË—¯ðå+|ùò¾|ù _¾|…/_áË—¯ðåËWøòå+|ù _¾|…/ß¿Ù7>òl­s}»&ö׿Â[ÓW[—÷}¯ËܯpŒº‰­­]5Q~Õ럽yLÍí¹]ÆûŽ·ˆúlEµ×ˆi¯˜úÕÉ. "Oͯnì´M\è|êj𮸖óÃzüvWÆè·ÄêbÌç ŽO w}ë뿺ÒjÐÍ=çÄ÷æüpvŸ]ÝÓ¯øã5Æ÷ùƸèy”•.·æß ÷Ùâ4JÍq=$¿_<´ä¾œù6œç‡1?l§§…~~X=?,^"¦oÎõ÷]MÝhßSæ»l÷v“Ë|©ó£¿”å©ðåËWøþ”ïûÞ7?¢O7Ê+ò¢_^\=$Lg;êç…Áq¬gq8ù X8ÍK—Œß|íAwšWü$;ª‡Ü©Ñˆòßya°YLWf,ÖÏ»§|óÀ‰‡y«µ¨ôÐ>·©7ÍÂàX/'¾ö»þvóÜìÙ.„\`m_GGõž7-AU¾Ç÷»8[&Žª ÕrW¤ó~õXú~aš˜6i_„KÞãšaä…Á49î7ʦ•Ʀñb}27Q.ùÎer監xjþùõ¬pØ+ªù¦ZÔÞ¯ÎìøÎòþÖ# 4QME绣xÆw÷ÂqÎ[,y\XåjV§ªûþië—–ÄŠ…ˆ¶ÛO¯´ëag#èißz˜>á;Ò5ù­¾‹Õ¿ÕšöZþª4Ú_Û⼕Åätú»YùãØ¢«¥Æ×æßz]ñt@”k†í–j­oL_¥VVï2Qþ/…ÜÄÉ(ºZiÖ¨n¦òÒ,ëK|íÐâØóßú¡«>qsSäaIÔô—øoÝ;†øª–C|ƒ.IâÖõ›|ʼn¿6yÉW2-ÿíæ+!ßfüu¦ÉW^å«GßÞ?äõHFiãK[±ÝS´gõý×?$”˳<ïìm|˜K=ÌÍôW›8®,ÃÀ·ç"j­§’ÃH{i}ËñýFñ2]^‚¯Süf|ùS—>™M¶ã{Ͻô³S÷/|òugW#ÎT±¹úä|ójáƒÉ”RŠ›rl¨ÞÕ'÷ˆ¿Ù<½¼^ ^c¾ñ¯”yïÀ7‰F‘rD—Yú¤´ïãBþ«}þ;QŸÜo‹:UŸÜvü ™ä¨ÙøaŽ>¹‹ÿ~2£ŸÁ·+ø¾ç¿ôIô3 ¾ð…/_øbð…/|1øÂ¾|á _ ¾ðÅà _øbð…/|1øÂ¾|á‹Á¾ðÅà _øbð…/|1øÂƒ/|á‹Á¾ðÅà _ø‚¾ðÅà _øbð…/|1øÂƒ/|á‹Á¾ðÅà _øbð…/_øÂƒ/|á‹Á¾ðÅà _ ¾ð…/_øÂƒ/|á‹Á¾|á _ ¾ð…/_øÂƒ/|1øÂ¾|á»8_ù±¹¹æù%E¤ÿúyâ+ƒìÐUå´î~šñ Uý"®²’lÄM|ä,u–›7Í;3Ý+ǯÝÈ‹­E–X²Ûè§ÎPÔ-BÊ’¤^Ÿ¿Þû1¾NUc¾bÓhëX˜w3/±˜’*‰¯’7cÛT³=gû±A%i#Ѫ\˜ýw¯ØlJ8âÝû¼,ë¿âåUð Ý÷ˆ¿AcQ)ÜMOʤD“¨&i¸¼’QíLœÕÑ=ë:$YTtñ^¾‘FÒ«¬,FÛ"ÄÄ_[ySUŒSØÛk}*w· »”ó Ê.¿[⥒%Y}Ä»ÙΈØÐ|T#¡]ÑQySUzøºñ¡î¿QËÐxÔ£½RŽ$Ü~×ꩆêrè ÏiðŒ­~¶Ö§÷ðuüUôs|E;ùŠõ2—ËPüíègÛì •‚ëj Êã•A‹­K‹oPùs3š¶Íh!޵ñCe‘iõÓªšj„ó”¼—f76Í£ÊkÜ”±Y_øÂƒï·øž}_>DÏ:Êž +úYa°6HÈî¶øÿ·Â –ùT.ÇáÔJ—Sü×^@xÑÑF0õ°#Yõ¹Y¡"îÿ­0è¦eJE?«œ¦³øZÇ‘Éx=-Ê ÚóÒÅÔ&µ.'çõÏnæEÏPéÀNGÕ›çe”Ç·œßIK&¯ žÜ%æ~›§U¾ÂDþØ$œ»xKÍP¬0h‚cz¹â>8ȔƠpGŸ´E¸’ožÆV>¸ŠYñWòŸzT(Î/Þx¢vªæV>À©öo1DG /h8•®bßdÂ-ÒÆëH*W Nyý~³ô.IÌ"ÂjÏÖB=¬åA³ùún:ƒ¯š6ùQ¾õ¯Ö@ÍYÕ§J>m“v)•àÔ|næ>«TÕ¥ñ,þúºbÓ!\Í0Üãi}š2¥Ôæ2â®R°E4–F8UõhLèß°w ¾ð…/ß/òµ|’›å"‘Í1ï›|ÿùþ ýë'» ñ¡Á7ÇT_?醸z:XŸª>ßGø¶ãÃÐú>øð½±~¾£|{×O*ñ÷V|è^?iÇ/¼çµ_ ¾ðÅà ß%ùÎÖ'í³ËpcõQÇú¤]éTÙX¼½ O6ß•®¼·¶,ß³­¦÷ôÉa¾+÷/è“£|—žô½ OŽÇ‡ â¯ê4}¾>ßYú$|+|gè“#ã³ÕßÒ}EŸ˜_èâ¢&ócøÂƒ/|—å;§w^Ùî6l0~˜£ŸÕÞÄŒ‡½»Œ'ò½·±8_·Íé“Ev²ïN| cú¤û]κì+ÑZ†ïWôIõ>á¸E|x Ÿ…ïšÖß>Ý%><Õ'Í«â5¾³¿ò¿ð½¯O:_3iÏÖÇû-}ÒyS{]¾|á‹Á¾KòÒ…ß?°~òA~ÝË&á;˜ÝزIÖOvê“CúÙËS'¯Ÿ”ÿµß±\’ïD}R‡žVÈý0ôLJ'ëû†ø:ŸÚ\›ïóõ“ðmò}ªOvÏìW+·ˆ÷õɱõÎ7*—å‹Á¾|á»$ßy=xsü`>¡ÏúÉ‘ÜÚÃ^÷Í,ôÉÎÌúŽŠnÊWõ¹>)=úï±]þ¡œ øZwÒ'ãµÓÎ_š0(gY¾³ôI«/tÉ:¬ŸÔ>ýLòÓ‡d3ôÉ6_½ç¿›ñ}ô~wÏcc5ë'{ôÉ„[ß²Éâå,˃/|1øÂwI¾\?ýõÙ•ùÎ|ÿ8ͳ¢Ojõ5PøÖ’öë“éžÅùž-<Ýüþ¤ô¬ï³ïÐnÃ׺ۈ>™KŽ-}Òü.Ë÷“ë'wäûÉõ“[óýÀúIø¾¼~Ò~£r³øðîúI;Ùáý, ¾ðÅà _øbð…/|1øÂ¾|á‹Á¾ðÅàû›ìå¦ÚÔâíÊ­IEND®B`‚urwid-2.6.16/docs/examples/subproc2.py000066400000000000000000000002551470350774000176300ustar00rootroot00000000000000from __future__ import annotations import time time.sleep(1) print( """factor: 1 factor: 1000003 factor: 2000029 factor: 3000073 factor: 4000159 factor: 5000101""" ) urwid-2.6.16/docs/examples/tour.py000077700000000000000000000000001470350774000226622../../examples/tour.pyustar00rootroot00000000000000urwid-2.6.16/docs/examples/tour.py.xdotool000066400000000000000000000001201470350774000205400ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 39 25 key --window $RXVTWINDOWID Return End urwid-2.6.16/docs/examples/tour1.png000066400000000000000000000122011470350774000172710ustar00rootroot00000000000000‰PNG  IHDR_wðdUégAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEÍÿÿÿåååÍõÅó€bKGDÿ-ÞäIDATxÚí] –«ªÒMÏ A/*øV1ƒ¬žÿ˜¾¨PˆÆÎ1÷ÁëwO¢¨;Û¶UÜn£üq™–¿åÓ÷®£ªåë›ÿ»uì|õ¯išÌ±ÓŒå¹õùõë{‡Lpýß\¯°sÒð7ךòA3Øù3ào:$]údÀêlG¯Ä®¦ð×| æo_óÝ™ïÅz@6–ùÃW2™å~=ï£Ø²œöùm½ôr®É½¹Ë À_ßmÀó¿Ó¼kÙ´Â_ïÄ|òõ–pEy'p“$äù Ò¥×óðá›û5ݦ¦ ó2àô'~ϺåÆ×ºqM~o7æ!ßÚ²‚Y™S™Êë6‡áÛ^ÀÓ|çJÀôÓ}ÀËQg–!ߌÅ>'A”aøÖfXœÿ ÀÓÍž9™1ñ{¦Õ²Èö” ³ILÒ†“µLÕ´Úôî”´ádÁ¹ŽmK¾¾]À È|”¼¶Óú@S+±p5å¾äkÙEmÏÄG¥Ýé²ÓÎCÏkjm¨Žn§é&ïÔíÖè8¶ÛÒQFåÊÚú/}ï¬d_0M“iq*•«j°¸ØÒöÙ¦mZOf+Wĉ!nÅ¥©Á­TöÕàÍÅ0y¿ÊFzíŠ|ÀÔ-ô®W¶âjàÊ}©ÎW¬žº*kÀÔMÔïô0œMë¶þ£mX`˜¤„æžî{»r!®¨ûþ¢»âÎ@:Y6·l¥"ÍWü*mØÜ™våpfïfäkƒaóBñ]ÔŸòs:ÝnUÀSWe£¿¦$wzO¢!šøÇ&Iõí3\¬^¹ê•¼eé¸ðú[¿U+áš„j©¤œôWš5¥Ù†¿Ó °|úµI¤3Oüv5i3& ª/X~ãd-¢R¹ÒqP+±"ÊÏû4‰×[¦ƒßë¦ü˜Ï:)qûoÊ×îË1ü¶>§Ägù¿x€àx€àx€çrÿýý­U\þÁ]ç‡?gøj_RÁ~4Ø®ÿ³€ŸvîÏÙÎq¾~Äç‡Ïqþ?.ÖËÏ[‚Üç- ¶`œ7#ž*æ-€\óQ›€—¿GúÄ€×?@˜ÿY?Ó®\mÆÌ[p1|±åyì|8ðá‰éu ðßüÛÝç¥ ø~& 0€… `8Ô 88€±ðÓ»uÀq¹É²I,¿cÝ¥Ó³uàfáóÍùb"^\E04à`LâÀlÊËÙžEÚ0–·{¶W +€“EAˆûS+ñü”9€ü,/­D~ðu++`Úòüظµ‘‡ëV‚p&óêÜÕyÞ½Ðÿj>ð?-öÜíC‚3_vmƒÔ ]ùp:ÃG,ºòäâæy?ðÜ RŒ‘»Ñµ{Ê­: Îe—’—³EHBqQŒ%àUyªf÷ ±O^bPº“>€…†ÄÔ#'cdy ¢«†$E À˜xl#iì5 47u—0Y¥¼ªÚlÑŠ«„#€a}–KÀ¡8(ÀëkÇ`m«ÅC€±Æð&àõ.t0¬I„£ '1 ‹qÀRp² † pf86LÂFþý[­„Ôus ô!Ú’ S ÎÜJ°¼Ì/$ksª€©rÙJ¬-ÈÑvx[ŸaG3{B錟:%šóâ¿|¡2?íx.?pì|Î:øf…açiãN†¡j ý(±Ü-ÓÏ[`ÃWlNyÉE™¼±âXÖiI•uŸ’µ¨ògf—Ÿ‡O~Q’ ˆ5†e_³s˜$¦$„pZ‚ôP¨“»mPŠN™¯•»v-n]†Q`v·+€…Ó’‡ààpÁp8Æh§›¦'Y˜+/Gíœ\Üq p“aÈ Ff÷£¬(a?}€CÖx%࢟• (é÷å]Â>à˜–By’•þLŒÒ†C~oQ7ºÑ/µó[$k¿¬ô’ªÝJHU™d¡j%H‹j&UV­Dj¤„?ìÛèÏ­Þ îj€ÿ¨(M-qEÀM†aÛsôŽ¢`|œZûOÈËcv»<ÜMçg[‚uZŸeÞºðú)J†/ØvÍ•Îo‘ÉucÖ¤*×Û¹4ø!?rwºÄ»uOÄF ejáò} ÇÀ‚!õ5Ù{ UÉDZ~€ä™ ì§2:SA[àà€„²ðUV0€Ã~ÀF 7dÀÉYhžwõŽêžëpçë  <†%½p¥†ÍCvÃ`Ï‚“ü±8ê[ìŽé€Ø°aØË0Ç/xЃôCª2ä]ì´$÷cFL¦˜¸ßJÈpy?Ã[¿¯¯žÑ=t2|uÀÀð¦(ÅWÀyùŽ¢M¸¹w¹}®[<æË†Þ—gÉp¿¨£ÊVÝo ÝD]‹Þ8ÀðU[†©yσèØWí¸Ê2\•⚪/ÀÀîPÌ,)ìnü¢(<œ] ³Ì¡Ov=é‘–èµÁ†Ú…ipZ»¿(öpv1Ì~¤ôËKÀ¡˜8­FØñ‹bqæm†A«4€¹S(ÓQÒ!YsØ}7à ÃJg¢zè&4`” ç›Äp8‹á(ƒ¬‡Úb0¦€Zmº€‘˜ÓÎC­„­GZ’‹2…b„-ÈјQ†?ÈtËxéáìm‡ÿ¨œ}~›–83\þ†Ï —yùŽÒa¯9¶ñD°oa«ÏáEº3»]³ôUÒI§%•ÏS”R»ljO™´áÒWÉ#$Í Ì¼+¥ÃP;RhÆT™A±šÑÁ°õUêáp°ׇâ¨PÙ%Ï,6:n·n†­¯²0û!ÓQA8?‰ÆÕ™éõÄfbX…°Á‰SkÀª2µ]V–go¸³õ³Áqå«Ô£|Q<øŽ¡Ò–vtcÞÅ£1³™c9ܱ«.|•rÄ Ø‘3Ê$ʺ±à3k½Ê&÷2\¶ŸÅË=¾¡;;ÚÓ]ðN†Ïò¾bùxy9§ƒ>ÚÕïΦGkïó¬b×½›Ë†—DÅàÇÖïÍ]êp8Ø0|O ® ¸døyÖ9a|±õÃ}ÉMóî÷ç.Úò˜ÿ–´bYù. ྦ–λèðÔâØ°—Ú‚Ò›Ñdøw%ù¾|¸•DþX öÎ[ù/Uþ]¾jÀéï—'†ÑÉáø£¡v΀>à ßóãÎG=2à‡Äœw;Ña?oåÇ<ß=¼æ’KÀ´å,À4ö’ÓÌ]À5†™Æß_ù>lKràjwx/Ãd İü0€ŸENGÝï ðòAØðC. ÀŒXe8wå¡ND›0?ðéÁÿÍ?ñnŽº¯÷„l(>ï¢ÃײÑJÐhðÆøa·ñìî›ÞQ6µÄÕo2¨ø†¯Vþ«c/÷&û”YáÅps(cgî ¾À] _pߨËJ²Oé¢\¿D'+œg(Rƒ0•?S§õ3\޽ô“}\eÑm•œãºÖ‡ü{ôëˆgn·Ã¯—Ó§+ÿk-ñ'ó«ÿiëÙó«yùŽò3 †ÃáÁð`x0<€ÿÎ$ö¯ØØX²|ílLnY$Û ]Õ)¶Š7Dk†Ð~è® ¸ÊpïŠX_Žœî²IäA1õúÆ$™a¯=+6b}9rSa5_4S¯c05Í$™Áqi}+6b}9r#ŸÍLæuÀj+Mÿ´o­ØˆõåÈ•!i³-W&ÉÜ Ž;€7VlÄæräAáþ è.•‹I2CØÉðÖŠåÈ®ÜúPŸ$“vņ{Vll,Gžz!?‘§8;I¦h%bƒáÃ¥Ûõ‡íàÝŽ™E/ø\†»}•[!õúy†¼|GñL¢mE›#$û sÌ¡y¶çÆvw°z¥ŸOÜd¸/‡&®DÔY¢‘»šùN¸r²Ôë!uÙù c_&òÄ•`óÍ3 ”ý4ˆ¹ñM–z;¤¾^«Îpg&𥯥ÍtÆþ:d€73qЬÜ-î`^+rpŒeÀ×Î+Qn1¼™‰£ëìZ,–¡¬1lŸ³"¤rÊv—áÞLرá<œ’ÛVµ¾ƒ؄ԃˆ¿W{º]™8Ëï(Z‰4xR,Y®óͽ,u7¤ža´Z‰²I|)d »«¿&/_Œ'~ ð;@>äå¿-Wvh«¡ßÁ°÷ø\:d`_“a1”ë3>ÀOak€¯Ê0‚ð”~ßcÃI¸JÀñÊ Ëì!,g\¡Û¿.ƒáx˜Ä`x0< †ÃáÁðüL¢2¦SvO’Ú1ÒòÃøz®:îÚ¼y¹ŸOÜÉp1&Iåz & RS !iy„áèM~ˆµ¤ò ã§jŠ!@½ IØiù‚ c1%ºIåô98S ™é‹$`¬Ž´&l¸?+ `‘¿ àŠ mÃ(lå2‘uÞ•nWù;èº1‰„Ú{©gS¯¶ÛeÛe·íÑÃðbŠÐ-±ß¸Ÿá-cOþÎ[¾HùÊËš·žÅb²Æ04žt(¬pg Í:X?Ö®öóI€Ø|D°ùÝ¥ò´¡pà- BábÍŽƒb2u‚*sÖÕójv(Û“h§ì‘}”«ÌäùªQ.ÄsHLàâ¾(m8/G¨gy'ÀJ9뉫Ôã ?AL® U^3k*Zœ&Ê™cJåY®'.W L‰ÞAÌÊ~LLJÀ«yV.ó»CÉpp^+¤t”‰ç/ˆIŒšak—Švr\)Ï †4“ZÙ瀘¤†’ÓûÔAbKGDÿ-Þ IDATxÚí][’ã*•w`vP!Æÿ Œ¼GÔþ·2âÉK#·Üܺ.—„’ãÃ!I%È=MTN.Lÿèw÷¦«Šåv¯{תÖoŒ±èZ¦°lG·?ow.ap¹ ŒOÕ; 8c4|µ˜»HUÿ)Àw‰mz0`d­°!Ö¼a)à›êõ×MõŽê s‹zs³’Ñýµõ#8¢Ín™¦µ-–í\}Eðí^¬~3uJ2ðMO(ã¦KBE؃„lŸÀ6mì„:÷Æ&VÕphÁ¶?àó˜#Shk 5Ãxœ¦ÀƒëÄ€ÑÈ"SQes,ÃðÔ ˜©žKûž¬¯ (ž ­OˆŠžê û#³)¬8QC |f”嵇4$Á †­Z˜CÅŒ¦'ßSPÃVÁ®NìKn÷,` D]6^‚™í½„抹¹ä¦OyßÃÂUö´m–MÞŽ¯ÖÛø:Ø%26Ážš¦ÊıïK©P¡rB1Þ_Ͻ*F€sc,ò8…ÊÅh0iLû¾Øµ1c,®\Æ|ð‚ƒ)iÚ;ÜBå|48e1°ÜÁ[ê¤ÍT”ì§…#€Ë•ãàªp¡_ €]‹eÀìPe ØÏDÌÏ;GvÒšÌÕök``0„3Ý}¿r\ùéûæ{%Ø kÌÉÍI"H]‹·TÃQÏÔ+§€{S¾VŽn(îI}æÆ)›¦"`v¨r Þ˜ wŽfÀ±ðamHuÏ3\Œn¹Ê•£hpr¡c`óYïÈKd%< 's€ n EƒAÃwk0†£KÂZfáîŠa{ h.П‘ÅŠ(T.LÞKDn¼3noᾎ¹@g•¡àöÏ”[só ¬ÌßSÄVþûM€Uù!†‰ab˜&†‰ab˜þ×~¶tåZ®üß8À«\ŒM)[»+¼‰‘€K’x®Þèê­Ç˜‹ÿµÄæÆ3|QÀ;Ãë¦Ïù!ÀrS´¾Jû\†—Õ´¾<—FÀ«~§_—u¶ÏeØ^×g?`ÜA§3¼ªŽ >X*9ÄŠ:ᥛa‡sùÃ[Qh{4¼•ÍŠ4þÃç6—v/±ª ¤\GH¢ÌðE§æ¯ ~Š [´vÙBwÄ01L ÃÄ01L ÃßÈ0e/ÿ@ö2 W@VÙpª.Í¢Ép&·o‚á½þà%Ê~:{Ùø9 p#Ëǽ´e/Í«ÑÆ&ŽÅ*êt ¿ Ø c5ù£O1ìizð³p£†ßdXªÏ)M¾ê^HÂfò?ã%ÞtPÏ1¬¤«²—퀽êí›s¦ì%e/|LC¯Å{Q†ql¹^ŸaìÝÖ¯`8L«ÀE†+%á¼pŸ$hqü/\?'¼ x‰W»®ÎðHÀŸ[Xô[.Ax¹\”a½3t5òEáåzQ†5À7…Xb½(ÃÏ7Áðr 8¯î%Ö(¾¸—X`9<¼<褎JmtªÜŵ½D9>½¬—˜>»PB›&†‰ab˜&†‰abø~v—ÿbçà¯tcgWÑc†ÏìÒíº¼$ë Ë?x>ˆá€1œÖ_ˆ·”ÒƒÝ Û®Ð—²pþ‹æú×—½ôúü >ó•t>±òëaÀ«K±­þaŸ'ÈTøïÆìa¼øï Tëkª‰x™»ðsuI!gp…ßÙÅ0ì¿•qëòK`ý•ì ÀõÄæÏ1IÀÏwKó¿9 íºèaÀU†ñ óÀhå®YÏ&À~¸Èðq·¯í€ýðêºsq»bü›vÀO·©tßÙÌpÔZùîÍáSóà§ xpðSf¸¥Ô$ñ¡híëâáKº§#†‰ab˜&†‰abøŸgxTû±xø;³—øæQ&Ûp»²—8©¸¤w[½ gêbx0à"ùHàG„Æ1œÒïe/“Ýû{vz¶)“Ÿy'{¹ ÔŸý'E¤{tb,Ã+x„déÍ^"À$ÁÖñ^MwÊw«¤¥¶#ÿ9 ¸‘á'øRÆ¥7{éÿ™$eG‚Ññ÷–Ü–½´™|gG"Àý.Lë3Ý­ÙKû¯ÿȼ7¦ìåÙ±DK¡ìe{<|éB÷tÄ01L ÃÄ01L Ø$A ÃÄ01L ÃÄ01ü0üý’óÌåö[ |°X¤~å…³…ãbÜ»Vhrß>/ 8fXhŠ/ 8b˜»o“ËMBn¯Þp÷ÆW6ú‘êú”Du¸½ÊV–¶YhP×éd8¬ÿج }\¸7¨2W5„¤ß‹`dæÂZó—;ტ8:v’ðöޞ€¡A#5‹,= ëþÌV§¸ù_–ëSbNd# \é{ƒêHà&†g×¹s°+Q@6šXÎÁXóQ—Ò0Gö`Α†g8Ò¹ÐR†€e7àÃÈè)DS”x ÝËö¼Š§žD;%e½Ä ¿QÄ»ÓAßLwyÀãöÝý- ¨üwÜEŒÁI¤ÝÏ ç+€S\f”ô.Ã<3šj€Ýß^Pœq_¢c¨þ|à˜a3ÌÆ«k÷¨ü4a[7•CjLÈUšFœ2ƒ`RNOÍ2LÐxWÁ-‚ƒÛ";I¹40,²Á¤þ(:Õ®Üe˜ÇáDp^ú J‚g‚IhÜr"Eà”akPºÂJÂFÑàà4˜LCI¼Åð(ž"QàË Ïá¹Xt3œÓ°Š¹RŸ€ŽÚ‡—>õ¿Xó†q ‚@uƒ)"/ÂK‚º¹ ºå„ÑiÑKˆ†ë¸ÝÅÏû>¼±Tc 5tÀ  ÏdøŠåÛÃˇ~ý'¿¿Ù‹ä~ÿjE!gU˜lCC$bøÏ¾ô‘×1AŠú0“ ßû€G„þ³ø1pÆC€s ?”~_Ûÿ¿†á‡yópÆŠIË€ITH—®â0JqÁÔÑvx¨\8†îçe5üR?/«JÒ2ǘp“»Õ°ù¼³Ÿ,Ñìï†åîÈjØ“é4¬?®»ŠIË2à a[ÙÖ<_‰,zŽa- ¯á—%¹Ø'-ó€¹K‰ KÛ I”ž½$<à<ÃqÒ2x†½\axöŸ›Ï5GWÓð+HÂk¸œ´Ä‚XÃ@?AÃB {Àü †µcPãLKâaÿ@^¢–´D€¥q Õõ—àÎoxoã+òíE ÌÉ»¾K|ðÛ MZ¸¥ðòß5ÇÝ饰’.w¤^jb>x¿œe˜·)ŽñôËðÅÇ s˜=H—«BÊ‚ƒ„¤ße®²•‹Gà)Ÿ@ã ûµláqJÐ\ 0FÌ!«o+ wyÜ„9.]ðÓ¬ádñ=l*ð9 xNÇi̤‰úŠ£©Ÿt%4ìó6³íS€UÀ‘X œjœcXäF¤Uó°²[d8HKW ¼ì`ØFzXÆ*þ2²ƒ?‡N-»72¬FnXÔF÷ƒÞKhÛh¹<Ý'ƒíE¡&$8&µï -ñ›ˆ\ôùáå§ŸÀpSÀ‰+Sxy‘‚$qdœÚ*ú޵V*΃±è>Ãü€ì¹­ò8(ÓƒP’ZâÃG»õì?8eÆ.iàðëa“¿/—͘÷3œ~ýÝVFe$ÞÜUdO ßë‹I=TŽè…S²µ g´$*ð¬íN[§f›ÝrY£œB0Ã6CŸÞÉpr¼?Sศ¸Âð\’„ëØ‡QîBv~c7ÃÉs’Ðuvç4ŒW$á^€á98“áäøU`ÏCð+áxðo¸–á¬ixFÇÆÖÜiVo’°9dç%ªNØ„õ<ã%ÐÎòLwF†‰%FþÃ#3œ^~¢ÔrkséÔ‘N>(„¦lÛ^ö2 ÞÔäQ¶¼?>¾ puÓ~e;%ÈUº…o.QT ¢=œn¦’eË2i½Î0‘^e;%ÈUÚÃæ3Áb1Úÿ–„gsɲ@»æöv¹©B¤‡û\“M\…݃°ˆÒP<‘D½­ÃÂtŠ(Dz> "1¶3VCIÈ ` ž©.0<"=Õ>ƒ(tp~ËeÆrÃ`ƒ%Þ, €…»1€A\š{¨M‚83µ,’Ôî%Òí”Q®2ä3y´ºf&õB/ZöQ¥h_ßwágqÄ_ðßÅðËæþä÷†Ûðo,ð|A€GFésQe¼8Þõè÷8À|'öÉâ¸h}ôûÀ!Û"2éD‹ã¢çÑïSF±^ïzôû#€¡BÑâxç£ß'Î?¤Ãs€[¶ä%òQeº8þ'_¾`L€ 0&À˜`L€ 0&À˜`L€ 0þ‡ÿC¥àÙïykIEND®B`‚urwid-2.6.16/docs/index.rst000066400000000000000000000002751470350774000155440ustar00rootroot00000000000000============================= Urwid |release| Documentation ============================= .. toctree:: examples/index tutorial/index manual/index reference/index changelog urwid-2.6.16/docs/manual/000077500000000000000000000000001470350774000151545ustar00rootroot00000000000000urwid-2.6.16/docs/manual/bright_combinations.py000077500000000000000000000027411470350774000215610ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import annotations import sys import urwid RED_FGS = ( "black", "light gray", "white", "light cyan", "light red", "light green", "yellow", "light magenta", ) GREEN_FGS = ( "black", "light gray", "dark blue", "white", "light cyan", "light red", "light green", "yellow", ) BROWN_FGS = ( "black", "dark blue", "dark gray", "white", "light blue", "light cyan", "light red", "light green", "yellow", ) MAGENTA_FGS = ( "black", "light gray", "dark blue", "white", "light cyan", "light red", "light green", "yellow", "light magenta", ) BG_FGS = [ ("dark red", RED_FGS), ("dark green", GREEN_FGS), ("brown", BROWN_FGS), ("dark magenta", MAGENTA_FGS), ] body = urwid.SimpleFocusListWalker([]) for bg, fgs in BG_FGS: spec = urwid.AttrSpec(fgs[0], bg) body.extend( ( urwid.AttrMap(urwid.Divider(), spec), urwid.AttrMap( urwid.GridFlow( (urwid.AttrMap(urwid.Text(f"'{fg}' on '{bg}'"), urwid.AttrSpec(fg, bg)) for fg in fgs), 35, 0, 0, urwid.LEFT, ), spec, ), urwid.AttrMap(urwid.Divider(), spec), ) ) try: urwid.MainLoop(urwid.ListBox(body)).run() except KeyboardInterrupt: sys.exit(0) urwid-2.6.16/docs/manual/bright_combinations.py.xdotool000066400000000000000000000000521470350774000232360ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 70 26 urwid-2.6.16/docs/manual/bright_combinations1.png000066400000000000000000000204771470350774000220010ustar00rootroot00000000000000‰PNG  IHDRv†ÑN-PLTEÍåååÿÿÿÿÿÿÿÿÿÿÿÍÍÍÍMMMÿÍÍ;]äbKGD Lò pHYs  ÒÝ~ü «IDATxÚíKki€Û|~êÒ^!„Þe„Z 2‹ÚÈ€¸©… ˆ«Ä]!ˆË^ ÆËPùi! ã(Øú &ÿ`È"‰7²¨ßðÛ{«êêºvÒ‰ïéîê·Î{ÎÛ9OŸK;› ¼xñâÅ‹/^¼xñâÅ‹/^¼xñâå\ÊUzë®R_órÁº–›]-so$+ð­ãEëUë¥ç¨k¹Ùzóg¹¬ìœxÖs <»³Êã9Ú ¸Bõº‘“†lŒæ_ØÆW/ˆû…áÕÀY!­`ÍŽF+ƒõÑnF"ip5ëSÒŒFŽf]ÁŠŒ“v_­¯·øã†vö]Àœº::‰vAlŒfÈÉ5´/\Õîúe‡XVp5XŒH³².;²Ùy´"MÏXá“H î+âÞ†ÝÕ;ÌÄAŽÝÕ;6CJÆø‚A?X»õ"»A)»õ;éh¸>²Î(·`eÈ솃jv¤¸1º‚vxÒì°èFµØ­pq›¹M>+ìÞÝÕ&yw•ónàäÊǰŒF—iV ©jvëÂßÉ;=‘:õ;ìS%ýîj¡ßQS»*]/Ïn1ýN³³ææŠêeëƒQ»‘i¦ßvíúÝО%s6§¹ÀÆWí,» Œ{³<õ0ŧ™³ºf± sÖI%¶YWìdkÅ|D«9{6d¥ðÃ.¯Y9·±/šÝºg7¿xçhVFç·æ¼x1òŸëüX×ÜVN –ÁìcKÜIˆ¯ßdrñ· ‡ƒÐºæ—¶oåùì‚%cw±À.\Jvƒee7XRvô þð´¸n± \·P×Aê ¡6äî=³»â+¼xñ7¨à0T‘‡áo0-°¸uð`ëöCì%bÇÏ$±Ø]†ÂMœàIs9±P]NÈëÑåGàzù‘ÐKÐ_Pûý±»2Æ×øÊ•§PÁã±õt { ¦-¸ÀÖº¾(8²eP]AW°Áã©Ð£áò|ZƒÂÇÁ+v =ìÈIóÈN/¹ç·ËÝ2ùºŽà쬠H®`üVL?Þã•6àˆÍS;r½ekž’ ¾á˜M‘æ¸ÖGìL–©LL4ׯIX†Êii2²`Ü;!ã°0;Þ³\Áš-G#P9-MFŒ«Ù%I v—ѫȴ'Æ*˜ÙñBØ]Ñ…]ÎŽlŠìô9MònМ]¢óN×ì£ÊÇžÙQc+°SĹN_Ân¬óîéØÉ»Zƒ"ß¹Ü~G™èô»|`—;i…Âhßy7ÐìÆ6»1‡>65+Ðíwƒ\T]r`f…|Þ×ìwzb".,8”—É`&¯íuñ ðİ»Lõÿ¨v46Ç¢.»¾¤—,+•­Â»¼f«]Ro´û{λ3Íîy¿„çïÍÖ¨ „­š¿„ÍîçÏ®»ZìhÕ¶³Øž8ö;3OÙvËÌÒ"¿½øÅ\s[9)XºNå±v6‘e`gPxvMع«e`G…¹³“N&;é”è¶fêTnwÀÆ®^2Îi¶Éf;Å-EO& ¹½j°Ãç‹_@¯-+æ  ^ª`6¿€W ¡®/HCîÆ«#;m‡gññMŠoÛ†ÝN.7HN3á£ßN:QùEÛä^ôª‹‚ LÍj²¼ïù p)í ¥áÍœWkv©°ÃW®¨IÙ¶&©ÅnÇf·Ó˜D1ò“^,¤/j*'˜b÷K¨/£àՉ݄ .UY—g7Á‚«`·=ázM'©Ë.%÷müˆ>ØA%»hS·ûc·-y—–ç]ZÅnGê¼£–¹“ª¼69Þ1ïÒ*vÎ;U³/± Ôé‰ÝöŽô%éz3û]» ÷Íézfžvd‡ib±“våö»4§ù…»[j5Ç 5ÜÊÂ^ØÉœ•¼sæ¬ÌÇíYsÖ¡À6;Šž³2ÌœmÊNhQÊŒ «ÇÓÄt(àœ}ø4)É;Y<°ûÉ»ÅIcR‹“ޤ<»³Än»Ñ/á…Ê §ÒÏ»ó,ž]v/o¦éË–¸@ «|f°»:Ä]69Ç•_wdòêÎËhæ»XŽågXZ›]Z`7/ð“f÷ªÀ.Z6vNHKÇ.]jv!,ÂðæÍ*øeJä°ÂÝTÜ*æ—`Ã…êeWp}yŒ_Þzàù’4)¾ŒWv¯"|E¯^Ý Ž"‰.zwø‚º¶Ø¡&zY¨^E¯Ðí¸G zr zF¶Wmv: RŒü&†n’;ZÜ´“ u ÞóùÉJÛ9¯*Q5!‚(u ¥ü‘ wìô’{~{uG¡§HÛ9¯Æì$ËnZ…*ƒÐN²/j*'˜ÆŸJ¢…E¯&ì04 ÓJ)Ô8[¢¨œ`ŠŸ…ÆE¯þØ…P‹uØA…–²yY8§+;¨àZìаŒHñœfì°W•°+!U¢ÑiªZ¦Ê;'‘²{E½jFÞåżLäþ©†v$VQ:ëœÆy—*vB—<rùª$uúw7:Oa>!4 ´EÞ¥š]dïPÃÊ÷;‡BDÃä•b'M-²¦wÍì°Ìh´†jªÒH4Isk6óK•¤–1ß )9B–¸Ðƒ»6ºHFë+”„OªŽpö܉l;9v8g_¡&eN&€4¯£Vy—köEi&³|»¸[ÉÓ °8°»0mò“â'eG5[”—a§ØÍínB%Ö§t<ÐÿwÏîtؽ¾Ÿ¦¯cXâ%®ã§ŒsçOˆÛ£%qn§éŸŽæÏÇs¼’’cfHÉ9uØ¥vU1 ;Šp)Ù9ázvØÅ°ˆãû÷c¨à×q¬²5÷Ó×¼ö¸Ð1kFß'ÿ”¼bE¯êœrvôü“êU±K\óSìP“<¶P%Ñ+ItM½$yü˜4t öjÉNÇ aÀ"¿Ï+•p§üx [¯c;™fjÄ”|ä ªsªðQðŠÝŸñaLšÄN/¹—·Dlá- oæ¼:²Ãuì°ãq‚»¸eeÇÊiYëœ vV–%J“X쌛°„‡ÓR³#+2.z-ˆ] U¯zìи„ÝìsªØ%IÒ‚z•°Ã-ü°õÆ»’ËŽ4¯Uc2ˆç³# {¼¾_ëœê¼³fE]v¤a'µ£¾Ý:ûÌ»T±ãÀè*jÒåHQçÊkbînì§uΙ;¥s¹ýŽ2Ñéwù˜pwc§ÄÌSÑG÷¼‹c­¸à†ëù(Iˆ£3ã)ž˜®æ>Ö¬Ê;µUuμē‰ù'ÒJ°ü’Äà°æ¬C½°$5)±“åŸ|Îãîy§s&OýçK·s’´'é@êL²Kz w쨲ª4í¤ý9&½¥×ì‚Øyñì<;ÏγóâÙyvžgçÙyñì<;ÏγóâÙõÏnꥭxvžgçÙyv^<;Ïγóì¼xvžgçÙyv^<;Ïn©Ù]£çt:¼fé¯Y×¼¼¥gÙV¿òѺ’¼q“7Eゼ±¼Š[½°›žAvSÏî̲CFÃ!“z‹‹!Tï5iÈF1z‹š·–1i#“ÉÇéÇÉÐ@‰jo@#\>¢…Œ?Úš7ÊÏ'»O»³ÚÙGium:Ì%ÚЦùöhÞ^ÓÆ°¸ÖÒélšpŽ`Øo¦óD%²û˜ÏÍ7ߨš‰q‡-4žtL:“w9v˜‰ù"u2‘4o5è·²X»Lä å‹S‰åì v§­7çrKvP³Ìn8­ÅnzRì>N¸^§“éLv»KÙMÉîZü;1v&ø²¼›ž»¡a÷¶¬ß]+ö;½²Pv¤‚/éwsÙMv =æ¯0,¯•ÍY‡NÕ·jë-ƒ\;™³oLð «tB£¶ŠsÖÍ»‰Ì†ÁØ;/žgçÙyv^æ±ûô¾óuš[¤dože[]$à·¿~5×ÜVNÀÆ5s¶~nv€gׄ»ZvŸöື÷ϧöö@!„>íí}" oMù)HÐnï‹Ý'ÒìÉ®?íõŸý@àðú+ E`±û 4Á_ÍŽ5¿ò–vïŸÇNpø ¢VD>)ÍtÏ7%¾¸iåY‘ñ'½÷ϼ n†Ð¦fÿúÕÆ€[­!cºðC¹÷-©D “a|RéDù£8ºìöfj¦{zk¾½^ØAÖp%*v]°œhØ’o0§{aZÆ·–Š],;¼®ÙO{þÞòåÝtYØQ£ãî&½LáPìhk/×ï\vÿ¨$ý‡s¸?v 6;âäúÃŽúݯjK»/†ÎÙO-/1U³DÖnýj˜Å'ÅjOR°/vB .zÆÒ ú5ßaE³V¹/ˆÝlÉßÏØïÓœµH¨ajåݾ°¢YË£¥3;“Gë;=Uß©ù‰[H 9vz˜Å;Å*”ì'ï좛.«”†ÚiZþÌì¬öwªì¦ö]:¡â4<´óÿų;%vÿ1þÁ(Q™-(›œDå^Q·¿/Æ×Y€¼P׫(à[æ^ïØ™ì¦v³ »÷vñ²±s¢]:vÓ¥fÁ"Šþø#‚ þ;Šþ^‚‰°®mv ­?,vGƘ,#ê=±{ã+~ÿþTp«,d ÜÅ´[ïc‹Ñhv¬yÀ[`Iš¶ìtv0šˆ8à`ýa¥ <" y)cf÷GI–6U³1 bÃ5ÐßÓcŠ[xc“ÏiÈ.ü˜Ê­¤ÈŽ ðo+¥8­œ‚­±ŒñÙ9éŠìpÝYìø†3ÓÊ.ØÍûj‹ží’®6;%gŸ";ìw³ónºìÎÔe÷>2±ƒø´ònªØ Ù‰fô;—÷DÕ áKX@ÞM5»XO¥¦Næ’ÒM-×ï¨-ÌÖ®ì¢HF+.8æ(2T)iÎTQ>ÿŽ´1§`_ì ¹x´â‚C}?°¶ˆÎ)=L­¯!V4k¨&Ð%ït:M{š‹¸ðÃ-ž¶’=ˆg×;ªÙ¥ªÐ Í)²óâÙ»ÿ³n?$pIlÍ4™é^Ž£»õS²#R<;Ïî$Øñ3I,v p“©<¥œAó!±Ù±æÚ˜4?;†]B;éHó? ÌÔ|H¦‰6¦›ŸG ;“e*R:‰fÓâSc<§Ï9»$ñìÚçÝÔ³kÌNu²$×ï(~ç°£~÷¿©1þ)Ù}HhÎâ Û¿ŒÎöd°&¯Î»DX)㟒ÝOôâov\®^Zå]’ø´k_³^<;Ïγóì¼xvžgçÙyv^<;Ïγóì¼xv c—yi+žgçÙyvžÏγóì<;/žgçÙyvžÏγ[jvkô̲ᚥ_³®y9°®sÌfÉZÓ¿ï^£M¼ˆb³Êc³ôu­wNµ¥Å.[»ƒn윿~3Ç`Qì=»³Ã†CìC¨Þ5'NÒÑñAf×Äý`¸¶¦½X3lÝXà¯?Äš³ÍÑnFip5›SÒŒFŽæ]ÁŠŒÊhSÜG››Úë5£Š~‡1ÛÙw€‰²– ¼9£r mãµ²ÁëåuÐ2éìoþž›Ø÷ #ÒnŠÃŽìGvÖJÓÄ3ù$º=´Ü×auzTT;ÌÄ,Çn-ǎ͈Œ6.²[ë›Ýf‘]VÊn3ÇN:nêáS`·ÙŽ–×db%»)ncLì†Pk¼=4ìÈxQì°èFµØrq›¹Íì”û!/ä(Óê°[k’wkœw™›w‚uM%¦°[ëžwÙˆkÐb„šCCªšÝ¦ðwóN°nêÑÄ«Í:y74ìÊúÝZ¡ß¡1³³ûle»6îœw:0knª^¶™*ØL{túlå?‚Œ+ónX˜Å9›Ó°ñZæþÊYÓ#ØòR'·=òFz>š9«k§0gÄa›MÅN¶h¡F°ûd\Å®wéR¡¥rXøa—×v9~³É?6λÍóÁ®Ë/áùÅ;Gs8ÚìrzÅ/ác÷Sˆg׉Ý.-vµêØYOûÝ™§»Žef³¤Â2à·£æšÛÊIÁÒuªñYý±³‰ôÇî¸5;ƒ¢'vUžvuB=YvT˜»»Ùd²›íB‰k Îävlìê%ãœæ˜lŽ3ÜRP&»666¬™Ôª#|Ý Dx´¬P@Š#°›à€BèÜ@íj‚àH{©o´eìR6#>¾ÉðíØ°ÛÍå&0Êi&|âÛÍ„ËîqÆ'ãYÆë¸A[$v¾©šÕ8dy Þó-®%3^rhSp;‰) :®„]Q“±!l©”*²ÛmÌîˆ3F‡)Éhðä4•ÓÒ°Ë˜Ý ã%л±›PÁe*ëòì&Xgì¨Q§«‘ØáÉ»¼=1ìŽí›± ‚JvGhSdZfÇ awd؂ݱä]VžwY»]eèä`ÝU‰)ìv»ä]VÅ.Ðyg׬ªSØòîx—Ö±êz3û]» ÷Í]éz6»‰T®aw¬šl5;›ÝÑ ·=qïÊiTS³š£° ˜e`jV:`Kv2%ïœ9KÓPÍÐÜœu‡mv;óÓg׸/5ykˆÐ¢|‘Öd ‰é°Ã9{ÜPìdKjVÎ9 qš¼7Z³;ÙmòÅJ» õìÎ$»Z¿„OFŽœ’?ìΡxv]Ø}¹•e_BXâ%¬ò™mÀîêwÙê@’_weòõîËhæ»XŽågÌúô*vYݼÀ{bgö+Ø}-°‹ÊÎì×fçD²tì²¥fÂ" oÝ ¡‚¿„¡D+Ü yAÅ­Bý6¬Q(¾„p×/·ÀøË-¡s µè¯/Ú‹?‚/Õì¾FøŠ¾~½ ETôîðum±CMô5²P|¾¢Û]p@!tî¢ö+jàdí7x ]*Øéo„°naè&)±£Å-;™PWÐà=¿‘iqÅç| Á¾UÝ^uÍbX›)ãàˆlàð¸k§—ÜóÛ×»ý]vŽôIhE°ïV€›ÉN²ì–UX¡2í!û¢F rZvìþå–ñ’hÄNBsRê.o…Z¢¨œ–†]Æìî/Þ#»Ê«;¨ÅYìØ· ;2îÈ*¸;4œÁŽÝyÛ°ûªûB]vÔ~f³+!U¢ÑiªØ)¬Ö—ñå–Îèzì¨3Íʻܠ˜—‰*Ï4;…Õù†æLžyy§Ó%…K.)î\yjj™u³ ™ehjV:`ý¼Ë4»ÈÞÁt,ô;'øˆ†ÉWÅNU%³‹˜edjV:`-va(s/TS5Sµ„¹Ba‡a.Ëxbæ4ˆù–b'[R³”w·ÌàÎèCoU²£¹ñ4”šUUG 0iTüÖœuØáœýÝUìdKj–òŽ.{ÑgÝ­›w¹f_”êì¨!a·sêÔÑIž\‹]ØàßW?-»löØûö’vÖ/îVÂ?Z!íNöÿ¥½xv]Ø}{˜eßbXâ%®ã§ŒsçOˆëþ!%&ÎÍ“,ûîh¾?™ã•”3OJœÃ.+°+ ¥wvqvØBØ%Ù9áxvØÅ°ˆã‡c¨àoq¬²5³o¼ö¸Ð¡jF?$ÿŒ¼b¡ó Œè0:P{Ű ã9ìèùêU±K\óSìP“<±P$OÐ+ÉtM„Îw0Bã'øz¢½XqCv:føëá ‘?ä•J¸3~|ƒ­o±L35bJ>rBSºÇP^{~–; ^±ûþvœ¤Iìô’{yKÄH‘+Þ'OŒÁnn&;ŽÈfÇãwqËËŽ34•ÓÒ°ËØ8¶¼â¬²C`hV–%J“X¡7a *§¥a—±qby% ¶;(¯‡ø?•¯Å‹ì¾±;m[ìbÝ ÊÙ%XgÙ¡W‘Æìx!ìè#Ú³ã†äTj¾©gÏgGgš`}è|U¹KóΚuÙ‘Fç™f'XŸHb ;×½UÞeŠÇCWQë†e…J+¯QM¼b›Ý·X¨óŽçü‰ºs¹ýÎI“™P5µ,3³‚Ù}g–Òø8ɸ;wßb™{ܰT±ÑÅó Ì¥ o;š‡˜\*ï2õPÍÒ–}Íâxþ/"51¿'j$&‰ÁaÍY'qÐ+ITÞe*s©fi‹®tR:¹]ÞéœÉ—OUA5–V&YßÒ¼BÏ$»¤{”'ÁŽ ªBÓQø=é?í2®Ù^ÙyñìNˆÝgYöc K\ Œ ¼r2Î5îxàÒ²Ë ìª"÷Í®êÀåfçDáÙ5b7†ÅxüìÙ*øÇxÌ!üøñlLjÚ‚ ØÓÍ+}l…ª¶ Šñ3:(ƒÁCèÔ?ð °Ó¡Â Oø¯$ Æ?ð~L[¸üAˆ§Ù²4c²¡·±ØÖ>ð,²ã@vh ·”Y®¾Ì–­¨œ–Í<'ì –$T^H¨ãñ¸’ÚÙÕ9ð¬±ã>T`'ž9ˆ³"©γFžÁ¼Ë;CBýÁ¡KŸâ”¡†åö»g¹¨š:ÎxVØá”û1–qÇ}j<¶KìÙ˜'áX c;Mhb:‰Ã^*ï2ë˨uàYÉ;*¥UÓ{Aµ õìÇŽëh¶ÌÙj'½xÊì¼xvžgçÙyñì<;Ïγóì¼xvžgçÙyñìz—ÿƒ¢ŠûÔ¿ÊIEND®B`‚urwid-2.6.16/docs/manual/canvascache.rst000066400000000000000000000054311470350774000201500ustar00rootroot00000000000000.. _canvas-cache: **************** Canvas Cache **************** .. currentmodule:: urwid In an Urwid application each time the screen is redrawn typically only part of the screen actually needs to be updated. A canvas cache is used to store visible, unchanged canvases so that not all of the visible widgets need to be rendered for each update. The :class:`Widget` base class uses some metaclass magic to capture the canvas objects returned when :meth:`Widget.render` is called and return them the next time :meth:`Widget.render` is called again with the same parameters. The :meth:`Widget._invalidate` method is provided as a way to remove cached widgets so that changes to the widget are visible the next time the screen is redrawn. Similar metaclass magic is used for flow widgets' :meth:`Widget.rows` method. If a canvas for that widget with the same parameters is cached then the rows of that canvas are returned instead of calling the widget's actual :meth:`Widget.rows` method. Composite Canvases ================== When container and decoration widgets are rendered, they collect the canvases returned by their children and arrange them into a composite canvas. Composite canvases are nested to form a tree with the topmost widget's :meth:`Widget.render` method returning the root of the tree. That canvas is sent to the display module to be rendered on the screen. Composite canvases reference the content and layout from their children, reducing the number of copies required to build them. When a canvas is removed from the cache by a call to :meth:`Widget._invalidate` all the direct parents of that canvas are removed from the cache as well, forcing those widgets to be re-drawn on the next screen update. This cascade-removal happens only once per update (the canvas is then no longer in the cache) so batched changes to visible widgets may be made efficiently. This is important when a user's input gets ahead of the screen updating -- Urwid handles all the pending input first then updates the screen with the final result, instead of falling further and further behind. Cache Lifetime ============== The canvases "stored" in the canvas cache are actually weak references to the canvases. The canvases must have a real reference somewhere for the cache to function properly. Urwid's display modules store the currently displayed topmost canvas for this reason. All canvases that are visible on the screen will remain in the cache, and others will be garbage collected. Future Work =========== An updating method that invalidates regions of the display without redrawing parent widgets would be more efficient for the common case of a single change on the screen that does not affect the screen layout. Send an email to the mailing list if you're interested in helping with this or other display optimizations. urwid-2.6.16/docs/manual/displayattributes.rst000066400000000000000000000275461470350774000215000ustar00rootroot00000000000000.. _display-attributes: ********************** Display Attributes ********************** .. currentmodule:: urwid Urwid supports a number of common display attributes in monochrome, 16-color, 88-color, 256-color, and 24-bit (true color) modes. You are encouraged to provide support for as many of these modes as you like, while allowing your interface to degrade gracefully by providing command line arguments or other interfaces to switch modes. When setting up a palette with :class:`MainLoop` (or directly on your screen instance), you may specify attributes for 16-color, monochrome and high color modes. You can then switch between these modes with :meth:`screen.set_terminal_properties() `, where ``screen`` is your screen instance or :attr:`MainLoop.screen`. .. seealso:: :meth:`register_palette() reference `, .. _using-display-attributes: Using Display Attributes ======================== Once you have defined a palette you may use its display attribute names anywhere that expects a display attribute. When no display attribute is defined ``None`` is used as a default display attribute. ``None`` will typically be rendered with the terminal's default foreground and background colors. You can also specify an exact foreground and background using an :class:`AttrSpec` instance instead of a display attribute name. Using :class:`AttrSpec` instances in your code may be trickier than using your screen's palette because you must know which mode (number of colors) the screen is in. .. _text-markup: Text Markup ----------- A :class:`Text` widget can specify which display attributes each part of the text will use with the format defined in :class:`Text class reference `. Some examples: :: Text(u"a simple string with default attribute") The string and space around will use the ``None`` default display attribute which usually appears in the terminal's default foreground and background. :: Text(('attr1', u"a string in display attribute attr1")) The string will appear with foreground and backgrounds specified in the display module's palette for ``'attr1'``, but the space around (before/after) the text will appear with the default display attribute. :: Text([u"a simple string ", ('attr1', u"ending with attr1")]) The first three words have the default display attribute and the last three words have display attribute ``'attr1'``. :: Text([('attr1', u"start in attr1 "), ('attr2', u"end in attr2")]) The first three words have display attribute ``'attr1'`` and the last three words have display attribute ``'attr2'``. :: Text(('attr1', [u"nesting example ", ('attr2', u"inside"), u" outside"])) When markup is nested only the innermost attribute applies. Here ``"inside"`` has attribute ``'attr2'`` and all the rest of the text has attribute ``'attr1'``. Assigning Display Attributes with AttrMap ----------------------------------------- If you want a whole widget to be assigned a display attribute, or if you want to change one or more display attributes to other display attributes, you can wrap your widget in an :class:`AttrMap` widget. :class:`Text` widgets have no way to specify a display attribute for the whitespace around the text caused by alignment and wrapping so :class:`AttrMap` may be used. Some examples: :: AttrMap(Text(u"hello"), 'attr1') The whole :class:`Text` widget will have display attribute ``'attr1'`` including whitespace around the ``"hello"`` text. :: AttrMap(Text(('attr1', u"hello")), 'attr2') The ``u"hello"`` text will appear with display attribute ``'attr1'`` and all surrounding whitespace will appear with display attribute ``'attr2'``. :: AttrMap(Text([('attr1', u"hello"), u" world"]), {'attr1': 'attr2'}) The :class:`AttrMap` widget will apply display attribute ``'attr2'`` to all parts of the :class:`Text` widget that are using ``'attr1'``. The result is the ``"hello"`` text appearing with display attribute ``'attr2'`` and all other text and whitespace appearing in the default display attribute. :class:`AttrMap` can also change display attributes differently when they are in focus. This can be used to "highlight" one or more widgets to make your interface more user friendly. To use this feature set the ``focus_map`` parameter when creating the :class:`AttrMap` widget. .. _foreground-background: Foreground and Background Settings ================================== .. list-table:: :header-rows: 1 :widths: 23 15 10 10 15 * - Supported by Terminal - xterm / gnome-term - rxvt - linux console - others * - :ref:`16 standard foreground colors <16-standard-foreground>` - YES - YES - YES - very widely supported * - :ref:`8 standard background colors <8-standard-background>` - YES - YES - YES - very widely supported * - :ref:`default foreground/background ` - YES - YES - YES - widely supported * - :ref:`bold, underline, standout ` - YES - YES - standout - widely supported * - :ref:`italics ` - YES - YES - NO - widely supported * - :ref:`blink ` - YES/NO - NO - NO - some support * - :ref:`strikethrough ` - YES - NO - NO - some supported * - :ref:`"bright" background colors ` - YES - urxvt - - some support * - :ref:`256-color foreground/background <256-foreground-background>` - YES - - - some support * - :ref:`88-color foreground/background <88-foreground-background>` - w/palette setting - urxvt - - limited support * - :ref:`RGB palette setting ` - YES - - - limited support .. _16-standard-foreground: 16 Standard Foreground Colors ----------------------------- * ``'black'`` * ``'dark red'`` * ``'dark green'`` * ``'brown'`` * ``'dark blue'`` * ``'dark magenta'`` * ``'dark cyan'`` * ``'light gray'`` * ``'dark gray'`` * ``'light red'`` * ``'light green'`` * ``'yellow'`` * ``'light blue'`` * ``'light magenta'`` * ``'light cyan'`` * ``'white'`` .. _8-standard-background: 8 Standard Background Colors ---------------------------- * ``'black'`` * ``'dark red'`` * ``'dark green'`` * ``'brown'`` * ``'dark blue'`` * ``'dark magenta'`` * ``'dark cyan'`` * ``'light gray'`` .. _default-foreground-background: Default Foreground and Background --------------------------------- * ``'default'`` (or simply ``''``) ``'default'`` may be specified as a foreground or background to use a terminal's default color. For terminals with transparent backgrounds ``'default'`` is the only way to show the transparent background. There is no way to tell what the default colors are, so it is best to use default foregrounds and backgrounds together (not with other colors) to ensure good contrast. .. _bold-underline-standout: Bold, Underline, Standout ------------------------- * ``'bold'`` * ``'underline'`` * ``'standout'`` * ``'blink'`` * ``'italics'`` * ``'strikethrough'`` These settings may be tagged on to foreground colors using commas, eg: ``'light gray,underline,bold,strikethrough'`` For monochrome mode combinations of these are the only values that may be used. Many terminals will turn foreground colors into their bright versions when you use bold, eg: ``'dark blue,bold'`` might look the same as ``'light blue'``. Some terminals also will display bright colors in a bold font even if you don't specify bold. To inhibit this you can try setting ``bright_is_bold=False`` with :meth:`BaseScreen.set_terminal_properties`, but it is not always supported. ``'standout'`` is usually displayed as the foreground and background colors reversed. .. _bright-background: "Bright" Background Colors -------------------------- .. warning:: Terminal support for bright background colors is spotty, and they generally should be avoided. If you are in a high-color mode you might have better luck using the high-color versions ``'h8'``, ``'h9'``, ``'h10'``, ..., ``'h15'``. * ``'dark gray'`` * ``'light red'`` * ``'light green'`` * ``'yellow'`` * ``'light blue'`` * ``'light magenta'`` * ``'light cyan'`` * ``'white'`` .. _high-colors: .. _24-bit-foreground-background: 24-Bit Foreground and Background Colors ------------------------------------------ In 24-bit color mode, any hex color code of the form #rrggbb can be used to specify a precise RGB value for foreground and background colors. Support for 24-bit color mode varies widely among terminal programs. Furthermore, terminal multiplexers such as tmux and screen can sometimes interfere with the operation of 24-bit color mode unless properly configured. .. seealso:: The palette_test.py_ example program .. _palette_test.py: https://github.com/urwid/urwid/blob/master/examples/palette_test.py .. _256-foreground-background: 256-Color Foreground and Background Colors ------------------------------------------ In 256-color mode you have the 16 basic colors, a 6 * 6 * 6 color cube and a gray scale with 24 entries (white and black not included). The color cube is weighted towards the brighter colors, with RGB points at ``0``, ``0x5f``, ``0x87``, ``0xaf``, ``0xd7`` and ``0xff``. The hex characters ``'0'``, ``'6'``, ``'8'``, ``'a'``, ``'d'`` and ``'f'`` are used as short-forms for these values. High colors may be specified by their index ``'h0'``, ..., ``'h255'`` or with the shortcuts for the color cube ``'#000'``, ``'#006'``, ``'#008'``, ..., ``'#fff'`` or gray scale entries ``'g0'`` (black from color cube) , ``'g3'``, ``'g7'``, ... ``'g100'`` (white from color cube). .. seealso:: The palette_test.py_ example program .. _palette_test.py: https://github.com/urwid/urwid/blob/master/examples/palette_test.py .. _88-foreground-background: 88-Color Foreground and Background Colors ----------------------------------------- In 88-color mode you have the 16 basic colors, a 4 * 4 * 4 color cube and a gray scale with 8 entries (white and black not included). The color cube is weighted towards the brighter colors, with RGB points at ``0``, ``0x8b``, ``0xcd``, and ``0xff``. The hex characters ``'0'``, ``'8'``, ``'c'`` and ``'f'`` are used as short-forms for these values. High colors may be specified by their index ``'h0'``, ..., ``'h87'`` or with the shortcuts for the color cube ``'#000'``, ``'#008'``, ``'#00c'``, ..., ``'#fff'`` or gray scale entries ``'g0'`` (black from color cube), ``'g19'``, ``'g35'``, ... ``'g100'`` (white from color cube). .. seealso:: The palette_test.py_ example program .. _palette_test.py: https://github.com/urwid/urwid/blob/master/examples/palette_test.py .. _rgb-palette-setting: RGB Palette Setting ------------------- A few terminals have the ability to customize the terminal palette's RGB values with :meth:`raw_display.Screen.modify_terminal_palette`. There is no automatic way to tell if this is supported by a user's terminal, so this feature shouldn't be relied on. :meth:`raw_display.Screen.reset_default_terminal_palette` is used to reset the palette in the ``palette_test.py`` example program when switching modes. Recommended Combinations ======================== Neutral Backgrounds ------------------- .. image:: safe_combinations1.png Choose colors that are fairly neutral with medium contrast for most of your application. It is good to use one background as a default for text, another for edit boxes and a third for selected edit boxes. Foreground colors shown here in bold text will appear as bold text on many terminals. Bold fonts are often more difficult to read so those foreground colours should be used sparingly. Bright Backgrounds ------------------ .. image:: bright_combinations1.png Use bright colors to draw attention to small areas with important information. They are good for buttons and selected widgets (other than edit boxes). urwid-2.6.16/docs/manual/displaymodules.rst000066400000000000000000000126021470350774000207450ustar00rootroot00000000000000.. _display-modules: ******************* Display Modules ******************* .. currentmodule:: urwid Urwid's display modules provide a layer of abstraction for drawing to the screen and reading user input. The display module you choose will depend on how you plan to use Urwid. .. image:: images/display_modules.png Typically you will select a display module by passing it to your :class:`MainLoop` constructor, eg: :: loop = MainLoop(widget, ..., screen=urwid.display.curses.Screen()) If you don't specify a display module, the default main loop will use :class:`display.raw.Screen` by default :: # These are the same loop = MainLoop(widget, ...) loop = MainLoop(widget, ..., screen=urwid.display.raw.Screen()) Raw and Curses Display Modules ============================== Urwid has two display modules for displaying to terminals or the console. The :class:`display.raw.Screen` module is a pure-python display module with no external dependencies. It sends and interprets terminal escape sequences directly. This is the default display module used by :class:`MainLoop`. The :class:`display.curses.Screen` module uses the curses or ncurses library provided by the operating system. The library does some optimization of screen updates and uses termcap to adjust to the user's terminal. The (n)curses library will disable colors if it detects a monochrome terminal, so a separate set of attributes should be given for monochrome mode when registering a palette with :class:`display.curses.Screen` High colors will not be used by the :class:`display.curses.Screen` module. See :ref:`setting-a-palette` below. This table summarizes the differences between the two modules: ============================== =========== ============== .. raw display curses display ============================== =========== ============== optimized C code no YES compatible with any terminal no YES [1]_ UTF-8 support YES YES [2]_ bright foreground without bold YES [3]_ no 88-/256-/24-bit color support YES no mouse dragging support YES no external event loop support YES no ============================== =========== ============== .. [1] if the termcap entry exists and TERM environment variable is set correctly .. [2] if python is linked against the wide version of ncurses .. [3] when using xterm or gnome-terminal Other Display Modules ===================== CGI Web Display Module ``display.web`` -------------------------------------- The :mod:`urwid.display.web` module lets you run your application as a CGI script under Apache instead of running it in a terminal. This module is a proof of concept. There are security and responsiveness issues that need to be resolved before this module is recommended for production use. The tour.py_ and calc.py_ example programs demonstrate use of this module. .. _tour.py: https://github.com/urwid/urwid/blob/master/examples/tour.py .. _calc.py: https://github.com/urwid/urwid/blob/master/examples/calc.py Screenshot Display Module ``html_fragment`` ------------------------------------------- Screenshots of Urwid interfaces can be rendered in plain HTML. The :class:`display.html_fragment.HtmlGenerator` display module lets you do this by simulating user input and capturing the screen as fragments of HTML each time :meth:`display.html_fragment.HtmlGenerator.draw_screen` is called. These fragments may be included in HTML documents. They will be rendered properly by any browser that uses a monospaced font for text that appears in ``
`` tags. HTML screenshots have text that is searchable and selectable in
a web browser, and they will shrink and grow when a user changes their
browser's text size.

The `example screenshots`_ are generated with this display module.

.. _`example screenshots`: http://urwid.org/examples/index.html


LCD Display Module ``display.lcd``
----------------------------------

Almost any device that displays characters in a grid can be used as a
screen.  The :mod:`display.lcd` module has some base classes for simple
LCD character display devices and a complete implementation of a
:class:`display.lcd.CF635Screen` for Crystal Fontz 635 USB displays with
6 buttons.

The lcd_cf635.py_ example program demonstrates use of this module.

.. _lcd_cf635.py: https://github.com/urwid/urwid/blob/master/examples/lcd_cf635.py

.. seealso:: `Urwid on a Crystalfontz 635 LCD `_


.. _setting-a-palette:

Setting a Palette
=================

The :class:`MainLoop` constructor takes a *palette* parameter that it passes
to the :meth:`register_palette() ` method of your display module.

A palette is a list of :ref:`display attribute ` names and foreground
and background settings. Display modules may be run in monochrome, normal or
high color modes and you can set different foregrounds and backgrounds for each
mode as part of your palette. eg:

::

    loop = MainLoop(widget, palette=[
        ('headings', 'white,underline', 'black', 'bold,underline'), # bold text in monochrome mode
        ('body_text', 'dark cyan', 'light gray'),
        ('buttons', 'yellow', 'dark green', 'standout'),
        ('section_text', 'body_text'), # alias to body_text
        ])

The :ref:`display-attributes` section of this manual describes all the options
available.
urwid-2.6.16/docs/manual/encodings.rst000066400000000000000000000063261470350774000176660ustar00rootroot00000000000000.. vim: set fileencoding=utf-8:

.. _text-encodings:

***********************
  Encodings Supported
***********************

.. currentmodule:: urwid

Urwid has a single global setting for text encoding that is set on start-up
based on the configured locale. You may change that setting with the
:meth:`set_encoding` method. eg.

::

    urwid.set_encoding("UTF-8")

There are two distinct modes of handling encodings with Urwid: Unicode or
Pass-through. The mode corresponds to using Unicode strings or normal strings
in your widgets.

::

    txt_a = urwid.Text(u"El Niño")
    txt_b = urwid.Text("El Niño")

``txt_a`` will be automatically encoded when it is displayed (Unicode mode).

``txt_b`` is **assumed** to be in the encoding the user is expecting and passed
through as-is (Pass-through mode). If the encodings are different then the
user will see "mojibake" (garbage) on their screen.

The only time it makes sense to use pass-through mode is if you're handling an
encoding that does not round-trip to Unicode properly, or if you're absolutely
sure you know what you're doing.

Unicode Support
===============

Urwid has a basic understanding of character widths so that the text layout
code can properly wrap and display most text. There is currently no support for
right-to-left text.

You should be able to use any valid Unicode characters that are present in the
global encoding setting in your widgets, with the addition of some common DEC
graphic characters:

::

    \u00A3 (£), \u00B0 (°), \u00B1 (±), \u00B7 (·), \u03C0 (π),
    \u2260 (≠), \u2264 (≤), \u2265 (≥), \u23ba (⎺), \u23bb (⎻),
    \u23bc (⎼), \u23bd (⎽), \u2500 (─), \u2502 (│), \u250c (┌),
    \u2510 (â”), \u2514 (â””), \u2518 (┘), \u251c (├), \u2524 (┤),
    \u252c (┬), \u2534 (┴), \u253c (┼), \u2592 (▒), \u25c6 (◆)

If you use these characters with a non-UTF-8 encoding they will be sent using
the alternate character set sequences supported by some terminals.

Pass-through Support
====================

Supported encodings for pass-through mode:

* UTF-8 (narrow and wide characters)
* ISO-8859-*
* EUC-JP (JISX 0208 only)
* EUC-KR
* EUC-CN (aka CN-GB)
* EUC-TW (CNS 11643 plain 1 only)
* GB2312
* GBK
* BIG5
* UHC

In pass-through mode Urwid must still calculate character widths. For UTF-8
mode the widths are specified in the Unicode standard. For ISO-8859-* all
bytes are assumed to be 1 column wide characters. For the remaining supported
encodings any byte with the high-bit set is considered to be half of a 2-column
wide character.

The additional plains in EUC are not currently supported.

Future Work
===========

Text encoding should be a per-screen (display module) setting, not a global
setting. It should be possible to simultaneously support different encodings on
different screens with Urwid. Making this work involves possibly changing the
function signature of many widget methods, because encoding needs to be
specified along with size and focus.

Device-specific encodings should also be possible for Unicode mode. The LCD
display module in development drives a device with a non-standard mapping of
Unicode code points to 8-bit values, but it should still be possible to use a
Unicode text to display the characters it supports.
urwid-2.6.16/docs/manual/images/000077500000000000000000000000001470350774000164215ustar00rootroot00000000000000urwid-2.6.16/docs/manual/images/display_modules.png000066400000000000000000000374641470350774000223420ustar00rootroot00000000000000‰PNG


IHDRH"n–[sBIT|dˆtEXtSoftwarewww.inkscape.org›î< IDATxœíÝyxL×ãÇñ÷d‘ˆ 	b‰¥¨%%Öˆ’±V©½ª¨¢ø•ÒjµßZºXJ«ÕM«EK•¶øª-ö­jß÷5DD"d‘åþþÈ×ÔT,µÔr?¯çéódæž9÷Ì3÷~î9gÔÈcË0Ë픳³³»b†ãýn<0¹
ø|«B‹EçƒÇØížÔoïs.\|0­‘ûª]>×Û.k±³³üC.gçûØ"y:ÈŸ™•™yÛåu>x<ý“ó¨<®rêv """òP11‡ݹ¿æÌ™c3××®]»Ûš#”LJýOÓ§§Î™3çÚ§s·k×î–käñ¢óÀõý@#"æsûDä±§  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb
"""&¦  ""bb¦
ééÄFFZÇ9Cú•+wUgRìyR““ï¶iò1ƒs§Na‰çb¸|éÒ]Õy)1‘äøø{Ñ<ÓKMN&)öü]דráâbÈÌÈàüéÓw]gld$ééw]Ï£.áìYÒRSï[ýÿä;y1!áž|¶+SÈÇXßúøÝÆ8¹gÏ]Õùý A¬ùóÝ6M2W._¦Ÿo%kÈû¢W/¶,XpWuÎÿ|¿}/šgzüö+ßöï×õ¬š>©o¾	ÀùˆÖ¬~×u®[‡è°ãw]Ï£n\çNìY½*Çm).°lÊ仪ÿËWz³é÷ßoYnl§Ž¬Y¯ú¼rWû{ÐmÚÈáÍ›ïKÝ÷¥ÖGÄë?L£ø“O>èfÈ# ë¨Q¸yy=èfÈ}äîíÍ»ÿ÷ ›a
ÉññÌ|ÿ=š¼Üó¾îçì‰ìY½Ši§Ïàèät_÷u¿m
ÅÎÞž'ýýïyÝÿjXóóÏxù”àäž=Þ¼™n£ÇÃúÙ¿q>"÷"EhÞ§EË•`öØz¾3ž%Jsò$‹¿ý†¶ƒ“ßÓ‹3G²sù2Z¾zó»‚­‹²ñ¿ÿÅÝ»U…ØlÛ³z
&wÞ¼ÄFF²èë¯8{òyòç§n›vTkÒ„ðýû8ðÇ-ÿ$ëû…<ùÝh5pÞÞ×íëB\,+~øð½{qtr¢zÓ¦ÔmÛ€u¿Ì"¯»Õ7¶–_?û7r»æ¥F³fw{h:§ìgå?{ú4ž>>´8÷"E˜õá4ïÛ—ü=ع|9i)—¨Óú9bÂÃùsöoø5`õOÓñ©\™FÝ_"tâDŽnÛŠ½ƒ¥ýüh=p=Õ³lò÷Ù²…|4{¥EË•²O‹¿™ÈùÓäuw§~ÇNøݴ͇6mdÕ´qvÍK£îÝm¶Ù¼™2ÕªãæUˆK‰‰,üêKN8€SîÜTiLƒ.]Hˆ‰aɤo©ÝòYVL@³>}ð©Tùº}]¹|™S§plDz23©X·.!=^ÆÞÞžÝ+W’xîA;[Ëï]³šó§#	îÚõN?’‡Br\¿OøŒ?	À®+8ºm+ÿó.}²³ØÙQ½iS6ÏÿíKBÁb¡^ûTilSßæóÙ<>îÞExnàëäõð¸éþ333	ø5Ƕoç‰êÕùßÌi—.±cÉ*ÔàðæÍ¬šþ#Éññ¸ñ¦yŸ>+ÿ$›çÿN.çÜœ‹8ÅÁ
ð©\™gûÈñBsêÀ~ÖÍšÉÙ'ÉW° !/õ ŒŸ¿Œü Î/P¸tikùÙ! M[k?~Ôe\¹Âœqc9µ?ëÖ¥I¯ÞØÙÙ1ÿó	¤¥¤0uÈ[´}óMN:DÜ™3ärvfË‚¸{¡Ã;ï¾oK¿ÿ§Ü¹i3øM
•,y[ûŽ	ç—‘bgoÏOÇQÆÏÊõê³rÚÔhÖœ•?þ€wÙ²„¼ÔƒS§¶k†aP¹~}‚»uÇÎ.{À<õâEþûÉÇÄ„‡ãH~//,X¨Ù¢gŽe[èbÊ׮ͪéÓppp¤ýÛïp%5•ùŸOàrJ
Í_éC¹Zµ¬í:ºmëfÍ$9>ž*
ÒðÅ®X,⢢X1u
þ­[³ô»ïÈH¿B³Þ}(ãçljݻٷn-;;Ò†¤R¤Lšõ¾w#ÿêÔÀ®åËøºoÒÓÒîÚ
ç*š\ÎÎ4ëÓÿV­˜ýÑG¬ù9{ÚÀÉÅ…ÇYËf¤§óãÛo“ï'®GÑÑ­[Ö¤1^>>´8ˆ’•}IŽ`ÅÔ©¤$&]Svûÿø€„³Ñü>á3æ>M›Q®f-|>­‹ѨûK4îÙÓúåÝ®-a»vÒ´woJU©Ê°&­k@ÆthO.ggZ½6š-ž!åÂ…›¶ùøŽŒíÔßÀÔjÑ‚iÿùÍö-ræÈ~xû-N:Dó>}hÐ¥éWÒHŽcÁŸ3{ìžîЕ*1¬Iãç&S“/’œLp×®4|±+æÎåבP Ha¦yÓ¦Íúðìý¼<
°jú4ÎÍ>–¡“¾aþ„ψ‹Š`Ñį­Çó·1£™ÿùžnß‘€Öm˜ôÚ¶/Yb­ëÈ–-ì[»–Æ/¿Ìå‹—Þ¼YYY7Ýÿ÷ƒ²cÙ2šôê…½£#‹¿ùÚºíRRK¿ÿÈ,#ŸkEÙ5ynÐ<éïOJRvÚÿÇL|µ).дW/ŽlÙ̽r¾³=sô(%*V¦Õk)S­>׊³'Ndï#>žÐIßZ˞ܻ—ÅßLijD‰tLfsÆŽÅû(ÏwfÑįY3cåkׯÁÑ¿F!ø5
ÁÙ%'víâ§aC	ß·†/¾HØÎ]Œ}¾‹¾þІ]ºàèìÌÇ/<Ûûvus£\­Z889á×(ŸÊ•Iü€±±,øòæOÖ­)Z¶¶~–Ô‹)P¸0…J–¢PÉRø5
¡lõ÷ê#ÀÔÀS
òÜëoX7èÒ…¬¬,¢£q-àÆöÐPv¯\EàóÏãÄî•+iöJö­[KÓW^aïšÕuî̾µkiÒóæÃJ‹¾þŠNC‡S³E’Οç—?̱lÔñã<Û€õnàZ†aÐ狯ÈåìLÅ€ºìY³šMóæÙܱ”ôõ¥¤¯/H:žà®]Ù8o.
^xšÍ[0eðÞ¼™'ýýÙº7//›¤ø¸˜;þcZ¾Úß:ZóO†²2®\¡ß×É“??¡ßN¤DÅŠøáàèHåzÙk<nø“³'Â:ïwììì¨P—°;XóóÚ¾ù1'OP%8˜
unk¿K¾û–Æ=^&ðùì½£#û֮ɱlÔñãÔlñ•êÕ·	&WÛßó“O)X¬•ëÕçØöí¬øa*‡°)—ßÓ‹öCÞ&59™Ä˜ZôëÇŒáÃè<â=|*U¦D…Šlœ;—]ºpêÀ~¢Ž' M›Û>Ž+;;;*Õ«ÏÞ5k(Tª4'÷ì!ðùÎì]³†ºmÛrtÛVÿ4ƒ´”æ}:ž/wí¡`±btJÆ’IßXGМ]óðò'ã±³³ãIÿ:¼ZÅ—½kÖP588Ç}§\¸À꟦óÍþƒx-J¥§ëqdË–ËÆEGa±³Ã¯Qž%JP¾vm›í¥«V¥Íƒ(þdz•/KÜ™3x-jS.à¹6†AbL®Üð­ȶŋhÙMzödXÓ&tyÿr9;³â‡©u~\ÎÎwuŒ&õ:v¤A—.D;ʾuk	îÚ•'ýë`ïèˆ_ˆí(­—Ï@V–ÁGÚñéÓävuåÉ:tö*Hr|EØÎ×Õq))‰_èL\Ô
—*EfF&ñÑÑÖíMzö$tÒ·4èÒ…?ü@àó›ÄSAAì^µŠRUªPºjUüBBØ<>E½)V¾<®
¾W®ðA«–Ö×¥§¥‘Û5¯õqÑrå¬AÌÎÎŽ•*}üØ
ƒÀ¹S§pvuµ¹XûT®Ì郯+[²²/
º¼Èk5«ãS©µžiIó¾ýpÊ€b׬-Êëá»·7ÑaǯÛ/fÊ[ƒñðö&¯‡‘‡ãñ¿þQ¢b%Š?Yÿý/uZ·æÏÙ¿1fuÎáóQåýÄÖ¿óº{p1áæ¿šñò)yMywòzxÛÕ€\ÎÎ8çÉÃÅ„„Û
7’×£ MHŽ‹cÜI:žB%}ȸ’NÂÿ®A1'Ãñ,ácýܳߓí5¬@á"8æÊõ¿º³ëõºfT'Ÿ‡‡õ×B§bÆðaü2räÿ¶¤§¥q)1ñ¯¶]óÞòº»s1!áŽßëíú׃€Ý5Û™™™L8€Ö®£D…ŠŒéØž«wEžx‚Ü®®,›ü=¥«V%ŸGAÜ‹aåS)Z®Ü-;Cžüù¹”ø×A¼””tòõ;v¢n»öÛ¶E_ÅGÛóɆM¤üíçñþ[ ˜=vÕ›6³&Úõ³ã÷O?µnoÔý%^«QýöpdëVÞ˜>ã¦íTåuwçbbÎ×ÞÑôô¿~²™zñ¢Ív;{{›ÇÅŸ¬Àø›9{âæÎáƒg[òÕº»S¢R%F¯\ã~÷x™à®Ý8¼y¿OøŒÏºwçýÐ%9–…ì¾rñ_F€”›ô•Êõê3qßÂ÷ícå?0¼YS&Ëž¦º’šJfF†uÿRb.×›«–~ÿù<‚ØÙÛóɆM7@—“mûÎ¥„ÄõUyòç'íÒ¥ë>Ÿé1v/Œx=«Wñë¨Q$ÅÆÒ}ô˜÷’”D·×Õ1yðëôÿî{ëHÖÄÿëǵšôêÅÒï&‘•™‰Oe_ëú¨Ç…ÅîŸÍ>[,–›>¾ìlÏ1‹¿™ˆ—O	>\ºÈ^+ôQÇ@ö…ýBl¬Mù‹		(ü×MEŽm¼A»óº»3àû)9’FD`gwó÷k\»¨åz ?ÌÊÈ ãÊÒ/gÏ	F:Ⱦ5¶‰Ø7(ˆyãÇ[O¾AÌýøc|ƒ‚nYÕàF¬š>Ã00ƒUÓ§åXÎ0¢Ã°ÿߊÌFÝ_">꯻³³aaì[·Ⱦ«Ø½j%Ur¸ëHKIåÊåË@öb°åS¦Øl÷ðöæ© |üÂó<÷.ùòÝò=<Šj=Ó’%“¾µÎ‹e¤§[ÿöò)É¡
ÙókÉqql]´ð¦uE?Ža.]šýþ§y2¾AAäqs£@‘"¬ž>ß ¸)BéªU™÷éxëë232¬óëG¶n!|ß>Â÷íãÄžÝÖnN
/N‘2O°æçì~)1‘ÍóççXöB\,prq¡Ö3-ñ	!áš›-’x.{}ц¹sprq±%¸*-%…+©ÙŸóùˆˆëú»«ÖD‡`öØ14~LGs’}“–h3
ö \NI±^ƒ233Yòí_ë6JU©‚SVü0€{öX×­Ý	ÿV­™÷Ùxëy²×‘ÜŽÏS
nYÇwßel§Ž¨îG.ggªshãÆëÊeeeñ~Ë8çq%w^WΊ ûGY·—zê)–~÷3?xŸèãÇi?äW‚·ì?€ÑíÛ²wuö]ª_ãÆìZ¾Ü¦L“ž=ù°u+B^zù–íT=ójbÂÃù¿§|ñ*éC|t4¯ÿ8'ýýéøî»|Ú­+˧LÁ)•ž~ú¦uÍûìSö¬^…gñD;ÆÓmÛQÚÏ;;;Oÿ‰oôÇ)·X²“z¿¯'òDõ¼Œkwˆ;s†Þ>¿é~¿Ü“ƒ7òU|)P¨O5hxòßèO\Ôܼ
uìG¼G77⢣p-P€ÓòNà Ί ö³ÏRë™–×ÕÒãe†7k«~UpÊÿg[åXæ÷	ŸÑiè°ë¶=ê|ƒ‚عl¹õ{äÄ¢¯¾¤B:Ö2§üÀW}ûüï7îÄGEñL¿Wi5p êòÓ°wIIN&êØ1z~òéuCóײX,¼úí$ÆuîÄŠ©SÈÊ̤rýúÖõµÎž8ÉØN)X¬(WR/“‘‘ÎàŸþÁó
bL‡öX,ÎGœfÐ?Z‡‡¯Õiè0>éÒŸÊ•qpÌ…_Hc›í޹rѰKVNŸ†«Öÿì >Â\ eÿ¼QÇ;{{F¯Ìùßø74íÙ‹-š1 º޹œ¨Õ²%{ÖdŸÃüÓÏ|ó}ùuô(Êת«V8¹ä¹£}u:ŒÉo¢oåŠx/N|t4Oú×aÐ?Þòµõ:tdË‚ùô(]’JO×ãÍ÷î߯±ÆœoYð~JŽøåOîTBLù<WfÏžmû\»v9Ž=Ù;8¤ÏˆŽq¸—óÐéii$œ=‹[¡B6ûéiiÿ^+|[õ¤&'s!.Ž|äΛ÷ºíIçÏ‘•™E~//›Å{	11d¤¥áîí}Û+î“ãâÈåâb3'˜“‹		\JJ­P!kÙˆCÞ¬)?†GšœLVV–Íz‡¿ËÌÈ áìY
)‚ýߦD ûgFcÚ·cÒá#wµ> “G¦ýø#¹þºPe®íÚµ»î
h±XøùàïR““¹˜˜ˆ[¡B9^l“bÏã’7ßmÿN<++‹Ä˜Ü‹¹i¹ÌÌLÏžÅbgGÂ…­ßçÉo¼N77ž6œ„³gÉçé™ãçwÕ¥¤$2Ò¯X2ûw_õéM>OOº~8ê¶Ú§þÉùàaì÷SFzzvŸðö¾nð߽܀æ¯ô¡^‡Žw¼¿«çÆüžž7½žÜ9õƒ‡â÷Hw³ðãv(tãEb×Êïyó0ævÚéèä”ã]ÉÅ„ÖÌø‰…_E¯ñŸÝV{uŽNNxùøäøü톀Üyóæ®ºÑçv»Ÿûµn7Œº(€kë焯ºY{¯²wp°YXx•a,™ô-+ü‘Öƒ^l	Þ©[~þ7¸ÀÞˆÝ-C€½½ýMG€ÛêÇ7
ƒ°qÞ<¶…†òÙ–m·¬GîGÇ¿‹½žçÜ©p
.ÂÁ?ÿ$íR
þ­Ÿ»«ýÝèÜø <Aàn|ùJ/Žmß~ÝóÍz¿B³WúÜuýÅÊ—'¸k·»®'33ƒË—.Ñû³ÏË@èQð˨‘lüïÜ랯޴ÝFÝý?ý›ßÓÓúÝ
Ã0¸˜@ëAƒxº}‡»®ÏLÃàµÕrÜ6àû)èfÈC ]>×Ýù)ˆ•aº	rX,råÊuÛåÕO‹åA7ARš11S1ûÝyx(ˆ˜P»ví.?è6ˆÈÃAA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄDDDLLA@DDÄÄD®±}ûv¶oß@ll,sæÌ±nûõ×_IHHxPM“»ÎÒ¥Kïy½©©©„‡‡“–––ãö¬¬,"""8{öì=߷Ƚ   rß~ûY³fpâÄ	Þxã
ë¶þýûù š&wiëÖ­Œ3æ®ê:t(ãÇ ::šÖ­[S @‚ƒƒ)X° Mš4!,,ÌZþ‹/¾ÀÛÛ›š5kR»vmÜÜÜxõÕWIII`Ô¨Q<óÌ3wÕ&‘»¥  rµjÕâÔ©Sºòùý÷ßiÕª©©©4lØGGGΞ=KXX‰‰‰¼ðÂ=z€1cÆðÉ'Ÿ0þ|bbb8uêaaaØÛÛ“˜˜ø€ß‰È_ä®ÅÆÆâççÇÂ…yê©§(^¼¸Í—a|óÍ7øúúâåå…ŸŸ 003gÎXË~õÕWŒ7€;vкuk¾üòKÊ•+Ç Aƒ8sæÍ›7ÇËËoooÚ´ic}mXX­[·¦xñâøúúZïìæÏŸ¯¯/žžž”.]šÉ“'ßò}íß¿ŸfÍšÙ<·eËüýý)^¼8¯¿þ:W®\`ýúõtîÜ™qãÆQ¦L†
FXXMš4¡X±b/^œÞ½{séÒ%k]Õ«WgîܹԨQƒêÕ«3bľøâ›ý½öÚkLŸ>ý–m}ÔDEEÑ¢EŠ/N±bÅèÖ­›ÍÅ100Ÿþ™ÚµkS¼xqLzz:§OŸ¦yóæÖ×¾ôÒK\¸pÁúÚˆˆÚ·oOñâÅñööfРA6û5j%K–¤B…
„††ZŸOMMeÈ!”+WŽÒ¥K3hÐ ._¾lÝFVVO<ñ¿þú+‰‰‰L›6
777ìííâ	VºIDATéÚµ+Íš5#55•Q£F1~üxj×®m­ÃÃÃÏ?ÿooï{{@Eî‚‚€ÜµŒŒvïÞÍÒ¥KÙ²eË—/g̘18p€iÓ¦1nÜ8f̘Á¹sç˜;w®õä¹ÿ~›¹ÕèèhëðûÅ‹	

%66–;vðá‡2räHŠ-Jxx82È>‰7jÔˆgŸ}–S§N1oÞ<̶mÛÈÊÊ¢{÷îŒ7Ž˜˜vîÜI:unù¾RRR¬åªéÓ§³dÉöî݈
¬ÃÄ.\`Μ9†Áž={2dÎÎÎŒ=šS§N±oß>=z´µ®={ö0}út.\ÈÚµkiРŸ|ò	YYYœ?žÉ“'Ó¨Q£;ýhZ>œ“'Orøða,Æ
³nß·oßÿ=Ë–-cÏž=¬]»–	&+W.Þ{ï=Nž<É¡C‡HOOç½÷Þ²ûbóæÍñññáÈ‘#„‡‡Ó©S'k½›7o¦P¡B;vŒ#Fн{w233xýõ×	gûöí8p€ððp>øàëk¯Ž@vð«[·....9¾¿Ý»wséÒ¥Çò³“Ç‚€Ü3ï½÷¹sç¦B…
øûû³k×.&OžÌ»ï¾K•*U(]ºômß¹ºº2bÄòæÍ‹««+öööÖ€ƒƒƒõnkéÒ¥äÍ›—†
ƒƒÍ›7gþüùØÙÙ±}ûvΟ?››•*Uº£÷8dÈ
(@øÏþÃÌ™3­Û¼¼¼2d®®®¸ººR´hQŠ/΂øõ×_)T¨ëÖ­³©oذa)R„¼yó„«««õ.uÚ´i4nÜø±¼{ôòò¢lÙ²,^¼˜™3gR°`ÁëŽÍ;#››îîî¼óÎ;Öc]¨P!Ê”)âE‹˜5kžžž¬]»€M›6ËØ±cqqq!W®\6wä¥K—¦gÏž8::Ò©S'’““‰ŒŒ$##ƒ)S¦ðÚk¯OLL]ºtá÷ß·¾vþüù<ûì³$''ãááqÃ÷—œœŒ½½½5ð¼üòËT­Z•ªU«²bÅŠ»>†"÷Š‚€Ü3žžžÖ¿]\\¬Ãà‘‘‘øøøÜqvvuÓ‘#GR©R%Z·n··7}ô=|îÜ9úöíký/22’‚bggGhh([·n¥L™2Ô¬Y“7ÞQ{
.lý»D‰DEEY*TȦìÚµk©R¥
kÖ¬!99™¬¬,âããoX@Ÿ>}˜4ißÿ=½{÷¾£v>ì¶mÛFÅŠY¾|9IIIdeegSæFÇzóæÍTªT‰•+W’””„aÖãI±bŰ··Ïq¿^^^6sçÎMJJ
ÑÑѤ§§óÁXûÏäÉ“­166–°°0k¨ðòò²™ÒÊi?™™™ÄÄÄXŸ1b¿üò©©©Z# ‡ÝyüùøøFHHÈuÛrçÎm3o«««õ±Åb±)ïææÆçŸÎçŸÎúõëiÔ¨Ï<ó¥K—ÆÃÃ%K–äØ†Úµk³páB.]ºÄÈ‘#éÛ·/{öìùÇï%22’êÕ«pòäIŠ+vöNš4‰Áƒ[y0uêTëë^Ó­[7†ÊŒ3¸rå
Mš4ùÇm|L™2…W^y…÷߀9sæð믿ڔ‰ŒŒ´Ž"]{¬¿ÿþ{^}õU†
À¬Y³¬wî>>>DDDžžŽ££ãm·ÇÛÛggg&L˜À“O>yÝö…Ò¼ysk(mÖ¬íÛ·'**꺛ŒŒ|}})Z´(³fͲ®Q(Q¢NNN·Ý.‘ƒFä¾ëׯ£GfóæÍ¤¦¦²sçN"""ðóócÒ¤I$$$°|ùr,XpÓºæÌ™ÃñãÇÉÈÈÀÎÎ{{{rçÎMãÆÉÈÈ`ذa$&&’˜˜ÈŠ+صkÉÉÉ̘1ƒ„„ÃÀÞÞþ†s»·2vìX"##‰ŒŒdäÈ‘tíÚõ†e=<úè#Μ9ÃéÓ§5j”õX{xx°zõjÎ;ÇÑ£G­‹K›…œqqqÄÇdzråÊ[¶ÇÞÞž¾}ûÒ§OŽ?Njj*ûöícÞ¼y€í´@óæÍ			¡qãÆ,[¶Œ3gΰmÛ6úõëÇܹs±··ç믿æ½÷Þã“O>áСCœ={–µk×rþüùë Èƒôxžeä_•+W.‚‚‚lž«\¹²õN©cÇŽŒ=š7Þxƒ
*0xð`ëjì	&pôèQüýýY´h|ðeË–²ïþkÕªeSodd$:uâ‰'žàõ×_çǤL™2899±fÍΜ9C@@µk׿Ûo¿%W®\X,/^L:u¨R¥
à§Ÿ~Êñ½”.]š'žx€|ùòáïïoÝV·n]Þzë-zôèAHHÍ›7gàÀ@öÅéêHÁUÇÇÑÑ‘:uê0tèPFMÍš5­Ûs¼;|ñÅIOO§G·<öªÁƒS¨P!êÖ­Ë Aƒøàƒ°)Ó·o_ºwïNãÆiݺ5ýû÷àí·ßÆÍÍ€€ÌÈ‘#­Ÿ“K–,!%%…ºuëÀúõëìáúªU«Úìãé§Ÿ¶†Â±cÇÒ¬Y3:wîLÅŠyõÕWIMM%55•
6Ø,ü³X,Ì™3‡.]ºðî»ïR§N^}õU¼½½iÞ¼9­ZµbÉ’%lذ-ZȘ1c?~¼õ×.>>>øúúÞ‡#,rû,€1çÂÅݹÚåseöìٶϵk—ã­ˆÅb1ÃøWÚ%7÷î»ïrüøñë†Êï”Åbyäú»»;6l B…
º)¬Y³†Ÿ~ú‰©S§>è¦Ü•Út]x<åt]Б‡Dxx8ýû÷gÓ¦Müù矺9ò?
4 Aƒº"÷‚€ÈC¢`Á‚¼ùæ›T¨PÁæf´páÂ;þ¥‰ˆü3
"	WWWêׯÿ ›ñP¨[·îƒn‚ˆih± ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰9´Ëçú Û!÷Iûöíÿþ”q£²‹åþ6F	êº.˜‰€aÜðÚ "¸za×ùàñ¤~ s?ÐÔ€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚€ˆˆˆ‰)ˆˆˆ˜˜‚ÀCløðá|úé§dßûöí#==ý_ÛßÏ?ÿÌ+¯¼r×õœ;wŽÒ¥K߃ÉÃhÏž=Ô­[÷¶Ë·k׎¥K—ÞÇÉ¿á£>bäÈ‘7Ü~ðàA._¾|ÇõOœ8‘·Þzë†Û'OžŒŸŸ%K–äÌ™3”)Sƺ­mÛ¶,[¶ìŽ÷ý0PxˆÅÅÅ‘˜˜ø@öýôÓOý¯í¯|ùò4lØð®ëÉÌÌ$<<üî$¥´´4"##o»|tt4)))÷±EòoHHH >>þ†Û›6mʱcÇî¸þ¤¤$bccsÜ–’’B¿~ýøòË/9|ø0ùòå£[·nÖíCsxÐ
x…‡‡³nÝ:.\¸@Íš5ñ÷÷²/2‹/&""‚Ê•+d}ÍêÕ«yê©§Ø·o‡"00J•*Y·ïÝ»—õë×P±bE4h`Ýö矲{÷nŠ+F‹-ptṯ]†a°råJŽ9BÅŠozá4ƒåË—sìØ1Ê–-KãÆ±X,lذNŸ>ÍŽ;èÑ£...6íÉÈÈ`Ù²exzzR³fMŠ-Ša¬^½šÃ‡S¾|y5jd}ÍòåË©Q£;vìàØ±ctëÖ5kÖHhh(/^¤cÇŽ¸¸¸°`Á¢££iÙ²%ÅŠ `Á‚deepùòe–/_N£FX¼x1.\ U«V,X€+W®ðçŸrðàAòæÍKÓ¦M)T¨Ð­?Xy .\H£FÈ;7çÎcãÆ<óÌ3888pöìYŽ?ÎÓO?
@bb"Ë—/'!!
P®\9›ºâââX²d	´jՊܹsßtßk×®åðáÃR¡B.^¼Èºuë¨W¯Ë–-ÃÙÙ™–-[rþüy–-[FJJ
AAAÖ}oݺOOOJ•*À‚¨V­ÅŠÃ0,X@‹-ppp`ëÖ­lÙ²ªV­J:u¬mÙµk›7oÆËË‹gžy'''¶oߎ»»;‰‰‰lÚ´‰N:áááqoþcâèÑ£¬^½šR¥JѤI¶lÙBjj*«W¯&,,???òçÏÏ–-[¨U«¡¡¡dddбcGìííùý÷߉§uëÖ· ++‹ÆGll,¯¼ò
¯½öšõuýû÷§sçÎÌœ9“cÇŽÀÖ­[X²d	!!!œžþyÞyçâââxûí·éÔ©“uÛðáÃéÒ¥ãÆ#""‚´´4›×ÈÈHêׯςˆŠŠ¢C‡têÔ‰
6pöìY¨R¥
+W®$,,Œ:uêXË/^¼˜?þȾ µnÝš¯¿þÚÚæàààÀ´iÓhß¾=QQQœ={–O>ùÄÚ–ÿüç?tëÖ¸¸8æÍ›‡¿¿?©©©|òÉ'tïÞwß}—Ó§OsñâÅ{rÜ›6mbðàÁDFFÒ¯_?ëTÁ‘#GHKKcïÞ½lÞ¼™sçÎqüøq:wîL=8tè“&M¢C‡¼ðÂlÚ´‰uëÖQ³fÍ[N'¤¦¦²k×.Ã`óæÍ>|˜øøxÚ´i“cù¨¨(ªU«Æ¼yóˆçù矷ö‘‡!ÙÒÓÓ
OOOcÑ¢E×m›={¶Q¦L#==Ý0ÃˆŠŠ2œ}ûö†a+V4†j-?pà@càÀ†aÆ€Œ\WçÉ“'
gggãÔ©S†aFff¦Q¡BcÊ”)†aF¿~ýŒaƆa_ýµÑ¨Q#ëkSSSÂ…û÷ï¿®Þ?þøÃððð0’““
Ã0ŒäädÃÝÝÝXµj•a†Ñ°aC㥗^ºé±È—/Ÿµ]†a“'O6êׯo}œ––f+VÌØ¹s§a†áããc|øá‡ÖíÇ7cïÞ½Ö68::_|ñ…µL:uŒY³f†aß|óѺukÃ0ãôéÓ`¬_¿ÞZ¶B…
Æ’%Krlë;ï¼c<Ø0ŒìÏÅb±Üô½™
`Ìž=Ûæ?Ã0Èé¿ûy>xóÍ7×_Ý0ÃhÞ¼¹Ñ¶m[cĈ†ad÷ɹsç†a!!!ÆW_}e}Ýš5kŒ'žxÂ0ÃØ²e‹M¿ÊÊÊ2j×®m|÷Ýw9î3  À¦¯Ïœ9Ó¨P¡‚a†qäÈ0¶nÝjÝÞ½{w£oß¾ÖÇ3fÌ0|||Ã0ŒuëÖåÊ•3Ã0&Nœh´mÛÖ¨^½ºa†ñé§ŸZ÷Ó±cGcäȑ׵eïÞ½†§§§‘””d}®eË–ÆÄ‰­¯kÑ¢Åà]zXúÁxë­·???#++Ë0ÃX¹r¥Q¦LëöâÅ‹[û„aƶmÛ;;;#<<Ü0ȉ‰1cúôéÖ2מSF}ÃsbXX˜áââb}|æÌÃÎÎÎú¸N:Æÿû_Ã0£W¯^ÖsÑÕ׿ɓÇHKK»ã÷~¯åÔ45p'N@³fͮ۶mÛ66lˆƒCö!+R¤¾¾¾ìÞ½›Ê•+Ø,b*Uª6l M›6<ûì³:tˆ–-[Ò¾}{
.Ì®]»([¶,%J”ÀÎÎŽF±sçNzôèa³ÿ7gsn°™‚¸ÚÖ€€\]]puu¥nݺìÚµË:P¯^½tl6lØ@RR’Íþ³²²8pà~~~9Öéä䄯¯¯µ
W§®=FQQQ9îÏÎÎÎf8õÚ²çÎã?ÿù›6m"==äädþÑû‘_pp0C†!==]»v±páBÀÛo¿ÍæÍ›™3g}ççââ¶mۀ쩠°°0ëÈ•‡‡‡µ_Y,5jÄöíÛéÕ«WŽû½v®qãÆtîÜÙ:§ëäädÓ'·mÛÆ¨Q£lÊwéÒ…„„üýý‰ŒŒ$22’U«VÑ·o_^~ùeâââXµjÏ?ÿ<;v¤k×®lÙ²…-Zо}{ÜÝÝÙ¼y3ŽŽŽ0ÀZDD°>þ§ßK3	°No–*Uê–k˜¼¼¼ðññ±þíââB­Zµ¬Ûovþ¹S6l H‘"6çÉ+W®pêÔ)Ê–-{O÷u/)\ÃÞÞþÚDlÃÎÎÎ:‡}•aØÙý5»’+W.ëß‹ÅZO`` 'Nœ`ñâÅÌ™3‡aÆqàÀììì®ÛWVV–M×ÖW³fM›€Ð·o_›Õ«7«÷ê¼äí²X,T«VÍfeß¾}­ó¥ÎÎÎ6¯¹𮲳³³y.§czí6{{{›ý_}Oýû÷§páÂlݺ•yõµvvväÊ•‹zõê±|ùr6mÚÄŒ3

bÙ²eüñÇ|ÿý÷<÷Üs;vŒ…ò믿òþûïsèÐ!Š-Jß¾}möéééiýûïß!ùËί7ò÷óÅb±yîvêø§,Íš5³®uìódÑ¢Eïé~î5­¸FÉ’%)T¨óçÏ¿n[:uXµjW®\²“üþýû©^½ú-ëÍÊÊÂÃî]»²`ÁÜÝÝÙ¹s'Õ«W',,Œ“'OÙë–/_n“Z¯
dûöíÔ¨QƒÚµk[ÿsww¿®¬¿¿?6làÂ…@öŠÛ
6ØÜùÜŠ‹‹111×í¿Zµj6û¿º€ïßtäÈÈ“'½C~...Ô®]›áÇŒÅb¡^½zŒ1‚àà` ûDZ¿~}Ž=jÓÏjÖ¬i
†ñññìØ±Èþn-[¶ì¦}{ùòåÖ¿CCC©T©Ò
úûûj}¼páBÊ–-Kþüù¬ëtž|òIœ	f̘1/^œ"EŠXÛT¸pazõêŲe˸téGŽ¡^½z=z”bÅŠÙ¼·’%KÞùAëÔ-00}ûö]×w¯]Œý0ÒˆÀ5ìíí™û,Ï?ÿ<Õ«WgàÀtéÒ…Å‹S£F
š4iBrr2kÖ¬!44ô_ÿÝ~ûöíyã7ضmû÷ïÿW÷-w'88ØæÂÌo¿ýÆøñã­e>ûì3š7oÎÞ½{)W®œ;wŽ•+WÙ¿0yçw¨Zµ*Û·oÇÉɉ_|ñ†ûŒŠŠ¢}ûö-Z”3fX%ædèСÒ¡C<==ùí·ßlÊóÖ[oY§‚ƒƒéÚµ+ýû÷·–iÓ¦
®®®”*UŠ­[·Rºti|}}É;7ï¼óþþþ´mÛÈžŠxõÕW­Ó
rgZµjÅË/¿L­ZµèÓ§ùòå{ íxÿý÷iܸ1!!!T¯^³gϲ}ûö‡þvµ?dff²~ýzN:…‡‡O?ý´õºð0È©(ˆ<æå€Ü;ê9÷­11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11s°X,º"òÐù@@ýÀLðù´çÌ™c<¨ÆˆÈƒ¥ó€úÙhj@DDÄÄDDDLLA@DDÄÄþ±æT´ÑÂIEND®B`‚urwid-2.6.16/docs/manual/images/introduction.png000066400000000000000000000617451470350774000216650ustar00rootroot00000000000000‰PNG


IHDR°ØŠž¶©sBIT|dˆ	pHYs
×
×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwTWÀáßR¤ Ø»Ø(Š ¢bïŠ=jì%±7Œ%Š5–Ø1Qc¢Iì]ÁÞAA”"bEQAÀBïì~ð¹JlˆÀ²zŸs<ǽ÷Ývß½eîH‚ ‚ dÔd2‘ÃAå!‘HPQt‚ ‚"	‚ JI$0AA)‰&‚ (%‘ÀA¥$˜ ‚ ”DA”’H`‚ ‚R	LAPJ"	‚ JI$0AA)‰&‚ (%‘ÀA¥$˜ ‚ ”DA”’H`‚ ‚R	LAPJ"	‚ JI$0AA)‰&‚ (%‘ÀA¥$˜ ‚ ”Ô€ $‰D¢è”šL&St‚ðA"	_½(¯CŠA)™ØuWt‚ðQ¢QAPJ"	‚ JI$0AA)‰&‚ (%‘ÀA¥$˜ ‚ ”DA”’H`‚ ‚R	LþãÈ/.øø¿³="ê9‡ÏzòüUÜG_õŸÀÛŸUgÀPn…>ü¬×·N$0Aø±ó×0ð§ÅÄ'&çØ¾~ça~p^ɽ°Ç}}pèCöž¸ðYunw=Ã᳞Ÿ« |ËD„÷¨Y©<'/ùÈK¥2N_¾†©qÉwö}O–T*ܦICVΓc©TöNBü\z½Löá²3³²HIMû¢z¡¨k!
Â{tkmÛy/úth€—ÿM,jWçVH˜|Ÿ­‡O±úŸ}èéòàq³GdTß.¹à…ëÙËüõË4†ÍZFéR%8qчäÔ4¬jWgë²Y¨«çþ£wÞÛŸ5ÿî#úE,‰É),súN¶¬þgûOzœšJ£’l˜?…Ц&Œ[°	—®!ABk;+~öcþ$AP0Ñ„÷hikÉõà{ò–ÍÁ3—èѦ©üù„¤d6îvãøŸËpß¶†]«æ°tÓN¤YR223HÏÈäúÍ{xíþ€Ãq7ì	ž~A¹Ž#úE,#g/gêоxï]Ïæ_~bÌüÕ<Šaÿ)ö¿À¡ßqíà&l-j3a¡™™‡<ÄcÛZ»ŽË¯Ã#E‚H`‚ðÅŠÑÒÆ’“—|ÈÈÌÂóÚ
ZÙZÉŸ×ÓÑÆc»a#ùkß1v»@RJ*Ré»·éÔ¢1ZšhijP«Jy^Æ&ä:ŽÀ»÷)UÜ€æÌ°17£JyS|ƒîrùz›Û`\ÒU9¶ÃçÆmÒÒ3h߬úºÚhkiÒÑÁÏk¹Oœ‚ D„pü7¢»?M¬êæèö{—@óï'°vëÒÒ3èØÜï&°·ÇÍTTT޻χ¤¤¦a¨¯›c›®É©iêëÉ·ëéh£ª¢"¿‡—q‰âòçtµµä‰M¾"	Â84² àv(ÛÜÎÐý­îC/ÿ`ÔÕÔØ³f.c8¢ª’ýQJÏç$aVµ"AwȧîÇ%&át‡:Õ+S»Z%Îy]—ï{ÞÛÊåË ©Q€K×nÈŸóð
IJvõ|MMLâ„PWW£¥­%ç½ýi²dzŽçš6¬Ï”%¿3cÅ&ŠS'äác´µ4yŸ˜çúöŸºˆoÐùãš•+°Ôi#út¦å Étr°åÄÅ«ŒØêË2¼WGŸõ¤ÃÈéÔ¨TŽSž¾üýVœ¾AwøÞi™YÄ&$2¼WÇ<Ç&E‘‰Û†_+‰DòÙwdö¼…yͪhijðìùKbã©U¥·C©\¾º:¤¤¦qÙÿ&ÚššØš×&èÞ*šš%•ó2–ZU*pçA8%
õ1*aðÎã×î‡GõüeŽmzºÚÔ«QȾ¶,ìI$õkT¡‚©‰|ŸŒŒL¼o‘–‘A£zfèëjðƒóJ[Ô¡FåòX׫…ºšêg»îˆï¡¨’H$"	_·¼$°¯ÁÎ+±³¬Ëàîíò\†H`BQ&‘HĘ |ì,ëR­¢©¢Ã„%ÆÀá+ô%-/AP¢&‚ (%‘ÀA¥$˜ ‚ ”DA”’H`‚ ‚R	LAPJ"	‚ JI$0AA)‰¥¤„¯šD"QtJM|7E•D"+q_7ñ,_/Ñ…(‚ (%‘ÀA¥$˜ ‚ ”DA”’H`‚ ‚R	LAPJ"	E–§§§¢C¾8‡BA	L(rŽ=еµ5“'O&..NÑá˜yóæ1oÞsçÎÅ××E‡%䑾¾¾Ì;—ùóç‹D&ä;‘À…{_âêܹ³¢ÃòIçÎE"
„H`‚ˆÄõm‰LÈo"	…N$®o›HdB~	L(4"q	o‰LøR"	N$.ácD"òJ$0¡ÀˆÄ%|‘È„Ï%˜ïD⾄HdBn‰&䑸„ü$™ð)"	_L$.¡ ‰D&|ˆH`Bž‰Ä%&‘È„ÿ	Løl"q	Š$™ðšH`ÂEFFrøðaùc‘¸„¢äS‰ìðáÃDFF*0B¡ I™L&StBÔ¦Mž?΂X°`sçÎIë………±uëV.\¸@‹-4h•+WVpdÊíèѣ̟?gggœ)UªgΜQpdBAH$"	ï·~ýzœœœHKK£B…
¬[·N$®|’ššŠ¹¹9÷îÝ F¢©©©àȾGeüøñ„‡‡£¡¡ÁŠ+3fŒ¢Ãò™D"]ˆÂ»¢££Y¼x1)))H¥R

éÔ©“¢Ãújhjjâää„¶¶6ÚÚÚ899‰ä•:uê„¡¡!R©”””/^Ltt´¢Ã
€š¢Šž´´4–,Y³gÏˆŠŠ"66–'OžP¾|yE‡öÕ9r$ÿþû¯üÿBþyòä	
4 U«V˜˜˜PºtiÒÒÒ–PD¢ (ˆ666
ŽD”A”’A”–H`‚ ‚R	LAPJ"	‚ J高FÍŠ•+ñòò&0ПĄE‡ôMÐÕÓÃÜÜ;;[œ¦NÅØØX¡ñDGG³rå
¼½½ð÷$!!Q¡ñ|-ôôt±´4ÇÖÖŽ©SŠÄy^µr>>^øù/ÎsnèëëbeiŽSŠÀyÌJ?q×®]L˜0	Û–]1·v š™†%ŒÖ7!öe¡·ôuÇû¼..kèׯŸBbÙµk'M cW[ìÌ©oQRF†
‰åkó<&–¡xºrÜÍ›µk\zž'OOG[Ú¶¨‹µU5LŒ‹²‰Š‰Ã×/”Ón²÷°7«×¬SØyÌJ?~×®]8ý4“Éó7QÍÌBÑá|ÓBo°zî(Vüº¤Ð?»víbÆL'ÖnšL}‹j…Z÷·æF@(G­fé’
9Ï?ÏrbÏæñX[V-Ôº¿6¾þ÷é;|¿,.üó˜_”:EGGS§N=¦/Û&’Wz;€eÓThÝÑÑÑÔ­W‡MÛ¦‹äUHn„2jà2nêy®_¯6Gv9‰ä•O|ýïÓ¥ß
nÝRÊîD¥¾lÅʕضì*’WRÍÌÛ–]Y±re¡Õ¹rå
:vµÉ«Õ·¨FÇ®¶¬\¹¢Ðê\µr}mEòÊGÖ–Uéãh˪B<ùMi˜——7æÖŠCøsk¼¼¼­>oo/ìÌ­>!›½ƒ9ÞÞ^…VŸm[Ô-´ú¾m[ÔÅǧðÎc~SÚè/Z_EP53ý­>ÿ@ÑúR€úÕð÷,´úüü±¶ç9¿Y[UïÏc~SÚiô‰		mþàáî ¢ªŠžA	jÔ¶BCSK¾×…#4²ošºúg×/ø:ÅKabZñóƒÿk—Ï •fѨiûÛãc_pãÚ%jÖ³ÆÈ¤l®Ë{ùüO…R¯ýG÷ó¹x‹F9ŽÉ—2,aT¨—1$$$~r¶all">—oÒ®“­|Ûý§Ü¹FÇ®MH$<~EÄÓçT«QŽ»·a×´þ;eù_»K‰’úT¬\&Ï1gef¡ª¦úÎvs~˜Õ­Œ±Iñ<—ý¥²²¤¨¨HäÇäCJêe
ññ‰¹šmø*6‰SçÞû\G;TT>þ¾ò"33µÿœÏW±I¸{Ó½s£|¯/?™(õeJÛû”Ëç\Ùúû.9Äùc{øÇe.?ôlHÀUwù>;7-#=-%OåÛ¿™ ë—ó%Ö¿VÏbÉôÁDE<ʱý쑬tþ;7®~Vy¡·Ù³ùÓýÚë—N%!îÕg•­ŒÔÕT?j1ѱòmëVîaêØµÜ

“oûçÏ£\8{Ga‘ìøçä{Ëú÷¯cxzäý«ÿõ{Ìž¶ñ½Ï-™ÿ/Á7ä¹ìüЫãtRR”÷Ö#ãùþ‡uì>èõÎ?©TšïõMŸ·ƒSçßý{xð0Š‘“þÈ÷ú„œ”¶–õ­›3nÖjùãÓ®ÛX»`ëvz¢«oÈo»^7éß}	ñÉ|×íg:4ŸDk»±<
‹”—éãu‡F?âØÆ‰Es¶ÐÎ~¸pbk¶¹óÛ./ÒRßüÂÏÊÌÈÑu’™™ùNWʃ{Alÿc	k¶¹óÇk´èØ—µ³[‚·º0tÂ|Vo=ÏÌe[¹qíÒgó¢ I3s®]ÍN`ÏûѬ…%
m̸FbB2¾Þ·hШêH¥222²¿P9o¡R•2¸ûnä¯í³¹ü€ôôÆXΜEÃ9幎ªÕËò™LÆ€PÖ.ßÍIµx\ûƒ®=š2m‚eLK1uÖì›[°hù¹ŽýVP×|náqõÎ\þqSú°Áåê´é`ƒëþ‹@öXŸ§Gm;ذláV^¾Là¸û.^ßÄËñü¾z_vìi¼}éçëÇ¿®ªª
û/¥d)å]Ý"=#Çï—çø·pùú÷²gÛž‹ò}ÿÝíÁ÷}šqáR0ë6 äša¿Ó¶¥9?NÉþñ™‘™Åù‹7¹éµ’§Áðâe"GO]gìˆvØ6¬ŽËÒ¡tlc™ëø&Ìø›ÌÌ,‚~@̳'X4røPPصìŠ×ù#\q?Jã]äO?¼U7ïdw9V7³ä¦ÿnúyaní€AñRhhjåx]nø]9O)£2\<½ŸCÛבœϽ`?’ã¨ß°›×ÌaÓÊ™¤$%0rê’Ï*»¨°lP“Ga‘$'§âqÞŸf--QWW£¡×|nã㌽û³Z½=ƒèìØUU*U)#ïZ
CMM•–màØÛÿwyœó£Œi)íóàu‡HJLÁÏ÷.ÉI©yнNý*lÛ?ŸËÙôÛ!<ÝHˆÏþ‘Ò«_KÜ^D&“qôÐ%Z¶µFGW‹+žAôìÛMÍbèèjѽws._¼‘§ú•ªª
Cú9äø×¦Eö„œïz4á¬Gñ	)\ö¹‹ž®&Væ•9~ÆreK²yûy–¯s#--ƒ³7ÈÌÌ [GkôtµPWWżnEž¿ÈûD¥—‚6 %êèп—=ç.|ç	R©ŒÞÝP¯v7ª»ç-LK§uózèe¯ÿ}’’ÓhÙ´.¦oaüô-$§¤ã²tè—>¥òM%0™LFxØÝwÆ•ê5°gùæ“”¯T·]ÓÇ–èÈìnÃo®P×ÒÑ#++“¬¬,ù¶´Ô¦hÏö‹ILˆÃ®eWd2Èd¨ª©Ó¼]O.ž>ˆûÉ}4o߉ʇy“V]¹|Þûw1*]}ÃòçÒÓRÑÕ3Ìñz½ìþîÔ”$tõÞüb.i”s†\ŽØ{ÆÉ’“âÑÒÑCG×]Š—0æÇi¿"AB¯!“˜º`#êêêløÕ‰™?tFWnQSWÅʺ^oððAõ-ªÐÌÁ¿kwñõ¹EÓ÷$°¤¤TßÃ*]&ûœ¨¨HPSS“ÿ*¦®†šzöØiB|zúÚèè o ƒ±I	,û!ÏÇíέ‡´°Í=ÐÖÑÄÞÁB^–…U
4µ4ðó½Ë¡½îôú®=.gXü͘­ž¾oÿl{ûoâukók¡ª¢‚c'ëÿlfŸo}=-:µµb¿›7Ûö\dØ€–ÄÆ%c¨¯¡†:Tª`ŒËÒadfe§r¦%åå«©©"•æý3œ’F‰âoþ¦^©%'§a ¯ªªÊ;Ï”6y36­­­Déé™ÌÞ››& ªªÂˆ	iÖi®R~Fóâ›J`®»6”‹íÿ[1¯;º“;A¾ô2‰å[NQʤ,wo^àN/éiÙ¿œ}=¨T­NŽi÷÷ïò"&’_6¸ÒkðD4µ²ú32³h[uîÏÕ‹'¸â~”–ú~4¾jµ,HJˆãà¶uït5–¯TƒÈ'a<{ú0»üô4‚®{R©Zª™Yä˜]týM7Ÿ†–6¯þ?n'•fÉ»OßV©Z¢"ÃiÝuíº¦Ž¥wƒ¯£¥­Ëæ5³ÑÐÒfè„¬ÙæÎƒ»AïTMšÕgËGhl_O>ºiK®xñ"&Žšfï^Qß²Ýߌox_¾™½Ý¢2™Œ[ÿŸôàs%˜¤Äì­fu*ñ8<š¾ß·¡ÿàv4´1#Ð?mMÔÔTå¿ês븛Í[Zá²i*ßí@rRjޤӻ+þýë/žÇac—}±o­Ú•¸pöš|Ÿg¯cV§2Ú:DG½àé“yk@UU•¬ÌüŸ­W”íß‚û.ávâzg_jbQ¯áOžóãÐ6ü8´
Væ•	ºþÞ	oSSS%+ëóŽWý:9vÚOþøø?ÌëTĬfYŸ<çNÈS ;9u¼nöße@ÐC^¼Ìnù¹{S±¼Å
u?}úÚ¬Y<„ÀK˹êJTÌ×9†ù__õ,ÄëWÎ2{lw²23xñˆbÅ4˜¹l«<ɼVÛ¢1?îÆ½`?ÒRSHMIÂܺ9·® ©¥Ó°6Ô¬ÛŸ‹'˜»zOŽ×Ö¨ÝUU~ûeúÅKòà^ú†%Iˆ{‰‘IY*V5CM­šÚ”­ðé1íZvåèÞMŒûyMŽí%Mé3t
ÓGv¤IË®\¿r–VûS£ŽUkÕçÒéƒLÙÓŠ„…ÜÄÀ°ìÛ±xú`b‰zúè½×|5míˆûÉ}LÐŒ[qõÒ)z™„DE…êµ­X1{6ÍÚLÃ&mÞiá)‹&ÍÌùÅùo–®~3Ó³rUS¢Ÿ½Ä¦ÉûWy˜½`ßuû™!O	
y‚ÆÿgóI$&ýô}ºÌ²aM´µ5ÑÔ,F1
uºölÎáýth>f-¬8{ò*§õE"‘P®‚1W<ƒøuÑ6~š=ðú–ÿ²?×’?îìØ”6í1ä»,[¸•¸W‰Dó¦÷¦iGgú8Úqò\ß÷iJ›š¸{ÞB_OûÎ4³3c¿›7·:ÐȪ½†¬Â±£5~7ÂèÞ©¥¿;1(íb¾‰„C^Q|>:ò±¼PEU…â%Œ)]®rŽñ¯[ÞÔ¬ÛUU522Ò¹ä‹4+‹šu¢©¥·û1ŽØÂèé+xò0„Zõ¬Ñ3ȾÈôÉÃtõ0,iLFF:·ü¯ QQ¡Ž…-†`XÂH~¡µË¢	˜Õ·¡M×ïõ^°e+VEG×€øØ—Ä<{LÕZÙË#=
¦xIŠg'¤ð°»<}B¥ju(S®²¼™TJÈí’“â©R£>/b"¨\=ûK9òIBoQ¹F=$	ªjj”4*à _ªÖ2G]½2™Œ»A¾Äǽ¤¶¹mŽ©ôI‰qÜ
ðÁ xIª›Y~´ »I¡uaH$îGúôŽÿçãLÝúUÐÑ}“ÈïÝ	GO_›2¦ÙÇ8>.‰'áQÔ®—ÝÕœœ”ŠW0eËQ¬˜:ZÚ”*e@ø£(uIˆO¢\,ª
  tªª*Èd2®_½CllÖ6µsL¥
%11…ÆöõrÄv# ””äœãd¦e(_Ñ„ØW	øzß´¬uêWÁÇ+˜ÖµPSÏîÎjÖ`»Ý~¡\ù7]Þ±±‰\¿z}},Ô@]ýÍïU?ß;¼z•ÛãGQT­QMÍbÄDÇr# k›Úè¿g*ùÛªšt/Ôó,{¹÷“û%&¥rÍÿþ{Ÿ³¬_ýì¯wBž¢§«EÙ2oºé¥R—®Ü&>!…fvfÜ÷íÇÉ)ix\¾Eª¦T­lòÉ8¬­ª¡£­AÌóx®øÞ£TI=¬-«¡®þæBè[wŸpûÞSêÕ®@ªÙ?/\
fꜭÚæD@ÐC[×Àø­»_Å&áé}c#}YUû¬©ô’}”²ËQ©W£×Ó×gÝ®+zï¯×	lÁºyz}TD8w‚®²mÃ/ü¶ëò;-¿¯QìËÆ÷kLB||¡Ô§¯¯ÇÙ+ërﯮm¦2tTêYTc϶3„Þ{Ìß»5†›÷9yì
÷n‡³iÛ¬B«÷yL,­'>¾pV]10ÐãžïêoöÞ_¯˜Ÿû²|-7*&ŽÖ“‰‹S¾›+õjôææ–„Þ~ÿ’1ùŨt9,mZäùõÏ£žpéô!¦ÌßðM$/Ⱦ¥Š¹yî§)KKsn„Z}oûuíxî?äç-hjcÝŸN…ƒ·×MÂF±xÕ˜B­÷F@(––…·ˆ²•¥9¾~Š9ÏEAiCºvh˜ïåúú…bUˆç1¿)í˜-¾î4lÒ¦Àê¨ZË\Þ•—u,í¨ci—}¾îØÙÙ~zÇ|bkk‡§{ |:{aªU»3ç
)ôzß6bt7…Ôë鈭máýmÛØØqúÂM:·kPhu%f5hø' IDATÊ2ozï|/÷ô…›ØØ(ïw”Òv!ŠZ=↖ßqC˯ƒ¸¡¥ãⲆÕsGxW¢ði¡·X=w..k
õÃ`llÌÚ5.LµZa]‰ß’¡Lµšµk\
ý<¯^³Ž¾Ã×áûIBîùúß§ïðu¬^³N)“×kJÛ…Я_?&LˆmË®˜[;PÍÌ¢@'voľŒ!ôv¾îxŸwÃÅeüœ¦×uŽ8Ž]m±w0§¾E5…Lìø=‰åF@(žîwófí…žç.ýÆÓÇÑ–¶-êbmUí›Øñ¹¢bâðõåô…›ì=ìÍê5ëró“Òv!¾-::š+WâååM` ¡Þê[¦«§‡¹¹%vv¶8Mªð_rÑÑѬ\¹oo/üýõ~U_3==],-ͱµµcêT§"qžW­\~þJ}?«Â¤¯¯‹•¥966vL)çñK)õ4zAáÛ¥Ôc`‚ ·M$0AA)‰&‚ (%‘ÀA¥$˜ ‚ ”DA”’H`®è„",.Nñ7
,
1|«bcc‰Uhßúw”H`àéé‰RÿÌ›7yóæ):Œ¯VëÖ­ñôôTXýžžž´nÝZaõëÖ¬YÚ5k>½c	ÇÆÆF¡ƒŠ&Ø{xzzÒ»woöíÛG…
ŽP=z{{{…Åðºîױߖ
*°oß>z÷îýÍ&1‘Àþãíä¥È/'¡h›?>sçÎUtÌ;—ùóç+:AAìíí¿é$&Ø[Dòrãu‹§sçÎ
ŽäM¢öíú–“˜H`ÿ'’—[E¥õõšh…	ßjû¦ؾ}û‘¼„Ü+J­¯×D+L€o3‰}³	,66–ñãÇÓ¿‘¼„\+j­¯×D+L€o/‰}³	ÌÍ͘˜vïÞM‹-Dò>©(¶¾^­0áµo)‰}³	ÌÕÕ©TŠD"áüùóòîDAø¢ÚúzM´Â„×¾•$öÍ&°¨¨(zôèÁßÿÍ;wèÝ»·¢Cа¢ÜúzM´Â„·}ILMÑ(Ê×zB…‚QÔ[_¯½n…åD+ž·“Ø×8ÎÿͶÀ!·”¡õõšh…	ÿõ5·ÄD„OP–Ö×kb,Lø¯¯5‰)]KLLäÞ½{¸»»³sçNV¯^ÍæÍ›9qâÄÄÄ “ɦ ä?~ŒL&SªÖ×ko·Âd2?VpDBQð¾$¦ì“×$€¬¨~á¿zõŠãÇsð+<{T*¥”qJ–*M	£Òè‘’œÈ«ç‘¼|þŒçÑÏHJLÀÈÄ„Š+Ñ¥SG»aff¦è·ShÂÂÂØºu+.\ E‹4ˆÊ•++82å1bÄ®_¿Nll,...téÒEÑ!}–#GŽ0aÂ

iРýõ—¢Cúj(ûçëõÂ
-Z´àüùóܹsCCCE‡õÙ$IћĆ«›&Àß‹FÍhФ=-zN¤D)´uõ?YFFF:¯žGùøÞWΰî÷vhjhн»#Ý»agg‡ŠŠÒ5>s­L™2ìܹ“{÷îÉôéÓ•r144$  œÉÊÊÂÑÑQÑaåÊáÇ™?>ááá<|øV­Z):¤¯Š²¾ìííiÑ¢»wïF"‘àææÆ AƒVž™oq777Ì-¬hаÇÏùдÓ0¶¹ÉÔ…›qhß›r•ªç*y¨«øLyÌ5gèÄE¬ßwqÎyú
†Ž‘±	óæÏ'))©€ß•bhjjâää„¶¶6ÚÚÚ899¡©©©è°”Љ‰	”*U
G”{666”*U



àÍ{ò‡²¾öíÛÇùóç‘H$H¥R\]]Rž)¼ÑÛÛ›)S§ù,šïFÍÂÚ¾-**ªZgäãìû{·¼˜7o.#†GM­È5F¿Øë)³_Ó maÙ¶m3gÎdÖ¬YŒ3FÑáäÉúõëY¼x1K–,aàÀŠ竣̟¯ØØXÜÜÜpuu%**J)߃D"Q\		á§é3ðòò¦×P'Zuþ®À×=¸{ƒ÷"’å¿.¥{÷î…ZAóññPªÖCQñøñc444066Vt(_$::š´´4Ê—/¯èP¾:âó¥X
I`2™ŒÙsœY¿~=ûþHç>£ÐÐÔ*´úß'àª;;7.¢T	öíÝMÙ²e ‚ðq…žÀé×á/˜²`ÅKJ½¹!“É8²g#'÷ÿÉ¡ƒį*A„"L"‘Þ$ް°0¬Ù’¥^çÕ{ŠTò‚ìƒÑõ»ÑŸú+;uaÛöíŠIAøˆBiyxxЧïwt8‰ö=†h]ùáqØ=–ÍD¿ïz³lé’¯zʽ ‚2*”.Ä;v2qò&ÎÝ@=«&VO~Kˆ{ÅŠÙè`ZŠC|•³AÈääd"""ˆˆˆ 22’/^P²dILMM155¥L™2hkk+:ÌoJ'°Ë—/ãØ½'óÖ¤l…jRGAÊÊÌ`éôA4´¨Å†õ¿QY‰$Ÿ¢*šòëoèk?NŸò©ãøµŸ¢²*Ppp0‡ÂÍõwï…––Ži™R˜–.AÙ2%(Y\‡ç/‰xöŠˆg/‰ˆ|ކF1ÌjÕ k·tëæHíÚµý6¾jšÀÂÃÃidcËè™.˜[7Ë÷òKrb<3Gu`æt'~üñ‡<—#‘H8ä•‘ÝíLò5Ý:”/e)›ª&Ýs•À¾Öã“›÷_®]»Æž=»8|è ©©ÉtëÐn­h`^…Åu?ùú—¯¹ø€ÃÇ®ãzâÚÚº8vïAŸ>ßѰaÃBxß–K`ÉÉÉØØÚa×¶/zÏײ!òñféÊÁûhÞ¼yžÊ	,w¾æ/èO	L1	ìöíÛ̘îD` ûØÓ­CZVý¢2e2×pø˜/;\ÁÜÜŠ¥ËVP«V­|Š:¥¤¤pñâEˆŒŒ$2â	OˆÇÄÄ„2¦å(S¦,¦¦¦Ô¯_KKKE‡\0	L&“ѳWoRe:Œš¶<ßÊU´À«üöË8®úxçiÑN‘Àrçkþ‚þ‘À
7EDD0wîlޏ¹2}bÆk‹††z¾×“žžÉúͧYºöŽÝ»3oÞBJ—.ïõ|®èèhŽ=Š›ëA<<.bi^•j•M0-­ié☖.޾ž6Q1±DD¾""*–ˆgq\ó@bR:]»u£kWGZ´hA±bÅ
=þYÌwÉҥ܉ó꽟Ü÷N/OàyôSÊWªIûC0*].¿CÊæšãøý:uîJ`€êêùÿ‡.BáX»v
¿,ZȨÁ-¹ç»}½‚[L¡X15&îÈÐ,YãJ½ºµqž;ñã'XóðáCæÌžÅÑ£Gi×Ê’¾],ù×¥7ú¹Ÿ„rï~$®Ç¯±pžßxÊô37n|¡¯	™¯óÃ###Y±b%æüŽªÚÇ¿à}/²tÆKÓ¢c_R’qÖ†èÈ¢{ï¢N½G¢­_Šõë7(:Aò --¡C±kû&ü=³èç>š¼Þf ¯ÍRç~\;¿ˆ·üÎðaCHOO/”ºž?ÎäÉidm…Y	·7°û¯qôëÙä³’@ªe˜6¾žÇ¹|rW.ºR³F5þùç¤Ri½ƒwåk›ã<—Ö]¾§¤±é'÷½rá-:ô¡ÏÐ)4vèÌ(§¥ÔkД ¿7‹J¦¦$áï}žÈ'aòmÃî‘Ëu¯3ÄǾ >ö%W/âV 7²ÿ¼'C¸â~”ˆðûòmñ±/ˆŽ|Ì‹˜H¼=Žç(ÿSŒžÃÂE‹ˆÏõkAP¼gϞѡ)éÉḙMÙ2%GÅòF\:îLbì}ZµlNttt×¹k×NjÕ¬NVò}n]YÁ¬ÉŽhiæO·_ªe8ðïDvÿ5šÍ›VÒÀÊœäKÙŸ’oc`wîÜ¡i3Ö¶ŽÞ'÷?íº½[VÒsðDš¶îŽ®~Ϊ……Üdî„^X4r &ê)*×dôôÌÛŒôT233iݹ•ªÕaùì‘ÔkЄG÷ocXˆ9«v¡ªªÆÞ-+9í¶K®^:I¯Á“éÒwçŽîäøþ-H¥R*V5Ã×ó“æ®ÇÚ¾m®Þëo‹ÆccQ_~Y”ëã#ÆÀrçkãù1Vpc`~~~8vë¨ÁÍ™=µè,Ú½`ùAþÞéÉa×£˜››ç{ù2™Œ9s~fïîmÚ6™:µ
~Qç¿¶ÇyÉvîÚ‹ƒƒCÕ“¯c`Ó~šã÷ãs•¼ÚvˆL&ãôá­l^=›êµ-iÕ¹?­»ô`ã¯?ÑäÚ÷BFz.‹&œ˜Ýê©mјÁc³ëÖŽ~#§Ó¦ëd2Îã{páø^ê5°çÀ6þØï‹aIcü0‹Ñ½Ñ´uöM	?¼ÇÖwÐÔÒf÷æå\¹p$×	¬ïÈLÚŠ±cÇ`júéÖ¦ ŠóèÑ#:ulËÒïéÝ­±¢ÃÉÁyZjU/Cûvmðöñ¥bÅŠùVvRR¿ïOBìc|Î,¤¸¡N¾•ý1#¶¤VuS¾З9Îóùᇬ®|I`—/_&àFk¦}ÞžíÑÎq1QO¹vù4;7-%>îÝú&,ä&Sæg5©Ó`ê‚?䯫Z³>}çåû÷nàqrW.àyT!·üÐÒÖAUU
—Eo”J
 ¶¹
šZÙý¾¥ŒËòèþí\ÇmdR–Vû3gŽ3›7‹[µBQ•œœŒc·ÎLŸÐ©È%¯×ú86æiä+»uæ²—O¾¬è‘””DófM°kXžU›~BM­poUeo[¯“óè:`‘‘Ì›· @êÉ—6oþBúûé“7Þ¶`òw7—ŠUÍ02)K‡CÑÑÑç´Ûvº÷‹L&%9)A¾ÿÃÐ[˜˜V ˜FöL‰	º?–Òe+É÷ÕÖÕçö«è–`ø¤…9ê-ilÊås®h½ÕRÌËâ=Mdl.\ Za‚PÉd2úkS&î¨èp>jòèŽÝzÂàAß³wß/ZqE&“1ðûþ44/ËÒAùåç©P®çÿL£Ös¨U«6ß}÷]¾×ñÅ“8âââðññƶùçý”0*Ãæ5³ydOظ|ÞjµÌ‘¨¨PÇ¢1^ÿoUŽzÎÂ)ýJ³r”¡ª¦Nm®_9O™òU(]®2ë~™DÈ­jÖmÈ«Ñ<Š lÅê,˜ÒÌÌŒÆû2Ç„ÑÑ5 AãV¸¹¹}Öû¡p,Z¸€çQøý×¢¿€8ÀƕÈ|zE¿¬µ2ož3/Ÿ?dÝÒ!ùØ(Q\×S˜4qׯ_Ï÷ò¿¸vìØ1ÌÚ£^Lã³^÷ƒÓRþX>ñý›¢¦®NFz:MZv¥ÿ¨ÙÏOû•_g
çò97444<Ö]ƒwÊ3c+àÇ^(¦¡E]ËÆ4jÚ€‰sÖ±rî”4*ƒT*eÀ³ÐÕ3|§Œ·yž=Ä…ã{Y¾åÔ'ßCûö8¸Ì¿>^™TÊãG!—./ïâ>L&“ñ<&ŽRFïüjMNNE&•¡£›»iÒ©©é$Ä'Ù­ò%
PQùºÖLJL!99í½Ï×E]ýëX´ÚÏÏM›Öà±uõ¼wŸeffñòU"ÆFo¾{†Ç £­Q)} ûo0ôÁ3ÔÔT©\1ïwð.VLƒÿN¼Ù:wéš§Õ.8Àö­[ð9³ð‹Þw~ªS«<®Aî]ñ½æŸ¯w9ÿâYˆ=zö¦œYSZvÊ[óP&“ñòù3K¡ªúî‡'1!ö“I )1-m]TTrž4™LFRBÜ;³?$++“?–OgÌŒ•ŸÜ75%‰‘Ý̉ˆxŠžÞÇ'¯äfâö‹9íº•â%Œ‰}C:
˜™
.øiöÀ<Çó¶üœ…غ•ýk3üû_TNðÇØ´žEâ“mòm=¯dᬾԮYŽƒG|;m3†:¤¥g÷î,œÕ—½›æ¹Î
[ÎpøÔ}N>÷Y¯KLL¤FõªÙ9…Uò\A™>/
ùó¯-ùRÞßÐ2--³gNÓ°Iîfï}(ˆ’FeÞ›¼€\%/ÈîÒûoòz]~n“Àý;të—»•¦–u,m9yòd®Ëÿ,Æßû<.Û/²vÇE6»Þ@SK›ÖÍýⲿUý·c‚ÓÇXw½LLÔ+ùc;ûz„ì èÁ.|nþM|\o:RСš)3²ƒ€4kaÁŒ¹Cä?'y„?|ƹS¾iÞ:uŠˆ§Ò/oë–~Lff÷B#¨]³žÞw>a#¶Nå¶ÏjøÿÆ®¿&2~úNœõÏs#µäaXçÎ}^[¾üWZ;Ô)’Éàç)Ž9âÊíÛ¹Ÿ0÷)_”ÀΞ=K¥êµÑ7TÌ¡Fò1³ÜhФ=þ¢:³23pÛýðdvóZM]‘S–PÍÌ™TŠTš…Û®ÌãȤ¬_:•”äDþZý3ÞîÇX0ù;¦
kÇi×ì_‹G÷þ‰çÙ7±=º›õK§p/ø:Kga€¦Ì›Ø›»7¯Ù-Y—E²·OêC°¿×½7Eºx!€ã®—ì–V?ÇÙtt˜ÄìiIˆOæ’{îçýØñÏIÜ^|çõºzÚ˜[U'1!Y¾íôqoÆ[F÷vÓøkƒ+YYRb_%ðà%Üy
ÀÕ+Á8w!3#ë2‹²è¨W8[K‡æ?r9Og_`ûïŸÇpY±©TÆÌÉ¿sÉ=€…s6ñ4†)cV+2ì¤R)Ӛʒ9}PUÍÿÑ^ñ½Gcë8/Ùì)ݱkTSþ¼Mƒê,žÓŸ_VÌsjjª,™Ó‡ÓrÝ"ŒŒä·u.,œÙ+Ïõ4}=-fNêÆŒéNùVæaW·#Xٵ˯X”’µ};N8AVVÞ¿¬†ÞBUU
Ur®T­oX’¶Ý"QQáì‘\>ïÊøÙ.üôËfÂîÝäÄì¦øÝ kìý{ýFNçûgñ×ꟉŽ|L¹JÕqÝõfÙ«sGwR¼¤1ɉñÌŸÔ—¶ÝòËz7jÖ³fíÂñìû{5jjj,Þp„Ž=‡³zþX²²2óüÞéIx¡!ÉÊ’2vø¯Lúé;v\@B|›7ºbm[›ÆuèÚ«9­Û7àqx]²~í~fOÛˆÇ9?èÀ¡}î,[¸•Á#:³`Ù=ìÉ¿ݸ
m̘>i¯^Æ3e̺öhŠZƒÈ
©TÆð‹0)S‚¿w9S·~UõžGzz]{6e÷¶Óx]ºÁ†µûyE“f挛Ò“Ò%˜½°èÜqb×®]èhA·ŽÖRþÉs´oe@àÍG4mlöÎ>Í›ÔæzÀ2¾àL.6¨«¦³ÿþ\í?wî†p by£<×YFkÃÍ›xzz~zç\ø¢vï^ˆüš¬o•a	#TÕ‹ñìÙ³<—‘œœøÉI0MZueöŠhëèñâù3Kñ<:Rþ¼CûÞT¯m‰y£æ”)[‰èÈǘ7lFìËhž†‡"•fqéìaZvú
-mÖl÷ ~Ãf¼ˆ‰@WÏP>TW¿8¡·ñ½|šZõ¬ùë°ÿ»w•…ŠŠ==޹^æÞpVý>‰I?õCS³ZZèéjÉÇϲ2¥ÄÇ'‘ŸLZZ:ñqI\¿€ë~z÷k]êYTcÈÈÎÚëÀˆ1ÝÐÒÒÀ±í4:w·§YÅßnâsܾÆÃû8´j@däÙÕ%--k>·)^BŸ_]&0m‚;þ9ɪõ“QQ‘P¼¸ªªª”(©¯èðåþÞ²‰©cÚXùçaeLIMË !1å‹êrÛM|úÚÚ””vïÞÍô	]¾¨¾ÂP¬˜SÇt`ýïëò¥¼/úfŠˆŒ¤D)“|	D™•2*MDDeË–ÍÓë«Ö¨GBÜKãcsŒ×eeeráø^ìZváÙ“‡¬Y0™T†™y#232ÉÞ¬ûX±Ú›»¿ÓÐ"++‰Š
-:ôÅãä~jÕ³¦\Åj˜˜V$3#ƒípÕƒu¬([¡š|
ÉÇ¡©¥Í·í¬_:Ûæ™0ç7ÔÕÿv	ùE"‘°ýÀ|þÞt„©c×’’’Æ‚e£èÔÍþ}+U)“cRÂÏ &ŒZA‡.vÄDÇbV§’ü¹šf‰K’×Ѧƒ
ógýI‹ÖÊwóÂȈ稩«²mË	ù¶Öf¨ªf·"­Ö$+3³:•16)®¨0?êÕ«W\õ½ŽëÖ‘R~tLjò…o-êVâöÝ'Ô¨Z&Ç~·î>¡rEã\ÝócÚµ²`èø?ˆ‹‹ÃÀàÝد>}ËúUå³"‹:ÇN˜½ø'233QSû²Ç_Ô‹ŠŒ xÉ'°ÐÛÞ‘óDäãl\þÃîå©N·]yøÿÕ4
Ãѽòà^ÐG÷)aT†ˆˆˆ<ס­«Y}ï\Ÿcû©CÿòïïóQW/ƶ
‹phß›ßv_fìÌÕ/iLÖ'®ihÕ¹žç\ñ8}€V³—éºâ~”‡!Álv
äçåÛ±¶oKæÿ»	¯]>C]«&,ÞàÆ_‡ö÷æNPѨÿÉI©\8{¹‹Gâéÿ'?ÍÈš_wg?)‘ð±a-m
RSÒ‘Ie”+oŒ·×Mùs>^7©^3{m¹Ç¢ømÕ^æ.Ɍɿ‘ô…¿¾›i9#’“S™»x.›¦â²i*6vu¨Z=ûGÙ¢9[hÕ®q±‰ìÙqÈÛéèÑ£8Ø×CGûó.éÉ­Ónжśõ
»wnÄ’Õ‡IOÓÅ.•ÊøeåA:´þò¸Ž¶Í›ÔåĉÝïðá8vTž¿iéâT¯bÊÅ‹ïŽ;®<§¿””Ò32ÐÖýpÖxüŸ‹'p06ûqø}æŒëA×~?R¾r<Õ{Õó¥ËU¢RµÏ›1•W¾ž§(i\†*5ê}pŸâ%M¾(L™¿©CÛð":‚Ú¹to÷cŒ¹
õbT®^—ÛW	ò»Ìý;øyŸ§~ÃOOÕ51­HI£Òø{ŸgÌôìK*U«ÃËçQ\ó:ƒD"ÉNœ2éi¤¦$ã²p<½†L"öE4YYT¬R4ï"û¶
käsêÖóÍ4mMN½Â£‡Ïhæ`‰ûY?6Ê»(QBŸ³§®Rß*{âΣ‡ÏX»<;¹ÅÇ%qâ膌쌚º*Ã~ì¸Ë14ÔEK[“-Ýp^<‚¬,)SÆ®aÔ¸îÞ‘[70ÿç¿øuíøB<_¦vÝÊØ7·`üÈåÞ	Ÿ+ÁÜð¡ß vœ=yON^táé“h¾ëú3¶vu)^RŸˆ'1ìü÷ý+~,üð¡ýtë`Q`åŸ<ÀäÑä§OìÆÝеžÉ°-QWWeûÞKèëi±ú—ÁùRg·¸>ðÁU,²²²8zä(sÎÌRM¥[KÜÜÓ²eË/*'Ï	,""#ãÜßUôix(ÎãzÒkðD:ô&ßž•™Á­WIKIÆÌ¼:º$&Äò<êiŽ$õôQÅßPF>	ãyÔSj›ÛäXÂ*)1Ž`ÿ+”«XÓ
onžž–JÈmâ^=§RÕÚò瞆‡b`XŠ»ÁרV˃â¥Hˆ{ÅíW?š´ÞfX²ô'°’Ʀ¬Ùz×.qçæ5jÕkÄà±så3<{•Gðñ8NõÚ–,ÝtŒÀ«´uHé²oýïã£fõ
ÍìzËW®ÁOK¶àçum]}f,ù›‹§’˜KÓ6Ý1,aD ïEŠÓ`é¦ãè–ü¢÷V4µ4˜àÔ÷½Ï5hd&o	mø{:Î\ç’‡?Í[ZÒ»_+è„d3DE¾ nýª8öÊN|‰„²åX½~26vÙ‡6vuÙ²sÇx‘””Êo›¢¾E5„>¥u;k†ýЀŸçãïMGˆ}•€añ¢{
_×ͨTåÍ2hü3“=;Îàãu“ºõ«2Ñ©/**žE¾ÀåO'´u4©^³‹W%äîcZ·oļ%#¹{ûR©L¡}§¦¦ræì96,]“¯å—2`Ö””66ÄÊüÍÝØ%	›]Fã}í§Î’’’Îl§´lZ—bÅògܸK»LsžJFFÆ{o¢DÉzT©¤\C9ÛX0àÇ/_G6Ï2_ºt‰ñ“§3o݇§_<}¶0nÖjfëAÿ‘3hÓu€üù”äDæŒë††ú†%¹åÏÜ5{Ñ3(Î=òç!?ô
K’™‘ÁðnõYõÏ9VÏKZJ2%ŒJó2æ*ªª,úýêÅ4غ~!žgS¿a3|=OÒ¥Ïô2‰ÑÌЛ*5롦^Œ«O2najÏ\ IDATÖjl:±pjâ^’••E³¶=¨UÏšù“úbÓ¬÷nù‘’”ÈÈ©‹iìÐùƒïóì‘$FÞàŸ¿?|ž¸Jî|Í·ùq;•¼_ÈìïïÏ ï{ä¹4Ÿ£R¼zö3غ}ÿ{Wæ8vìë]plwΩé—}î²mÏû»èúõl‘“×™íÔCƒÂY¡þ¿S0­=š„„¤<—ñE·S‰‰‰ÉÕ/ó1‘8ï2)o-Îplï_–0föŠíÙ³‰­ëðóòíX4rÀóìa:öŽïåST©Q_~£ÌÊ5ê0fÆ*¦iÍùc»©ZËœ#{þàßc·ÐÖÕ'&ʉ{Zc߯‘èÈÇ´í6®ÿ¿@y÷æåx{ÇÖ!»; š™%£¦.‘—7|ÒBZuîOrb<ƒ:¼;Mö¿Š—â~`L.œ ù-""‚r¦E·§àK”3-IDDÄ{Xdd$åLßTc ¯M­êÙß—®'®‘•%¥GçìKEŠf¯R@·^Ë=]-TT$$&&¢«›÷É.yN`Å‹')!î“û½ŒyƤ¹¿c\¦Îã{PDz1Uke„Þ¹ygO²`rvÿnrRÏž> Uçþغ–޽†sáøžKUYÙ¶’ÿ¿¾u3îß½Šªu,ËÇäŒLÊR±ªî߯¦YRS’زvCoñäafõÉ˨Z3»«03#ƒGîPß:»I[WŸêu¬>ùã_Q¢xÑœ™%ß‚ˆˆÊ–ÉýŠ;ʤlÃQDFFPî=ï»®YyêšeO0zñ’´ô&½5~7¤Ÿº:šÄÅ'‡¾ž>×Bh`QåíÝy\MéÀñOQ*ÒNÝ’l„‰²dÍ.TÊ6ÃØwJ–²ŒìŒ}7c7вd'D‹-;‘Ò­´¯Ê-÷÷G”¦(íæwÞ¯WÜ{ÎyÎs“ûœóœïóý¢'ÒÀÿq‘ñ´oÝ ÛÔðm¿ˆCchnR‘vÆwÞçÜwý_cXK„¾½áé1†µtø¹¡A®}×U!44”:uòŸ8âß
<€‰D"¢#óž«Sߘ֟ŠHZ
Ǫ¹£Yµç§¼…24iaNW묔ÿ22‘MÍ:²Ùy/ŸÞãÙC_eÍ—¦¥eEý$ÆÇPIY9yyÿ5 &ÄG£¬¢ÆÉƒ[8ïö76ƒ'Ñ£ïHn\:ÁËÇ÷2÷“¯ñl¨\¹rÈÉÉ“”‡VÕŒè«üDúEG†Q­ZÁBèAá…†Ši=ÔüG&ÒV!444×mâ`L¾?R×>‹¹ï¹ÿÇALuÜ‹ªŠº:ê¿‰!Úr÷A åË˲d\æÐwèjž<¡I㌜²•¿¶L {'cVorç¦÷3$’tÚµª¾;ö]¤K‡Æ,p>†ew–Ìù%Gtu2î,3€8Œ^$e[H›ý†ÙQQY…-+¦ÐȤ-wo_FE]Ýêu¸qÉ
×a÷åÊ•§}×>lužAËö=³-ôõ<œ”÷IÄÇFs÷Öe~nnÎO
Myð˜×/2œïû\#!.ƒÚ
xýâ!f,1ïÞ
-|®ŸãÇœ¹ede1jbÆUcH¥RÂB	xú s{XH q1‘9Ž‹
GW¨	&”qÈÛÌ;‚ÿ‘¶âà\·½ŸŒ’báÖh†„Fsö˜ÇöÚÓ®U}üqíÔ|Οƒ·ßK"£p;ë‡ï½\_ɾÍ8²k*cì¶g¶¡©®Ì}Ϭ_6w?¦O²bí’¡œ=æHõjš¹žWI©ïßn¹IïÀ”••‘"%å}r¾Ë~”+Wž©ó6a7´#—O¦»í0_§Š¶
Š…{Øš‹‚bEÊçÉô£JLH&,4ŠÚ†ÕJ»+eNdD,jê•‹%`Y&F§mþ—ÖÄÄ&efÿ’r%Åb[GVP:UU9{5÷µ¯Ú:ºˆÃ
VÓ Jæâl}-4>EΖ+'‹ºZ%¢bðòyŽD’ŽE¿Œïb©‚ÞF‘1ëÕ¤qVáI£,4z=«6ºaÙÝ„‘C:åz^qX4ÚÚùdÏM¡b=µµEÄD…££W#×ím»ØÐ¶‹M¶÷Dúµ8t)0óõ§µŒžîLzš$Ç—´T*E¿æO6hšùÞ¢X©)ï‘“—Ï–¾cÏt°@Rb|¶Œuê³~¿g¶LƒÇÍ`ΪÙΩ¢¦É²m§HNŒÏ±Æ­ÏЩÄF½Ëñ9c"˼*óþmKñpÙ‹ªºq1‘ԩׄ©ó7åZ-?Úÿ€Ó1n^¸òeA˜8ŠI£WòâY0Uªª“”øž‘ã¬ùmd¼þŠðÐ(Ž¼È»~nÃóÊ=Þ'§ÐÅ¢EÛ(*͆qÕw+zÕŠ®öÒ÷HNNaó!fý1´DΧ¤T‘ä÷ò½ÿß×pýÖÓáîNö68L´,Ò¾…Gıi§ógìo+)9%¥Ü/`uttyóüiaº‡¢Bö;¸Ü–ÈÊÊШAuV/Î^áY]µRŽ6¬{˜òîÅÎ_~Àa—›45ŸAÈã­(TÈ~ñü6$ìYL¾W¡.ÏtttˆŽ,xÀÏäääs^ÇÿZÏÊ9#é=hB®ÇTPP̽|ЬìW˧|OY•ÜhÇÇFgF.~)ò¸H°ƒÛ—ã{ý<ëÿ¾Æúýžì<ñ€ŠÊ•Ù½N(­Ï >séÐÙŸG{ñð\Ïßÿ,`ÝÊC<¸÷²Àíú?À÷váÊ<¸»^'â]l¡Úø¯GqÆ­ä*ˆtõ	þ®c–ÿñ+±{²ýõààÿ8ˆK׿½ãWˆÃbéêåºM$šw0]auhcĵ›Q¨ ÏOutyöB̰ñ›(_>çÒsÀ2Ü=î`eaʆ忓˜”BBBö©Â´´t¢¢ãÐÒ*\òáB
`5kÔ øÕ³BuàkTÔµ0ÂöÝúKûѺ“ò²MLNŒ'))¡ÐWŸ}ü˜Î‰ƒ›™•¿wŸa€•ƒlçråBFiø÷ïS9hqæþ	ñÉ™¯ÎÙIdD,ã‡;—Èç‰ô‡ÍÅÃeÏG8-:˜ùúÇ4¬~u&6.‰Ä¤¦8î¡qº÷]‚ß½Œ× ·‘Lœ±‹µ›OaÒa&=ú/åÑÓ`Þ§|`Ö‚æôÑŒ/ÞÝëçñúÅCìæo¡YÛn¬œ;:3’ò܉¿èn;Œ_G;²iÙ4"ÃÅH>¤2o¢-uL˜»ú *ª,™>¤È&µû~/06©›ãý¶Œil\©TÊØaË
cúX´`PŸyˆC"HˆObÏvw¢£ãùs»U´ÕY8g'•”•6²'uëé3qZbcd3‡n=[²ýo'>¤J°¿€ÀWb8î «E6lŸÆ«p9r™æ­Œ0m^›~í1ïl²ù{Ñ7ÐæˆûR:woÎôÉE“}»°NŸ¼É_»Î°xåX¶ýåHHpÛ6º¢o ÍÓÇo¸u##ïçÛàwœqó°n5æÍÜÆ•‹~,Y5Ž!Ã-˜6q=w|žò1ý#×®dn”HÒ2_O´ë‡šFe,]"ŸKGGqØ÷݉\¹ñ˜µ›Oeû‰ŠNÀ¸‘wxû)Qóéów‰KBU¥"#&m!2*“¦3À¦ý—ö.–Ĥ6ï:Ç«7ï8´c
ujé0aú.*Èá0ÑÃZ"9
àÔ¹;¸œòæÔá™ìÜ0†¥k\yðèÍ7û)‹ûêrýúõyø$÷)_Ÿ>]¹p0–ÿží½È—;ÑÕQ§[ÇŸñ:—u²vÉPfL¶Ê|x#ukgÌ.9Úõ&ñí_ܹ²ßKËha’=¸~Ù0æ8Øfcj\Ÿ‹Kñ½´ŒÇ[±Ÿ3KþMïg˜˜4Íñþ÷*ÔÖ­[7žÜ÷Î,¬øÿÈïúYlz[å½c>%'%P^N>×+›ÏÌÌ{2wõ!*VR!*"5*DFd¬ñ<œÞ¿N@G¯-Û÷dæ²=™Çõè3]ýÚ42iC
C#‚ŸqßçéééüÔÈ”¨ˆPš·íNЫ'„¼)øt\qJHHBAáëQWoƒßq×÷³£«§E¿¨[OS·‘•aü”¾èhceÛ–·AáÈÊÊPYµ"òòr¨ªVÂã”U´Õ©YGwá1ô°nÍ¥ó>ÄÅfü›4¯G§nͨY[—vŒ	
GAAÅ
TRVBIIUue|n=æúÕ{Xôj…û¥²Qô±}Ç&pYˆºFeBC"ÑÔR%LœqÇm; 'þÉÈÞàrä
–6m‘‘•Åí¸'íûS³¶.],ZЩ›).G¯|ó<ªjÊ”“•EC³dBÛE"ÑwßEÇ$‘íçƒ$
U•ŠôèÒ„#®^üuäÚ“”œÊW/l{5'4<–ºuDÔ©©Í?'ow·³ím¨]S›ÁýÚò*0ÔÕ*!'W
ueÔT+òVÅÑ·••%øáf5¨þ­n~ºËý…ºº:MŒæüå¹n/«Nœ¹‡¥•MÞ;æ¡PAÊÊʘµjÍÝ[—0ëPôsÇeDò;·/sìÀö¼wΧš†
IJŒ'>6*[¦“l¥UÄoX;éééÔoÜɇH?~$9)øØhôke%ßÕ¯‘u·òyJ@A±"’©DG„!•Jq;¼5s›I«Î¤åcý[i0jT‹‡r¼ÿèÁ+bcPRR ª¶F¶„_–=ÑÕ«‚ò§ˆ+yÒÒ>æh+4$’÷É©ìÞê–ù^÷^­Hü”W±^ƒ¬ %ùlÙÈ?›9÷7vn9ÁŽM'˜6a=¶:°Ðyô7/LJBhhSF¯"))ÓõIMù€ì§¨;Û~æX˜Oaþ²Q?r™Í»gŸ„D’FÝzY_²uëUϬ‘ö¥ôôœ¿Ë’R½zuž¿|‹T*Í÷ïØ¦g3&Žêžë¶a¿š³Àù}­ZrõÆcömžÀ[q²²2vÉz¶§'ÒÈ,›¢]E•*Z¶¢¢B¶Ð)“---™1sééi®Üëáº99y:YþŠªzÖ•ÈÉC[ð¹~Ž=~¡ŠNÙ[ëã{ý,¶6ÖEÞî”y›˜ö{¢"B12nÅ3¼®¸3vÆJ*((f”VyQZåÕ³øy] ±Ö¿ŽgߦE$&ÄñòÉ]R’“¾Y³Í¸¹9:z5X1{{Äë²;ï“èÙ¯xŠV}£LšÖŸßúÏ§Ï€Ž¨«WÆÍåéi錛ÚUÕJXÙ¶ÅnÜZ†ŽìÁ«““£u»Æ¼yýõ…÷êê•yþ4ˆ“ǯÑú5×Ã~üZ,,[qdÿ´ªªQ®|Ψ×/©©WæÂYoŒ×"â]×3b¬/žSYµÕª—lHûî­nTþ"YkçîÍ©gTƒWïáuÝŸÏ‚9sò&õŒ²î(ûþÒ	ûñk™5/«ȨñÖ,ž»‹È^<æÙ“7Ì^ø;òòrÔ6¬ÆÜ[iÙº.G.SþSL5ÊD¼‹eïöS…ZÞð=¬{ÛâzÊ—æMó—ÙáÌ…{DÅdüaXK‡}Z###Ão¿´gÉêã\qûȨÏ5y´£¦leæk‚ÞF²iç9y­æ­8ê«çÑÔ¨ÌÓ!ì;t•¶fõéÖg1û£RY‰ÇÏÞ2nø×KÑœ8í—çT›¸õú‡i¾>{iڵߓþI[…¾ÓÖÖÆÐ°.÷¼¯ªÆfx]qÏ|”Gpà4«dŸûMLˆÅÛó,¡ÁYQ]q1‘„‹³„ÆF½Ëöúéon^vËv@B\Þž<ºç•Y‘8¿$Rñö<‹•UÑ=ÿúLCK‡5{/Ñ´e'^¿xH݆&l>ê™’«ÏÐ)´êhÉí«§3×­}Àúü6™¾Ã¦òì¡5ë6bÚ¢mtê5(Ûz½ýÑ­^YÙr,ÚäŠQ“Öøû]§©Y'&ÏÍ»Œyi:²'›wϤR%E‚ƒÂ™æ4ˆÓWסúiMÊŠ
“é;°#¾·ŸÐ QMö»,¤²JEªh«3tDÖ—é—¯71dÌD[_…¢¨XWþT¯ëþØèÀœEÃhÙ¦­ÛgÕœúòõ°Q½hظ6ï¢6²'c'Ùâ{û	R¤¸œY޼|É-"Ÿì0 ÛàõÙï£{Ñw`'ι‚¢<ÇÏ:Óæ‹ÏÓ®ƒ122`iÛ6ó½	vý˜:s ýÐÐTáà‰ET«žqQ´÷ð<´´Ôxñ,ˆùËF1Ñ>£´ªj%–­ÀÛàwH$9§X‹ƒµuo\OßÉ×¾ƒû·¥YÓÚßÜgäŽ,˜Õ³fYÓð«
ÁÑΆÛ~/Q®¤È³©¬¬ˆ–Feì'd+|ùºQ}Ìêϳ—bôµ¸à:—˜Ø$|ï°cݺuüz
3×Ó~XZæý3Ábf-<’ë´eYâs7/ŸFS$í¸œÊ—ÜÜܘ6s˶{hê ·YUÆÏZÃU£,ü3c¡òE÷„pý‚+ÓnǰAölø¯+î4lÚoÏ3X‡ÍàIx¸ìåùã;Ltʈs;¼À—˜è´ŽK§üú9µëýŒ·çY~9óîýxöÐgÇá40nIðëç(WVeÞÚÃÙj‹}‹Ëþ?‰yˆ«Ëñ|í/”SÉŸÿr¹¼”v9•””Ø{–‡÷X½ij±œã[
SN2¦oE:U¹âæ”9÷#{ù*Œv½ó6$4_ß«;´£oϺŒÖ¹zW0í-1tøT†Zè¶ddd
ЫW/4T•ñ<_ðÿX
›¶âí›—ÄFg”%ñ¼àJ›Î½3·?{èËév±îï«Lp\ÃŠçØ¿uYžÏÇ|oœgø”EŒ˜º˜™Ëö øiÁô®usé7Ì»ù[X³ï²åÊqÁýà7Ûú,1>·›p^þß«=$øÿõ‹•‡ÿ:ÃìÁ¥Ý•‘‘‘ÁÊÚŠ#.·J»+Eâï£×±îÝ;ß7+V®aÁ
“RйgãîáGdt*C†É{ç|*š²¡ÀêU+èÓïÌÌ{(ßž¬l9š·íŽ×wÌÌ{N:F™Û_<¨I«ÌŒUEúèÔ!èÕ·Ó¨X
‡ÓXKj6¤y»ît±Lzš„€§÷)_^ŽÛ×Îð.4˜ïfËŒÿ5ÇÿZGŸ>¶~÷çÊ*¥Ý…B›6m­Ìš3~D—ÌèÀÑ»ˆ86î8oþ¦Dš4iB·nLžµëGcï¾_Dd<gîcËÖÝ…^¼ü¥"ÀZ´hAÓ&Æx¸ì¡GZw´æð® •Òªcöyßòòò$%d_瑃²ŠQ¡¤QbåËuiÖÇÑ©×@ün^àòéÃx]rgùŽÓ #ƒÕÀ±èêg…§*VÌ»ü{DØ[.:ÈÓ'ôAñ©]»6ýú÷gñjWV-TÚÝ)°ùÎ.2ƒï:îÏ›iݪk7ŸfÊØ¢O0^I:¶C×òÛÐtíúõ€•‚(ÒTÕÎË—âºCfZ£ïÕàç„¿ÆÃum:eî«×°¯žûó& #_ÝÝ[—xŸœHõZõPVQãMÀ¤?’ž&É(IOOc€VD†‹i×µ¿ŒœAä»deËadÜ’;·.¡S­&:Õj²q™}føù·Þ±œ	ãÇ:‹²@ (sçÎgß!Oƒ~Ì*éÏB9ìr'§9ß}¬’’®'ÜqÞpšs—ïCï¾ßøé{ÐЪÁ¼yó‹¼í"»¨[·.ôgǪLš»)ïþEFV–æí,xþÐj5³mé×b”ý2ÇôB£Ši	sV@A±"MZtÀíÐVFX£¨T‰†&­ùšB¹rå4Æ‘µóÇ!'W˜¨pFO[ÀØ+Y9gclM‘WP¢~ãf¹&êý’·çYž>ðÂåÐŽoî'J––S¦LÅiñöo_ÚÝùn³ÆÎ~yïœ}}}9F[kNrÀÄøë‹ ‹ÛâÕ®xùáuË»XÖIâ—$	íÍ;R»q;zšXdí–¶7OX0¥/Î{`llüÝÇQˆù#D!–^bi+lâ—’““ihTåóúÐDzôËÛä×c7˜½Ä…‡ž ¤”¿:‹_ãææÆˆáÃX·t0lÌŠ¨‡ù“š*a´Ý.üŸFpÒí4ººE_±¾È¢¿$''Ç	×ã\=µoÏÂeè(+âc£qžõ,Ðà%JÖç©´	Ó÷r÷ÁëÒîN¾øÞ
`Šã_œ8é^èÁ2¢Ã/^º‚ã¢ã8-:\b	ºÃ#â0·ZL²DÏë^Å2x}V,åZ555qs;ÁŽUÓóŒ,ëÒÓ$¬š3œß‡ýFß¾e§´‹@ ø¶†
²uÛNzY“Y9¸¬
{‹ÍokÙ¾c
6Ìû€|222ÂÛÇOïP:Û,ÅÿqP‘µýoR©”Ç®cÒÁ‰®ÝûpøÈ±"ˆ¿¥Øê7lØm[7³Âq(ñ±Å_¯¦¸ìX=‹Õª²`þ¥Ý@𬬬5j<½¯!5µl&¨NM•Ð{ðÆŒ™P,™}455¹xé
–6CéÔ{)¿OÜöÝÅ?órõÆcšušËºí×Ùà(óþ˜_"ù0‹üØ¿-wvfóÖÌXº/G`FY–&‘°m¥Qâ—\»z™Šs/é_¥…¼¸å3°ÿgùyö_V\ßEƒ
Dü„£»'•©õaQÑ	ôû}zÕ°wßßÅ~¾øøx–-[¶­[`ÓÛ^ÍhÓò'Êç‘ç37QÑ	¸{Üáàñ[¼|É’¥Îôëׯz;™âÀö8€Ý4Æ9®§±iÛ¼(eq1‘¬túŸêTgßÞ=(**–v—A!H¥Rœœfqôð~N°§žañ=—ɯGOƒ±úu5¿Æ,(Ñ‹“víÚ‰«Ë1Þ¼	¢G—¦ôìÚ˜:5uÐi ©‘}M¬D’NhxoÅQøÜ	ÀõÌ]|ï¾ ƒy{llû1pàÀ"+ê›_%6€øøøÐÛÆ‹þc±°^ìç+¨×/²ÂqcÇŒdÎìÙ¥Ý@P„ØÝÔÉìÚ0
‹Î¥uâ´£¦îdý†Môïß¿ÔúŒ««+çIDATqó†'jJ±ÿÍœ[WN•äé¿*$è%+œ†qtÇ._º ^Á˜©©)Þ>~¼‹«„¡©뷞͵ªvQJM•°òOwM§’(ÑÄ×ïn™¼¨Y³&ÆÆÆèè蔹Áë³½ûÒåË—±³w@òQ–cfS¿qÉ/6ŒŽàèî•x_;ͬ™3™0a<òòò%Þ@P:=z„ã¬é<|xŸÅN‹žÐð5iié:~“ÙKŽbllÊ’¥Ë©W¯^‘µÿÿ¬Ä§ÿM*•räÈfÎrD¤oÈ€QNT«QüÞSÞ'sòà&<\v3bÄgÍDEE¥ØÏ+ʦ7nàä8ÿ‡Xti‚Uwcºuü™J¾»­Ä¤Î^¼Ç‰3w9}î5dñ’嘙•l6ŒÿºRÀ>“H$lÚ´™ÅK–PU¤O³®4kÓ
Ýêù+
ž‰	±Ü¹y‘;^ç¸ç}+++/Zˆžž^‘C üØÄb1'Ožä„ë1nzݦu‹ú4m¬®Ž"muDÚjèŠÔÑPS&2:qhâ°ÄaÑ„„Æàw?ˆ·ŸÐʬ–V¶ôêÕ‘èÇ/®Y•™ì³´´4®]»Æñ㮸žpE¶œ<&­»bÚºuê7ù®:cR©”pñ|oœçÎM^>¹O{óô±íMÏž=QWW/ÆO"~t‰‰‰xxxàïï8$±ø-b±˜ÐÐp¢¢cÐPWC$ÒF$!é¡#Ò£qãÆtéÒ¥ÐëFy+sؿݽ{WW\\NðòÅs•Ь¢ºfUT5´QQ¯ŠŠšÉI‰ÄG¿#6:Œ˜ÈwDE„º†;wÆÖ¦7]ºtÖs	ÁD™Àþ-::±Xœí'44UÕÊŸ®‚²~ªV­JùòEZ-F eÄ7€	@¥°L ‚¢"`@ ø!	˜@ ~HÂ&‚’0€	à‡$`@ ø!	˜@ ~HÂ&‚’0€	à‡$`@ ø!	˜@ ~HÂ&‚’ dòÁç•ÚÆÞD¶IEND®B`‚urwid-2.6.16/docs/manual/images/urwid_widgets.svgz000066400000000000000000000246641470350774000222300ustar00rootroot00000000000000‹í}ûoãFšàïù+xnÑe²ž¤»[‹éÉö @‚l²;9,[¢mmdÑèn;¸?þª(‰bU}ŇDµHÊLÚ¦(²ê{¿ëý¿>?,œ/ñj=O–®ü±wåÄËi2›/ï>\ýǯŸÜàÊY§Ñr-’eüáj™\ýëä»÷ÿËu¿®â(gÎ×yzïü¸üc=cçÍ}š>Þ\_ýúu<ß^'«»ë·ŽëN¾ûîýúËÝwŽãˆ÷.×7³é‡«íŸV‹ìÆÙô:^Äñ2]_ûcÿújûtûT¾}þ%ž&Ér}s¹þ¾pójv›ß-Wóg7ùa^{è!WÜá®_–iôìª_k„¾Š<Ï»Ÿíï¬w×ÍZôQü?¿}wa¼NžVÓøV|//ãôú‡_È?t½ñ,³ƒ§òVÈËè!^?FÓx}½»ž}ÿë|–Þüz1C(»xÏïîÓWœ±dWç³Wbõ87wíVs“S‰7Æ›v¯(~D¸³BˆXýò,™Êµ}¸zZ‰µü.þ§ë±xÑŸÙôÕgÇÏÉ*uoç‹xó„ëûä!¾žGËëâõiòx­<ów4~\ÞÏxž=
„øáËîÉøôý,¾]Ë»6ð	€ì3ñéC´ú#^m>/±ÂÇ(½ßݼyŒ¼BÂпÚ_W:æ|ä9n0ftDoäúž³ýãÏÂÍÛW
<,ÜÕÓBÀíK¼Lf³wët•üß|ïe?Û?ÝŒânüÇôÝX®"«ôf)¤Há¡é*Z®õ?š›F‹ø y2’ÿy{å\o¡}½yBCØÿý)møÊÓ…¾ðnC_¿ö9´»>ÆS)€s ìX=}‘J½Ï®Œ}yüýY€Û¹q(bc?ð½Püîƒ7¾lnôÅÅ?xÏŸIVýÐí²\Aws!w0GcpDÄí˜zcp¬}U¢q¿oî¡Ô€&j•&:–&±O®Ód h28XüeµJ¾¢ŸâåìxÈÏ:î…(“eê®çÆ7>z|~gƒì”Þ˜!º»´˜/ãÿIæË›UòTX^†ÒŸö¸Ѐ#Á sL‘ã¢1ò8
B*àëùÌó1b»;!…Å­žÏ=8SÇõÇœPŠë‚àÈc‚±äEL	ñ‚3_Üà‹‹,vù(¯Ÿ`…`
¸}ˆÒÕüùx‚?òF’ÄÄ/t,–r¢ýŸ2ªj	ÕúÓF6õtŒˆM;täRD$·ílXÍë ‡•I}M(¢ÐãÕ²ÖS)ÅÇxûcûB¤ûÙô(ͰU3úcª4VŽFؚї^4pàk¸( zá —M#2<œËéwYn¿Ûq3¿ËŽ®×1PåüSúß¼Œâm¤ñЋgn¾k¤ŸÃþh²Ïº
ûc"^ºÖ#îûP‚kÚ
vá†	.Ûô§Œ_¶ûOyAk\$_8è…kÐûmæû—­†f	¾¿–5Ùoùd9ÆìË<þú]„ÏÑzgn?Fwñ4Y$«Wßßf?Û>'«Y¼Ú}IJå£ä1šÎÓ—M9ÒwÅSóÏ=øóõ}4K¾~¸Bú‡&Ƀü'!ö¨çëŸOˆ/
cÔøP¼‡xìÄýÃY2}’KîÓrž®1<_Z­ä
‹è%ûÎþ1ðu¾+wwÕ:,0ö·½cWºrcÛ;ž÷^þ‘ØH¡]ß'_ïžæ³X,ú6ZäØË¿“}è~þœ<«7d_\Iœ§«§ÝEy!M±ð²¦‚–ŒµÉÏ·Ï*~K¾¢ð5ß3Á·^FîÝ"ù-”ïê{{ˆžçó?c±.ºÙ3Œ·«§‰[
Žì&á†ZoÏw¥ïk»!ùü?BJìž‘-D—0LEÑò7ñ÷§UòðUìöKœ¦óåÝ:çà
ã>¿È¯½!e$äÀÜ]|Q/Êz1ññŽ´Ñšì×E”Æo¼‘‹…ŒFŒó]ÔÆxùöY?É9†¨Zš_ûõ[°¤ñsjh\½¬ÐvÃsÕ
e•óeºJfOÂêH–yÙãn3rY”û{Nl@?&<Ä{ûDð½OÇæíÔ–z”ìOùÑÍR’æbsåk¦ŒòK‚Êòà\½Ûòú¿ÝÉ Üæ«·ÑÃ|ñróQ(ÍtGÎ
%àü"€½_ßÃâ&+iZUz¼ú"æûT\ZîùÜ™eo[àÈPŽŠ…¹X%r÷RzH
{-!¹Åõ]Âw„€»Û¸dÕWq×”B”ÒÕl•+¡b
ûÚ`3Ñ
jn¾Ã)‹n-xC¬Þ8Ì~I…â#± ›ÏOij$	ñ¯vW³?BE§7dwM¯;‹„	·ZE/J)\Mno×qzãmƒ¼›Ï3?_ÈGleÿ»Ù|ý(xùf¾”Ëx§Ç­ßm´‡û9šþq—LÝDSaÁ=I×E‚¼˜CØÚg!-\ÛYd«ôÇQ0.Þ'­HFÆC~14~0í(”SƒvJ¨g¯‰VC¦ôÞƒ·¸m(B
°(8ðF/wHÀÕqÖZ÷Xt(P
¿aQ7ù$XÄÙ]È<ù=Eör¥[t!Áƒ‘ÁY‚¼ºcj’É7¦ë‹<“† ±ÝNY¾ŠÕ¢XµŠˆü»Ê©0§IÒ#¡Á™I…:&ëñ‰‹0	H=ù£Àeò1yn"]JØëö6Ž£è•½*¬–·iµ„ìÕjiÁj		²ãâã[-ÐkY-¡R>b³ZæÏYjSÁ²%ò§‰(ñ‘ ù£>¥ÜL>õý`Tì«TBYµãf$¡ ©ÃÄ	ˆ½=¦}À€<!öÒaÆ > Uè<_	ó°Ll4Ž’µý1C±£?'‹Y[šã»£wóÒÇx:¿O#»¼ù‹œ0;?'ËÄù(ÞªašÃ67à
Ÿ¤cð›[l‡)ŸX ¢ÁeòÃüË|¯,zQ«Úìß…cÅN!
ՒѧÕâÍ÷…bæ·»æ³ÍJ¶Ç‚/•›eùíÛ²zW&Lé¬ãrD¼`#Æoh$›UËr¤È£<•Ìb™ÁYá4-²§Ñ¯ܵQŸ]€ÙNôk0ë-uXµn£åîlо1Š @Ac4à†Bî¡1Ú¯‘qWþÏw=7PALlqðKß`V"ü!%•ÅvŒ¤2$rˆJ>øzNzÕÀÞ*åC<à%ÊÀÝÛ”Y"Ó¬äC‘AÉÅU—Ûä¦á8ùì­J> Ö–|¸SÁǘ’e´ô`ô¶k¼–ߣy®µˆ¼Ö»[jUØ¢]Ön‚hGª]F¯,ñ«½£Ì0Qÿþe{
aU¥¾zÉʽMФÔZ@ux*=ˆ'Ê´_µþ+H9†©¯}f’^zù«€,ðùpæØV‘i)™ÜA‘fˆ(!.kûEgˆ¤ev÷Χð7Ô‰èõæ¹N¤kÎËÅJ~ŠüŒFÎa–1²3Hˆ—©óu=>Ƴœ±Z™í¡Y§l„]M’•3]Ìå*œ4q$á8é}|’”£¬Èø‰¾Dó…T¨i1r¢—Ó¬ÇçUò‘XÏbd‹æKç4ÁK©¨ŠÒt5"9^;o2³g=:ÍrH%å#m"ìYS¿ŒGŽ49ÞZìñ뻞ç^}V|_óÜ«ð?ŠJ°vëÅä^}Æšæ^ré	¨ñ\A±Ãƒ ¢ç^ÿm6o^ºÆ´«Øgðo­´«—v=£šA\Ͼùf¨aÏ‘J§lÐpG»¶î»È½ÜÃW“ÿXËâEÁ™
•Ƕ–@l¿,¬•ìT¹hé$RòF‹Öy9,°°¶:äÛO‘J@çå	= Œ‰½q2¤L‚ØŽ‘IЂ‰
@&£õ|êüs³‰#[WO¬´úÙ¨Ê8ª˜¥¯*o¶¢¡JÉÁ˜dÓèÙï¼J½«qûY|™,ï}ÂË’}ñCÀ¸"åjTÐ’<°¤¤¼™!0÷¯3Q+%¬Ø“|`ï`¯uÁ.vk´xO6å çâ P&ðí fr£€F^¶/e˜ÈG ¤;á!ÔÎMmª$X²–õ¿,ÒwÎGA>ÉÒù—»ô5gÓ+Já%¾¿N)¢½
5 -+TJà¢P$ëP؆¸ÖNœNÇ'Î)A™Í4Û½"#¨iP{dUCÞkJð[¥}#'H¸YæZŒÈÒE¼‘51”Jp•°«Éç­óïÑlžl¹6wPAd¡ àÄF#ȯF^Õͨg ÌÖ’9<#Q0½	í¿‰‘÷_Î;½§|LlUˆ…¬g
T1ÂZe2E`2Tñ¤»­]Ÿ
m
"æt$ £qD[E÷•qÆBÐÈ:žš³²bÔóȘ4è_jHlºJ/2³Ô£U¨*쇰ý¦ÉÎÒHQg*1òùõ*awÄÜìa/ô!“¶­’nA i×£±ý²12]·[F;në’EÚÀ90TJà¢XZ#¤-­2“¡vˆ°¡‚1÷8Œä½ï!>Ö9²Ð)%G×銇@Õ¹àŠ´5M~«¶Ý/ؤš›´.¢kèkÀ.ðNK^Àóê°Ë!8a†¸+õ/'ÀNk㤖«“AÉ.—É.¡Ž—]ŒN”Ȯݚân‰`-ŽO –ìUÁ¯04Ÿ€@²*…ѵ¡ã9¤ã-hSÖt±8”B'A8	t}^‰ÿÙ:ÐÔ]ÐNëÊ® e_À‰Q\eÕ'„'V}ïUʼnþÙÅéH5ôI½â!
Ðv9ú„ÛôIÒlûVœ´¡ãC@ÇÃ+ÒÖtì
ÍMؤu]C_“°¸ÓR€ïñ„½–ýøþñ	¥‡Å»°‡Žçìéúܺ¢â]Øt¼%Þ/¢kèk'ÞexO-ëø=N|Ǵ캚Ýï´d¯
Nê~YvÁ@²*…ѵ ã±èxÚ”5
'¨OüüxŒ?^QúA±zXŸÀ‹èúÚÑ'6€ñt*?#CÇ[õI84_¢Oཪ8¹x?R
}‚[ðã±1צm‡ê“Â’îck¼‹/ÏhwÁ{Uñ¤ëý‹ãHÕñ.ŒÛÐñÐñ6´]N¼Ë÷6Z×,0,waÒ†O?^Qñ.LLo‹wÁ‹èúÚ‰wÙ^ÄÓ©üxbâÄ®O†ƒ,Ó'•1HÒ6Nú'»¬±ú
}Bhv1nÊcõ¢OÊpR–'mÔA¨²'Gäã	TiÉÇPvYóñ5dWÛuýãk²"ÞEXv1jËcÇÅ»«Ÿ?\¬¾$ÞU«'FmdkvTi©"NlõÂ5pòZY"»Jë…Iuªƒ,•]Ô÷'Öd•>i£’@u%1È#õ	TiÑ'ƒ‹Õ—è“êX=9Y$ê -údˆ8±é“:8¹x?¾Dv•ë“6ê 	TY*»Žé?!aýüÉàb%ñ®ê$1j#/ŽO¬±úªxWØ‚Ž§FÍcy¬þBâ]e8)‹wÑ6ê )TYŽ“#â]ªƒ´Ä»(»¬ñ®jÙEOVIÍ:H»>ñý¡áÄ®OÀ½*8y­ƒ„T­Ohuªƒ´¡í‚ôI	NJõIuªƒ,ÇÉ1úªƒ´é“áÉ.»>©–]m×Aö‘OBŸ”Æ»(jaf5j­+j!ÞE1«‡ã]ð"º†¾vâ]6€ðdÔF¶fwAu–x×qb‹wÕÁÉ«o—]¥ñ.ÚF$…ê Ke×1ñ.JoóOŸØýp¯*ž^u<¤þ	iCÇÁß´]R‚“Rÿ¤š;
ÕÜ•ãäÿ„:ÞæŸOvÙý“jÙeÔá5‘]ʼn⃘!MZ£Tvù~>£oŒ°®èø6)ñ¶úc¨ãíLjVêxß#pq|©:¶â·02€èáöR´]Pl…[b+åEœœ-øŒ¾1Àº¢ãU >ØžnñOÀEt
}-ù'€ðÔv{zùÄ&»ªø·àŸøF+z¹ì:ˆOzˆ«ìâa9NÚÐ'P{z™ìÊ×tì2ÛӭǾ‘p`qá’cßཪxºxÿÄÊ'~¼ßB{:1g“–óÉ‘~<­?‚fˆ:Þ6‚¦†Ž7ZÖ/O‚Ãò'~G]ˆ‡@¶0¸¢Ãùd@8©²»ÚÈýú`î׎“#ý(÷k‹­MÇ—ÅVªuükî·DÇ—Ç ƒ6t¼‘S¬ÒñGÅ Í<£=V?4_«¯Öñmç81âÂVÿ„âÉ®ÿÞ«Š“‹—äOÊãÂmä”g,ÍŸ\D\¸'e5w¨…qñâ!€-\Ž“#jîPƒ±¥t€#2m>#¼WO§[*ž\léqb‹Õ×ÁIËúd“#2éðÆÌYýx¯
NÚŽAîqÒ`ôßùÄZYƒOŽý7§Õñ.DÛ°»Œ£uKÌåˉwÁ¤:Þ…Úè?APÿ‰M¼ÕŠwåÝÝû_ò&ïM·4/Ä=îÃþ)o•Moö†·ÝºÕÔnµ™KÈSoL<¦tô
\q¥l×QÊÂqHièûívM¨Š¥½kYeØ+¶Œ:QìöiÎþXÌÅ?7ÄhÞ]˜EëûhµŠ^”¶_y5¹½]Çé÷æ™hÄSl§þVƒ:ÿsìJ¿Ù¤…–ÀNr)X ôœgýé^ÖŸŽN@é0Ð…ÜyfÞB0Pa­÷÷ƒ8±·Ã4žï"““u}w e¶ßú_œÒ-4Yæ‘Pæ½ÕǨ–
éæ˜	˜Dhvؘ	hG¹+r±tîÙO(òM„un¡vŒt;cÞ`|wdî@Ç´BšòO½ñ}þAvþ9=mö'˜§½'žR{ô^§f}_ð€xðœ¤FK´TrʵØ0åÚ6OÓäÓJìwÀ¡;©„|ìûúôÕ"©¸ƒ%·!¹ä šÜÇÑ,^9o’G¹µhñvà¤úžo) ̯2î ‰ÇGIBÀÒÀ5¹M’ôrÈ9›7È…5ÈGòÀÈò0À4M>'³—FCHû‚ ÄA\{†ù?QM±ßhªyͣꋻû¥÷‡…I¤¨Q^(@úàdàåûw|Á+t䊋Ž£ p\Ÿî®x#7àΟ֎¦þsö]&³8}yŒ×Âû‘?
¢*·ÙÏY"âjÅ…¿Úa–žÓ]?\zXÂ,ö#Åš‡Y4J8/¹öI~`‰»èÇwq„¢×ÈKw#/ÖóÅWyqODª0h³JÎÛƒjòI 1ò™ÙéE„X='hÀT“¹A#MÛ$YÍïæÂ‡Þ:V"*3E+çö¶RX_´JožV‹7ßÿº(º[Ä?.~»ûøa>Û¬dûw,Ä­róߟҟß*ÀÈŒbi–a̱4Œ·ºJؾAQ8ëX½V<üè}{¼tÅ!²¬¸·ø©í-œõÀYNq¹®i1FRRàtFwN5áWµ´ÈIq€Â›M©qæwô¦È©/1€½Ìºw)€=Å}ã´Y^å™ÞHÇp§ZkÝ
;ª†¸[–ÀE¦ñn9â¡·îKÍž4¬ð0\Í ƒâœ¯Óɳ%ŒÝ:ÚèŠû“•5s5é¢N9,
(Ø¡u³ßEÕt"sÿåý·…NTë´[tÒ[#v¿…Ì`'ÒLÏvÕÌbGšÁ^4øÝ¢
Ré`”`'Y‰¹¥å±TÝ1¥=¬ªrR¿ªœ  ;ä›_M¦\Õ)!ël”‡Y20eì°¹²Ó³1VuçÛÔ]h…eP^Ö]‡Ç˜ú¯Ñ‡¬‰	(¡5LåqÏ©ÁÕýØÐ<óqhô`9*½=Ô‘ý§e—ÄÂ1Rµ‡FÆëS„V׈&úUæ#oD<‚ÉæìübOê0²È} ãaõ”R°À£óÁÒÆ­j]©é£´Q8áPíëIãÝIiQ^4fZ¯¶”àåûwBª[#Ï‘™nÀÅU¸ÔRð†UWy¡¥e7'7œóÜôM¶£Q®F$ÌFˆd@ôDF®øÃ
»ƒŠTKÝS¹-H±Qƒ	tÀ¸dÐêÎém2¦Ùˆ(*%p)Úcí3ЂüKš®~Ž›VÑ@Ly{ÇQÔY¦™42·L)…Ú‡R²ÉÿޤŒ#ÞÈ%À˜Y˜›†{\cÌó%Á_†xÌCª5gôõ¬t*Ò××À–ìUá5¢Ï—yÍV¹¯H+j¶¢•xãðjòæë<½w"!æÂ‹×NÕšü )”­‰øW“Çd-ùûʼnÂŒgö^¹žøƒ 9	Ÿ‡Í&ä«Çoj P@8µÕÒª!¼‚Uå³ÜªrÛbUõEYÁujý
âØzùhC8pQ£½AZâXZ9¶³¬^Q•£ÊbãôW¸2W¢´Y†Rº
wU\Ì}——õX6;´ÜFtîVæ=„eϲNš¥ã(åƒìtûÒO+40í
ä23´z-ïãx^uqb-M»Klî ȃ\;±}}CÁ±¼¡#wP‰Wæ öÏÆévH™Ó:êÁR6ôNš$zÑì.0ýµ˜óaB½CPϬ2ÙÝa³Sµ!‚Àn•¹Ëý˜æ9¶™ûˆ‘]à2Ù‘öªâ¸v&«W»2µ:feè+ ^ÇX£OÓ"­;_
²4á4»Va©šx6®XG9tQ¬éÓ1í»Œòo” ^²/×D½MÞÏHÕ”¢ºÈG{›,Ô]<+ô!ÿˆf³ùònø©OéXî%A­ÈyÁSµ+—Ô#T6[÷HAåê‰Ó"55jê¥L+Žçu½©·ÓMv5”㞀iÑ®Êø	ºß¢9$ÈÙ#˜ú§5¥X®{«%kqbÔÁH#æ”\rÊ;Æw€¬Ò{
ȵ\’*\&?‰'Ú{±[=¨ý€Ý^*+Ù™cOâÌœÏ×úè‹\>´ü4“Ê>F[ ¦“>÷òžU7#kæÊqÂòœÍŒ®˜°ge¦Ck­	o`/•±ù] ðã 7Zð ¨hp©“l*¾ÃR–kY]é|¾M@•&™:Íê;Œ}E™Œì96€;B$gf–d[wóÇŸŽï;ò™ðì3*9}ó«K)dÞ=ÁY_§6°N)3(ך¹97[–öÒ"–¹æ’qÔÁ"×òÓçvLj“à°!2Ý¢ãÚ]˜ÐÞ@¼ˆ$´t°Ëy"Œ¾Il4¬J.	ü1ÅH·ÌwF†{nWp(*¾F¶AB&`TÐLþ_û"À–ìÏ!¯–g¶5y5oíÕßp×­*û«Q³]“êöêÚ¯&殇ÃûŒÂdfT·ªxßíš]Íý5f“éF>œù¿¹ð±¿¼±pËë˛ˀ“j¿¼±hsç•r _%ý.!Þˆ  ª_vøö´›?ÄGvóQŸ
 Âï¼¥—!nt†Íà0Óv7?ÍÎNÊ÷¿éy•1TÇ¥lDÙæ‚˅Јfå#»PWX
1ƒ%åp:ÓC³ˆŸÚFžR¡æŸéfd—ÂÃ
:`÷*1$ª|,B	,.s­ÆWÊB°@¿áË,dž/ôï‹ù:µÎÜëM-,Ü@Qx`ûSHÀ³ŠzÛþÄ<è|
š%›vÐDYóÂ[¹(ËdÈvì	Š_LIyLT¿È
h֌ܰ•ÚÎv&È€+kÃæùÚ•µ½:)€\Fim¨Ÿ³‘˜rÔ)!êcÑÅÕÖîÁe3Ô+ëä*eœb_@''$
L“¿&‹§‡åú Ã¹òu¬ø:Y÷¤/]ÁŒ”7Â#ÛŒæ6¸ì(—ú-rq`cí9
ôÔGºÿ=å„ñ1õ=-UmuÄö’H;~º#Ø9¼D©OZôôh—³øýaG/2÷e·.§íT“o¤lOK¶”Ñ1
=n*P!z9÷´®÷Ù†-h+¤G¬+ÒÖ4mƒ”{;ŽÇõ}4BÄî	­»ï¥§ÃŒŽXÑ-y@É)©2º[Úµöyh‡Ö=*ª+°žWLæn•­¥/²¦¢HÃ
/$„Îø¶õcý`I¤œÑ(‘€,	™M}eI`‡%¦áž%‘W=ã{Ë’Ö´oŸ
HdYàY /r
ò=—Cê+¹k,Íbeé‚(@Õ"¤*ð×ùîQ€1985Í=íìܤ¦/%'Ê}sØpUN‘ay«ÄðV!¨è9Ñ¿ŽÖã…ë´¬äQ¤JÁÏc:ÚªàÆçˆ|ˆC/a
‚4$:H/ºÇÛêoLfÁ‡cŽiè¸âJ…ç4òƈß!¬ÄƒÀ
©–ÛÛ8ޢލ–Þ…~õcÃP ¡PóÏ84ïÑ•*†^øîЭ{l$i™Õò¿Û
IºÌáûÙ±á
%bõìˆìd'| ;9TLö£Lì¼ñWî£1Á/Sû±ì+åµ5söÌ…½ÆCPÑà2ùœ¤iòÐKŽŠcʅ̨tL±rêVµcÚiÄÕŽÌa¥ÉtcJ¡aGæ:¸¾ÂWç8.ä¶·ÐéÁ†’„N	Ð÷/‘í”SP4e©f½%»Ë²38Ñ"\Ü7
‹g-2fØH¨cÞAç MPí$®Ÿ·	%ˆ§É*‹Ÿ9ÿÜìäE	gE¸"yºzˆìÖ0']‹ÖôãJ§D1
@&€ÄãVôÐñ¼ÇfsßóæÄ(o¦^ÜÆüC(oFj	·à•»²èý±˜‹nˆ©'¶fÑú>Z­¢%&¯&··ë8½ñÎk?ïpR'ƒýUœ„Ö\‚ ›!‰B¸!V ¨hp™ÈÞ*§¬¹¥<â…ò$¯”<îN´‘l€Àå"3›¯øÚÑBÈ]àÆ°¦ÍaÎê–L¬r‡vX"=Ч†5û1yn"]ºE:ŒœÈjဃÚ8…v¡RuàVK`TÙK¨à$ðlVK`4Û­–.“OóçxÖkÁ"G—‚%“ŒwÙ‚¡þªCž¬@¸.'nHí“,ÙNuÉþy½þ"þùÿ÷]fÛ0?urwid-2.6.16/docs/manual/images/urwid_widgets_1.png000066400000000000000000001454301470350774000222360ustar00rootroot00000000000000‰PNG


IHDR@h~›0zTXtRaw profile type exifxÚÝ™Ync»†ß¹Š,S±Èåp²ƒ,?_ñHvÛm·só–ä3‘¬á(·ÿõÏãþÁOÎ1»,ZK+Åó“[n±ó¡úç§ß×àó}½?|L¯£_Ž»R_#ïvÉs™¯åyïã¯Þï¡óI~yP¯ã뉖Ÿ÷X¿=è5P²E>¬×ƒÚëA)>'ÂëýY–/­ê¯KûyÝÿ„?g/ç}Þ¾ÿŸ•è-aœãN!y^SÊÏ’ýe—:Â}­ñ9|?óÒ{&ä§8}ü4ftö“Š.ú’•Oß²U^Kuß³•ãë’ô-ÈåãýÇã.ÈÏY¹¡ÿµ~>ËäËñÓýyfô-ú7øgÕs×Ì*z.„º¼õ^âýÄuƒ!lèê˜Z!;…ª¼Ûoã·RÕ“RX~úÁï-DÒuB+ôp¾ï3L¦˜ãvQùãŒé¬Ic‹óf2Ûo8QSK‹<Æ4Ÿ´§ø1—p‡m~º;Zeä¸4¸åÝŸÞp޵BKRžüÆhÁf–9{å22Î+¨rüþýþcyMdP,ÊÖ"ÀŽçCÂ'¤›èÄ…ÂûÓƒA×너¡…É„DÈZHJð£†@ +	êLŠƒ‘¸˜dÌ)rC'14·h¸—F‰vÌÈ„¤’”Ü´ÔIVÎBýh®ÔP—$YDЍTiÒK*¹H)E‹bפ٩hQÕªM{M5W©¥j­µÕÞbK€¦´Ò´ÕÖZïŒÙyrçîν8ÒÈCÜ(CGmôIùÌrÊÑSO;ý#k¯´þöûY¯¬Å›)»P?²ÆQÕ÷#‚Á‰XÎHXt9qµPÐÑræk€_,s–3ß"]!‘IŠålËÌ;D9á;ŸŒZæþVÞœæ/y‹ÿm朥î3÷{Þ~ÊÚ2š7cOZP}¢û8¿k_ÀÐÈq	&­§´žWËñ(ßB#\©Nt
¤pæ¬gîQØAê©KyŒÌ±Î;3îÊ£M)uÕ=W›®­À´õLúê›’M©–f	½®³ÃÔ
ö•Õ×gL¯‹¦>³¤æu$ï¶°t\ûn¯ñשc0>­®§ùçq“…ͰDÏ3ÏÆžO$MËY®Qá©hàÖ¦IDs˜ýŒÐ:%Û-Ç÷}&?—¹pV[àõË÷åÔœcô"L²Äg’?M‘€~?îÞ'Z#ê[š–DõÄ~v_=nJ&,J ï®DúܳFj•ÈD^)ûÓ*NÊ>Ÿ‘6•ñÄaÑ=o
2FÖæo™ù¼Þ}ÞÀâJƒnÈ´¦Sµ[|ôÉŽ\õ¾Ù…ç%{OǼxPÜv(†³i¨$ÅB8Zg‘
§J>’$K¢èŽÊ‰:«¡÷Mµ•CWÄч¬°¹¬F?ëî`E
û¬Òoú+…°ã?°_.üEäÍ]…÷êõñ<õ×g~}¢GnÄD‹Î;&3ó°š`i„¶µÒ
?Τ
…UüÞ´	Â\±¦¦(H¹ÎLÜdÁÚ—ÌÁÊ1<>ºñETd˜ã1ÈÛþõ]|MrX5
	sËÜÁ>wìv;ÍŽ•|–Ó0ðH͸‹NS0d,¡ˆ%
H’¬¦A{œ[./6‚¬¹­Vd´_D"û²!)yl4c×iL'±{A³¥˜ý,7/𑹄Ê§ÃbHæA& 8p~Ÿ}"«&UaÀš`±óÍJ.Q=&1óFÞö…ªíSPTtRHt¼‚–ÂîHXÆ@(ˆ	øtÁÝÒB^¤äá	—åjvȯqpÓ
:‚¹#às«¦ý3Šýmr@€:£ýÒ9îMHµ¬+ SXµFYæ°ˆrf*sçõQ˜ë%¶ tòM‹½Mó-ÇZ¡iÀÜÁIR›§JbZsL⨑xXq5ÉoÛ4,ð‹d͙ы6AÙ+ã˔¡‹ˆëÌåûÇC¾eô^†U2.8 ´¡ |ÆsñÔ¦°rØâ¬ÔϧA.š5€+Ÿ2J¿ÇR@&Ôòä–¡ä×â4bƒÅ
µoSŽUÝÆVÀ¦à„@ìPÔ¼†Iî³à4€z³<ù`¬‚¼8q›¥aÉg´ºàO~pA1W&’NoÊn!çPÀ¼AÙPÌWL€f´k6dE¨<2Sz¤K#ó~ÚÛƒ"–æ2r±±~¼»ïþú;lB^HOjÇ{„3(5l0Oëhã¢`±®BxéiÚ#ÆÔh nz¥@I†Añ8Íi…ˆIƒ0H÷Fˆ
=f[¿-¯d¿vI±|Q·M¸1jv«^rA DÎTœï½!ü\ž&íPl¼<™6ÒÙ^ܶYÑŽÉ  ¿ç€§©¸EêÙÓ@‰1ånûhÕ÷èüûÝ,Ä3P¯t‘pAÆwÒÚ`j›
°ÔÑêAc<$6Ñ~~<.uV
´ãOƒ™(Ð4m/("Ù!Îèç°}¢Ìa@ð¨Í¬î›¿nÛù=º÷‡¿ûþóƒ¬v=ˆ#XºaÍ”½xŠ˜âEäƒøÙ‚ðDÌ)ÜA)KHd ÀùÄÀs#_¥¼mS屿½0â¾»ÿO;míñòk0|+Ö„¾&¨ðµ(·X%¤5·Ä`tX“Èß5o Õ6GÅÙ~'"…Å<ÂìŠDÖd FÆa°<Þ£±/ËV¨~¾»ò#-1wS`z€ÃÛ†gÍBa@6*J ¥b›WïßóÙÒ»‹±ÊÙ‹lØ<¢1{Pÿì¾Enä¢.>(‘¤b*'RÓ
šг@¢‡Å”á½®wã<nÖÃÁ“ +ÿ„5æI‡ÕD8NèSLǦ…4ö1¤“lßÓH¯çGƒÐbpv²ïq¸ÕF_ˆ=ºšvÈP,³lÐyâù)ûn %
s#ôßqŽgqeG—
©©*B+Nñ0-æ:ÅJqJ"ÇÓ`Š8Že”gŒœgê±€€)ØnEe¦Å+¿ŽÛH%Ì Ï–!Œÿ¦ˆqø˜Ô_*Leâ•Ðd˜ ¤¹Y4–€“u;LÔÇB¦™v¦±aˆÞpkÙdü˜	*)âsWw«á7âÇæÖE!FóÁä,J¹…¶Z7}|̤+#ÄúíB‹!©P¡ÈÄèAÓ§”j"66+Ù&IÅùÚÄŠâUŽmÒ?L™?Õ×NÃ÷	X¶Á¼G£·¼šÝBæÂÛ¾]ÂSê4im©”Ú–¼Ó–y¤¯$ˆà® i0„+‡ür놓~ÈUèyÙ¾&ÑLòÊtŠ¥*íæžÖ©zAE dõ`6o€iö}/)`šÃj…X’eº‰u+ŠÚ®K¯UJ( 'ÐÙ˜<ÕáÃNE~W^($´Ä §ÌB×á2G±–pŒø@4ê⾚ƒ™xþÁ£õzHÍE*@Ùþ6f³ïÌšúµIsî·ý2Z656û¨ðØfjk
›Á$ÇØþ›í¾l–Œëßb¶…Õ%Û®¯X–£Ȇ@ûæR£m©•íZ£f§m³ðÒî÷Ø¡“V<ðÚ³ú7ÃâÈi&LS›ì†Cl't–0(`ÔéÇ«¢Ë£}ÕÛŽ[
™XL¡ø&’<*É»_€aôN1€1Mx…ƒ¤®
ß·9dªÚ ¦á;”A=S#»æ~•4
ä8Ñ ¤ o®j”n{ÎÊ©Ó:ÈÁ¶æ³y2Mçp™íø3I´1:5Ö2Avëtìt°í+ªuäÒš¸lyÙßõPžÈ£‚’7ȵÿX&­^Í/^IŽŒ„3íY2i5e¡Å¥QeØUÈXˆJÑ(mR¶˜ÉRÛº¤ÞyÈÕu{ÙŒCÀ<&ó18éøÇâE¿û˜ôª9W0K‚ãÔµ‡Ûfëz”€1ß5äþG:ëÿìA°ñjîß‹wUûÍOö…iCCPICC profilexœ}‘=HÃP…O[¥Eªvâ¡:Yq¬U(B…P+´ê`òÒ?hÒ¤¸8
®«.κ:¸
‚àˆ³ƒ“¢‹”x_Rhã…Çû8ïžÃ{÷þf•©fOP5ËȤ’B.¿*_áCˆbBb¦>'ŠixÖ×=uSÝÅy–wߟկLøâÓ
‹xƒxfÓÒ9ïGXYRˆÏ‰Ç
º ñ#×e—ß8—öó̈‘ÍÌGˆ…RË]ÌʆJiTXtXML:com.adobe.xmp

 
  
   
    
     
     
     
    
   
  
 

                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                           
IÁøbKGDùC»	pHYs
×
×B(›xtIMEè⡘R IDATxÚìwxTE‡ßM'	¡½÷Þ{HQì°
l
¢¨(ŠX‘¦€Ò{/Ò{OB„P’Ýï9›½Ùl HKà¼Ï³Ùì[æÎ;3¿™3gl€EQEQEQ”{@Š¢(Š¢(Š¢ÜÚÇKÓ@QEQEQ”{@Š¢(Š¢(Š¢¨REQEQEQ¤(Š¢(Š¢(Š¢HQEQEQE¢(Š¢(Š¢(Š
 EQEQEQ@Š¢(Š¢(Š¢(*€EQEQEQT)Š¢(Š¢(Š¢¨REQEQEQ¤(Š¢(Š¢(Š
 EQEQEQ@Š¢(Š¢(Š¢(Ÿ»øÞŠ!ÀvàJ:ŽgN p8“ÆcŠY2À½)Š¢(Š¢Ü84	î(¶=Øq£'!õ…‡ígCÀ\ù¾ÌZy€S7ñ¼#@àࢇðOàW`©‡ðn@=à'`%ð*ð±lÿ%q˜úè+¨(Š¢(Š¢d4nö s@&˧:0Hï·ñÞÁŒDÜäó.–ïFœÛN¤žMT4°U¶‘xþ¦ÙQQEQEQ2–¸lùl>&HXóTޱa˜” 4Æ;PÈøzØçÆ<Ì~•óøË5s_GZ8Pcaàk ×-¼\g).ÛÑ‹ÏËW¹fAŒÙ]ZñÁ̺Þç›KÒóFñ•8—¹Î´UEQEQ”'€<±I¾/¸m/LÅÌ:ìb˜ù;žŒÕ9ü•ã¿qÛï7ÙžËÃ9Úɱäš'åójîe­ˆ–Z˜¹>VËE©ˆ¤Æn"
 ·Ä³“‡k=+÷z3º¶¨p•¸aæöËy'‹xŠ&¥ÉžSŽ"pIÏ3ÀnÂò`¶üÿœœÛùi!Ûƒ±rýÃÀI×pÔdNQEQEIøÜ¦k”E„†uþOjdÞ”}kÇ,éòžGSºˆø™tÅeb÷ÄÑ“·Œ§AÀ‡–íßaFržÅŒÄÍ!zEÐy6V¼öÀ ¤“OQ38EQEQ%p³¥~/hÎOKÌHÌù¸XÛöˤqnc´üö4çÇž†øõ•†únâÇIÜu Hîè ‘ÜÏ‹¸+‰™žçÿ¤F'Lc,âÇÉ{xž/ô„%ÜzþxbD¯bL‡{wn{8iâÀ˜Ãù¥’¯ìúº)Š¢(Šr/0eüH&}÷1“¾û˜Es¦}úÔ‹KTäIzuIÝÈé¯ß¾ãç1ïßðuâã8zpÏ=)€Ü½ÀåÀŒ‚\ÆÌEñä»ð‰ˆŠýÒ ?%Çù’Ü‘Àç˜9>b̲ÒJYù^tƒ÷éœTרQ#`®õqœ£]ݾÓríò½ÆCØiÀSî*.bg—‡°¶ĘÄÁŒ(õsû< ¢¦LÓÄü,‚ï(fä©­äEQEQ”{†IãGs??þÝ´ŠmÀÚeóîH\2e
¢q뎩†_Œ=O̹¨¾Î‰£x÷åÎâùÜŠ9@—Ýþ_‚q:pcÖõ5f¤À)
æ‹hXˆ™«rVÓí1.£­ÞÏfȹ^¸Ôî€AúG¶ý{¸’kݨO±Ó—‰Z~̈“Ur­FÀ$<ÏÿI|òšï’6VrIÚy]Šô°­¨|—$¹ù›•8ÌÜ¡´òfÞO?àù\¦`Ìí"PEQE¹hz_gŠ–2͵Üy1{ê·Ôlàòñµo×föîØD–¬¡T«Ûÿ€LœŠ`ß®-T«Û›ÍÌ^øwÓJ²…†‘·`±dç?zpv{"…Š™¾êm–“=Gù
™~ôMkQ²\U|}ý)\¼\²c÷ü»‘»·R²|µñ¾t1–µËæâp8¨Z»÷l£XéJ‡˜&á…Øsl^³˜Øóç(_µ.ù
`ûÆåÄÇ]fÍÒ9T­Ý_?ÿtùln×¼ŒH`ŸˆkÃ}Œˆ‰êÀý˜‘ˆ·€!©ˆfò}S̈G`‹‘%xöø–,¯ˆèËyîi±EÄ5–ÿ—XÂ/`Ìá!˜ùGQÀ¶4œÛiž—#•ð°Tî-câ—š òtY$µsÿ”¿Nñû­S]„U8ÐCÄ­M‹CEQEQî%._ºÈþÝ[Ë[(iÛŸ“ÆòFÏ9¼'S¾ÿ„OµâÒÅX‚C²1ù»ü9ék6¬ZÀÈ·ž'08$ÅyîÙÆwŸ¾ó³÷_íÊ/cMŸvlÌY>ÐooÎ9ÍǃŸr]{â×¼ÝçaŽÜÍgïôbÝ
×ÈTB|¯=Û–?'eßÎͼ÷Ê#|2äŽÞÀ‘»x¹G3–/üƒÃûw2èùv¬^l|œÚ·ƒ„øxölßÀžíHHˆO·ÏÄç6^Ë)Nbå;ã¹m-)Gnü0žÝ®Æ~Œ—´Ò¸¾h‚1µK@5ŒW²i7I5ÂŒ]Äx³²TDZ&i™ÿf̨¿ÜÂB0£6îì’ô¬GJgM<ì¿OÄgIï´äRç>×2ksˆøÛ¼
ATã€BQEQ宿ýWÃÇ×—èÓá”(S™¾o}	ÀÅçùõ›y}ØTªÙ‡ÃA¿n™=e?ÞWßÿ–O·&w¾Â|5üU^~çk²fOÙw_±z¾øð%âãØ¹u-ª5`Ïö
8ìv¶m\N±2•	ÈÄùsg’޹t1–‰ã†óƈ	”«R‡Ýžl~Т9S°Ûùý|vo_Ïk϶M
ÿáó!4nÓ‰Gž@ÙJµùñËw©Ý¸-÷wz–ÍkÓ­çéþÙÜ® WEø®c^'B'ÔÃþžF9R›ïsÎM\¥Æiœ¿‹q2p#é±3ÊSã®{•
wfí"«hºSEPõÆx„³òž‹ýL¾? ¹»ë¼·ßî\ÁxzË…q-žZþÈnù}D¾«{Ø×?•x90ë¥åù(Š¢(Тܼòî׌úéFŒŸGް¼|$£0Gî6â¡Jl6Uj5áÀc$–· ϼ<”a¯=N‹v]©X½Çóg
ÍEž|…Ù¹u-[×/£zÝæä/\‚{¶±uý2Ç8²‡ÃAé
5̵½¼(_µ^Rø=Û¨Pµ~Òïâ"¢œìÚ¶Žý»¶2úý>Œ~¿«—Ì&üÄaâã2Ô³¹Ù#@À–ßA˜µdÊHCøq\£‰s¶Ž˜5h~ÄÌi‡ñ<¶”‹~îÀx‡›IÈŽÍé‚Yxsé5â·
³VMOŒéܘQ¡,˜‘“ìÀ#i¼Wçz@-Dd|åaŸå˜ùLÅ®S…cFOVáZ¨;ÆK^	’&-–4ì!i73RsfT¬)=±½ŽñÔ×_Òz*f~Qnyfȵ¿ýO›1fŒ0£wñÀ‰ËF̺G«E,Å8B(…q¥©Å¡¢(Š¢(÷™ƒ	
ÎBPñ,t~²?}º6àlt$^^ÞØíÉ›d‰‰W°y¹úá·o\ApHVŽØ}ÕkT¨Þ­ë—±uýRú½õ%bÏÉïeô|mdŠým^^ØívìGÒœ	»ÝŽL7Â×ר˜³®Æn||2S6‡ÃAµºÍ(VÊ5£¥]—çðöñÉPÏæfùJÜù¹OÝ?I£y†Ûþ=1žÖÅŒFLbFT6{8ÿ"Ìú;_Kƒz¢4ì­q4\^˜µlBDdÌ’|7<{P»VA³ÄCøY\s~Ò:ÿÇÉ` Æll<ÆÌïIÓÎ|ìvÌã"fNcÖ*ŒÂÌ«²aUµT•}cÖÿ™|¼,é±Ö²¿CDÑlŒG½"ÒJ1˜µ›—ãbæÝ/Ï¿‹…Š¢(Т܋ìܺ†Ð\yÈœ%Š”Ä××›Wñs%M«Q¢Le–/øƒMk1zÂR"NaöÔoS=oÅê
X³ô/ÎFG’·`1*VoÈâ¹S‰Š8A©r)
vò(†¯¯;·¬I^Û7.O
/W¥.[Ö-%æ¬ñ
·jÑL¯¸œJW¨aœ'”«–ô)Q¦
^^Þ„æÌÃ…ó1Øí‰éþyؤQ{§'§Æ,ˆº›k{
óŠ`F].cF~"þã½”kG`æÝ¤Çñ;_Œ™`.´ë¥>fäl$Ƽðjéšßr­ÿáZ’¦¹1.¶á2QTEQE¹pL_žjàCõóP²lUƒ2qêñq—xä™4iÓ	€ùNà»OS«Ñ}ìÞ¶Ž¬¡¼ÿå¢"O2ð™6¼9r"%ÊVáøá½¼öÜýùt
ÅJ§œ16†n­KÓ¨õÃôyc4v{"ÝZ—¦T¹j¼5j‘§ŽÑ«s¦,9
À¼?ñýè·hТ=;·®#köœäÉ_˜^¯ÂápðÉÛϱnùßä/\’¬¡ܳÁÿBñ2•9yôï½ò(þ™¨X£!gN‡s6:’w?ÿ€÷_éJ̹(Âòä¹þœ9ëMOøöuøAíâH/H¹9ø{qþ˜Ñ²F˜ÅX—k2)Š¢(Š¢Ü:ô秊8d¢BhÎÜä)P4Å>'Žìgß®-dÉJùªuñöñåÔñC\¾t‘ÂÅË&íwìÐ^l6’Ü[»³çß
dÏ‘‡aféÌ»·Bî|…HHˆgÏ¿(W¹NÒ1‡öíàèÁÝ/S_?âã.%¹³8yì 8`³Ñ³Sm¾Ÿµ=ÉCB|;6¯&*ò$Ùr„Q¶Rmü\þ±¢"Npêø!J•¯¯¯
 å–3ãÑmf¾NÌœ¡R˜…c;j)Š¢(Š¢ÜZ”‘™óûxŠ”¬À¹3§™óûx¼¼}x듉é&~7Cùhþ½«X¼<è|ÀݯaÜ…+Š¢(Š¢(Jª$$$0c—Øí‰”­\‡v]ž¿ëîQG€îN‚0‹½Fòßæñ(Š¢(Š¢(©s׎¥wtHI*|EQEQ%%^šŠ¢(Š¢(Š¢Ü+¨	œ¢(Š¢(Š¢\M‚;®aþó³S8EQEQE¹}
på£&pŠ¢(Š¢(Š¢¨REQEQEQ¤(Š¢(Š¢(Š¢HQEQEQE¢(Š¢(Š¢(Š
 EQEQEQ@Š¢(Š¢(Š¢(·€^È=E‚º
»|3tüÃê¶¿!?÷á+§kþ¸‹¹Ñü¡å‡–WcúÊpÍw1íë†iý¢Ü²òã6 ùïÎrCùCBÕÆ‹¢(Z~(Š¢(×/Â5îŒ8¾ás¨	œ6^EÑòCQEQîTiãEQ-?EQE¢EQ´üPEQ@Š6^EÑòCQEQTiãEINøÊéÏÝÈñû§h“ÎŒþé7v‡ƒßM¤Gÿ÷‰:{NXÑòCI3v»OßëÃÓÕàÜ™Ó8v&~7‚÷û÷àÜÙ(Ç?¼ÎMŠòÛO£q8®íðÉýœî×tÿ­Üæ¯ýè1ìµ~íúfWl¶ÿî±.1ÑβU;(]2?¹seÕÆKÆà~뱓þà­Ñ?$Û¡|ñBÌ?ïkžÌT+WŠ‚yÂÈäïoò…ÝÎêÍÿR¢P~r…fÓÏÀ|úÕL^ü3¹B©V¥8
ë–åûjR¦dþ.+Òkâ©üÐ|’˜sÑ,ý{:«ÿ…ÞHéŠ5¨T½ªÕÇËë¿ôåÙ(U®ay
âïŸ)ÍGý1i,?Œ~+Ù¶Rj1ô«éØl^ÿéœÊc­_ò„f¥z…R4©]fuª’;ghªuÈõÖ9Š¢¨J^”ÈǧC»c·;ødÌŸÄÇ%ðR¯vd
ð£@¾7|þ}NÒù‰ÌžòFRã%>.‘ˆ£ñä/~ç*?×OÏGÚ‘G*$__ŸT…±ûf›ÍF“ÚU“m;xô$Ïί£ÞÖ†â]BÿÞmÉ›';»÷gèÈßù~Â?,™ý~šE‹§²ÂÓöø¸DÎ Wé„tW~h¾NΕ„~øü]V,ü“¦÷w!$K6¶mX@ ¥+ÔÀÏ?àº;Øl6Uk7¹ÖN)·I/ÿ]{“=4?À–âœv»]_è;P¿„åÈξÃÇxsÔ8¾ø9ㆤ\‰"ëkå‘kío³iš+ž9~
vGò2 _Ž„ŸŽæ‘WÞcÙ„Ñw4~‰v;5~ž…?~BÖÌÁ)Âg-^żeëøüÍ>7|]ŽP®xá»SµlR™–M*“˜hç—)‹¹w…žlM–@ââ˜ý÷&O_Á¾'hצ&=iBႹX´|;<ò!=ŸnÍotÅËËÆ¨132l2?Œy‘re
òîGSˆˆºHfƒÌƒ™8¶­ª©øIß,Åm CëÆ”/Q$ÅÎgbÎóóŒyÌZ´’†5*Ñ´N5B‚“5&ú}0šU›¶3绑œ;ˈï&sVO
à—Ñ¢^
MùL×N¨T¾0‰v;!™ùèóYœæófüÌÊE³¨T£!Õê4%08Äc‹·i›N)Îí~ÎÌY²ë}›±Ö/Û4á‰Cy÷‹ø~Øëøû%Õ!Á·Sf2nò,þ÷¥‹`Ûž<ðÜëôîö}ïÈK–:'G¶,׬§ââX°r½Ç÷ÛYÌF—¶ÍÿÛlÖnÙÁô¯†âë£K3ÞMŒ?‰®ÿkAXŽäXW9v*òŽÇÏËf£KÛ¦øûznË^¸x™Èè³7|ó.Ò¬ÇËœZ1-]=ŸÛ2ÈápðÝ/éÐãü|½yøºL›¹šî/|Æé¨Ô.ÃO¶b̸¹¬Û´]{Žóá¨ßiÛ²:mZTåÄ©hx¹ç}|:´;yrßÙ^Q?×&¬nûÓº¯ÝnçóŸ~烯'7W2z<1±S=æTd4W®\àùÎ÷ó^Ÿ'ÕÇÝBLÌENœŠ&Þ,äÍœ¦cŽ[ÊŠçu•ÇS)Cã~Z@ûÇF`·;xøºÌž·žÖ¿ËŽÝG“{ü„…tþ3²gËL•ŠEøpÔ¼3|2‰v;‡ŽDСÛpÖnÜK·ÎÌäǤߖáëëí±<ütÄbžô¾>Þ´kVÙ‹VÒkÈ'DŸI5_;~œ6‡'^Î…‹—xäÍ9sî<~¾w£É? A™Ù³c÷íÀápàíãK`Pæ¤4ýûÏ_þúã8ìvê5kǺåóy»ogŽÜ“æ2è÷Ÿ>gÂ×#W^?zccôeÌ Ô¨Pšö-²në.ö9î¦cm4«S
€;\ydö]4®U%Å2ói©§¿ÎœŸêûídòì…ô|{$‘ÑgèØ¦	Þ3N»ÝN|B‚>À»€î¶¢ÿS]’>¾©˜ûoÝ}€ï§Íá…+¸xér’Pš»l-v»u>ÅßË×%säD8›wîKq®#'ÂÙ´coÒïõÛw³mϤ߫6ÿKxÔÊ—(‚·+N;÷æ‡isY»uWŠó&$\á…+øuÖBNDD±fËNNFºæO^Ž‹ç¯%«ù~Ú¶ï=˜´}ņíÌYº†9K×pö|lºx6·¥æ<ÃÈ/þàÑuøvt/ü}iÚ -ڿúMûhÓ¼*½Ÿ¹­eبidË„—ÍÆ[:’)À&õ˳gï	¦þ¹–G:Ô§z•â*~20Íz¼œôÿàç»òb·DDŸeÚ¼%Ô©T†Ñoö%$8ˆzÕ*ðH¿wR=O½jØwä83¯¡}ˆT.SB÷. rÃIÿ—,–‹Çô¡@¾$¦ÁœÈZV<õTò²ÂSyú#¿øƒ¦
Ê0ntO2g¢UÓ*4¾ÿM&ý¾œw^ï’tü±çøþ‹Þ4o\‰s1Y·i?+Öî&6ö2—ã¸t9Æõ+ÐçÙ¶úsþ™üRÄqçêKŒ™0öÍë1jpüý|©_½"{¿Å¦{iV§šÇ|}–o§üI­
¥øüí—	ºgL¬‚C²Ò£×Œ|ë=ÿÍÛ=J›ö=È_ؤMÌÙ(¦MCÙ*õyqð(2SµV½ð KþžF×g^óg£#X2oe*ա	
¡Bµz¼Óïûÿ3gJ’	\þBÅ©Z§©¾¼é///J)È…¸xNŸIéÄ lñÂ)‡Ù‹VñpëÆØífþ³‚²ÅQºhÁ”uꩨ³1W}¿›Ôªbö;sž·{?IÛ&uR˜ozyyáç¥>ªîÆMÍû_ýLÇÖÙ°}77‰¿¾NHp ¯ò-¹sd§r™âŒÿí/¾˜0•“¾¤h<|þËtr…f¥r™ämâcá§yuøVNú€gÞAXh6æŽûˆD»î†ò÷øØm;ÿú‘ìYC˜ö÷R^6†‡Z6d⬅øúú˜)IØw~é]NŽ¢yÝê|;e/]fÐóñ@³zœŒŒ¢cŸ·)'%
à“ï§ðòxâ¡6ìØ‡ÃÁ†í¦£¡RéâMîîJq:†ÈÓ1üúû*~ý}U²°ð3¼V Þü(žü€±ŸÒ+ç‰è³1D9OÇ6M“Þ¯{¨‘T³A+F|?…³&2ç÷Xðç¯:ŽªušrîlçÏDÑì¾Nd
 ,_A²åcëúå<òÔ«Ø®‘VçcÎr16†Š5$,åÎWˆÌÙB=îÿÇ„/’þ¼Ï»*€Ò)NÏlžæˆe
¤ó}Mùô‡)9Á•Ä+lÚ±!}ž 0S@І´ÔSi}¿C‚©^¡Ô
9‡RÒ?öŒ··){îkT›Ï¿˜,üÂ¥Ëýú¾û`ÍêTÅápÐüñWøfòL^}ª3
«Wdéú­T.Sœeë·Ò¶Qm–­ßJÑyX¶~kŠóÔ(_ŠQœˆˆââåËäΑˆ¨3ÄÄ^dßác„Q$žŠï|ñ##ö¤C«†<ðÂर…«7²}ÏvÎý	o//އGRµý³Iá›D
¥5¨7÷7®C——Þá±v-y¦ÓýŒ?…7zvKWÏÆç6•@´mQ‰M*%*T0WR!é²5<}‡Ã‘®
?7‡ÔæØ,F^Z1Ü“8ç”-]€'zM§öõhÿ¿ZîE
‰‰v.\¸üß‹¦ëØ×Ï×??W‘i5ðóõáõ—:ЮM
¦L_Á³ý¾¢~2Lø¦Ù²'+?rÕæµ«Ð¨fåd×('ì*qM½!w¯P p	zôz“zÍÚ1tÀãü:îcÊU©}íyIf-ƒl¶ÔEÓg¿,Mu~‘’>H´ÛÙ¶çAþ~©:©W­ÃÆN`ËνÄ'\!Àß—:UÊ¥9¸×Si}¿}}½ñÑ9?w=¿Œxƒr%L}æçáyï=tŒ+‰‰Ô«Z>©|oR«J’ÉZƒê™ü×"º?Ø’è˜ó~á1~šñ7ÍëV%<êUË•LqN__jW*˲õ[¹xù2
ªWäTd+7mg÷#4¬^1Å1‘Ñg9u:š†5\a
kTb˜„nß}ÚUÊ%™jæËIá|¹“ö]·m¹²g£Ïû£“êèKqq$kHpº|6·åíË™#9s„ÀÓÝ[hÜI^I´'µd¶ï<»Má™n	

aبi4mXÚÕÍÃõö1‰/vü*~î.²dðb4æ IDAT&$8U›¶óLçÿ‘9(£'#ˆ:sþªÇ9_Æ„+‰šˆw!WMU\¼±‡

àìÙXbÎ_"4{fNœŠ&ât!™]“S++Ó–*ušR¼tEN;ľ›x¨[ï$sµÝz3fØË|þÁK-U%ó¦áë@ýæ¦é9f
£~ó˜5ùk>{¯/ÅJWdãªô…Ì€õ˲µ›Ù²{?/[G©¢ܳ™üý=Ιó÷óå¡–ø`ÌOÄÄ^ä³7û¤ê--õÔµÞo§ù·¢”(”@Vnú—¦µ«p%1‘VoJ2CË#;òäâÓ£O·øûQ´@^ÆNžI—¶©›Ý6¨^‘o&Ïà«!/}.†‘ã§p"â4_
y9Åþ¡YC('KÖn¡ó}Mp8,]·5iPÝ*åùpì¯;IþÜ9Y¶~k2q5+–fÃö=ôÊ5oÖnwàåeÃ×ׇÌA™ˆ>Cö¬!÷–²Ùl<ѵòç`úÌÕ|óã|ŠÌEç‡ê“%KþµŽ_¦®äÓ¡Ý)]"6›w^ïB·ç¿dÒïËyöñ4ª[Ž‘ï=Æw¿,äòåõT?1ÿLú¿|ñBt{ ÞÞÞôíÑ‘ÌAÌ\¸‚…0æWè?üË«ž«N•òé݃_ÿœÏå¸xÊ¥bZ§d>þb6™ƒ|)Q,7/õüªG‰byx¬s#.Ç%ðóäÅDFÆðd·fÜ×¢*Ã>žt|je…§í6›î]š;,“§-ççÉKhÓ¼
Ý»4¡BÙBiŽsŽÐîoUƒ™s×{!ŽVM+óX§FÄæõX>ú¿ä˓ًVñÓŒyÌFû
È"C=åk///zwë@¡¼¹™±`ßÿ>›–õkR$|¼½)Q8?^^^ÉLóî²çÈE÷^o°mãJÌœHÑ’åé5èj5h™dØô¾Îd
céüé,úk*Õê6£é}(\¼Lš®áååEÇ}	ÊŠ…3)P¸¯¼3†/‡÷×—2ñã´¹äËAŠeùz/Õ¬|ÍFWÍŠeˆOH $8ªåJ]5\«žJËû­(N2øóNß'xrÐpÚ4¬Å†í»É’™g:¶MÚ§aõŠ|÷Û_Ô¯^!é÷‡ßL S¶¤¶U‰"\ާBÉ¢øûù’'g(>Þ^Η‡\¡ž×Ô{¯ï“¼0dK×maçþÄfË’V¹Lqž|¸
µ;½@ÅRÅH´Û)U¤`ÒÔàºÑå¥w©Ó¹'ÍêT#&ö[w`ñÏŸâíåE»¦õèÐçmJÎÏ çº&3Ÿ»SØ0ÖÓÿÙ¨Ü=å®ÓUñauÛßЄƒð•Óoyþ°ÛØl)çF$\¹‚
>>Þ(é3hù¡ùãjL_ž.ò‡Ãa—9©^òÛYæ¸~;¿JÚi_7ìŽ×/v»ÝÔ6¯d.åívíØl$r`Ì}°%Ÿÿã]²æÍw¶ü¸ECøÊé©®Üô/•J#HÌ ÄÅ'°iç^jW*›´íÀÑ“lÞ¹—ìYC¨W¥<¾–¼xêt4GNDP³¢Yï-úl»¥Nå²WÍcÛö 80EĤs÷Á#ذQ²HW7n§FÅ2IBfÿ‘lÙµ²Åš5„Èè³”µ,`z<<’/–3;åÚ>Îì±Ã¨TºXÒ;³f󎞊${–ÌÔªT6ÙY§ÏœcÏ¡cT,U4idéò7¢]‡ÎÀÓÆK†Çé¥Ë]TNÑòC¹Øl^XÛîmèfäúÿ”îê½½¼’­Í“ô¬›‡:ÇKÝV+nÔMÅ™†¿Ÿo2ñP´@Ц2O-wŽìäÎáZã0{ÖTÏm¥BÉ¢É~—*’Òµ{]q¾à¤XÁ¼+è²\È™Ý5Z4qöB
çËÍå¸x†;‘’…ò'»†·—WŠóYÉ‘-9,£Jwm!jãEQ-?EQ%UlØ7e6qññ”-^˜_Hµ:# H/Š¢hù¡(Š¢(©Ò¥mÓ«:^Èh蘭6^EÑòCQEQîTiãEQ-?EQåžAMà´ñ¢(Š–Š¢(Êu"ÞÈ”È
»ÁVE¹™„¯œÞ¨.?'‡Õm?MSEQEQ”›„#é¢(Š¢(Š¢(ÊÝ.€t¢(Š¢(Š¢(÷*€EQEQEQ¤(Š¢(Š¢(Š¢HQEQEQE¢(Š¢(Š¢(Š
 EQEQEQ”tÁÍXUÝhßÝØ4(š?ÍŠæ%ƒå[æ¿œ?|î‘LªÜÙ—[ó‡æÍš?4(7=8Ú½+[¶¶ŒñÚ;¢§èúù#{§>‡šÀ)Š¢(Š¢(ŠrÏ HQEQEQ@Š¢(Š¢(Š¢(*€EQ%}Pø’ß6àm 
|ä²ìÿ2ÐÈ|·„=<xïÕ,a% ?ÐÄÖxIþxÐVxKâÕèn	+|ø÷/ZÂò#,@=`°%,DÂò€_	ó“s–
#€`KÚ¼	Ô²É9r[ÎÛh
À’–°'€NÒfÔ°„už‘ÿ_šYš¯j6UE¢(Š¢ÜI#þù=hl¢€+ÀOÒøxX
\N“D4´Þ–‰À¿ÀT Õ€¯%`-0È~•s¬Æå€rŽ­˜	ÿËEt5%ì'Çõ¡å-翜Öí¾rq@Và°]â÷¾„}Tvʹs_IØ °8#×ýYÒ¦ÐX	\–sOqÖBDÕrÀ.÷3UâPøFâ°FÒ£€ˆÓ‰²MQ%]a“‚ùFÜmÜèñJúåf<[Íš?4hþ¸•çÖó€®@uà°„ùK€-@gÌèÄf˱Ó ð°À6È#‚âmàKØ˜Ñ YžČv‰°qÒ+BÃ3ªâ¤:0˜	”šŠ(¬QÒ¨)	 §¾©" ª§%,PÒf¾Ü_Mà€„y‹€r\KGN¦Šj.âq®%l4P3Š5øÁöÐ3*õ§„ß²ü¡^àîÒÆ©ñ—îÝ`«¸;”?Œ¸Ò.·ZyI¡ê‰%˜^¸´à$pk}®ûbzÿë5¼¤BIÈ ù©Pøþ67`2I¥¼Ä²­$¦wòðH‡öÀILîà;LO²¿ÄéZzØÞ؆é=MÞòx‡òóÝ*€ŠH^Gò×Î4¾Û^’žž~+w>´æ½D¸X©(è.VrG1#!Oz(›œeAkybp3
âþž­ÄŒ€âÝÂ~!”8ë63SÜ"T¬ÂâCŒIÜJ·°îÀ"d~sk.¨¯œßJYÌh×p9¿•œ"â&‘ÜtŒ™Ü	^ÍÝÛ&À)/+\¥R¤ÜÕèÝ~Ãî–?½Ôž’5ú²aÑ0B³g¾iYµno
ÌüéoþçsÌ_¼•1ßÍcúÏý=†÷|uµ«— {—F7×3g/pöÜŠÊuGЭ6óËçUà=Ëï€ëy.$·G¾Lð_i(•hF%¿‡Šìv&·•'EˆÜ	j[ȱ“/à`ï‘ÊöFÒJ+ƒ1ó	þ+¥¡¤$§3¦7¿1f>Äiè^û0&PNºIcZIø`æÙ,zzxžïbL´z`æYycbÖNı•WD„Ôªº…=Žéì
#e'ß}˜Ñ‘‹À#na5$ïí^p+<бyɃé-÷ñŠ[X ˆ—å"r¼Ý:RÞ–°çqÍ•rO›nr
+oŠÀi+q³ÒcbW…äóœï’Ö÷iö¼9$&&2vìXMˆÄ;Måä©3)„Ýã6& Àï¦^ëò原Œ¾¡sÌŸƒ¶-«¦y:†˜ó—n8®³æm ïëßßñçs«Ð)˜_fIAëüŠé«o)¨ûâš°Y×p~Ì$Ë©\§¸ÖƒqMx
’cž”
ÌHÒ³˜	²ï[O)XIܜ⬂œ×š#ÚbzûKÕY¡´“8¿f¹''m,HùT’Îÿ{cz‡b&¯6Áôö½*û9é"û
ÁL¶u*àŠR±>/qËâöœ[KÜò¹UŽ]å9„¦Ó2¤¤ÜÏ(’O.Î+ù¡%WHãÄÓÄÛ àiÌdæ@Ëö¬˜ÑžgÝz΋j'ùò5iÐØ0=¢#åZYÜ®SSÈÕ®r_5$o•õ–MòPK¹fAÙ^x]ò3ž/X.…¥‘WJ>]åøLÜ[””÷»~*áK$]öcÌŸœïV·²ÂùnW”cjKPK~W°¼Ëƒ0fKÎgSNÞãÇäËŸJ|J9¢M†ÿÄÀédØMòQ޾òlš3¤³Åù|:Jý>y—'ár&Ðè< bd2Ùò\?–ã{È9óHX>Ì(z7ŒyÛ'@i	Ë"çyQʲW,~ö¡ÄçAà!g›I:×&Ë»_3ÒåäKÌWc̈ñ»n'A¶áš+…œ£¤œs
®¹Rȵ”¸“k;[lõ$îJM¶”e¤¬î$ù~¼[£üG†Ê[o½Åþýû512Ï=Þ‚!;&}|}½©\¾0>Þ^\ºÏŒÙ눽p9iÿÍÛ±n“ëoßy”±?ÌgÊŒU\¼”ÜeÿÁpÆþ0Ÿ¿m¹ê‚À«ÖíáÈ1cëp8øã¯uœ7Â,>þ
üµ‡ÃA–@JË›tœÃá`΂M|ûÓB
OqÞ§Î0îç…L›¹†ó±—˜1{‰‰ö¤ðc'¢øqâ~øu1§"Ì`wÌùKlØr€Ság™1{slºkPj<Žr_)•È[Rp'`&lf–Bufþ,fâébçzÓ;»c^”M*±˜!vŒÍ··›O¤ñº
c*Q	ˆÄØLo’k\‘†æKÀ2ù~Z®×	cÃ}Hz?“í;€9þ[‹Hci$
²œËÙë8\®³@*ó|ûé=Ûë ËýŽÆØj·ÀL@u6¨•Jê´ôj:…Ü©´7`Ì#œ#Ó¤¾ã*âòNbÃØÝo•o§9X3ilÑ3N¶×&HCr‰Q±
3ªsÑ’—¤Ç5BDQ7·B6IŸ89f³¼7Á˜Q_àË1ÁÒèZ†™ÐÍÃ}”ÆÁ2i¼¹v]’<´G®yøð­Äµ¥‘%
—ygŽH~Ž´äç„{ ž	•çµøTžÙ†Tö-"/`L ÖÊög0=úH¹ÑßònŸ”´<‚™×qT~Ÿ’ÆåT\óLœÝ´UåÙdÇŒ$,H%>“DÐ/–<Ն佸Jê´‘wì1é¬xJʇG0sjÞ”g'"y0žàŠI]Óã`˜¼gäCa‚”µGEଔ²6PÄ )—æ‰à™ eÁD©þ‘:hˆì€1§](û”´‰’?FH^úDÞéG%’²9›îKRo¼+e_wúÏJùø¨¤Aé@yR:B%7ôª"çè$ç(â€t¤Œ•4Œ–¸…‹è•8?+eÔÌ<¢o¥£eŠÔç›ä^¿‘zIóó
pæÌ~þùg"""èÙ³§&HÆápоÛÇœ‹¹H¦?¦Ï^˯|ÀÞý'iÞþ½¤}‡}:ƒ¶?d÷ÞŒûi!U
LY´ì_*7ìÏÒ•;ùhôŸ¼9trª×œ6s
Ÿ}ý[¶¦}·ŒûéV®ÝÍÀ!°Ùl¬Y¿—o»¦:öèù%OöþŠw¥MÇ¡ìÜs<)ìàáª4Àï®aæ¼
<Ðõ#Úwû˜¸xÓܘ9wµšbÙª,X²*
°mÇ.^ŠcÿÁpΜ»Àêõ{ذùÀ}·éø~Ò0A*‡vÒ›ÚãÉÆÉTŒMòón=¶¥®Ò›ëÖ‚äfUS¤G/Ó«`étÚ=ÿNr“¡­Ò{[¸ßÒ`þ—éB.Ó¨ÆÒñD)9>³T
s¤Ò›k‰ûK/¢³G°¾ôþcé
\€ËÌÊG*!o©ô¬“T¿—ÆACµå3c(
9k/ô„›œ7ÒrŽÂã0É/^Ò£Û3Jc½·g-÷´[žÅs"êû+,ŸL˜I¼_YŽùã¶µTÞNZÒwµï…±w·"ù¢¯äçÈc¼EÔÖ²ˆ£o$Ï{K|ëJ<“¼çÎ[skî.ÇÔqä#acä±ÿþFê# é¥ì¸Yç˜$⣯EÀ¤Æk"„‡‰8™iy^¥£yW÷Êÿ÷[D6’?†[~ay?ý,ïd7·gûô_êÒ©²/ñœ¡êútrå.Î÷;wvHz9BBB³fͺëï9ƒ¼ûGô”T?^^6GpP€#KH #KH ãñG;ìQ“€#|÷·GôG챟eJæsŒõ¬£r…ÂŽQôp8¢§8lúÂáïïë8¸ùˤó5oTÁñá[:ÑSµ«—p}󑤰®8Ê”Ìç1sì¨T¾Ã=Å1â½nŽÿ«åhT¯¬Ã=ÅñÆ«½žnåpDOqÌø¥¿£VµGôdžEÃÞÞ^ލýãŽè)ލýãÞÞ^ŽÏ‡?épDOq¼ðdKÇ#ê%]c̈§€ãÂñŸW"'9òæÎæ˜öÓ«Iá¯õ{ÐÑᵎè)ŽŸ¾êíø_ëjWM»k}n†vñ¹C™¦,ƤÄ9‰øiÌ]áq?°4ç*æ¡]ÒmÛÙ¶Izrãgð<<„1W©eÙ6Ãí|ÈyÒb^´[„Hl—¥á]DÂÀŒÚœ´3_gÿJã»°%ì°E4]1eÝŽÜqéõæŒón¹ïp·ý+Þ¼pIz½qðäQaǘ'õÄŒÈý&®)ûz´KÊóp
à‹¸LJ­Ý[g,yÖeô¢Ž”U{µ¬(÷¸Jt8¨Y³&9rä`òäÉôêÕuüq˜5é5*•7³üüR6¹ƒý™úÃËTjПÖÍ*Óï…¶¬Û´ŸÀL~îr²u&–wÅáp°yÛ!FwùmiÞ¨·xM©_»4»öžàtÔy.ÙÎàW¢ó“£¸x)Ž…K¶ÑÿÅv)ŽÙ´í U*!{63+%{¶`ªVtM“Ü´õ O>Ö4éw“å\
Ô£‘œ?Ë´YkøcÎ:ŽŸˆæØ‰ètõl.¶?å·ÓcU}ŒÍuKEõ¤!NÊyNfc̃¬žoþ˜H½ƒË]»kÄÉzó¤˜ÑgÓz¼'–azèŸò#¦'Ú¡3Œÿw0æNZbF”
I\#e{c‰g"Æ¡ÁSÒ÷ÇØeÇYîÅ97+Æô£åÊrõ¥a(
Ù¯E–Fê˜ùïȳ¾„ËÌy?VÖ`Ì[œ`ìÖ‡¹åÿKr­-BÑ]|Û%íœ^›H£ûiЖ°ìë'áó$Mç¹ë„4l§XǾ©ä'kcù7I‹ïÜ‰Èè,û¿%ñ]yËŒyÚ‰ó1i®ÂŒRmǘEþ‰1
r`zÛ+IúÌð 2<±
cò·TηZänI¯5òIm†é˜–O$oU“†ïL\"
מ"P»“Ò%/˜Ñ¬ÏäØù¤tÍ‹ÜS3y>UD¤5”ýÿ”û”wã	¹‡a¸Ü˜OÁÌypæç{]’–•0&kþ©ì÷„ˆ˜\çB‘DlÏ'ùjöE@/’wg¹§E"”~•kþ#ùdðuÆû"ÆÜ­©\?Š¢(÷[¶fðûYö×»”/SÞŒ1Aͪʼn8mª‚ÚÕK$}ŠÉÍf£z•bÌ_äZUÃú¿'š5¬À°OgP­rQ||¼iÖ¨ïŸJÅr…È–5(ÅþÕ«c뿇‰ˆ4qˆ<Ãæm‡’ÂÕ+ËŒÙë¸t9‡ÃÁ¤ßW$…)”‹Ü¹²²wÿÉdq¯T¾0ò…sç;¸³¡fvæ&ÜK¨4öÏ[¶ùKüâoð¼	S£ÛM(ftƺÌLoñQÌ„~gØS˜UÉó{<Å7¯ˆ+«+?ùÄÞäg{½çðÁ‰ËD̨L^îë®ɽF^G|¼1ßObzÜ­yñ×·¶K.Rïé•´¾9¥ñ{áz:”$_ŸNe÷ݸª
3áü¿ŒÕ‡Ü¡r"½¢å*·<Ü+æ`»víâÁd×®]÷ÄýÞ
ëyçè̺…èZ©ˆ5ÃâÚ™ðÝßèOµÆy­ßƒ<ѵ	1ç/Q¥ÑÞy­ujÀ—ãæ1è½_iÚ°,X²GŸùŒ2%óQ³jqöì?Iþ¼¡|ùñS$$$R¶ÎK*“b…Ã;êÙëÏ`!Tåæã@Ûݶ;Ðà›ùrkFÑü¡hþPT©Rt}hÉŠT«\”à äË^.^þ/õj•&úl,ûž¢nM—¯£Ç£8Kõ*få”#ÇN³vã>âã¯P¦d>ªXæá=ÅòÕ»(V$Œ’Åò°÷À)jT)æ1.W®$²|õ.*•/œ4â³tåNJ•ÈKXNc}žÃGO'lKWî$<ò,
ê”!úL,YBÉ—ÇXç'&ÚÙ¹ç8Ù³³rín^0ž“»¾I:6úL,+×î&úL,E
å¢N’øø¸BîÚ{œè3±Éî_r5²aF¹ÜG*2aF7bµ£hWÑü¡¨ºÉá`Ô¨Q8p É	BõêÕ¹ÿþûU¥st7s9.±ßϧ^­Rlßy”‘_ÎäþVÕøð­GoOþ¸	È%£qÏfZ—n²øQEIïƬOã4b·aÌ€kaÌP?Áå¢Ì@-0F‘ÜKãSÀÃ˜Ž¤wH¾qG	3W´‰%¬%.“Ï‘|ÁæÚ˜¹–6ŒS–î–°2ç+þ˜G_´„åÅ͂̕qpbÙ‘°üo—àr¤â'ç,‹™s:×ââ6Œ#ž:˜Ž´‘s¢Ï€ IDAT`'ý0sÚ0ó	KZžÀ¬ä…™[ÃÖã…Œ£ëü¹æx^”Z¹!°víZ¾üòKNŸ>ͨQ£œâ@QîÞ^^;ÅáS™ý÷Fz=ÝšÞx$CÝCF@Þ¸ŒÙñ¯rN0ñŽÃ¬'•CÎát’²\DWC)û§bÖ“ãú‰Ðò–ó_Ä8ÄXY,¹¯\cœ¼ûÇ0¦ÐÕ$ˆqÊsDâàtò1ã
u¦#-ã-Õ†qÂÑã粜{²”[-DT-ÇÌ›Ü*qÏŠYŒõ‰?G2¿b–(,÷±F³éñÕW_Q¢„q6Z»vmÚ¶m«‰¢ÜQ|}½ùøÝnÌšôSx™çŸh—WÆæÍ®˜Tfõ¤À®ˆkµö[AI©lºmÏñØôÓužïqLà*Œs;¦n¦§n%f5ðÜŽŒéés6¨×“¼‡îV^LX˽úKú9;51^¿Ú¥³÷ª•4¾œ.ÔÇb<Æýq¯QÓ›ÝáÞ§š8)é!ë0ž»b“uº?÷Áx¾Ü‚ñà×ãÐÉtŒãŠVo¢,ac0Þ:+bF•~±„½Ý1ð‘%ìÏÐqŒgÂ~–°6R¬Ät@v²„UÇx뜉q­ß—“›bRoLÆŒ>Õäì Ú(ñèˆñét’(i3_î¯&.÷ûÞO†;世"ŽœL1Ô\Ä£u¡íÑ/še€¡ï‡N^ÚJ'ÍŸ~Ëòǽ2è½÷Þã‹/¾`åÊ•+V쮿_5S®š?n‚	ÜítÊm“דùV ÆS›§°Ùî6Ó»dÅG
ÕDçñ'¹÷³$!KÊ5dœû§F^Œ©ÄOi<—“§0n¾rý#/ZÐÜWF¤¤Üc©ˆ¿¸Žt°¦‡sÝOyÓ'
éuµ¼b½v#`ŸEõ"¹Ç¹«Ë¹&–Ã-~A¨‰£¢x"VÇy׬k?]^ô›øAÂŽbFB¸…½‚ñ¹ÚMü ú'1ó1?v<-Âɽsffé¤\„{½œÞÓ=EÄMr?`F“NˆðúÁ-l¸Ôuq_å&0hÐ råÊuOˆE¹m
ö6ÿfý•¥˜ž4'Gq­¯RÉí˜b–Ji¹úÅp
µ?Š«gé)ü×bz»œ½âÍ0k­‘°Op-þÚÓû7³vŠÓ¤¬¡\o¥Äm©‡û™,ã̈„/fg‰|<B¾éiœ-ÇmÆeÒ0
¨+ÿŸòplOLOÜ¹NˆTò“¥rž‹k­—â’så¼ï`ÞHË9^Áeû>Bž—ó98Ý‰ìÆ˜©|‚9›!
žš˜^ÑùòÜIå¯J>š‡YǹæÑ‡òìWK©G*Ç?†Y×g)¦§×éÆä ð­äÉy»{çÚB%ž¥D°Ýï–'—ȱN˹3›rlÙ^³>Òl¹FmÙ^³¾UF.;œçÐÏÝû¹yÌGÊëe“°Lná3¤L?Š1?¶2FÊÓ˜µÜËì—èUÝž”÷üfôÈÊ}"öy(Sjȵ¶`Ös¯óNK}õ¹[XNŒYÚrï} fg™|¼-aÞ–í;pÍ•rò›œó8)MGKÙ%q³òšÔaQ$Ÿ„Ô‰{¥Žl{«Ëåîä&åÛQê'cÖ/ŽÛ!€²Û¤—ÜG‘e%,ÁCåa­˜¬î$¼ÜP7KÏÛ÷˜Ñ ç{åÿ–":²Èï߀ÆrÌKeø.3…Řa}¤ÇГª‰ÚÇÒ¨ýU®"½lžF×þµ\³¿4þÓ"€Ü·‡Hº——ߟJÏÒ¨vNÐ-'½wéYÕ°¤å
ùdÞ•gŒˆ=é1Ýàöb-‚f.fR°§’µâîj\¿YòÍvÇúJC'Hö›Ë$ò$®EoÛáZ„ÔÙ;ìd,ð€¥—· ¥§ô9ùÿ\¢6ÆŒpºÇ½’t
ÜMHQn4
—N/y'¾µ„õÅ,<í/¢b.³‰Ž˜9DÙDŒ¬Á5³f¯"böâšsZNþ¥þ:…1•CʨpŒùZUÙ¯´¥.< dEDìÔ“0?)^‘:â(ð„Ù¤l)eã6ÌHnõŸ·Ô_VK¤lñ–³ï-a½0óy2£0£Sδy3‡(»ÔUë%ŽÎ²'3ǧ«Ü“³.#÷\EêÅpRŽt©Rî¤dðºåV ŽÀ—–ßqÍÛ9—ŠP@
ßLzÈR@Vï:¤±ØR
u,BÇéÝ&R³3¤òœ+=i{-û×K£úDF	¬=Žõo±:dùÝCîË&ÎËçÿÝNòv /¹—|Û÷÷å¹­À˜^KYM3>´+
D Î‘žâqd*ûq9¦pÒDÖ<4Ü"€œ¦2I£åj(48°Œîüi@•-
%çb¹d$lŽ<Ïc*€@I´Á˜b…É﬑Q]D†s®¤¿”¯àm©móDd8G[¬æk?ŠÈ”rü9·rçOK1䜼(ï|€t¶XÅY‹Èí&ÎHUH:m¬âÌ)2*ãmqг¼Ræ·rô¤Eœ…`F¥º‰@±Š3?Ì(ú@6‘qfÃŒ@&õ׌S'ßaFÖ=‰³÷0£ÝÞ*€@Jzª[nÇ Ãn=@,¢âžçý ɼÒ(MÖkäÆ釹mÏ'…÷)ù<èv›|ü0s’R빺Brzî÷˜_®s3qŸCïV‘xÉ÷!Œ]÷©’	í3“ÀBŒYEg©ÐO\#ÝSKwFHæqŒé\•ë8ÞSþ=ì–§"D¨œ¸J<‘G0.ç#îçŠ÷>¸Ì1KcLYE1̱t”€1‘-jùmõ–Gr×Ö9ÜÞýVne8nLNʹ…YMÙÜæ|ŽËœía·°ß-}Ü–á2I&';-i›I>õÉèä±üCr·ß¡neÕ”-§[ÚXMÙ
ºÅõ)Ëÿî#ðojU%=r;Ð:ŒG›g¥nKJÛgOü‚™;1L
àãר¿‹ô>•—©s"haÌhËnÌ(M3‰ÇfLOþdL{ ôr-”󟘹KžØ!½Šm¥2š!½‡;0s>nÚÝØFòQ*w~ÂŒ¸}*~Lïezf!f­°3"6ÁÃ~á˜^Üd¿´'
“XŒYåÎë8ö¦çµ¯ˆ•nnœ×1æš½,™
ò¬àr„àld¬”|÷7f"ñ+׸þLOöÉv-¶EQEQÒ·r`¥ë.b¤Æs"4Rã+ÌÌîÒ€ýãà;	ßBòQŽ“¢³'30½‚
0îqfT¢œ‡¸]ÁÕDâ¶@„Õ3˜˜&©ÜÓ—¸¼÷¬À5Ä;Ù¯ÔÒä	‰S5Ì\ë$Ù-Sç5–JCÝOD]zçŒéŠsÎ7á0
×HaŒi‡7Æ,d¢›ú?{ç^UÑÄá÷¦A€ ô¡H“Þ»,€‚Ÿv,`AQQAEAЍ ‚DÁ‚ˆ‚TéR¤÷¤÷à~̜ܓ›Š„dÞçɓܳ§Ýs6»ûÛ™Ùëqþöús	(áÌîþT‡’Ø"äÐ@ëĵˆkãWÙ;ú¬‡pÇ› ÷[[…Ë.Ñz/âZÓXÅØ*Ý>q;q›ó~é½ß®Ïa‰nßLb×<Ã0Ã0ã<¸Úò%Ç'ˆò„ íM«Ï=öªÿ³w›šó¼ì@ÜoüV?Ãê˜qùû—´’(Í
N¯Ž<@ÆUÜþ„¤’/2o—³-ÈâTøŒÄj†a†a\]"›Ôa2.‘BÆ,@†½[Ãê˜qåö/Fê£Æ%iÂì†ñ‚À†Õ1Ãú㪪d—³’ö@7dáÜ‘äb‘Ès^tGò2¤·!‘º‚)<¡‡’8Bk$8Djo€.gýˆpý“sGX3ÒVý0R_ý(Œ"Éè¿¦mk]2§kÿg9‘@o‡…~@ÛòPí#Ü!³Û÷Ü™Änš"áõAò¹Ó+Ô^Õûú‰×£–Br¥CÜ<é*Ë‹ä$ŠF‚©¼ì*ˬeù‘°Ó=äŠÐs–FÂU÷ABï;Ïæ &’üµ/‰CfwnоÑÉ—çp?Ð	ÏßÄ!³oE¡mn#WYcà9k?{·Æ•X¿®&ô‚vNÁ	1½8[¢ÈÕÚ‰¤-Ä«ÁT$˜¡’ÐÎÁ‰fÜ”!IPèÄÉ

@F꬟!pP¡0Ç%z#Ñ}:ˆ_M ih'$Úd„üw¸A[$Œ}´
¡=HŠ€úHôÈ|HΡÝ**@Ò0ìF¢‡fGÖ¡:‚(¿WI»°Œ@ª…h$ç]íã¦!IDQ´	Åðg¸KÔüB 9ó$ò§cùTŸÓÎ'0Qô’ܧ}ÑZW¿ø°H¯ÝDïÛÉ·Ô‰všIƺGH”ÌÈä_a$²e]k?{·FZ@íÙ¦ûµÑ™«áÚ'ÖÕQ´Ef®&q²¶Ç<PVíú ³háA¨
2—7Šä•yŽÄ3…y.äpÏHRù°þÝ™Ís:¤[‘YÈ*€2"¡'¨*®èA$^G×s9hû·Û®$NÖy—>ïöú|@fìj¹ö)JâlÞÿe¦Ïª2K
23û22»Y͵o5}·Îssf~ï׎·‹~¯¬H˜i‡¼:hæz±úù6}_E­M0d\uõ#’Û«¿¸µ- ¹ºöè€ÝÍX$}b­p3XË×!ÑDÝt¦ª x>¨¬=’¯í½'7Í€=_«qSEÛ°a*€BƒúÇðøËT@áê—¶ Ö®-$NðšA'†ú«0s·q¡HÊ„¡úl*ÝÏ7*žv¨@t3I_°Éy榒Èu𒵆½[#- '´qoÌÔmI¡m€
GèTÑNÇméÙ„ÌH¹PILUõo¸Ð^@·Ö2ÖCÍB’X6’Ø%Àé@Öêß÷´£Èªûƒäg¹CÿþD;‡›õ»NPQÖÉÑSC}Éér·Ì8ç¸LEÝÚùþ¬Âí.íp×çw¿~?ç9Íví3H˯„Ì\Ä­¢ºk q’™½®~Ïæºý9döña$§Ïß*hª#3¶5T`–ÐÎô,Q¡Û£Û[è€ãyÊë¹<¹±¬ƒ²NÎêGÊžãÝ·ƒGÙµZö¦GYn$qõÇe‘H¹_<ÊB´½X‚÷”YHNº²1H‚c¯I¯H²c¯É˜.ú=jy”Ý£e·y”5Ö²§<ÊJkYO²H^´‘eéµäQæCòž-ãìës¬ý0ìÝ©^eÖÁfGd6ͱÆ,AÌäa:¨uDI&íÐ:ªp)~âZp—
‰¿\h£kŸøi;¨œ
	Çõ.‰ý°¦é€z$2ó÷¼Šª¾A(D¿Ÿ3s÷gw{ÍÕal"`½:›r¬!w	Èù¬@Q*:ªxsNF\õ"
úû¿À”M¦u:ç‡T8~è@îä§ãôÝ»À¹Ðë.q\2jœ40©mí‚	 㪪a*8fhÜ®Óɨ-$ö(±òÌF¬#E‚ʺ"“\{JAeí‘$Æq:Qã¦9°MÛÞ{ƒÊªêµ–tk¬–Í%q¢kGŒlÕïìÚA'‚fèÛrêÚþ7‹¹»Í›©÷›ÃCŒÍA¬C±bl±–UõckËQk?{·Æ•X¿.G (íœòkçtYœ‰
Š{ëËà 2³4[;£åÀ>IÉш€ób×¹Áä°‘Ä^Áw: þüCÀÚãf22‹VR;̆zÝÉAûeCföN»®y6Ö»þÑOr~œcö»þ±LeUñ8ñÁ^®ÏÔq	Ь‹iƒ¸ä»*a)í,ñ|<§쪠:p®÷L‰ w±CŸAÏpŸ¾CÃ0®zh»[_ÛŠ®²ŽÚþ5R!4‚€U¢Š•æÈdÖ×&¡ê#“[‰³QÖ•A&°Ú¨À¸JƒLB}‚¸}·E&ծѲh=Ï“ˆ»÷³®	—-{[ï§%2ɆÞïgZÞDÛ³Ç]ßñ}T
´ïyÝUö:báj€x	r•=®çj‚¸ãt=›ÖzÍëÐ(Ö¬Úzï-õÒïæ´çýô»ßXÖòY5#5ªìs#›Ñ:èvÖþäÖkâòät<Žo´ã‚ÐÄ5ð
¶
 °näFXN#}Œ€›ÁpÄ-
 ÌÚ)¸g̼¬0µt¿Oõót`%è:n¸?]
þ;,@wh熫Ìa«ƒ­“Ì},#àß}#â+î0I]3×}FG]l¸>ïyÚ‰_Ž˜s#
™™žµçn=€_õïçkW(2ó»È¥óê ÑãX€îACƒ¬óZ©·pÕM€€›¬I¸lmGJÃH»õ£âj–K?gÑI;7àÝ¢™¥ÓöùYÖ–.‘1Q…cm¹ÙuˆåÙYSã°ò6b×6§»«ìIÄ+!½ös¹ÊnEÖeSÑö“K€Ô%°ž©b
w‰g=Ó=A}G^Ä*Õ‰N·Ã%Î2#|íô=.q¡íê‚Ôv=›ñÚׯè=»××>;RûI·8{ƒ¤ë™¬ý0ìÝWDýºÔ(RïŸKÐ\‡¸cù\ƒô*æi‡Òø,¨:2ûõð²¦Ã@ó´QŸ¥3Q‘AÄ•aŠvN3’‡!–g}JOÝÔq›˜¡‚#€ré ~ŠÞÛÙÐA¼£ÔJ¸-üŒX‚f“Øgü-ë°®”îCZ/Æpïx‰|4ø\'ŽúIßÿB»‘ôÑçÛ=HE«(œ‚,ˆne(Õ \ˆeóÝdÊûiyr–=ÇÝöÁ v©W
=#¯ó„ê5o@"‚EZý°AµV?{·Fê@9/ð¼98ÿg¡$õívðˆ„v6òqî58çK—€¥Þ‹N/”äž÷p$rÜ•ÖAù7I÷LaF’ú¬?§?.Áx¾DÙÿ|ªÀ\£9Ïò°Ä‘Áª!®‘7§¯	XiãRèÅ%SOèDÊ@ظÝê‡
‚¬ý°úaØ»5RŸ2ÎL—h ¥½/@X^‰”#€À8hê9ö™ë!€|*<îÚã.¥57ýÏäCÜ’®Úž
‰Bö6¼`nÔY?EÀU=ÿo®ÏYש;¬»hK Ô~²¶%Ôê‡aí‡aõÃ0.¼~…Ø3¸â848JÉóÞ¸x]Í
Ë`ý1Œ‹¡â&ùSÐö=ú;Y1‡À‚oG¬|ŠX•ª!îu¨8™¡ÿ»?“8Hȇ	„¢wÜànUÑä¸~fEÜv÷ QgÿµÈ”Œˆ«æ	ÁVÃ0ø,çIR»H8׫£ö* âþv0™r’@y³Š•›¤˜w#®r«ÍHÄJs;ºø3s
ж¶-’ã%\ÏÙYÓ˜™@DÆz/õóˆ•k!²æo²¨~¨½BÃ8'­t"ýêB  HîÒKôA¢æ9kKIœª¡9’ÂÂY3ØW'$œ¤¯K—ZwšŠ‘ÄÞ5õs?$raWýübÕu§e¸I«9¼¢Ÿ—!'¦¹Ž¹Eï·Ž~ˆD7uRn,Gò,NzFÏ 7@"î"Ìc²N×½v¸µÞo}ýü>²þÚ‰@¸IÜþ‡ë˜Ûô~êç!HpÃøOðéÀØw縘ãsè?ÚÕDfà8¶:-ˆ&ßpÃêGr\£b ÁYö™«âÃ>=‰VÉL¤ÈuÎmHÔÁ_è”è=|HÒÐøqHT²ÁH0¶í\]³"ž|:ˆŠ#`åüDÑ:ØøMYý0¬ý8ç9b¤<8Œ\*ç*_†¸Ç:ÿ‹[õœç:¦ @E[ub£ ë˜õHÞäŽÙ†XpcŽ 9ÝÇ,×û8Û1k	¸Û:Çä'°ypŠ€°×1kÛã³]E sÌQ$HÓ¹Ž9I ]ÅQ$ØT… còˆÌ»I=bÿûÆÕþü§k€â® ‡qÞ¥ƒùXg€ÒL¹ÎaXý8›šzŽ}æ&ó¿ýÐß5øI‡Den›ê 
Afls•9SþGâ+î6.“
£\Èíæz-bi
CÂ2ÕŽ?"2!.u+¤“Ë„/¶úaXûaXý0Œ+XE‡Ê¤~0ç-=I]úB_~/‚¯ó-¼û…yœ35@ÖˆYuÕ	 ÄÕl d9P÷É	 ÜˆUæ7à{î4—ŽÙHÂÍŽAç‰DBþ?®máiÄš´q¯qrÎø÷˜éz®ò°8¡ök aà#­~PI)Ñõü÷¤$<º;Bà3HŽœH$7˜;‰òˆ•/qÿ©ì*kã°	¸ñ€·xZÿ~„Àz1ç]½ª÷õ?$oC)$`F:dRíIWY^ĵ*Zû¥—]e™µ,?b™èáê##ôœ¥KAùê|ˆûTMÄêØ—€•ÄÊyƒö¥=	äO¸±¢†ÝTˆ;ÜŠ¸H¬—kä*kLòAk¬1ìÝ©^ÍDO%q’4gpñ—ýñg½ÁÕéLÑmïb¿ûIzGÄ<¯ÇMwuNQÈŒê(dqó/Ú™ÔḆÓuð¥À¯üüˆK ÑŸ@’о‡ø¹Îæê˜•5d¤6´’¤ÑÜîD\KÎfÝÐÁä……ɤíV0’Ù~¡dàÊòv¥ÔÏT”¢BaŽKôÖvÞ§ƒøÕ&ã:!¹Æ"´ÏÙám7¦hB{€’ZV_û®|@QÄ•ÒYÛQV?—A¬v[\‚(¿WOßï2ÖÄh­§mô½OC’ˆ¢ýÞ—¨M ™O¿Ÿ“oj€ŠeÇuèSëÒ^@l8‘ßÒþ͇ä¤[KÀÝê	dýK:Œq*Ê@ÖlD\™*è³)íš,؉¸nF\ÜëZÿbØ»5Òªr,3!Ú‡	 ‡IœÐÒÙ×IXêCfj›yœ{pÐà'™ùš­×‰@%æÑsùµ“qi£[€ÜÖ¤füÜ.p]×nïê°¬²FÌ:¨ËsŽL:Àl‘Ly-´×•jëG&Ä=°¿¸µãh?±‡Äk@Ü
Gh_ÔØ£o«Âäî ²®È„Þ|d²ÍM{`©
“þAeÍwǯUȸ©¢âa˜
 ·ŽEÖK¼§Â)ƒ«,‡
­wõwö !½\ïc·
6‡PdÒo¨>›ÊA÷óЧ&%Ý}â÷ÚGßTÖ‰ˆ8‡@`ë_{·Fš@¯"Öµ1­$€
kc9™‰Š@¢²¸CÔ&çc¿Ùcpó(h$ ³€íUmtm¿±ôx	 µƒš€Dy=ä̘UDÂßZd˜uPV?ŒË[?nÐ};x”]«eoz”åF|ìQ‰»øÅ£,±-Á{¡ö,Ä« £l²=‹GÙ@dA}Q².ú=jy”Ý£e·y”5Ö²§<ÊJkYO²ÈÂv¯ÄÜ鑨¢“<Ê|È¢ùeœ}»µ†½[ã?«_—#Pu$zÈÍHXÇÃÂFÄWú9$ŒãcÈLV^×>HÅÉ!x?t¿|É{2èÌy§\‡!~Ôí‘»ÉÉtdîó¹Ïe†a\ÂI¶™*€‚'Ä^G¬@÷Htëð*â•p3P$¨ìY9ÕLÚ9ܧÂ)²ÈMsÄ
u”Àº-‡ªHĵ$¦bå¹±*=í!FžÐïñlPYG3Iš 7Y5™̘̳iGR×ÍWÉ¿zon:!“I¼=ú¬›[õ4#µªìs_V;˜jÚÙìW±PsdáddÍ}º}²àôFd½M1ó?†Xiê#¾Õ±*VÖ n÷kg†XkV»Ž½±¸1ÒÎ)	Ùx’ßc²`Pzý»Þ3…i}Æfq¬í°úaõãRž£²–%±ä»C„w$°–å=Ä«À±J´Ñ6<+â‚=—ÀÚ¡úˆËYNk¬*ƒ¸Œ]‹¬C\¬A&ÞvשhÚC`mh´
ª;Tlí&àu¡ýä³*¶ km °Î§¯Š»¥$^Kû	’$7qËëá*묯
EÖ¾~â*{É?‰Þ˜àz6­UàdÓ>ØY+…Þó.Äsã.k¥@;ìQaÔHŸE>k?{·Æ•X¿.‡\+ÄŸúú“_·;Ñaªkã>Y|êÌ`¥Cf“^'—ß‹ŠÚ€÷tuDYµ3éäjœÓ‘8ÒNyd'Ú¹µAfË"‘è:}‘Eµµø‡ß׿;X+”›¤~âÖY#f”Õ«—îÍW³\ú9‹KdTQ‘QÂÕþÿ©ýB¬–Õp‰Œ‰Úæç@r»ÜìºÎΚšG\eo«ÈGÖÔtw•=©"#=âúæ ý=­ IDATg·ºDÆÀ qV—Àz¦.AâÌI<·8Ë«‚¬	b™Úáê3#“í´Ït‹³Äòôàµ]Ïf<`!F﹕ë{GÜŽÄÙ$]Ïdí‡aïÖH3ȰŒaõÃê‡Õ«†ÕÃÞ­ñŸ×¯0{†aXGg†aFZÁaÿ%>{†a\6b†	 Ã0Ã0Òˆúñ›þIø|67f\Zþë°ÍéÎñ9'Ûua’†=¡¤lfõgH>4öÙh†Dš3Ã0Ã0#	 w^‰“”zqPç"ifêsñ’!%(‹D®;ù/Ž­¬Ç†a†a†q™¹\.píË€±H. ¦HR¶.H¾…=®Ï› ­‘¦7#¡E÷VѰ¥ Éâéß\Ó‘°¢G‘p¡_é5
 ¡« ­ýH¨ÐÖz¿Û‘П'‚®û<0Äõ9É
TÉþn/´DBˆ.LæÙÔ@B–ŽFr$žÛ¢Û7!!V[ëù6!!¿Íîo†a†aÈå²E«èð©°IHzIÜ6I<ê|^¬ÇDòN]]笇äyøø(©ÛÝûAµ6 ù¦ûäªÃTüÔA’ä$ÄË‹ä}ؘŒH¬$hæ©MÀï!$Éê< I³wƒä7zR¿ÿGHÒU\HÃTÔ-ÞBr,|‚äx±ªk†Aaàdòíc^C,ô1À»@N×þÏ K‘Ú>»“k?܆¸JwG¬õm´$1hCWYSàiýûdÒË¡’ܧ}Ù=®²RH¡tÈš;G]^$'Q4’çeWYf-ËLŠõ #(BÏY(ô2¹žÍ+@M$O^_$C'ij"=’S¯„«ì~í—B´?«ê*»Uû;´Ÿkä*kL ߟaÆÅåÈ”±jtÖiãŽæq5Î;\Ÿó‡\
»{ÿê,>N£ÛÙãœõT” 
¿;AÛ($Ãw
ýY­bç}í4‹%ó]2"Ù­qÝË÷ûý†dw„Ø\—@{Bÿ^Ž$îsDÕý{DP§ñ¥Š¥BWYÝH©s©³í0Œ”¨cŸ!“C¨P˜ãê7z¿hÿr¿¶óQ®ÿ
7hÿã‚¶ˆ÷A´
¡=&Ùêk?“(Š$T­©eeõs ;bÉoéêÓâ´_Ê€xD8kY£µol£l’DH´ÿDû´á®~óëc †Î
òOõù€$9O`íê[ÚOù/µÚW£}Ô"½v½o§ßnLfA<%ö¨à™L܉xWFªÖ½”ý‹‘:±þŸ}Ë¥@QÚÐ?¢3C‹	dß>—Zt.·ú#hêý{GÐìSrh¹ÎœuqýdÖ™ÁÚMÑŽÊM¸6ø÷é,[0\3oá®ûrP°Íµ>Äšå ëƒžáÓúæò¹.š2ìÝWzˬúë€Û=Q¦íæ`m·+;VÛÛ8í/ÜÖòuˆ«´›®¼ž*k,UaÒ?¨¬°øÚÕ79TQñ0LûwОXÄUü=íOÝýRZïêï쮲Ú×õWaVÔUЏ‡ÕgS9è~¾Qñ´ƒ¤knêÄßjíÝtf¨}éR÷/†	 øRЮ٪hÄÍ@5ä\3Wç@'€lúù•˜Œ¸ ÂÄédjŒ»	÷o´“ºÎã;-E1 Í’F°´ruz_	 wÀ*®û뙌rßÓD "âŠPßq• ¿ý¤êŸËUÇnÐ};x”]«eoz”åâ=Ê"‘õª¿x”… ¢%x粚…¬!õŠ:8BÀú,,Î	·°ðµ<ÊîѲÛ<ÊkÙSe¥µ¬§GYÄ-}¤GYz`/²v6ân¾Œ³çù2d˜2RµŠDÜ¿~ÖNa®
„©.ô*buéwh²¾æÄÌï©[U LUÑá BÚXOA#8ë|~~ÐûøQÿþ‡wèì7;]ŸŸÐ{ã:¥TàLVÑRÁCÕÐ{š¬×*’Œš¦÷5YK…vžM©@V?.æaÚ·ÌÐAwdPù8`&b‰	*ÌF¬#E‚ʺ"k=÷jŸá¦=´&.¨­Yϳ
q)»7¨¬ª^k	²ÞÔM¬–Í%°&Õ-F¶ê÷ø6¨,ƒN&ÎÐwŸêÚþ7µRn16Sï7‡‡›ƒX‡b=ÄØb-«ê!ÆÖ –£&€R†S§Nù‡jÈ0®"äó|²˜3˜tÜÏÎE‰]@¬;Ñg9&‰Ì|/ùI  óyÜÃù쓇€ï:ˆeª—
p
@F¯½t"+DÅÁG®²ŽÖ²¼§“RŽU¢
²–%«Š‘¹¬íõ—³*bÖ¸Úß2:ð¿VÅOœ¶Ï ®Ì;µ}®¤û]£eшÕè[»‘ –¢ÈzÖk­]}Ë/HЂHàs»s¢c¡:ñ×ÃUÖCûªPdÝë'®²Ç·ëHdÒq‚ëÙ´F\õ²!ëkµRè=ïBÖøÜE`­”3ñ·ñRh¤Ï"Ÿ	 ‹çõ×_÷ç̙ӿvíZ@†q•	 ÔFMþ]"Ô”"Úc¦Ó¸†	 #-Õfˆ«™ã’œÅ%2ª¨È(ášûSE†cm©áUd8Ö–›]ס"ÃYSóˆ«ìmáÈššî®²'Ud¤G¬-nqv«Kdgu	¬gê$ΑQ€µÅgyU5A¢Óíp‰³ÌˆUª
·8‹@Ö3½@ xAm׳XˆÑ{nåúÃO/qöI×3™º@öîÝë/^¼¸ð7mÚÔa˜2l€kXý0¬~iµ~¤n¿ýö„uu™3göÿôÓO&€ã"ÛŽ0{†a†aWà(Íï§ZµjdÏžQ£Fñøã#úÀ0Œ‹Á§*Ûw‘³8¾K|ŸÙ€H2Ôó%+$à4âÒ°+…ï)=â’pèŽÉޏ]“Iïó˜~×½WJÛ›ïörÔÃê‡aõÃHƒõ#­ˆ•+WÒ²eKV®\™6§>ö¿o\Êö'ä*¹ÑŸ¸ð$ ßðý^z	î©
µîB˜÷ºÎ2„¿CÒˆC†a†a†a¤WŠ
OFé§;Ç1^¤÷(+tçÿ÷ÿoÏåDa’ÍÜ0Œóçyö,„¤I*ÝÔEòÇÔæß4©„<É)VìûWÕë5%°hÿ\„}¯ð+¨Ý6Ã0@ÉPøÕõù9yp&"Y¨§"Ñyêèö,H¸Î©HŽw˜èVHDšA.ñ±	s:¤‰âVéïšH´žÉHîƒO]ûÔB¢ôüˆ$~Ësß­4t&Ð͵ýn×½÷Æ;΋HîŠ?Iœü/ ,çÛ0‰äUI éæåªz2Ç
×öäNÄJÜê¯[¨¬NcÿÞˆ¥·1Qì“ó¸F?$Z˜Ã ½rÃ0ø²P‰gWÃôݾ™}xT··Dr0ÔCò.€Ì~vÓÊ
À>$*ˆåg.Ð	»é&½ë^*ëù«"9œ0 =‘LÙ7 ‰P۟種À"áIÃWpõUÖ:.¿ÞC
^ו¯×açæ$¡¥ƒµ<9ú"a{Ÿ:¸Ú¨öZv'‰sŠE"8OµkG¬QÑÿÿç=&R>Òí׫°ÉˆX¿;¸ö)‰„1Îçj¯ºÕtÒåýìÜW-$ig	×9nDòÔ<’f÷öb
¸à4bͺ±b-Ö‘±(gÃn‚$î;œ@,Enþr[õ4Œó¢&°$™²%*ÎF(’å€kÂ$ø	\ò•kß9Únî'`ÉIYDÿþN'=Vëÿv9×~U·¹7kô½ÖK®}Ê·èýlEòÎLE&‹vjû2UÛŽ:I´ø¸MÏñ?`°qÍ{7•¾ûÂȺIGàù€×«_Œ~owîg´ýD,rn·Åôù…"¹|*»ÊÚh9Ț͆®²¦*ˆAòµt•Õ@ÖŠúôÜã*+…äJ§uâIWY^àÑZ—ÜnÑ™µ,¿Ö•Ü­#ôœ¥‚@—Pö!“s5‘ A}ƒú™NÚ‡¥G&Ý‚ú~ ­ÖýnZnÒ¿ŸE :4F¼>Œ‰ÏçcÞ¼y¼ÿþûìÞ½›~ýú9ø.Gìý²D£ƒ„®Ï'‰— ÛB {tù
HÒ·–É\ëàyÜÏI÷äŠ^s7’<®5IÝhÎF¾ ¿6m/ ÛÜlêëÀÖYõ4Œs’KEDrîgñú?í8n¾G"/žRpq‡­¯íCy`Òvb îWÏã|QˆE·¥ë»@,ôze´'ù|‡Uð¬Qá’¼r…ësk쎫£e#€o—ᥩôýoÔAü ¤¿„Lн¥ïþâÒÜL'¶UasI ú5bA»xSëÀißèçbˆ«¶ãZ=¥çI§Bù&-ûCÅé}wߨ°ñ#®ÒôžhÙû*dÿ@\¸ãT@…LÖÐ}h}C&ñ¶Ûô>ÞD,ïèý®ÐkfW!ܱÞôÒÿ‰Àg*àîVA_IëÑVýŽ5´ž¿­eg7ío´.>DÍ€Ï]m’Ûr0YËCuÀép§èÑAqWÙ—zOèàÛ}\AmkÑk6v•Å¥âú‘IüýµMv½	Sq1XÅc… cǪPŒz^è1cµ
¿;¨¬«¾çù$
´Ñ^ç/zOÕ‡Í*¼F•UQÑ4Lû9÷ÚÑXÄ3à=`ÁU–CÅô»ú;»«,ƒŠ¹þÈD_QWY¨Š¬¡úl*ÝÏ7*v –!7uò`µ
K7]€*Ò_ºÔõ#­ðúë¯ûsæÌé_»vmšø¾6v0.Gÿt9P‘ëŒ,à­¡Ûïu5ÖéHìRRVíV:ÉêêÐnÖY§W	¸Ä¶Œ³çq1tœ:uÊ?tèÐ4ó}mì`¤t5ó—w­Ô»œˆ\@†Õqº)™²›I:ï%€@f«ëÀo²q
2ƒîHŸÑ²ÀÉ Û¸B}¬âÊ@k‘5æg†a†aFšÁa†a†a&€Ã0Ã0Ã0L†a†a†a˜2Ã0Ã0Ã0d†a†a†aÈ0Ã0Ã0Ãa†a†a†	 Ã0Ã0Ã0@†a†a†a&€Ã0Ã0Ã0L†a†a†aÈ0Ã0Ã0Ãa†a†a†	 Ã0Ã0Ã0@†a†a†a&€Ã0Ã0Ã0L†a†a†a˜2Ã0Ã0Ã0d†a†a†aÈ0Ã0Ã0Ãa†a†a&€Ã0Ã0Ã0L¥r€,pLQ=&ì
ÿn9ô>³^À1E®’ïf†a†a„ÿˆªÛžÀÀBà'à= 5z…>‡—ôY´½€c~Ñcr§ð½ô†’)ïê%SÞNËkéççô>ï¾€{w‰¾Û¹ðéMx†a†a—Jû¤¤*ÌÕsúƒÀ_úÛÙ¶hlè¬ü¦çmäQVÒõ,?JæøEZ^þ*@õôºíÿÓ0Ã0ÃHi”’.pkO5à3 0¸ˆFÜÅ^²URɼÈü“Â窿ë{”9Û¶'SžUŸù^Ÿƒõ>ÇX7Ã0Ã0Ò2)%€|:ÈŽ@¬
÷›ÜJØôJœÇ9ù=®“¸Èt÷ä»Àcò¨`KŽ}@pæ,û¤C¬6¹/ày;¨GYÄ¢6(ä
*¯«×™NÀºwTïóøY®Yä]î¼SQ{¡u(§Ç{ý7„ë=—ºÀgk†a†a¤qR®­žg5ÿníÆgÀ~dá}_¬û_]ƒó¯Upø]?k€›<ÎWJÏ÷bmZœÖcvwzãvk¯‚͹ÎJ ªÇ1cô:9=Ên–§\çÙ©ñ|÷G€Èz7Û€ñ@=gðwyW·wtm{BïÓ˽ïaÄ‚åâù@9’wË|#àæ8JÅÓ^`r2"°/°Ëõ,ö=ô»:ôiù	½g秉î“Ißëñ º°xÊþ
Ã0Ã0Œ³iŸ”Zh^MÔÿ…’q“‚¸o}lu•_ëà¹4ðhá&$ЂC¨ž¯ªú¨zJÏ|êq/÷5€á*Äî*"ë}Š\ûfÒë[Þ^ÔAyO`bIªC 0ÁÙˆf!k¥jSt»cñ Bå8â÷¥ëXÇ-nªk[z½Ïˆ ë<ôQQõ’
´æ*bvzÜ—YŸT˜ŒUqs·Š²,ˆ¥ kÏF\$Ðãâ[ôše€–ºïd Fßë}oëô÷½ÞÝ=KŸmIûŸ6Ã0Ã0Ω‚Rà?èyîø—ÇGÀMÎËš’Ä–‡Bˆu`YÐö²,¯••@\¶‘غâX€«Ðqó9I­*à¡¢ŠÀõˆ]0éÎ󙼬çîîÚön«î9«\åш¥kН YõÙí!©KÚ×ós·;tÛ—Aç@ÜýÀ¼ s½ªÛ_ôøŽhÙõ®mg‚ªïgS2ÏÑÜàÃ0Ã0Œ³jŸ”0:‚a“GY%ÄÒâþy;™óôÄ; ÀQÄjÌ&Ä
Pš¤®b .Y}ƒ¶­F –”ÇSñ,t@,0碣Ô»"îvÁœ8Ïg:U»ÔWð§~ž®‚Î)^ë’£­
¦Á$¶¶¼÷z¡û]åîóŸÔwŒOÅWÐË£ÜÙvÛˆõý*¸¼êîûŸ6Ã0Ã0ÎFJ¹ÀíÕß^ID÷m[…·E`ñY®QYËR	àAȬí‚*nÜ,UñÌ,àÑdÍ_Ûœ 
¹ÎãY”ÖßS.ò™ÎÓ{¯X;N¨úƒ€›átýÝY#Õà®í|÷¹e»õY^´½˜Š•Ç,LæÝG©Àz*äGÖlgõb]€-ˆåð{àwdM’a†a†a\´±>õ(GÀ]*7ÞV‡]Él¯XD|ˆÅçgd-ÎiàF 2ÞÌ’O½]{E…;ì±Í±ÚœO×B*Pâ.ò™Æ«Øi¢ß+âª6صÏl½V}@^ë’#ßy<£`”±ÀøÏóÝ9õ¡É[ýN$#œ“£;b¡ë„¸>¤âg4ð<)’Ü0Ã0Ã0ä)€jƒ.Á}B\žª#ÝÜ”WäEr!¬KÎŽKp¯[T(äHÁøT@
¸©Ms•AÜá –°ŠÈšž¥çqîçùŒ‚¿[q‚§“T^×ø‰@ ƒ‹å8’vâ^y+²6é^$ B5R6¹¯a†a†‘ŠH©5@Ÿ!á‹Ûª Ii‘v-âv,~Buœ¥Iù‰X×_‚gºB×IsMÕßõõç(ýÍÍt$/Ò­ú<Îgýû»Wð(ËŒXm‚Y©×¨íQÖÐcÛZÄ’U3™÷àÅIýyŽýü*þ^Ò{ݮﵰý[†a†a—Zí^Óó";%®wJþ¥HUíIdIräB\¤Üä@Ö¹Œ¿Ït°Î_G‚\Ì3˜Xyj—·x5Î-šÎÅ7ú\Ÿ@"¹y	MÌý݃Äá®ó⽦ëé-'ðæYêD6×çÍA"ÕMºdî˯¼]
Ã0Ã0H98€÷Eò!A~DÂS¯ÓAk,¤/ðÜã§ïOÖ-€»À	’9n%i¬²P¾ 
…Ì*ž]‚g:ÉUÓX‚¸ï­P1T[ûç.ÜÉÔDEÆ}f"Áb/PíÞQá:èM Ð=H’Ùâ$¶&ME"èÝ‹¸ÙM@,5ÍUø>NÒHl/MÎH‚ÕokMnµwèµ¹ÄôbÄê7		qÉÝäG‚-|	ÌQ±TTëBI`"ɯ#3Ã0Ã0 å×KÔUp”@.¿bg#	0}AÇ8y€Š&sÎ,*~ÜçÛˆ¸™
ÕÏnääú\ÅÃ×qGT¤ãäjëQ–W˾Úî•Èáa$:žûžªà¸^r_7™}kùngÞy€žG‚8ר¢ÏuÜY¾ÛsH¢ÒƒH †+’øÖcÿŒÀ»A×ñ«Àû•¤ÃkÜRÛ‘µF~Y9‘5PÇ=Î3‚¦`†a†a¤Qís©Œ‡¨ ©áü"¨‹‚*rŸc?·r(Ь
»Œ؇XŸê#ÖŽtWhEG\Î
^Ä9êè3ïsŽ:ëzÿåµÒ«Hj€¬9‹¶ÿeÃ0Ã0ã¿@ÿ%^ÈHÒ%³mª>ó:öˆÃ0Ã0Œ+U…Ù30.‘HD·ˆkadÍPI`²&É0Ã0Ã0®\”
¿“Y€.‘àÎú›3úùRÆÍÑ0Ã0Ã0.©öI(Y#’ÇÞñ%##’s'£=
Ã0Ã0Ãa†a†aÆ&€Bì†a†a†‘V0d†a†a†	 Ã0Ã0Ã0@†a†a†a&€Ã0Ã0Ã0L†a†a†a˜2Ã0Ã0Ã0d†a†a†aÈ0Ã0Ã0Ãa†a†aÆ9	»ØŒµÓo1õÒªV.ŸÕãRÕ³ÆZýHÅäªÕê¢ê‡ïh«©_¶¶>{
†aü˜È0Ã0Ã0@†a†a†a&€Ã0Ã0Ã0L†a†a†a˜2Ã0Ã0Ã0d†a†a†aÈ0Ã0Ã0ãv)O~âø1Æ~1(p±Ðp¢²d£BµäÊ[0E®á÷û™?s"ëVýÅ®¸­dŠŠ¦p±2T«{™2g¹¤ïû¯†0þ›a4l~;w<ø¼Õ¦I|üIþ˜ü=[Ö¯bßވΚ’e«P©ÆuD¤KI¯=¸ç3,ž7vu9v! IDAT¥n“VIêÖ·#pêt<
›Ýž¨Îîܾ™)FÆm÷tÄÂ’yÓX8÷wíßKæ,1(R‚ªu®'s–˜‹¾Ï·×äTüIz~8žlÙs§‰zqìø	}16ásˆ/„ÜÙ³R»r9
çK}Ï`ßC3>ásDXÙ³Fs]ÍJäÉc
EG ÷À>‡‡…’=&Ц
ËS¤PNŽŸˆçšjX6«/™2ž½=Ù´eŸ|95ÙòÜ9³ðèýMþõ=oܼ‹ýŽ/O6rdÏl/Ñ0#u
 £ŒÞ'Év_Hï}1|…Š_Ôù÷ìÚAï—Ú³zùB9¯Ï‡ß/yóê4¾…g_ÿð’>¼#‡°+n+‡쳚ô/Y»r1}º>ÌÎí›’¼Ã»y‰ÛîíxI¯`ßnvÅmåØÑÃIë©ÏÇÊeøsÖoœ}:ípŸ ÏðQI­ÐPzu~„»on’ª¾ïž=¿oDx8ó¿JîìÙ¬Áp·¿GNн×7I¶‡„øX>û]®)ž€ôéÂ/H xÓ¡|ÙB%€:¿úc~˜Ã;¯·ã¹'n²—h†‘›¾ŸN"{μôïþ8‹æNáû¯†Ò¡K_Ö­\Âæõ+9tp?£2S¼TE
½&áØ?gOâT|<å*×aÍò…lÛ¼–æ·=Àûo=ÍêåÉ“¿O¾<€e*qäðA–/žÍ®¸m?v„%ó§FÙJuX8g2~¿Ÿêõš±ré|¶o^ϱ#‡È’-¥+Ô${®¼œ¸‚EKQ¶R­T[WF÷ïFΘ,ô1†ï'ͤkÿáÜqc#vü³‡¥«×“3&+ùsçà÷9)Q¸•Ë”àÔéÓL·˜[ãÈž5šZË’3&`>ÏÄ™ó9zì×ըȆ­qìÙ€kK%_®,[³-;þ¡Dáœ9s†™—Ò´vòåÊÁÃG˜>	q»÷Q(ON®«Y‰°ÐЄsoÞ¾“ùKW±ïà!òçÎA•²%ÉžUêå?{ö3éJ¶íÜE®ìÙ¨\¦ùsçHô}ÿüîC"ÂùÿÅž,X¶Ša£ÇÓµC;)[¾šÕ·pàà²FGQ¥lIbæM8ö—óðûý4¨V?-#n×ÞT'ƒY8µùóÆÐîÑ÷˜øûÞ}ÿ'>ìÿáaô|í®$Bh朕,[±…ZÕJ1Cz–þ½™¼y²R©|fÿÚ€ISÿâ•·F‘-k&Æz€ŒÒ±{Ï!¦Ì\ÆŽ¸ý+š›U $ÄÇ¡Ãǘ´hZ‰°°PV¯ÛÁß+·’3GfBBBØ'“eËVlfÜøù.˜ƒ
å
[g`†‘ZСý{	àä‰ãráðˆ„²WŸ¼•£GA|üIî|¸mî{€þÝçðÁýT¬ÞEs§™!ª5`ÑÜ)<þR?J•¯@æ,1Ôlpc`¸k=»ÜGúÈ)QŽKæR±zCbK–§k‡–øBB		åô©xBð÷ï¥g—û		¥DÙʬük^Âùßò}¢Áéšåyêîú=|€ÛîëÄ]¿hµëü9{qÛ6Aç7?J–19óÒäæ»ö[ù×<ÞéúyÆrúT<;·o¦å]säðAÞ~á^BCÃ8ã?ƒÿÌ2dŒâíÇS°HIÖ­\LÏ.÷‘=W^BCìLŠ” ç‡?“!cTÂ5¦ÿúöéÂéÓ§ð…„ðL·!ÔiÜ’ªuo STöü³e‹fQ®rþ^2‡];·‘!cÕë5“QT4{ví`åÒùä+TœEJ$q©Ô£“Ç•hÛð–-{nº<Òü~BÃÂ8Ïçãþ§^ç¦Ûö|vÓ&ŽapÏg9y⸈²3g¨^¯/¼õ1¾Ô·´/OŽl”(R€Vëðý¤™;~‚ý3}þž~û}JÅbßCÄíÞK—‡ï$îÜöÔk¬Þ°%á™2D2èÕŽ4«WøøS´ìЕ?—¯ WLV"Ó§cã¶8ú½ôwÞØˆ¿À?üF­JeY°t'ãã‰-—­q»¹ÿÅžìÙgÎø)W¢(ßzèLùä»	téó!!!>BCC‰?Eû[›ñö³3}þÚtìˆeçd|<õ«•gtÿn‰¾ïî}ˆãÄÉxÂÃ¥©>}æÍzŸÏGX˜œ;$ÄG§¢ý­Rï}ámçÕ+2eî"òçΑêÐˆˆãØqé?""äy8O«vïp`Ó2GEòêÛ£xã™À

¡Z¥bÌž¿šV7V㻑ÏQ£Š´í›¶ì’gš°mÒ´¥Ô¿ñ5öí?’ðîkT)Τq¯•)’_OeÜøù¼öBžép#M[¿ÉÖí{˜4öú¾ÿ³æ­`ÄWÓñÕ4¸û:†
|Ô:Ã0ŒËÌe)uëÔ–ŽwÕcùâÙdÏ•—»é’Pö|ጞ¶…QS7Ó}àÆŒèÿÌ™DçˆÛ¶‘§º¤ÓkƒÙ¸v¹v`a\S®*³~ÿ¡½;3´wg>z÷¥DÇ?v”P^ì5‚Öíž"*:+oðc¦oeÔ”´ïø§OÅóíȉŽ;sæ4…bKÑoä\‡ƒû÷0ê㾉öÙ¼a%?û6O½òžÌ.þ6ÎjÖy°q¼Ã¼cÄÏ£>Lx‡ß}60ÑþÛ7¯£xéJ¼Úïkê4º…ÂÅÊ0ð‹|3}+_OÞ@Ó[ÚqôÈ!~õAâÁÑÎí4¿µ=½>š@Þ‚±lÙ°š_¾û$Ñ>;·oä¥Þ#¹íÞŽøÏœáÉßëÀ3‚Úo`ê/â3}¢žj7º™ðˆt4¿µ=¾~õ!Ú5àáÖUøî³	¢xú¯ß1yüW¤Ìȳ¯ȈŸÿæ¥ÞŸ‘>}™x÷+FOÝÂè)›y±×Hü~?£?éëùÜÜÏÐÞÏsòÄqºöùœÏ'®¦l¥ZÌ>ß'ŒJ•ueÜä™ú|,=†|@Á¼¹ˆÉXC±bÝ&V¯ÈèþÝhZ§*o¼?’Õ¶P«RYf~ù϶oËá£ÇèôÖ ŽŸ8Éè	SøsùjrÅdeÜà7yù±vlÜçyíKWñd»VŒîßb…òÓé­÷سÿ ¯=q/ë'}ÅÝ77aéêõ¼ûÉh¾øa!!>Žýˆ-SG3gô`Z5©Àç?N`ìûo²eÚh–ü0œw¶LrÍëèLÃ{žféêõ/”§Úµ–Ûçctÿnl™ö
›§ŒfXΜ9ã§ß§£“œcÇ®=éö4½:?’êÛ’¦­ß¤l­g™>kòÅðæËÿóÜoëö=¼ÝOÚçÁ}dúøî	V™sq挟û:¼Ï¾ýGÜçAmÉ]mê2gÁz”öbøÀÇÈŸ7†}¿£åݽٴe¯v¾uÊ0°çý4®_€§kÁ¢i½yí…6Ö†aü\6PëvO‘1S³§ŒgíÊÅôëö8]û|À®ÛèßýqÖ®XÄáCqAÛ·÷ŸD¾o½·#
›ß.ƒ’?~àôéSìŠÛJî|…Ù´n³¦üÈ¡ûà¡gÞJt½ðù
K6KÿœÉ˜X¿ê¯„5 ;·oNrïmï†lÙsÓø¦»XúçLV-[¨¼L…šÔ¿þ6âOž`àO·m#‡î¿äA®v"ÒE&Ç·ò¯y,ž?£‡R¬TZ·{*aÿÈ™èøÊ ÂÂÅ•åÐ}L;‚5/dÓúœ8~ÌófÎÃMÿ{ŸÏGzÍùîó÷’¼Ã:[Q©f#¢¢³1fÄÖü½(¡ìºæ·3qìfOù‰ö_gÖï?Ð@Ýß®ou/å*×áÇѲxîTâ¶mä³!=8vìw=ü"-˜@µz7PGUÕ:MƒÕ­˜8n$ëV,æÈ‘C	Bçè‘C‰,UëVýÅñcGgìƒûÅ`vʼn¥cÙŸШÅ©®®ôý80À/Q¤ï½òT¢òìY£é÷Òãø|>f/q}Ï-M)^8?îlIÿOǰÿàaÖnÞÆµü4ªY‰šÊP³Búø†õ[v$¹výjåyþAy¦ÿìÙŸ°Ï„ésùíìÙ/ïë?—P£Bi–®^O¶¨T¦MkWá–ëjP³Bi¾Ÿ4“¶_£\ÉXÕ¨Ä-j'¹æÝAhHc'ÍdźMtî=”A¯vÄçó±zÓV>û‹W®åÐá£	÷uâd<é"n^/>r7Ô­–&Ú’.Z9ßþ8—‹Öq÷#ïñÓ×]’ŠÙEë9uê4¹rDóX{ùÿk}c5ú
Îk¬ß¸“m;öðõw0jì,vþ#ýÕ”ËéÞ²eÍÄ>Eƒ›º1eÆrê×.M×çn H¡œd‰×Û¼y²™ë›aFZ@
nhC"%(Y®*];´dáœÉ=|=»âÜó¢³fç>OddFúuï â&ñ‚ïâ¥*&ü](¶TÂbó9SÇÓò®Ç¹ã¡¨ÙðFž¾çº$×Ì)Aüü1ù¾ü°'…‹•¡]‡®ìÞ¹/?ìÉEæÎ}œ>}
€ÐDå¹uGhXàqž	²^I)\¬4Gà¯?gR±zC:÷ÆÔ_¾aÀëO$Ù¿P±Ò	â`Ü—ƒùîó÷¨Xã:zæm–̟Ƥ¿LRoΜ9
~?ø|ɾÃu*É÷Z»b1öéBLμÜÞþYÀÇàžÏÈ}xÔGç\!!¡)^FWú»@‘’©²®íþ…óå&WöläÉ‘-Aè8”¿&6ѶP}F§ôù9ãÇ?¡,½
…#ÇŽ'säèqÏkW,UÜõì×(Y¤ éÓ‰+o½ª×’%*¯t¸‡ZË0aú<æýµ‚nï}ÊÔ¹‹Õÿ5îiy=±òòã”Ù,X¶Šw†ͨ	S˜?fh¢kÞ}sSrÆd¡D‘Ü×¥'?MMÿ—Ÿ`áò5tí7Œü¹sйýíÄŸ:ÍóïMø®éüT(U,Í´%ín¯Gé’ù©U­$õo|	“qààÑW8‡ôéåù;~’Ó§Ï¡ÃÇÏëλ÷ù|”-UðpiC®oTžÜ9“]6ý“eÓ–]:|Œè̬Á7ø‚¸l.p+þšËÜé÷ÅûDD¤#]dþ^<¿ßOÞ±ÔmÜ’m›×&{Ÿ/p»9rççúV÷ðÕ°ÞôëÞ?gýÆòE³½
Z±|Ñ,Ä”­L•ZMؼ~E²×5¼«—ÿɯߋûM©k«YÍI*×lDéò5è×í1†õ{™¿Ì`ÝÊ%Þ•Õçý+×lD©k«±qíßžÇ>¸Ÿo?{¥ÿ`Î4™é½æßaÃæmU8‹«KýÚ$pwj×€‘ƒß`ÎÔñü9{KLH]©†ˆòù3'2qÜHvlÝÀŒßÆräð–é÷(X¤$µ¯»™m›Öœõ^Š”(K†L™9yâ8‹^Ãݾ̭÷t¤Hñ²äÊ[(UÖ•2Å
S±tqòæŒI"~¤n$ÞV§ÊµŒ7‘…ËWÓÄ7œ9ã'G¶,ÄÌKƒj˜4ëO}>–ú|ÀÎ=ûÎ:ð±4•(R€è¨Œ<÷Àí<÷ÀíÔ©\ŽÂùÅZýÃïpMÑBô{éqFôzQ¬6ÅB7qÆ<²gæ­§ä›ÝÈ•‰»öpàð‘D×ücáR~š:›FK}ÊIXh(³‰•©t±Â´hX“5·$߸ûÒNš·™sV2nü|ÞyOÂb§OAÆé’ìW£Jq2GErðÐ1îôïþ‰o¾Ÿ}^×(R('…
äÀï÷“#{fºwiË«o£nÍR-œ€•k¶ñxçaDgÎÀ£÷7aãæ]<ðä„s”*)‘é¦ÌXÆäiK,J†aÆåå²Y€†ôz.áï¨è¬<ül/BCèۤ_
ëÍßKæpWÓâT¨Öà¼ÏyßÝ		eÂwŸ0}â·	k3|>_º ähzK;¦LÍÄq#™8nd²×
		åŸí›xá¡ædÉE›ûŸ±š“øBBèüæGéÝ™y3~aü7ÃÿÍ0BÃÂ)^ºÒYo~Û¬ù{Ãú½ÌÇ^¥\åÚžûåÈ•)FñÅâY¸X®oyÏÝkýëÛðŇ=Ö¥5t¹¿9unìçƒmË•…»‘`5´àæÿ=£?bhïÎ	ûÿa	
›µåÛX4wÊyýdŠÊÂS]2¨G'÷|–!½žK˜qîôÚûV±€WkÇŠu›˜÷×Jš=ôÙ²dfЫ‰çúºÕ¸ëæ&|ñÃo¼1x$UË]CÉ"Yµa3a¡gƒ^éHû—z1èó±¼ÿŸ„gï¬ÓôùXV®ßœ(ú£ÿ»€1§1~êœDe÷µ¾èLÙµwÂ5}íÝ„¿sÆd¡ßKb½½Åuúb,¿ÎœO‰¦w'¹´Î#ORÄd‹âýw §[P³Dgdè»óÀ“Cøø‹)d‰âºzeûÓ¼DQü¼'à||ùÑS´½¿Ý{}Ãë½Ç$¼Ã7_þÇOÄs{ûþ9z‚/?êHÛ–5Yú÷f¾ýq.ƒ‡O¤Ã×ÓúÆêþ+?ÿ¶ˆŸ[Ä{½ÚóÄC7Ø4øÜcPÀ¯¿ÿcgíô'WvúT<+þšïðúÈCîü…	wE;}*žÅ󦑞Òåk°r©S²\ÂÃ#Xù×2ÉuvíÜÆ–õ+Ù¿wÑÙ²S°h)rä’™¶“'޳zùBBCC"Å9;z˜Åó¦“#‹^ÃÚKãšk«±{çvjU‘аpFý¾egqäðÊU®KTtVþÙ±…vl!&Gnò(
À²…2›_êÚª„†…_õ¤U­\¾‹9þlõÃͶMkضyG$[L.Š–¼6á9Ü¿‡ÍëW‘1*3EŠ—MtÜþ=ÿ°lÑl
Å–"*:+[7®IØïÏY¿ñæsw{My^ï[–-œ…Ïç£|Õz		V7¯_ÉÁý{ÉW(–¬1¹8~ìkW,!<"‚’e«$ºÖêå9yâ8aááž{ó†Ulß¼Ž#‡%kvJW¨Id†L‰ö‰Û¶‘
k–qâø1ò,F±Rðù|ÄÇŸdñÜ©¤Ï‘kÊUeÕRY£Tª|5BCÃX¾x6þ3þ„ÿ	×ÁÕ˲ww™¢²P¬Tbr乪êÇÎYc“­ñ§N3ÿ/±ÌV,]œÈôIgôÿÙ³Ÿµ›¶’5:ŠR±‰­_~¿ŸËV±q[1Y¢©\¶Ñ™áÏ?Á¾ƒ‡9zü8Ù³FS¶Å}ÄŸ:ÍÏõ¢r™¬Û¼»÷R0o®$aªOÆÇ3éJ¶Æí&Cd:JÅ¢XAisö<Ìâ•kÙ¹{Ù³FS©tq²FË:®#ÇŽ³dÅZ¶ý³›Œ‘é©T¦DB~Ÿã'N²P×&„„†“%šÂùrœŸ8Ï”¹‹Èœ1UÊ•dÁR‰,V½BiBCB˜µh9øýT-wMBô¸ÿŠ\µZ]Týðï|ýˆ?ÍsWº&«BÈElá\¤Ó×gÎø™>K,Ãuk–"44„£ÇNpêÔÖoÜIé’ù¹¡M¦ÌXÎË϶NÖ†a†a†a¤	íc Ã0þßÞÇÊUÕÿ¾öõu“²•V©,‘6PDhYÄ–¥ÈR*CL)[‰†‚5¡ˆ©Äh!VÖ¤%Ò¨P)[C 
X%RÂ"•‚K‚*Kñs^Þí0¯ó^çÝ9gæ~?ɤwæÌܹ½sîïÜß»ËO’$©2L€$I’$™I’$I’	$I’$™I’$I’	$I’$™I’$IR+uÁ<¬#ÔÙºì*±HÆã‡$µ]dsçÃþaÿÜI–ñCR[ð8I’$I&@’$I’d$I’$I&@’$I’d$I’$I&@’$I’d$I’$I&@’$I’d$I’$I&@’$I’d$I’$ÉH’$I’L€$I’$ÉHj©€ƒxÿp`GW›$I’z}øóêܾ1yL®ÄüVâýS€âô$àÇÀCÀ™þ¼YôÉ>æo+I-?Ý®%¶3°w?m#ã ùN¶àÝ:ƒèðøÚæ~ÜßÄÏMrÕK’$U	rµ˜šs
ÉÎ7€}âôeÀjÂ霋iñõ;jæ¹pi? ËU,I’d$åb!ð^œ¾	8x">ø<á(Î=1:ؘ“›G€å®FI’$™©ÌNÆÆDgF!º;þû*ð>°0›pdç®Ø¶p$ðœ«R’$I&@ÊÙ(àû„£>ÿ®cË;¿õ^¿ÓlüxpªÜÕ5óšâê”$I’	r²0§ð|
ᇻG·ÚÏŒÉÎÀSÀ&Âénk€×€§còtOÍ÷ô'Scò4‡pMÐ&I’¤j°R[G¸†gráÑœœH8Ú3x8¾9°8
X_Hœ6‡jþ, œ:÷&ðpk|Ïð8ÿ¿ŽÓ#ü	$I’ªÅ:@*󷵨?ÍÃGç>ª?¾B8·×gâd¼¥cƒ¤ÄñÇH&@²Èþ1ô†¿'ÜÄ¥øáÔ^¹íK2’;0²ÈþÑ‘ýc
á´Û¥ÀUv·}I&@rFöÙ?:½üpÛþQv·}Iéã7A$©<ã	7ty˜æê¤L² ÄŸWçö
û‡ýÃþaÿ¨zÿø9p>°/áî“cínû’ÒÇ ¹#û‡ìCïdà·ôÝõí»Àìnû’ÒÆO“”«øð)C»Šç~º¦m:á¯ëûŸ,¼Þ
C¹uϺ€Ïgû×´[x|¤ÐvLͲB¨¥Î0˜WØÙ_lƸj$) ÆÕq(»]ªªÿׇÇçËâëo+½
Iâø:À¯qqz0Ø\â²ÎŽß³3p%pG¡íRBÁÞ/ÆGï)P‹	Åx—’¦ÙÀÇý鳉ßÍÚ“â^S	w…ÛäO#Iiy
ÜÖ5ªãPv{;÷*ôcG¹ó˜
¬‹‰Å3„#@½N>¬'êupsL,ZqÝEq‡¹x˜Ÿ¯¬ó™û„:¸€pT`50Âþ1dóH­äØ )ÓøcÔX£:e·›©Ê;¸—Ä„âÊ:m7Ƕy5¯‹ÉÑËÀ-^/»F µñßÙ…dé*à$àF©~—ÇeûÇÐÎ#uüµcƒ$ ¶Õ¨ŽCÙí&@ªêîé„Ó׎ªÓ¶x—¾SáŠn#Üzx\×Éà1¶¼Žg_`;ÂÑ«§€™ñõÑÀ¹1éÙø)áz k€+hÿ»…å?RÇ_ë96H2j;ã	§Ø¬%\hÝêvw`TÕÜ	qÛ8ø[^<>•ð—õ9À¶<
íà`!áK+Œ"œÖ6+ï™ÜRçõ„knˆIй´ÿÍr‰©ão®ñÝH’	Al«Õq(»ÝHUÝÁ]	œ§¿\§»'
;”·Çm`à¯1‘F8"st’ŸÕÀ7ûiëuKáÿÓëËÀÅqú.`{ÂÅùØ?†d©ã¯u€$™µFuÊnwFUÝÁ=¸·ð|á¯è‡î¬ö“BÛÎÀóÀ1ɸ°Ð6xŽpZYÎÞ!‘ê}ôÞø`}ÜÆŸ–×쎉SOó‘¥SIDAT!ºø°§ý£éy¤Ž¿ÖrldÔ–æÇª^Ý„ÛÝŽiQ»	Жv`pwÉìè¶n‚œÐ0Âém#ë´!Ô5*û­ý£ùy¤Ž¿9Çw I•?Öj¬Q‡²Û;ÝtÂßu+¡VÇ@}
¸3N	x”pÊM„#RÙ6þW§ímàš×ÞÞwµ
‰ë—
Ïß#Ô]z{€íÍÖ	jßs¯g;I›%þ|î¬Tîþ¹íK2jcÖjmÔ]˜¾	8¨]§'OÇé/Šb“¥G	½÷—-cËS7d$ûǶÆÿÔãGîãŸÛ¾¤¬Æ'¯˜ñÀ‰„#Ó´WѬ˜ÝM¸›ÖŒBÛÝñßW	×Jì©ý·þáÂò#û™÷¥„#˜Ij6þ§?rÿ$)Ï,(ñçÛu€Ê›G½#@£€géûkâu„ÛÇB8t^œî^ ÜJøBà[uæ_{haœÇ7ý¶è²äÿS¹nû’²ŸL€¶Î:@å'@s
‰„B–3€3õ]Š	ÐãÀLÂÝãVÆ×'뀯Žͯ ³×€¯¾«Û8`$ûÇ6ÆÿÔãGîãŸÛ¾¤,Ç'Okl0¯¬—è«ãPv{§[G(9¹ðèN#œV1)&3Ç÷/G
LΉ¯o%œÚ¶€ðWÈ7	·¾µðžë½
ßå6ÇŽ²Î{T!þ§?rÿ$)ë2?ßîuš]þÜÛËìC5ufìÊîc)ãg+Æ·Ü—Ïø!)Û±¥ì¨ëàÅòçÞn$[ujK]‡§ìñ-÷å3~Hªlí[g¨–?÷v ùÛªSûXê:–ºOÙã[îËgüTÙh>°Gáy7°ˆ¾:ÚSkvùso7’¿­:µ¥ŒŸ­ßr_>㇤,Ç–VÜœUx>•p×™MlO­ÙåϽ]’ÚU£:6×/Úß#ë|{€íÍÆÏ²ãoîË'IùfA%Þ:@Ö’±CJ1¾”ÿqùŒ’Úsl±PùËo ™IiÆ—²ã¿ËgüdÔ/ëYH&@RŠñ¥ìøïò?$™}ˆu€¬$ )ÅøRvüwùŒ’L€ê²u€d$¥_ÊŽÿ.ŸñC’	ЇXÈ:@2’RŒ/eÇ\>ã‡$ z¬d ™I)Æ—²ã?.ŸñCRû-íP¨Q‡²Û­T®‰ÀƒxÿùÀžƒxÿG‹âô8àxà`·©íU½ÎŽu€$i[³ ’?ßéut¬ÔÜf ëIR–	t~ë
mt:p(ᔵuÀŽ…èwÀI„£9?‹¯Oþ|8x¬Ÿhb|ÏÀŒîÀ¨#ú˜u€¬$IŽ?ÃZôEã×i¶gמ£ÀfàÀËÀ	…¶Ûb"´0þF§k€W€ç	ç°×»¶hðÙ˜ýÛ µ½²ã[³ó¯úòIRžYP>ßéut¬´íóèïÐjàà8`áö¬ÄÄç¸Âûž$ÜÅèZ`9áF½ýèÿ¸1YšdH;¤VŒ/ͲñCRŽ-¹×²½³ëÕK€Æ/Äé.àÁšhiœž<§§·æÑE¸À·˜oí:xmpFmÛǬd ItüiÅ)p€y…`·ØPص=m{f.Âí}ìÜO8¥íIàŸ5ïßx¸øN|íAÂ9ì+	×	íZó¹oÇù­î.ÃÛ½Jí¬ìøÖìü«¾|’”oTòçS×ñ±}ëíeöf籡0_=ãúië&œÖÖ_r¿=¡6ÒÇ©ì>–2~æðý$^ÆIÙŽ-Ö²P®	L€dkFÊø™Ã÷“xý?$U6ëìXHîœJiúXªø™Ë÷§^ÆI•M€À:;Ö’;§Rš>–"~æôý©×ŸñCR% ñÀz`-án]¶çÕn$[ujK?sùþÔëÏø!©²	ux¬$wN¥},UüÌåûS¯?㇤J&@Öá±Ü9•Rô±”ñ3‡ï'ñú3~Hªl4Ø£ð¼XD_ÛÓ¶›ÉßVÚÇRÆÏ¾ŸÄëÏø!©²	ª½ƒkÿ°Hö1ùÛJÊ&þsH’$Iª
 I’$I&@’$I’d$I’$I&@’$I’d$I’$IYè¢ùx;KÙ?$?$I.’$I’”Kîã)p’$I’*ÃH’$I’	$I’$™I’$I’	$I’$™I’$I’	$I’$™I’$I’	$I’$™I’$I’	$I’$™I’$I2’$I’$ I’$Ijs]À®I’$IUðÉq¿ÉŠ_»:IEND®B`‚urwid-2.6.16/docs/manual/images/urwid_widgets_1.xcf000066400000000000000000001763541470350774000222430ustar00rootroot00000000000000gimp xcf v011@h–B³üB³ü#icc-profile-nameICC profilegimp-image-grid¬(style solid)
(fgcolor (color-rgba 0 0 0 1))
(bgcolor (color-rgba 1 1 1 1))
(xspacing 10)
(yspacing 10)
(spacing-unit inches)
(xoffset 0)
(yoffset 0)
(offset-unit inches)
gimp-image-metadata


  8 8 8
  2023:09:22 17:04:32
  190
  360
  832
  1
  3
  GIMP 2.10.34
  248/7
  248/7
  1
  8 8 8
  6
  110
  256
  328
  4081
  1
  6
  3
  2.0
  Linux
  1695395073461433
  2.10.34
  image/png
  1
  GIMP 2.10
  2023:09:22T17:04:32+02:00
  2023:09:22T17:04:32+02:00
  gimp:docid:gimp:4d9cf2f4-e0af-4cff-946b-e1f53b18658a
  saved
  /
  xmp.iid:7a837885-513b-4668-86d8-b616f06caf41
  Gimp 2.10 (Linux)
  2023-09-22T12:04:57+02:00
  saved
  /
  xmp.iid:aea740ab-0b1b-445e-af3e-89472a139341
  Gimp 2.10 (Linux)
  2023-09-22T17:04:33+02:00
  xmp.iid:adaf6b4d-981e-4479-b4cc-9261ec7fbf78
  xmp.did:e82c3808-93ba-4862-b41e-41821c4b8d2a

ÂEaö…"qT
Buttons etc.ÿ!?€	"
 
"2%$ÿÿÿÿ#ÿÿÿÿ>gimp-text-layer"(markup "Buttons etc.")
(font "Sans-serif")
(font-size 14.000000000000002)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)

T19TQˆ@ø¹ÿÿýîÁS
ýz3ýz3#÷¹ÿÒ¹Üþú3	ýPýgýPýg#ì¹ÿ_Ñÿaìú(óù!Nåÿý‹Nåÿæ‹
‘æ÷ÎWáÊQÖøÎ:tÜùæ¦Å”ç÷¹ÿ_&äü=ìú(óù!{èÿÔ¸d{èÿÔ¸d²ÿçÂøüUáüöÆâÿÞCý÷Áàïù²ÿä¹ÿðú~ìú(óù!¯ÿgã¯ÿg.ûñdþËáÿ ùù!QþÔå.ûå¹ÿÒ¸Òþè0ìú(óù!¯ÿgã¯ÿgVþÈ%úô
áü<êú(
Äþõ$þWþÿê¹ÿ_§ÿ—ìú)÷ù!¯ÿgâ¯ÿgWþÈ%úô	áû/èú(ZÌþî%åXþí¸¸¹ÿ_«ÿ æýDCüù!«ÿpâ«ÿp+úñdþÌáû/èú(ºÿdÄ/ûç¹ÿÒ¸ÐþüMµÿÚ™æÿù!‡ÿ츆‡ÿ츆ªÿçÁøý[áû/èú([÷É¿øü?Ä®ÿí¼¹ÿÿþ÷ÊU#½øè†£ù!»ùð{»ùð{ŠãùÓ\áû/èú(5Ê÷úØjü‡Þú@gýz3ýPýgûÈBNåÿø‹’äúápðøó+{èÿÔ¸d¸ÿëÁìeðlþ‡¯ÿg1ûó"ÿõ§¯ÿgWþɸõ{¯ÿgXþÈÅ
«ÿp8üò !Æò>‡ÿ츆Ëÿë½ã‡!òñï°»ùð{¨ïû×LÜÚc*
Editÿ!?€	"
 
š2%$ÿÿÿÿ#ÿÿÿÿ¼gimp-text-layer (markup "Edit")
(font "Sans-serif")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0.91372549533843994 0.7764706015586853 0.68627452850341797))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
":000|øPþÁÇë-þ¹ÿþ÷ôPþÁ„¤z3ø¹ÿÒ¸¸²üNþÁýPýgü¹ÿ_ò'ÁöÏxüÁáû/Nåÿú‹¹ÿ_êÑÿêÅöþÁáû/{èÿÔ¸d¹ÿíÁ7üò!tþÁáû/¯ÿgç¹ÿÒ¸¸‹XþÆ7ûÁáû/¯ÿgü¹ÿ_ïXþÅ4ûÁáû/¯ÿgü¹ÿ_ï9üðmþÁáû/«ÿpâ¹ÿÒ¸¸²ÔÿéÃóþÁáû/‡ÿ츆¹ÿë÷+ÆøÌ?îÁáû/»ùð{Textÿ!?€	"
 
2%$ÿÿÿÿ#ÿÿÿÿHgimp-text-layer,(markup "Text")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 1 1 1))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
®ÊâDDD‘ÿ¶ÿþÏòz3ƒ¸ºüýÁ¸•ýPýgé÷ù!”ç÷ÈB{þлþ•OåÿÇ‹÷ù!²ÿäÂøó+	ÑþmTýá{èÿÔ¸d÷ù!.ûålþ‡>úëáýT¯ÿgù÷ù!Wþÿó§™ÿÿ®¯ÿgø÷ù!Xþí¸ó{´ÿÿïÿgç÷ù!/ûç
[ýÞÏþm«ÿpÈ÷ù!®ÿí¼Æò>äýS=ûí ‡ÿ츆÷ù!‡Þúï°ÿ¸¡ÿ¯»ùð{‘7
SolidFillÿ!?€	"
 
ª2%$ÿÿÿÿ#ÿÿÿÿgimp-text-layerú(text "SolidFill")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
727JLLLíøáû/Çë-üPþÁëÇë-áû/áû/*´ñòÎøáû/„¤úPþÁ¹ÿéó„¤áû/áû/
àþØÈòÂüáû/ôNþÁ¹ÿи¸¯Ñáû/áû/:üß	
‘æ÷ÎWáû/áû/'ÁöÏxüÁ¹ÿWîáû/áû/áû/&ùøbâ²ÿçÂøüUáû/áû/ÑÿêÅöþÁ¹ÿW˜áû/áû/áû/ŽþþÖR.ûñdþËáû/áû/7üò!tþÁ¹ÿи¸†áû/áû/áû/SÑþþ†VþÈ%úô
áû/áû/XþÆ7ûÁ¹ÿò¹áû/áû/áû/ÝgüõWþÈ%úô	áû/áû/XþÅ4ûÁ¹ÿWÍáû/áû/áû/)4ñù"+úñdþÌáû/áû/9üðmþÁ¹ÿWÍáû/áû/áû/[þèÁÞþŪÿçÁøý[áû/áû/ÔÿéÃóþÁ¹ÿWÍáû/áû/áû/,³èú眊ãùÓ\áû/áû/+ÆøÌ?îÁ¹ÿWôáû/áû/áû/2Dividerÿ!?€	"
 
22%$ÿÿÿÿ#ÿÿÿÿÂgimp-text-layer¦(markup "Divider")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0.68627452850341797 0.7764706015586853 0.91372549533843994))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
 W2 s2 ‹èèèÒüÇë-üÇë-üPþÁò¹ÿÿüèª8„¤ü„¤üPþÁ÷¹ÿÒºÝþú]üNþÁ˜¹ÿ_týïáû1ÍþYaþÅáû/'ÁöÏxüÁ”ç÷ÈBáÇ@Ñô¹ÿ_×þbáû/tþ©±þkáû/ÑÿêÅöþÁ²ÿäÂøó+áûóâ°¹ÿ_Ñ®ÿ…áû/÷íñôáû/7üò!tþÁ.ûålþ‡áþ–¹ÿ_â°ÿƒáû/¸ýPXþ®áû/XþÆ7ûÁWþÿû§áü8Ú¹ÿ_Ýþ\áû/]þ ¨ýSáû/XþÅ4ûÁXþí¸û{áû/ѹÿ_…þëáû/îÜåèáû/9üðmþÁ/ûç
áû/ѹÿÒ¼åþõTáû/¢üþ—áû/ÔÿéÃóþÁ®ÿí¼Æò>áû/ѹÿþùÞ+áû/Gýü=áû/+ÆøÌ?îÁ‡Þúï°áû/ý@hurwid_widgets_1.pngÿ!?€	"
 
%$ÿÿÿÿ#ÿÿÿÿ#}@h#¹ü¼üÈüÔüà@h&9*O.50©0Ë2S3)4™4Í6#6Z7l@­H¸NLTY>\Á_Èb¥f&kôqýtw}Ö„ð‰½ŒíŽ=ŽS’”˜™¨žyŸ½¡Û¡ë¢÷«±W¹6¿EÃ÷Ê$ÎlÒhÔÔÔ Ô0Õ<Ø@Ý‘á¨áÎãçäÈæüê˜ì¦ì¶ìÆìÖíâñ>õöö÷÷÷¢ø˜ú®ûÞûðüü/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿþùãÿ/ÿÿþóëÿ/ÿ/ÿ/ÿ/ÿ/ÿ/î/î/î/î/î/î/î/îîþçÊî/îîþáÚî/î/î/î/î/î/ª/ª/ª/ª/ª/ª/ª/ªªþ§¡ª/ªªþ£Ÿª/ª/ª/ª/ª/ª@ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ4ý"0ÿÿÿ˜ýˆh ý¸ÿÿÿÿÿþûˆý¸ÿÿÿýÿðû3vöÿSýÿÿýÿðý›ÿ—ü'û*þÿýÿðýjÿŒý#«ñÿþ×Gþ{ìÿþõšý¬ðüNÕüÿýÿðüÒÿA÷Jæ“j[‡íÿBømÿÓ†Zj™Áý¸ÿû†ÿê„ÿýÿùû¹óítþý%ôÊýÂû#ÿõ¸ÿ1üÞÿÿÿþÏV
ø·ú°ý(õ¸ÿ ÿZÿýÿðû?Çÿxï#ŽÀÝèè÷ÿQÿö¬Zõ¸ÿÊÿÿýÿðæ÷õ@û÷´’‚€Ñÿ&M¾ÿÿüº+õ¸ÿéûÿýÿðøÓÿ>Öö+ý¹ÿ&í#tÍÿì¸ÿÌÿÿýÿðùéÿ2
þÈüèÿ&ðµÿW¸ÿ£ÿRÿýÿð÷tÿð÷÷ùzÿÿ&ð¦ÿK¸ÿ9þÔÿýÿú û«ÖÿþPæªÿÅfPžòÒÿ&é¬gFX”þà
¸ÿû•ÿàtÿÿÿüõÔ¡,þ·ÿ÷Ü5¢ÿ&¨ðÿþ»(ý¸ÿüfîÿÿý =û#=5ýÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿÿ'éÿ(éÿ)éÿ*éÿ+éÿ,éÿ-éÿ.éÿ/éÿ0é
ÿ1éÿ2éÿ3é
ÿ4é	ÿ5éÿ6éî'Æî(Æî)Æî*Æî+Æî,Æî-Æî.Æî/Æî0Æ
î1Æî2Æî3Æ
î4Æ	î5Æî6Æª'¯ª(¯ª)¯ª*¯ª+¯ª,¯ª-¯ª.¯ª/¯ª0¯
ª1¯ª2¯ª3¯
ª4¯	ª5¯ª6¯?ÿàý0	ý!0ý—
ý˜>ù]˜:tÿ\	ý°ÿý»ÿCü'ÿÿ›ùÏÿ/tÿ\	ý°ÿý{ÿ‚üfÿåÚøýí
	ý°ÿý'ý;ÿÂû¦ñ}ÿýNÿ¯
ù
°ÿý"ÿþétüõùûåµ<ÿZøŽÿolðV÷fêÿûË-°ÿ÷¤÷ÿò©
î½ü[o«®õ»ÿA%ÿv÷™øÍÿ/tÿ\êsÿäyb¬êÓÿÆÿ¶elÈ¿þÊÿÿä{ÿeÿ7ÀÙýítÿ\ðíð‘ÿÿYÿ¦ÅÿÊÿ~ð;ÿÀ¤ó‚ÿLÿ¯øtÿ\gÿƒ÷÷ÿºÿ)ùMÿÊòðõøã¸DÿXŒÿnøtÿ\ŒÿLøÑÿÞùùÿÊÿ
ñºÿ?#ÿyû—Ëÿ.øtÿ\§ÿ6ù·ÿôéùÿÊAÿ
úzÿcÿ:úÈ×üìøtÿ\ŽÿJøÎÿ×üù!ÿÊ(ÿ
ú:ÿ¾¢õûŠÿaÿ®øtÿ\lÿz÷ôÿ¯ÿ>ùbÿÊúÿ	ûô÷å¼ûLÿÜÿn÷tÿ\õæðÿÿ@ÿÃ
ÛÿÊû“KZ•§
üºÿÿ|ûýÿÿ.ýtÿ\ö€ÿ×aJ”òÚÿ÷ ÿà— ê“þÈûÿÿò
üzÿÿ=üÐÿìýtÿ\÷v÷ÿÿã<°ÿøtÌòÅyÿ­ý?)$ý4
ý<ÿŒ6ÿüÀÿ<6ø©¯rYzØÿ«7øuâÿÿþâw9ý Èÿé/é/é/é/é/é/é0é0é0é0é/é/é/é/é/é'éÆ/Æ/Æ/Æ/Æ/Æ/Æ0Æ0Æ0Æ0Æ/Æ/Æ/Æ/Æ/Æ'Æ¯/¯/¯/¯/¯/¯/¯0¯0¯0¯0¯/¯/¯/¯/¯/¯'¯?ÿý<ý>ÿ’<ý>ÿ’4þý>ÿ’û)%òëÿÿï……óÿùðûE™øÿþën$òÏÿ±hc²ÿ°B˜ÿÅxö"£ÿ¾{Yp¬•$þÿyø™ÿG>ÿ’üôáÿ$þçø"ÿ›>ÿ’üäç	)ÿÖpøqþÄ>ÿ’ú‡ÿìšK	&ÿü÷úÔ>ÿ’øeÕÿÿö $ÿ°	ý>ÿ’ù3†ßÿË#þéý8ÿ›	üåÿ!#þÿŠùÿÏþüØÿ#åÙÿ·bH`‰âlÏý¼†€%$ò˜`Bd¯ÿ¸$ýŽ÷ÿýûº6÷(²æýÿJÁøÿýû¡&û#?)û(@/çÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿg/ÿÿ³/ÿÿ³/ÿÿu/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿéÆ¯?ÿÀÿéÿéÿéÿéÿéÿéÿéÿéÿéÿÿùéÿéÿþóêÿéÿéÿéÿéÿéÿÆîÆîÆîÆîÆîÆîÆîÆîÆîÿçÆîÆîþáÍîÆîÆîÆîÆîÆî¯ª¯ª¯ª¯ª¯ª¯ª¯ª¯ª¯ªÿ§¯ª¯ªÿ£ª¯ª¯ª¯ª¯ª¯ª?ÿÀÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ'ÿé&ÿé%ÿé$ÿé#ÿé"ÿé!ÿé ÿéÿéÿ éÿ!éÿ"éÿ#éÿ$éÿ%éÿ&é'îÆ&îÆ%îÆ$îÆ#îÆ"îÆ!îÆ îÆîÆî Æî!Æî"Æî#Æî$Æî%Æî&Æ'ª¯&ª¯%ª¯$ª¯#ª¯"ª¯!ª¯ ª¯ª¯ª ¯ª!¯ª"¯ª#¯ª$¯ª%¯ª&¯?ÿÀÿé¯é¯é¯é¯é¯é¯é¯é¯é¯ý¬©®¯é¯é¯é¯é¯é¯é¯é¯Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/ÆÆýÿÅ$ÆÆÿÅ'Æ/Æ/Æ/Æ/Æ/ÆÆ¯é¯é¯é¯é¯é¯é¯é¯é¯éýåáèé¯éÿèé¯é¯é¯é¯é¯é¯é?ÿÀÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ
¯ÿ®ò¯
ÆÿÄòÆ
éÿçòé?ÿÀÿ¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯éÆ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/ÆÆé¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯?ÿÀÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ¦éþçãXé¦ÆþÄÁXÆ¦¯þ­ªX¯?ÿÀÿ'é'é'é'é'é'é'é'é'é'é'é'é'é'é'é'é'Æ'Æ'Æ'Æ'Æ'Æ'Æ'Æ'Æ'Æ'Æ'Æ'Æ'Æ'Æ'Æ'¯'¯'¯'¯'¯'¯'¯'¯'¯'¯'¯'¯'¯'¯'¯'¯?ÿÀ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿQ.¯.¯.¯.¯.¯.¯.¯¯ûYWWn¯þœ¯¯úK]C
y¯þ¥Z¯¯ú¯¯[.¯ô‹fu¨¯Ÿ{®¯¯„•¯ô’¯¯‚”¯¯~¤©z¯¯ë¯§8E¯¯aG¦«$`¯|Ÿ¯ñq-¯ª8¯£§š¯¯a¯ì(§¯¥€¯®[¯˜x¯ñŸª3®v(¯š¯¦¯í„¤<1¯ˆª¯¯O9¯¯sT¯òSg.ŒHV¯š¯Œ¯í¯¯“ŸŠª¯¯L:¯¯G6¯òKS%•\^„¯š¯¯ì¯¯j¬§
€¯¬_¯sž d¯òy¯Š«¯š¯§
¯ë'1n¯¯j>%©–
Н”Œ¯ò¥A¯­
1¯¯š¯¯h¯ü„ƒƒ–¯ù•q€¬¯—¯þ“”¯ö‰›¯¯—¯¯ª„¯.¯.¯.¯.¯.¯‘.é.é.é.é.é.é.ééÿvtýäÍ&ééþ]týäÉ
ééþ»éþÉ
éñ»ˆšßéå¡çéÏ£éé˹éö˸ééµ¼éé°Òéþ»éìÉ
éé…	^+%Ûé¹émÇé^téö¯#éé1éæÈéæXéÉ
éÞ¦éè,uéX{é0LŠé!²éöä	ÏÅ2Û·é鿝ÚÚàéÉ
é¹áéémHé•>Ù«MÎ
äé÷A’ˆoW¡zZééþ»éíÉ
é»áééiJéјYáCé÷~UK­”d<—ééþ»éíÉ
éà§éå'zéé%(–é;(€é÷»âÐ!ÓéQ.Æ.Æ.Æ.Æ.Æ.Æ.ÆÆûdcc}²Æþ±ÆÆúUiL‰ÆþºfÆÆúŸÆÆf4Æôžt„¾Æ³‹ÅÆÆ•¨Æô¥£ÆÆ“§ÆÆº¿ŠÆÆëŸÆ½?NÆÆnQ#!¼Á(mÆ´Æñ3ÆÀ@Ƹ½®ÆÆmÆì-½Æ»ÆÅ"fƬˆ’Æñ³À’:Ć.Æ®Ƽ	Æí•º¢D8ÆšÁÆÆY@ÆÆ‚_Æò!’^t4žRbƮƞÆíŸÆÆ¦´œÁÆÆVBÆÆP=ÆòU^*¨hj–ƮƠÆìŸÆÆxý‘ÆÂkÆ‚³$qÆò‰ Æœ"ÁÆ®ƽÆë,7}ÆÆxG*¿ªœÆ§žÆòºJÆÄ7ÆÆ®ÆÆvÆü•””©Æù¨€ÂÆ«¢Æþ¦§Æö›¯ÆÆŸ«ÆÆÁ–Æ.Æ.Æ.Æ.Æ.Æ‘.Æ.Æ.Æ.Æ.Æ.Æ.ÆÆÿdcý®!ÆÆþOcý«	ÆÆþŸÆþ«	ÆñŸtƒ½ÆÃ‰ÄÆ°ŠÆÆ¬Æö¬œÆÆš ÆÆ–³ÆþŸÆì«	ÆÆqP$ ºÆÆ\©ÆPcÆö•ÆÆ*ÆÄªÆæ	KÆ«	ƽ	ÆÅ%cÆKiÆ(@uÆ—ÆöÁ°¨+º›ÆÆæ•ºº¾Æ«	Æ¿ÆÆ\=Æ5¹‘A¯	ÂÆ÷7|t_J‰gLÆÆþŸÆí«	ÆŸ¿ÆÆY?ƲK¿y9Æ÷kH@“~U3€ÆÆþŸÆí«	ƾŽÆÃ!hÆÆ "Æ2"mÆ÷ŸÀ±³ÆQ.é.é.é.é.é.é.ééûvtt“ÑéþÐ#ééúd{Z¢éþÛxééú»ééy=éô¹ˆ›àéÓ¤èé靯éôÃÀéé­Äéé¨Úá£ééë»éßJ\éé‚	_)'Ýä0€é¥Ôéñ–<éâKéÙÞÍéééì5ßéÜ	ªéè(yéË ¬éñÓâ¬Eç6éÍéÝ
éí¯Ú¾OBéµ	ãééiLéé™oéò'¬o‰=º`séÍéº	éí»ééÄÔ¸ãééeNéé^Héòeo1Æz}#°éÍé¼éì»ééåÞ
«éä$~éšÒ*„éò¢%é¸(	äéÍéß
éë4A%“ééŽ	S1áȸéĺéòÛWéæAééÍéé‹é¯ÿÇéùÆ—ªäéÉ¿éÄéö¶Ïéé»Éééã°é.é.é.é.é.é‘.¯.¯.¯.¯.¯.¯.¯¯ÿYWý¬š¯¯þFWý¬—¯¯þ¯þ—¯ñft§¯¬y®¯œz¯¯˜‹¯ö˜Š¯¯ˆ¯¯„ž¯þ¯ì—¯¯dG ¥¯‹¯R–¯GW¯ö„¯¯%¯­
–¯æ

B¯—¯§}¯®!X¯B]¯$9h¯†¯ö«œ”&¥‰¯¯æ„¤¤¨¯—¯‹©¯¯R6¯p/£€:š¬¯÷1nfTAy[C¯¯þ¯í—¯©¯¯O8¯rC©k2¯÷_@8‚oK-q¯¯þ¯í—¯¨~¯¬\¯¯q¯-`¯÷ªœŸ¯?ÿÐ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ@.¯.¯.¯.¯.¯.¯.¯¯þeR¯þª’¯¯þ[D¯þ¯æ‰g[D¯¯¦po¡€£¯¯¤th”¯¯dxxª¯æI&D¯ª"K$‡¯‘8Iw¯D	RR¥¯鄯®D¯i¯¯ˆ‡¯1V¯¯s¯¯鬯¯ED¯GI¯¯¬‡¯	3AA;ª¯𪯯CD¯L@¯¯§‡¯f‰ý­¯ð¯¬D¯w¡¯n‡¯0D¯û®¯£	­¯æD+D¯®<#1‹¯– N=-¯¯3%<£¯ûr–𔝖÷Ÿ¯¯ª€m~¨¯ýœ„«¯¯ûf‡x/1"¯¯ûa75Y§"¯.¯.¯.¯‘.é.é.é.é.é.é.éýæÙéþVéþæ¾éýètàéþ’AéþÙéáè äééÀ‰³’AééäšÏ²Ðééៈ½ééž  Ùéáæ×é—	[05Aéè;#g* šéÐ%BgˆétmmÏéäæ×é–éé8A饿éΚéZZéé³äÙéóæ×ÔÔéévAéyGéô šé%;WWVÉÙéóæ×ÖÑéésAé;éûšé$t¶ýáÙéëæ×é’éè5Aé¹Í鬚éZAéûçéæ×é@.Æ.Æ.Æ.Æ.Æ.Æ.ÆÆþr\ÆþÀ¥ÆÆþfMÆþ£Ææ›t fMÆÆ¼~¶¸ÆÆºƒv¨ÆÆqˆˆÀÆæ	S!+MÆÀ"'U)™Æ¤@S†ÆM
\\»Æ镯ÄMÆw ÆÆš™Æ7bÆÆ‚Æ£ÆéÂÆÆNMÆQRÆÆÃ	™Æ
:JJCÀ£ÆðÁÆÆLMÆVHÆÆ½™Æ	t›ýÄ£Æð’ÆÃMƇ¶Æ|™Æ6MÆûÄÆ¸
ÄÆæM1MÆÅD(8Æ©$YE3ÆÆ9*D¹Æû¢ª¯¨Æôª©ª´ÆÆÀ{¾Æý°•ÁÆÆûs™ˆ58"ÆÆûn>B"ééû‚IFwß"é.é.é.é‘.¯.¯.¯.¯.¯.¯.¯ý­£¯þvA¯þ­¯ý®W¨¯þn1¯þ£¯á®x«¯¯g‡n1¯¯«tlœ†œ¯¯©wfޝ¯wxx£¯á­¡¯qE$(1¯®-N t¯œ1Nf¯WRRœ¯ä­¡¯q¯¯*1¯|­¯št¯DC¯¯‡¬£¯ó­¡ŸŸ¯¯Y1¯[6¯ût¯-Aü—£¯ó­¡¡¯¯V1¯_-¯ût¯W‰ý©£¯ë­¡¯n¯®(1¯‹š¯t¯C1¯û®¯­
¡¯?ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/î/î/î/î/î/î/î/î/ª/ª/ª/ª/ª/ª/ª/ªÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ0ÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿÿHÿ		þÿÿÿÿøˆˆ¼Ëˆˆ	þÁ'ÿÿÿÿîp$|<o)U1ÞŒwð$}{eb
f7„:ÿÿÿîp`­B‘~j£
Ò1ÎV7ïd¼GM'ÙdiÚô•KÐÿÿÿïp¾N8>Ô«ÕhÁ'þÁ'ôE¡ª?óoÿÿÿûpè|x÷}÷*Á'þäôkˆeÚ^ÿÿÿïp—N<Ì=Î	ª6þ™Tô!ÄÎÚ^ÿÿÿîp$§¸µ¥Ö&rœNÀ¿ï'°»º<ÅÆ{Ú^ÿÿÿ
þþ
þÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿû
ÿÿÿÿû#Å]‹ÿÿÿÿþþ
ø#ÅþÿÿÿÿÿöiÂÂã ¾/å#£¼Ä;#ÅZ†š¡ÊËSCºÉÊŽ?¯ÿÿÿ÷ÓÂ*ûGå–Z#Å]‹Ÿ§ÛFò¬@Éÿÿÿ÷U•wuñþâ	ê#Å]‹Ÿ^¼/F·cˆ&ë°ÿÿÿø6°”VèþÅ#ê#Å]‹ŸyÔFÒ~jÓÿÿÿøÕfXß
èåi·?>##Å]‹ŸÑLx¾FòuNÚ£‰ÿÿÿùjxtæ1ˆb.EŸ]†lF¢e‹BÿÿÿþŸIþF¢ÿÿÿÿþ^+þ)`ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿü…yþ#füÿÿÿüwÍþ;­üÚÿÿÿØ]a88#PVŒfiÍoŒJ;­U~ÚÿÿÿØl4bÄ1¾‘di4\ÍáÍ'V5‘ƒ;î…R×#ÚÿÿÿØKoÝÑ àHo×%áÍ[wÒ;àwqÚ-ÿÿÿ×
ÔgCÐ8s~M£ÓiDÆEáÍC¿[Gð;ÁW•ÚYÿÿÿ×L÷8Ù¬B?ª	ðEáÍ‘XLó;÷NÚÿÿÿ×	ɶÆÉ7µß÷ÊÂDáÍ7Ѹ¥é;ÊËÆž	Úÿÿÿÿ
þþþÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ
ÿÿÿÿÿ
ÿèÿÿÿÿÿ
ÿèþÿþþþÿ7éÿ8éÿ9éÿ:éÿ;éÿ<éÿ=éÿÿ>éî7Æî8Æî9Æî:Æî;Æî<Æî=Æÿî>ƪ7¯ª8¯ª9¯ª:¯ª;¯ª<¯ª=¯ÿª>¯ÿ?ÿÂþ
þ-þ­;
þ@¨,ë'Ó—x-}2K.ugŽÑx<ÃI#EIIl0†A‡tX0€P.@huHx/¿f8yž@£d¡ÇUˆœd»8Åj}¿Ãujõ¡B\9N×¼ÂM•›cænaÛ¹sEÇ'±x­;ÒA8LÀ¡iß@¨Ò'À
½·¸'üÄÆ Â÷©ç°^ŒÁÁYõ|òµ°Ðo®:èðjø°Œá©È¸$¡¸®.øcpÀ)…Âö6²C¦ÞöÕus®:èñèÿ,U›A+¨÷°»¸
Û;Âö6²Ž\°<öÕus®:èõèà_ˆé°þèúÜVÂö2´owÌöÕus®:èôæÚ^ŠÉ	÷2AK™’D¿Â÷ÚK"ØMtÇöÕus®:èñ¼`3Ú^Šk­=6_÷–…-[Ž6aø2sD€`öj	:9Wtñgfm/E-ŠEþT4÷lR70þK/÷1·px‹]üOtåSvfC)SK·³·x%‹dnoæc,ƒeW|5:"QÆkIÎå
Ü_ñ3ÎYŸ’NÄ·•8‹Ñ\w²æÔ­LÀ¶t[Ók4aÆ'Ç”aÀ88ŽmåÁ&¢|vRYÝe·px‹áêÔ6\¾Ë
Joܽ/	ß	ÇxÿIå~jÉ·•JYÒE·px‹]Û
æÔLœ¼,ÔgDÎ;U–^’Óâ:Ó ÂØYÍ‹·Y‡‹]Û
<æÔLœ¼,I 
ö;ÙÊ+b¿¬ÃMâò\ˆÃYJÎê·º¿;‹]Û
òíÔLœ¼,ȶÇÇ:†Äþþþ)¢ÿþW%þ
ü
¹³;þOþ3þ[þžJ3þ­;þþÿ-é/ééþçèéþäåé/é/é/é/é/é'éÆ/ÆÆþÄÅÆÂÆ/Æ/Æ/Æ/Æ/Æ'Ư/¯¯®¯þ«¬¯/¯/¯/¯/¯/¯'¯ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿÿ'ÿÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ(þ'aÿÿÿÿþ	?þ&þC¥ÿÿÿÿþ àþˆpü[|V¥ÿÿÿÿþ àîˆp&uPwI0=`t÷dc
…RÍ¥ÿÿÿÿþ àîˆpÊG3MC½Fyžgân5ûÒXYÊþw¥ÿÿÿÿþ àáˆpÙ[Ÿf88Ùg®
  	a—88¶9W¥ÿÿÿÿñó¡g ŠÔ€ËŠxög7¨¨1«xû0¥ÿÿÿÿëè?
ã9ãwngþ9¬ûÏÆ¬¥ÿÿÿÿìIéÃîˆÕ«¸t¼°¾gù„«È#ÿÿÿÿýýþ
ýÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿÿþÿÿÿÿøál6²ÿÿÿÿöÍ6²þ	
ÿÿÿÿìsmŒº>šÙŽð°q6ǻˤùt¼¹ª‡ÿÿÿÿë5²ÅÅ	‚]áÍ6ô¸-ùx[ŒÿÿÿÿëೄFÅáÍ6¸‡`ù=¦·ÆœŒÿÿÿÿì®’p@—ÒáÏ6²†bùÉq¢Œÿÿÿÿìkù,î“áÖO)6²†bùÂYMË¢Œÿÿÿÿì{d0p)oRYC1ù$†ƒ5QFÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿûsÿÿÿÿúÕgÿÿÿÿáKŒX${…66;noaéxQZwI#ÿÿÿÿáÉ€7\BG=¶JtàfjÖ7Þ8%&Â0ÑO¬Kÿÿÿÿþ,¼ç*e•št§°8Õ&‚dÞKÿÿÿÿþU—æ|žRnºtˆ\Õ&¨DÅ(Kÿÿÿÿý
Ö	æÊ…ºtËÐÚ&Â^ŠØKÿÿÿÿáeɬ­d̤¹t´ÍÎyŒ¿& ¿ÅQKÿÿÿÿùtþ
ÿ
ÿÿÿÿtÿÿÿÿ
ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ&鯝ÿ?ÿÄùQ8vÿ‰þ[.	
þù‰_jæÿèþšN	þ~j
þ¬<àvfŠ_]PòxY^`è6…3šN5}*뺰xyEi _'Ó—x	ÞÛ`pç_È &ë8)j4eÀô¥H½]šN‘@¯Q뚊8M¸D‚“V·ÉA¾f8õH›½_È æìLoßú!7±šNÙ:8\­î~jª^89Ú–Õ}¬<âk{_È æÔfCÔ3õÕšNèxÿgú~jÖ„x÷hû;¬<Þ'¿Ý_È ßQ˜ù3ûG]ŽšNÃ"îgy‚c,Ô6Õ•K߉ËϺ_È ¿Ž˶ÅË2ì±À»$šN<³³»ì ¼¿1¡»²µ
Ð5]°?¿¿þ
ÿÿþ
þ
ëþþ$úG4™Oþ;­þÿÿú†bÿþþþ;­üŸÈÑ_Ù\ÀŠߛŹ*Ùΰ'”LCºÁ›¸˜ÅÎ=:õ¹8;­ü¬ÔØÙ°>ç`NŸ†b™Ỏg¾|@¥/£;­ûbÝ
ØA©c‰ç	ã†b™OÒ:²¾0Øš·°ä;­û\ÜØ"Ä€jç1É‚d™OÞW“¾*Ú„`(ë;­û\ÜÙÉoPÝçª@«vO¯B™O¤ŠDÍ@¾*Úy”:£é;­û.nÙ	b~ç5’HK}L'Qˆ'_m
o‘2tVþç=ÿ‰0üIT";ûévUÞ:û¿¬;:û›M:û›M:û›Mûéÿéÿéÿéÿéÿéÿéÿéÿ$é
ׯîÆîÆîÆîÆîÆîÆîÆî$Æ
ׯª¯ª¯ª¯ª¯ª¯ª¯ª¯ª$¯
×ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ$üu©©R×ÿÿÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿü!HH)ÿÿÿÿüxLj¦ÿÿÿÿý&„<þxˆÿÿÿÿúkŶZ	üx©Hiÿÿÿÿü¨ÑoüxLj¢ÿÿÿÿûlƱV	þxˆÿÿÿÿû(„Ò¡üxˆÿÿÿÿý=&üxéÐÏÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿý˜Žý˜ÿÿÿÿý(ÆýÎÿ6ÿÿÿÿþ(ÀþÉþÔÿÿÿÿþ(ÀþÉþúÿÿÿÿþ(ÀþÉþ7Úÿÿÿÿþ(ÀþÉþÿÿÿÿÿþ(ÀþÉÿ®ÿÿÿÿþ(ÀþÉÿ'éÿ(éÿ)éÿ*éÿ+éÿ,éÿ-éÿ.éî'Æî(Æî)Æî*Æî+Æî,Æî-Æî.ƪ'¯ª(¯ª)¯ª*¯ª+¯ª,¯ª-¯ª.¯ÿ?ÿÉú0þÖ	úLœ_‰0ä³PXfšÊx8ªÀx-zB+A\|ýwbä˽+
Ûp±8‚£8@ÊK™š]ãnZØú|ΧIã³!½+
ÛLœ_‰”Rà]­¾)ü4‘Ü]ãv¼,ÛLœ_‰º2Ö]‹­;ûwÊ«8ã—|ŒZ[Û5«H˜px#Ë]‹­;ûPÀ¾cã 1лžÛ³¿W¹¿H§¾ÃF]‹­;þF þÿ¬?ÿ?ÿÀùHd7‰ÿ‰ûHH>	ùà¹q¨‡èÿèú
ùˆ’ÔWÿZÔè)‚HxGj‡/èFA
óH»mYV)
oÿ×ê J»QH»F}™)Õ]=Cèp¬
öHS²hÛZuÍ2ШhòëMš¤c88Ú€hêè‘•
ùˆÀ_\ŠÁ(lÕ§ÿøè<¬Ðˆxý©CêèÆ\
óí‚jŸN?úeì
>è<¬|iX”éè½c
ó.â8¯Ú×5Ô(Цí¹Õ\è<¬Ÿ»±º
—ǰoè	µk
ýÐмNÂÇjµY8Í	é¯é¯é¯é¯é¯é¯é¯é¯Æ/Æ/Æ/Æ/Æ/Æ/Æ/ÆÆ¯é¯é¯é¯é¯é¯é¯é¯éÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿÿÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿð ‹7ŽrE	Šÿÿÿÿñ£Ÿ{ÈЏaÚ
pÿÿÿÿô×ëÅ–í0ÿÿÿÿýHÿjý.ÿ„ÿÿÿÿûÆÃÔ	ü°Äãÿÿÿÿñ‰»­›oÏ“´UÿÿÿÿðIæÞY4ì&Ís#ìÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿð ‹7ŽrE	Šÿÿÿÿñ£Ÿ{ÈЏaÚ
pÿÿÿÿô×ëÅ–í0ÿÿÿÿýHÿjý.ÿ„ÿÿÿûÆÃÔ	ü°Äã*ÿÿñ‰»­›oÏ“´U'ÿÿðIæÞY4ì&Ís#ì'ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿ-ÿÿÿÿð ‹7ŽrE	Šÿÿÿÿñ£Ÿ{ÈЏaÚ
pÿÿÿÿô×ëÅ–í0ÿÿÿÿýHÿjý.ÿ„ÿÿÿÿûÆÃÔ	ü°Äãÿÿÿÿñ‰»­›oÏ“´UÿÿÿÿðIæÞY4ì&Ís#ìÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿð ‹7ŽrE	ŠI¯ö®­®®­®¯¯®®¬¯IÆöÅÄÅÅÄÅÆÆÅŬÆIéöçæèèççééèè¬éÿ?ÿ€„$cT3UcuAFqgP8~X^)‡Jm;{-…‡*Œ}9ÌHçVÞ3í'?è"ì9,ì+ãPè>
ÔiÝUÁƒÌoª¸‰‘µ £wˆ¯¡èDü–¯Ý\ü|ÀËvübÓ¸üI⨪à4éœÁ#ê“Ô
ãŽã
Õì"ûžý	ð¸üÞÑýÆæý¬õý’þ"ýxÿ:ý^ÿTýDÿnü–Çì!ü|Îî2übÙèGüJåÜ_ü5ìÑyü#ìÉ“üäÄ­ô
ÖÃÃÃÃÖ€€àyÉ?ê_Û
+í.GèéB2í(ÞY!ì;ÌsãR¸	ÓkŸ§À……¾©Ÿk8¹äN¡§
Õg‡¾ÂmÒ¬›Sâ’´=ëxÉ)í0^Û
èEFçÜ]1íÀ€„$cT3UcuAFqgP8~X^)‡Jm;{-…‡*Œ}9ÌHçVÞ3í'?è"ì9,ì+ãPè>
ÔiÝUÁƒÌoª¸‰‘µ £wˆ¯¡èDü–¯Ý\ü|ÀËvübÓ¸üI⨪à4éœÁ#ê“Ô
ãŽã
Õì"ûžý	ð¸üÞÑýÆæý¬õý’þ"ýxÿ:ý^ÿTýDÿnü–Çì!ü|Îî2übÙèGüJåÜ_ü5ìÑyü#ìÉ“üäÄ­ô
ÖÃÃÃÃÖ€€àyÉ?ê_Û
+í.GèéB2í(ÞY!ì;ÌsãR¸	ÓkŸ§À……¾©Ÿk8¹äN¡§
Õg‡¾ÂmÒ¬›Sâ’´=ëxÉ)í0^Û
èEFçÜ]1íÀ€„$cT3UcuAFqgP8~X^)‡Jm;{-…‡*Œ}9ÌHçVÞ3í'?è"ì9,ì+ãPè>
ÔiÝUÁƒÌoª¸‰‘µ £wˆ¯¡èDü–¯Ý\ü|ÀËvübÓ¸üI⨪à4éœÁ#ê“Ô
ãŽã
Õì"ûžý	ð¸üÞÑýÆæý¬õý’þ"ýxÿ:ý^ÿTýDÿnü–Çì!ü|Îî2übÙèGüJåÜ_ü5ìÑyü#ìÉ“üäÄ­ô
ÖÃÃÃÃÖ€€àyÉ?ê_Û
+í.GèéB2í(ÞY!ì;ÌsãR¸	ÓkŸ§À……¾©Ÿk8¹äN¡§
Õg‡¾ÂmÒ¬›Sâ’´=ëxÉ)í0^Û
èEFçÜ]1íÀÀ$cT3UcuAFqgP8~X^)‡Jm;{-…‡*Œ}9¯é¯é¯é¯é¯é¯é¯é¯éqÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ@Æ/Æ/Æ/Æ/Æ/Æ/Æ/ÆÆqÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ@é¯é¯é¯é¯é¯é¯é¯é¯qÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ@ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæpHˆ&aV€5SesCDtÿÿÿæ»]ÜlÏEèRà0í*<é ë=ÿÿÿü˜í3ü«£çHü’±Ú`üxÃÈzÿÿÿý*ÿˆýú¢ýî¼üÛÔÿÿÿü¬Äåü’Èí#üxÐí5ü^ÛçKÿÿÿæÒ	·RâuÌ;ë [Þ)í1Céÿÿÿå(Ëw ë;µ‘âQª	ÓkƒÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæpHˆ&aV€5SesCDtÿÿÿæ»]ÜlÏEèRà0í*<é ë=ÿÿÿü˜í3ü«£çHü’±Ú`üxÃÈzÿÿÿý*ÿˆýú¢ýî¼üÛÔÿÿÿü¬Äåü’Èí#üxÐí5ü^ÛçKÿÿÿæÒ	·RâuÌ;ë [Þ)í1Céÿÿÿå(Ëw ë;µ‘âQª	ÓkƒÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæpHˆ&aV€5SesCDtÿÿÿÿæ»]ÜlÏEèRà0í*<é ë=ÿÿÿÿü˜í3ü«£çHü’±Ú`üxÃÈzÿÿÿÿý*ÿˆýú¢ýî¼üÛÔÿÿÿÿü¬Äåü’Èí#üxÐí5ü^ÛçKÿÿÿÿæÒ	·RâuÌ;ë [Þ)í1Céÿÿÿÿå(Ëw ë;µ‘âQª	ÓkƒÁÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿûQÈÈ–Èÿÿÿÿû00$0ÿÿÿÿæpHˆ&aV€5SesCDtÿÿÿÿ@éýçâãéýæãäéýèççéçè¨é@+ÿþü˜=ÿý‚Ü;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿý‚Ü<ÿþü˜ÿ@@ÆýÄÀÁÆýÄÁÂÆþÄÅÆÄŨÆ@+ÿþü˜=ÿý‚Ü;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿý‚Ü<ÿþü˜ÿ@@¯ý­©«¯ý­««¯®¯­®¨¯@+ÿþü˜=ÿý‚Ü;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿý‚Ü<ÿþü˜ÿ@ÿÿö’ÈÈTOÈÈ—Èö‘ÈÈUNÈÈ™
ÈöÈÈWLÈÈš	ÈöŽÈÈYKÈÈœÈêŒÈÈZIÈÈÈÈ#0000$0ö"0000$0ö"0000%0ö"0000%0ô!0000%00€'é'é'é'é'é'é'é'éX&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿY'Æ'Æ'Æ'Æ'Æ'Æ'Æ'ÆX&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿY'¯'¯'¯'¯'¯'¯'¯'¯X&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿY'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿõÈ‹ÈÈ\GÈÈŸÈö‰ÈÈ]FÈÈ Èû‡ÈÈ_ÿÿõ0!0000&0ö!0000&0û 00ÿÿ>ÿÿ>ÿÿéþ»éíÉ
éé	R0ßéécÒéy½é÷ç=éé%'éééþ¯Þéþâ±éüÇ—©äéùѳééׯèéø¼ÈééÂÃéé.é.é.é.é.é‘.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿÿÿýùìÿþÆD
ÿÿþfýúôÿþ¾)
ÿÿþÍÿñ¯øö¯óÿÿÏÊÿÿÓ˜©õÿûܘ¹½)ÿúÆáÿþ°æÿþÍÿèþéÿ^\ÿÚÊÿ‡qE'ñÿ¾ZC:)ÿútrÿÏ‚ÿã`þéÿô)™0ÿÞßÿÿD‡ÿ8†ÿÿ[)ÿú¸/ÿŒS?ÿïÀïïõþéÿÿÔKÿÿ©_÷1\þÊÿÿŸ)ÿúôëIÄÿþÍÿôþéÿÿ—)õÿ§ÇøÚþ	Çÿÿœ)ÿû>¤ûVÿþÍÿóþéÿÐÕUaÿÝÇÿøýÿ=‚ÿÿW)ÿû,Lÿ™ÿþÍÿèþéó&žÿñ"£ÿ”Pq?˜ÿÆR;E)ÿûÄÿÜÿþÀóÿü¿úèÅÿøâËÿÿá«¥Îÿûâ¦ÉïÊÿûû¿îÿÿ.ÿ.ÿ.ÿ.ÿ.ÿ@ÆþŸÆí«	ÆÆ{F(½ÆÆT³Æf¡Æ÷Ä4ÆÆ!ÆÆÆþ•½ÆþÀ—Æü©€ÁÆù²˜ÆÆ¶•ÅÆø ªÆÆ¥¥ÆÆ.Æ.Æ.Æ.Æ.Æ‘.î.î.î.î.î.î.îîÿxwýèÜîþ¹?
îîþ_wýéwäîþ±&
îîþ¿îñ£çæ£ãîîÁ½îîÅŽžåîûÍŽ­°&îú¹Òîí¤×îþ¿îèíÙîXVî˽î~i@$áî±T?6&îúljîÁyîãZíÙîä&-…îÏÐîî?~î4}îîU&îú¬,îƒM;îï³ßßåíÙîîÆFîîžY÷.Ví½îî”&îúäÛD·îþ¿îôíÙîî&åøËíºîî’&îû:™êPîþ¿îóíÙîÂÇO[îκîøìî9yîîQ&îûx)Gîîþ¿îèíÙã#“îá ˜îŠ
Ki;Žî¹
M7@&îû·…îÍîþ³ãîü²éÙ¸îøÓ½îîÒ šÀîûÓ›¼ß½îûê²Þîî.î.î.î.î.î@¯þ¯í—¯¯l>$§¯¯Jž¯[ޝ÷®.¯¯¯¯¯þ„§¯þª…¯ü–q«¯ù‡¯¯¡„®¯ø–¯¯‘’¯¯.¯.¯.¯.¯.¯‘.ª.ª.ª.ª.ª.ª.ªªÿVUý¦ªþ„-
ªªþDUý§U£ªþ
ªªþ‰ªñu¥¤u¢ªªŠ‡ªªeq£ªû“e{~ªú„–ª©u™ªþ‰ªè©›ª?=ª‘
‡ªZK.¡ª<-'ªúMLªŠWªã

@©›ª£f _ª”•ªª-Zª%Yªª=ªú{ª]7*ªï€ŸŸ£©›ªª2ªªq?÷!=©‡ªªjªú£1ƒªþ‰ªô©›ªªe£ªo…ø‘©…ªªhªû)m§9ªþ‰ªó©›ª‹Ž9Aª“…ªø©ª)Wªª:ªûV3ªfªþ‰ªè©›¢iª¡mªc	5K*eª„	7'.ªûƒ_ª“ªþ€¢ªü§›ƒªø—‡ªª–rn‰ªû—o†Ÿ‡ªû§Ÿªª.ª.ª.ª.ª.ª@/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿáæ×é 	T+>Aééh30'ŸéÕ-"eW)éé\*OÉéöé¯äé鯗ÁÔ¿éôÎÃÚºé鿝’¤ÛéýÕ¯Ýééú°¨L+çééû‘LEoØé.é.é.é‘.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿÿþºPÿþi¡ÿþîÞÿÿþØ›ÿþT“ÿþ¢MÿÞÿù°þçÁÿÿþ¶šÝT“ÿÿå›­ò¯üÿÿã Ÿçÿño5¯¶ÿÞÿ¿'ÿ°7ÿÿ]o&+“ÿÞH`Aõÿ²c[ÒçL$wƒÿàÿ|kÿ°7ÿÍíÿí“ÿiYÿÿ–õü®ÿÿxSÿ¢Mÿ÷õ9®ÿ°7ÿœ4ÿì5“ÿ8šÿÿ×õÝ\__D(ÿ¢Mÿ÷«îÿ°7ÿž1ÿó2“ÿ?ÿÿÊõÛ¸ÇüÎÿ¢Mÿç,4ÿÿ°7ÿÒéÿê“ÿ~=öþrõü“ÿüýÿ¿7ÿßwÿÿ°7ÿÿjg'0“ÿð6(`ùÿ»>tNjÿú$CfÿúÁèÿÿìÍÿôÁªçÕäÿÿüÓå«ÿûí³¡Âûÿþ×Åÿÿú÷ËŸ,xÿÿúútKT—ýÿ.ÿ.ÿ.ÿQáĶƈG$57ÆÆY+)!‡Æµ&VJ#ÆÆN$D«ÆöÆ”ÂÆÆ¨€¤´¢Æô¯¥ºžÆÆÄ”|‹ºÆýµ•¼ÆÆú{–@$ÄÆÆû{@:^·Æ.Æ.Æ.Æ‘.î.î.î.î.î.î.îîþ®Kîþb–îþÞÏîîþÊ‘îþN‰îþ—HîÞîè¤íØ´îîíªÎN‰îîÖ‘¡â£ëîîÔ•”Øîáh1£ªîÞî²$î¤3îîWh#(‰îÏCZ=åî¦\UÄØG"ozîàîtdî¤3î¿Ýî݉îbSîîŒåë¢îîpMî—Hî÷å5¢î¤3î’1îì1‰î4îîÉåÎVYY?%î—Hî÷ Þî¤3î“.îó/‰î;„îî½å̬ºüÀî—Hîç)1îî¤3îÄÙîÚ‰îv9æíjåë‰îüìî²3îßoîî¤3îîc`$-‰îà2%Zèî¯:lIcîé"?_îú´ÙîîÜ¿îô´ŸØÇÕîîëÅÖ îûݧ–µêîþɸîîú熽”)pîîúélFNìî.î.î.îQá­¡¯x? /1¯¯N&$w¯ "LA¯¯E <—¯ö¯ƒ¬¯¯•q‘Ÿ¯ôš’¤Œ¯¯­ƒn{¥¯ý „¦¯¯úl„~9 ®¯¯ûm93S¢¯.¯.¯.¯‘.ª.ª.ª.ª.ª.ª.ªªþ|5ªþFkªþŸ”ªªþgªþ8bªþl3ªÞª¦u©šªª©yg“8bªª™gs¡u¨ªª—kjšª¡J#uyªÞªªu%ªª>Jbª”0@+£ªwB=Œš3OWªàªSGªu%ª‰žªžbªF;ªªd£¨tªªP7ªl3ª÷£&tªu%ªh#ªì#bª%gªª£“=??-ªl3ª÷rŸªu%ªi!ªó!bª*^ªª‡£’{…ü‰ªl3ªç#ªªu%ªŒ›ªœbªT)¤©L£¨bªü©ª%ªßOªªu%ªªG
E bª $@¦ª})M4Gª§-Dªú›ªª‰ªôqšŽ˜ªª¨™rªûžwk§ªþƒªªú¥`‡jPªªú§M28e©ª.ª.ª.ªQ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿíÄ´Àkè¾²ÁHU¿ÁŽï#£¼Ä;vÁÂq
کȸÿÿÿöÌèfyúÚ¦Iï–ZÇÑÿ,ÿÿÿí~²³ç2è ȹ]6´X”þâ	ôh‚Šbà_ÿÿÿø7­Û8è÷-¬oÎuuþÅ#ôI§CÚ^ÿÿÿì,Ì6~ð8èJC+™`ÁuLÙ$ïi·?>#Û\aÜÚ^ÿÿÿîQ–XXt7˜V]‚ð1ˆspm/ÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ
ÿÿü‰ i
ÿÿÿÿ
ûèçü{7±
þ}kÿÿÿÿØ]aFóxbOòxY
_=‹l7±SlOº°xÿÿÿØl4bÄ í8-%ì8)ñ•@	è7ï‡QÖ'è>ª™‹8L¹ÿÿÿØKoÝèçóè7äsuè>ª}k©_ÿÿÿð
ÔgCÐ8èçÌêè7ÅS™çDª}kÕ…ÿÿÿðL÷8Ö
ßÌêè7ù™R½)Œªfzdÿÿÿð	ɶÆÉ7pÀ~¿Ìêè7ÌÊÅ 
SÓÀ¥ª¼¿2¡ÿÿÿÿþþÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿþ
üÿÿÿÿþÆ"
ü¿)èÿÿÿÿþüÆ"ÿú¿)ýÿÿÿ×&ºtl¹˜ÅÎ<ŸÇ½Ô"Qµ¸”Þ“¿?¿)à9¾½Ë ÿÿÿ×(Àxp¿{A¤`‰&ÿ"уPæ\¿)è;ñ
½ÿÿÿ×(Àxp¿/צ?Û"Fá°°Ä™æ¿)è;³ŒÿÿÿëÏ™p¿)Ù‰Zò"%¼ñæ¿)è;­‹ÿÿÿØÏWXßp¿)Ù;ÌDô"Áu.I=æ¿)è;­‹ÿÿÿØ!†u@8_l,QcNš~!s_tVEÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿ0ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ì:æÂ°
ˆº»¢™GÙ“ÅÇ(+ìU­;’AžJàZbƒ+숭;Lª·Î‚žJà1¶+슩=Ó‹ˆžJà0¸+ìŠv”@ÖHW׈žJà0¸+ìE
U	-{:DO%p\:þrþI@.þ,©þ|l!÷yE`vߢ@OŒR-}2|l8}'K.sTd‘r÷E”‡z1VßßÏ{6Ye·G²s|l‚¥HÂW¡ÊW)˜m0Z8ûÚ•Ž$úé7±ê»+!É|lÕ=¬¡t¦‚xú	jÁ¿
úí`Œèßç|låÒ¡G
qŶù¨BúÓÑæ—QI¥|l´4fˆ¡G¹1?÷»²¶³¶®°Ý„Zlʬ¥)´½¹0|l8¼½°!¡GÁ³¯§òþ
þþ¾ýÿÿú
B‰þ9þ	žþ
ÿü	(þ]‹
þß	ûg2ÿü]‹ÿõß	g.­û:¬¸¢ç]µÌË€\ÀŠß	yÌͱgÙù(ÂX{ç]á	Á)Ù°>ß	ÉÙg·5ù[î°°¸Âç] zqA©c‰ß	a„–g¡Mü]Ö	ç]»•S"Ä€jß	DŸ±gÀ*ö]˜3?O\iç]ëiWÝÉoPÝß	Ü[dègÜö.>”ˆ1”Xè.Hp…	b~o‚tD3M‹!þtþŽÚ?ÿÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ&ÿ'ÿ	À?ÿÿÿÿÿý(ØZýZÝÿÿÿÿýPKýKP	ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿþvþvÿ=ÿÿÿÿþ8þž7ÿÚÿÿÿÿþ¯3þ4®ÿÚÿÿÿÿþèþäÿÚÿÿÿÿþÝþÞÿÚÿÿÿÿþ
åþåÿÚÿÿÿÿþßþÝÿÚÿÿÿÿþ‘MþNÿÚÿÿÿÿþ¶þ·ÿÿÿÿþ<þ<ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿÿÿü	þþ
þ
˜?ÿ?ÿÀýH;û?Jd$ü5HFúü™•ÙQ
ûk}Z û¿¦Š¾Ÿú“U¦BÅ&L»bZ	lqq}OKv¿Aòj'EÆ¥x6Лx:|&L.vfÅ&}¬e3n³ÑnaÝ}ª>°†PÖ-¿vJˆ°äT”ªz8¹k8…£HÄT¤ÅUŠ™Å×éåPpâ*¹Ÿ}ª>
Øk~¿¦ˆ³–äT”“U¦B×@©¤f
ÝÅ&ž‡ÒcBà#M™}ª>,ÀI¤¿A½IäZ”“U¦BäϤDèÅ&
åaˆþ#ÔÃ}ª>Ø”Z¿AØ5Ñ¢”|dQ·1i…¤DèÅ&z–϶¿Õ"rÏ̲}ª>TÅÀž
¿ÜÐÉ€dÒè”.¾¿!;¿¿:½¾¯ ¤Dèÿþÿ
þ
	ÿ?ÿÿÿÿÿñ£Ÿ{ÈЏaÚ
pÿÿÿÿô×ëÅ–í0ÿÿÿÿýHÿjý.ÿ„ÿÿÿÿûÆÃÔ	ü°Äãÿÿÿÿñ‰»­›oÏ“´UÿÿÿÿðIæÞY4ì&Ís#ìÿÿÿÿÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿð ‹7ŽrE	Š'ÿÿñ£Ÿ{ÈЏaÚ
p'ÿÿô×ëÅ–í0ÿÿÿýHÿjý.ÿ„ÿÿÿÿûÆÃÔ	ü°Äãÿÿÿÿñ‰»­›oÏ“´UÿÿÿÿðIæÞY4ì&Ís#ìÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿð ‹7ŽrE	Šÿÿÿÿñ£Ÿ{ÈЏaÚ
pÿÿÿÿô×ëÅ–í0ÿÿÿÿýHÿjý.ÿ„ÿÿÿÿûÆÃÔ	ü°Äãÿÿÿÿñ‰»­›oÏ“´UÿÿÿÿðIæÞY4ì&Ís#ìÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿÿÿ¼ÌHçVÞ3í'?è"ì9,ì+ãPè>
ÔiÝUÁƒÌoª¸‰‘µ £wˆ¯¡èDü–¯Ý\ü|ÀËvübÓ¸üI⨪à4éœÁ#ê“Ô
ãŽã
Õì"ûžý	ð¸üÞÑýÆæý¬õý’þ"ýxÿ:ý^ÿTýDÿnü–Çì!ü|Îî2übÙèGüJåÜ_ü5ìÑyü#ìÉ“üäÄ­ô
ÖÃÃÃÃÖ€€àyÉ?ê_Û
+í.GèéB2í(ÞY!ì;ÌsãR¸	ÓkŸ§À……¾©Ÿk8¹äN¡§
Õg‡¾ÂmÒ¬›Sâ’´=ëxÉ)í0^Û
èEFçÜ]1íÀ€„$cT3UcuAFqgP8~X^)‡Jm;{-…‡*Œ}9ÌHçVÞ3í'?è"ì9,ì+ãPè>
ÔiÝUÁƒÌoª¸‰‘µ £wˆ¯¡èDü–¯Ý\ü|ÀËvübÓ¸üI⨪à4éœÁ#ê“Ô
ãŽã
Õì"ûžý	ð¸üÞÑýÆæý¬õý’þ"ýxÿ:ý^ÿTýDÿnü–Çì!ü|Îî2übÙèGüJåÜ_ü5ìÑyü#ìÉ“üäÄ­ô
ÖÃÃÃÃÖ€€àyÉ?ê_Û
+í.GèéB2í(ÞY!ì;ÌsãR¸	ÓkŸ§À……¾©Ÿk8¹äN¡§
Õg‡¾ÂmÒ¬›Sâ’´=ëxÉ)í0^Û
èEFçÜ]1íÀ€„$cT3UcuAFqgP8~X^)‡Jm;{-…‡*Œ}9ÌHçVÞ3í'?è"ì9,ì+ãPè>
ÔiÝUÁƒÌoª¸‰‘µ £wˆ¯¡èDü–¯Ý\ü|ÀËvübÓ¸üI⨪à4éœÁ#ê“Ô
ãŽã
Õì"ûžý	ð¸üÞÑýÆæý¬õý’þ"ýxÿ:ý^ÿTýDÿnü–Çì!ü|Îî2übÙèGüJåÜ_ü5ìÑyü#ìÉ“üäÄ­ô
ÖÃÃÃÃÖ€€àyÉ?ê_Û
+í.GèéB2í(ÞY!ì;ÌsãR¸	ÓkŸ§À……¾©Ÿk8¹äN¡§
Õg‡¾ÂmÒ¬›Sâ’´=ëxÉ)í0^Û
èEFçÜ]1í@?ÿqÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ@qÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ@qÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ@æ»]ÜlÏEèRà0í*<é ë=ÿÿÿÿü˜í3ü«£çHü’±Ú`üxÃÈzÿÿÿÿý*ÿˆýú¢ýî¼üÛÔÿÿÿÿü¬Äåü’Èí#üxÐí5ü^ÛçKÿÿÿÿæÒ	·RâuÌ;ë [Þ)í1Céÿÿÿÿå(Ëw ë;µ‘âQª	ÓkƒÁÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæpHˆ&aV€5SesCDtÿÿÿæ»]ÜlÏEèRà0í*<é ë=ÿÿÿü˜í3ü«£çHü’±Ú`üxÃÈzÿÿÿý*ÿˆýú¢ýî¼üÛÔÿÿÿü¬Äåü’Èí#üxÐí5ü^ÛçKÿÿÿæÒ	·RâuÌ;ë [Þ)í1Céÿÿÿå(Ëw ë;µ‘âQª	ÓkƒÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæpHˆ&aV€5SesCDtÿÿÿæ»]ÜlÏEèRà0í*<é ë=ÿÿÿü˜í3ü«£çHü’±Ú`üxÃÈzÿÿÿý*ÿˆýú¢ýî¼üÛÔÿÿÿü¬Äåü’Èí#üxÐí5ü^ÛçKÿÿÿæÒ	·RâuÌ;ë [Þ)í1Céÿÿÿå(Ëw ë;µ‘âQª	ÓkƒÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ/ÿÿ@+ÿþü˜=ÿý‚Ü;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿý‚Ü<ÿþü˜ÿ@@+ÿþü˜=ÿý‚Ü;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿý‚Ü<ÿþü˜ÿ@@+ÿþü˜=ÿý‚Ü;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿý‚Ü<ÿþü˜ÿ@ÿ@&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿY@&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿY@&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿY&ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ/¯/¯/¯/¯/¯/¯/¯/¯¯ÿ¨Wý[h˜¯ù®yVAMm£¯¯ÿ¡þ”¯øœ&<¯¯ñ¡¡1E¯¯Œnal‘¯é|x¥€e¯1†¬¡va¯§x|¨vu¬„¯å¡€‘+I¯¯
Z¯¯1	•„¯ù”)1˜¯ÿ¡Ý¤¯¯€ª®®¯nyu¬¯uGGJ¯”+yƒ¥‡¯×¡R]$3¯¯k:
¯l¯¯{—¯V
¯””¯¯­Z¯Ü¡’¯„¥’hqš¯‘¯¯š_¯¯¯”	¯ÿr¯ë¡ix4¬h*¥Vš¯’¯ôH>|d¯”
¯ÿG¯ÿ¡ñ
s¯¥0š¯’¯ý¨Jú-¯”
¯þ¯ÿ¬ƒý„’ª¯õ¬|«ƒƒª¯‡ƒ¨¯öš}m|𝝩ƒ†¯ÿ¦/¯/¯/¯/¯/¯/¯/Æ/Æ/Æ/Æ/Æ/Æ/Æ/ÆÆÿ¾cýfv¬ÆùʼnbJW{¸ÆÆÿ¶þ§Æø±+DÆÆñ¶ ¶7NÆÆž|n{¤Æéˆ»rÆ7—¶†nƽˆŒ¾†…ÖÆå¶¥0SÆÆfÆÆ7
¨–Æù¨	/7¬Æÿ¶ÝºÆÆÁÅ’ÄÆ|‰„ÃÆ„PPTƨ1‰”»™Æ×¶]i(:ÆÆyB²Æ{ÆÆ‹«Æaƨ¨ÆÆÄfÆÜ¶¥Æ–º¥v¯Æ¤ÆÆ®kÆÆ¢ƨ
ÆÿÆë¶wˆ;Ãu/ºa¯Æ¥ÆôRGŒqƨÆÿPÆÿ¶ñ‚ƺ6¯Æ¥Æý¾Tú2ƨÆþŸÆÿ”ý–¥ÁÆõÂŒ’Á””ÁƘ”¾Æö¯{¯ÆÆ¿”—Æÿ¼/Æ/Æ/Æ/Æ/Æ/Æ/é/é/é/é/é/é/é/ééÿàtýy‹Ëéùè¡sWf‘ÙééÿÖþÄéøÐ2OééñÖ¼ÖA\é麒‚Áé饠ܪ†éA!²äÖ‚éÞ ¤àœå°éåÖªÂ9béé
xééAưéùÅ
7AËéÿÖÝÚééªãè¬çé!’¢›åé›^^céÅ:¡¯Ü´é×Ön{0EééNÑééé¤ÉéréÅÅééæxéÜÖÃé°ÛË–ÎéÁééÍ~éé¾éÅéÿ˜éëÖŒ E	åŠ8ÛrÎéÃéô`S¤…éÅéÿ^éÿÖñšéÛ@ÎéÃéýàcú;éÅéþ»éÿä¯ý°Ããéõ䤬䯯ã鳯àéöΦ‘¥Îéé᯲éÿÝ/é/é/é/é/é/éÿÿ>ÿÿ÷Wˆ²Î´“R"ÿÿõ+ÉÿÿòÒòÿÿàD!ÿÿô0ùÿ¢!vôx!ÿÿüÆÿsþ=þü(
þ
ÿÿüVÿÚõ£ð!èÿº¨ïÿþÛMô)ð™4ÏýÿçZÿýŒÿ|ï®ÿ¸æqq@é•kZ„ëÿLô,ÿÎï¢`~ìÿ`ÿý²ÿYü®ÿßþ
ýðÔü,ÿÿ}û&öåÿýÔÿ:ÿ2 ÿ‡ý®ÿg÷­þ,ÿí
ü—ÿSÿýÂÿJÿPÿÿØý®ÿ7ò‹¿Üèèöÿ&,ÿÀü]ÿxÿýœÿlýÿØý®ÿ"ñ8øù¶’ƒ€Ìÿ0,ÿªüCÿ“ÿývÿ§ýÿØý®ÿ"ýÌú1ø¯ÿ0,ÿ¾üZÿzÿüòý/ýÿØý®ÿ"ýúÒöáÿ0,ÿèüŽÿXÿüqÿØ6ýÿØý®ÿ"ýðûöpÿÿ0,ÿÿkûñë
ÿô•þü¯ƒ`kŽèÿ¸ý®ÿ"è ÿÊiN™ôÏÿ0,ÿ×ôŠHfáÿlÿý=ÇöÿüúÇOý®ÿ"þ±ÿíà<˜ÿ0,ÿ¤Eçÿÿõiÿû">!ý=ø,ÿ¤"2ÿÿ3ý,ÿ¤ÿÿ3ý,ÿ¤ÿÿ3ý,ÿ¤ÿÿ3ý(ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>¯ý`V<¯ýUI-¯ëjaq›¯¯©xyªxlŸ¯¯UIœl*¯êx¯›(”¯U$
T)¯ê­¬b%¯›G-=¯Upn)¯é3¯›¯q¯U1¯ª£(¯é nZ¯›š¯n¯UI¯­¡(¯éG¢<¯›C‰(A¯UI¯­¡(¯ë0¯›-–¯UI¯­¡(¯év‰§ƒ‡¯›¬€u¢¯¯™ƒ–¯¯ƒƒ¬.¯ý›<¯ý +/7¯Æýmb<Æý`S-Æëxm¯ÆÆ¿ˆ‰Áˆz³ÆÆ`S°z£*ÆêˆÆ¯.§Æ`(`)ÆêÄÃo*ƯQ¢3EÆ`})Æé:	Ư²Æ€Æ`7ÆÀ¸(Æé$}f
Ư¯Æ}Æ`SÆÄ¶(ÆéP·D
ƯL›.IÆ`SÆÄ¶(Æë6
Ư2ªÆ`SÆÄ¶(Æé†›½”˜Æ¯„·ÆÆ­”©ÆÆ””Â.Æý¯<Æýµ157Æéý€s<éýqb-é뎖Ïééá ¢ã ÓééqbÏÀ*éê éÏ6Äéq0p)éêæåƒ1éÏ_¾<Qéq•“ )ééEéÏÑé—éqAéâÙ(éé+“xéÏÎé“ éqbéæ×(éé^ØOéÏZ¶6Véqbéæ×(éë@éÏ;Èéqbéæ×(éé¶Þ¯³éÏ䪛Øéé̯Çé鯯ä.éýÏ<éýÕ:>7éý0	ý"0&ý0ýzÿV	ý¸ÿý—
ý˜>ù]˜:tÿ\ýzÿV	ý¸ÿý»ÿCü'ÿÿ›ùÏÿ/tÿ\ýzÿV	ýý{ÿ‚üfÿåÚøýí
ùzÿVü'ý;ÿÂû¦ñ}ÿýNÿ¯
÷zÿVbßÿÿØ5ý¬ðùNÕüÿÿétüõùûåµ<ÿZøŽÿolðVözÿ·ïŒtÎÿíý¸ÿø†ÿê„[o«®õ»ÿA%ÿv÷™øÍÿ/tÿ\ùszÿô"ý¤ÿo÷¸ÿ1üÞÿá{ÿeÿ7ÀÙýítÿ\ðzÿý\ÿ”ø¸ÿ ÿZð;ÿÀ¤ó‚ÿLÿ¯ôtÿ\gÿzÿbý!ÿ«ø¸ÿÊÿðõøã¸DÿXŒÿnôtÿ\ŒÿzÿVýÿ®ø¸ÿéûñºÿ?#ÿyû—Ëÿ.ôtÿ\§ÿzÿVýÿ®ø¸ÿÌÿúzÿcÿ:úÈ×üìôtÿ\ŽÿzÿVýÿ®ø¸ÿ£ÿRú:ÿ¾¢õûŠÿaÿ®ôtÿ\lÿzÿVýÿ®÷¸ÿ9þÔÿ	ûô÷å¼ûLÿÜÿnôtÿ\õzÿVýÿ®ý¸ÿø•ÿàtKZ•§
üºÿÿ|ûýÿÿ.ýtÿ\ú€zÿVýÿ®ý¸ÿþfîÿþò
üzÿÿ=üÐÿìýtÿ\û?)äÿ¯/¯/¯/¯/¯/¯/¯/¯/¯¯ùªoRAPw©¯¯ø‹[¯¯á¨*’­œjs¯x†¡n„§hau¤¯¯Ÿx„£qr§	¯¯üuŸ¯êuPy
‘¯|¥¯¯àU¯¯bGG]¯u<~Œ™’®©FE¯|a‰]¯¯à[¬¯:
$¯u	«¯¯¢R-.¯|®¯R6¯¯ó{~¯¯o$¯u(¯ðR1sD+¯|®¯O8¯¯ó®*
KL$¯u*¯ð'dœ$+¯|[‚`¯¯þŸ6úI¯u*¯ðq/+¯| §¯	¯ö”yn€¢¯¯¡ƒŽ¯òŸs’ŸƒŽ¯|#¥yz©	¯¯ý|#
¯¯ý‰+F
¯/¯/¯/¯'¯Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/ÆÆùÁ~\J[‡¿ÆÆøžgÆÆá¾/¥Ä°x‚Ʋˆ—¶}•½un„ºÆÆ´ˆ•¹½	ÆÆü…³Æê„[‰¥ÆŒ!»ÆÆà`#ÆÆoPPiÆ„Dž­¥Å¿ONÆŒm›iÆÆàgÂÆA)Æ„
ÁÆÆ·\24ÆŒÅÆ\=ÆÆó‹ÆÆ~)Æ„.Æð]8‚M1ÆŒÄÆY?ÆÆóÄ/U’V)Æ„/Æð,q°)1ÆŒg”mÆÆþ³=úSÆ„/Æð€51ÆŒ$½Æ	Æö§‰}·ÆÆ¶”¡Æò³‚¥´”¡ÆŒ(»‰Š¿	ÆÆýŒ(
ÆÆý›1O
Æ/Æ/Æ/Æ'Æé/é/é/é/é/é/é/é/ééùã”mWkŸáééø¹zééáà 8ÃæÏšéÑ ²Ö“¯Þ©Š‚›ÚééÔ ¯Ù–˜Þ	ééüœÓéê›#k¡
Âé¤&Üééàq)ééƒ^^{é›P¨ºÌÃèá][餷#{ééàzäéM0é›äééØm;	=é¤èémGééó¤¨éé”0é›6éðnB™Z:é¤çéiJééóç8d¬e0é›8éð4…Ï0:é¤z®€ééþÓHúbé›8éð—>:é¤#+Þé	éöÄ¡“ªØééÖ¯½éòÓšÃÔ¯½é¤/Ü¢£á	ééý¤/
ééý¶:]
é/é/é/é'éý!0<ý°ÿýý°ÿý>ÿ’ý°ÿý>ÿ’ù
°ÿý"
þý>ÿ’û)÷fêÿûË-°ÿ÷¤÷ÿò©
î½òëÿÿï……óÿùðûE™øÿþënëÿäyb¬êÓÿÆÿ¶elÈ¿þÊñÏÿ±hc²ÿ°B˜ÿÅxö"£ÿ¾{Yp¬•þíð‘ÿÿYÿ¦ÅÿÊý~ÿyø™ÿG>ÿ’üôáÿÿƒ÷÷ÿºÿ)÷MÿÊòçø"ÿ›>ÿ’üäç	ÿLøÑÿÞùøÿÊÿÖpøqþÄ>ÿ’ú‡ÿìšK	
ÿ6ù·ÿôéøÿÊAÿü÷úÔ>ÿ’øeÕÿÿö ÿJøÎÿ×üø!ÿÊ(ÿ°	ý>ÿ’ù3†ßÿË
ÿz÷ôÿ¯ÿ>÷bÿÊúéý8ÿ›	üåÿ!
þæðÿÿ@ÿÃ
ÛÿÊý“ÿŠùÿÏþüØÿ
÷ÿ×aJ”òÚÿ÷ ÿà— ê“þÈäÙÿ·bH`‰âlÏý¼†€%$ò˜`Bd¯ÿ¸÷v÷ÿÿã<°ÿøtÌòÅyÿ­ýŽ÷ÿýûº6÷(²æýÿJÁøÿýû¡ý4
ý<ÿŒû#?)û(@/ÿüÀÿ<6ø©¯rYzØÿ«7øuâÿÿþâw9ý ïÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿ¯ýEr¯ü®[W˜¯õ˜WZ®¯€NAOl¡¯ý¯ý6i¯ý3Q¯õR2¯\u¯ýš¯ç6i’j›¯¯u®¯®s¯u¬Ÿrޝõkd®¯‰man”¯÷š¯¯œqeƒ¯é6+s¯ª‚¯„
ª¯M­¯ÿCë¯b¯¯š¯p7®
¯Ë6}T8¯¯GB¯FE¯¯h]¥¯qs¡z¥¯«®y¯¯š£|¥,l
¯ð6P¯Š¯¯‰	¤…¯ö•HT¯C>¯íf8
¥¯š€++@
¯ù6i¯¯÷;®¯¯¬¯ù^¯E;¯ñ‹jj£¯š‚Lÿi
¯ù6i¯¯ÿ\ÿV¯ÝQw|/-¯wh–r¤_1¥O£¯š£
j¨Ÿm€
¯ù6i¯¯û›—¯ÿúˆ¯¯Në 0£¯š¯xp
¯ù‘ƒž¯§ƒˆ¯ý‰ƒ‡¯û‘wl|¦¯è—voŠ®¯ªz‚ªƒƒ¬¯„ƒª¯¯¤~lxœŒ¯ÆýNÆüÅfc¬Æõ¬cfÄÆYIYz¶Æý²Æý=wÆý:\Æõ]9Æh…Æý¯Æç=w¥x¯ÆÆ„ÄÆÅ‚Æ„³¡Æõ yq’ÄÆ›{m|§Æ÷¯ÆÆ°€r”Æé=1‚ÆÁ
“Æ–ÀÆ"W£ÄÆÿKë²ÆoÆÆ¯Æ>Ä
ÆË=_?ÆÆQKÆONÆÆuiºÆ€	‚¶ŠºÆÁĉÆÆ¯¸	Œ»2{
Æð=[ÆœÆÆ›
º
—Æö¨R`ÆLGÆít@»Æ¯‘11H
Æù=wÆ Æ÷CÅÆÆÃÆùj#ÆNCÆñxx¸Æ¯“Vÿw
Æù=wÆ ÆÿhÿbÆÝ \†63Ɔvªºl7ºY¸Æ¯¹x¾´{‘
Æù=wÆ Æû¯«Æÿ!ú
šÆÆXë²µ
6¸Æ¯Æˆ

Æù¤”³Æ½”šÆý›”™Æû¥‡{Œ¼Æè«†~œÅÆÁŠ”Á””ÃÆ–”ÁÆÆº{ˆ±ŒÆéý[˜éüèytËéõËtxçéªhViÖéý"ÑéýGŒéýEléõnCézœéýÎéçGŒÃŽÏéé›çéè™é›äÓ˜½éõ¼…¬çé·‘’Äé÷ÎééÏ—†¯ééG:™éã­é°
âé(fÀæéÿYëÑéƒééÎé•Iç
éËG¦oJéé_Xé][ééŠ{Ûé—šÖ£Ûé©äç¢ééÎÙ¤Ü:
éðGké¸éé¶Ú±éöÆ` péZSéíˆKÜéΫ::U
éùGŒé¼é÷$Oèééåéù})é\Oéñ¹ŽÙéέeÿŒ
éùGŒé¼éÿzÿséÝ%lž¥?<鞋ȘÚAÛiÙéÎÙàÔ‘«
éùGŒé¼éûÏÉéÿ&úµéégëÑÕ@ÙéÎé •
éùÁ¯ÒéÞ¯µéý·¯´éûŸ¤ÝéèÉ”¸èé㣮㯯åé°¯ãééÚ¨ ÐŒéÿ¯é¯é¯é¯é¯é¯é¯é¯é¯éÿàtývŽÉé¯éÿÖþé¯éëÖªÀO	æÍ ¶Ò¶é奅™Óéÿã¯éïÖÃé Ö)xÒ&û éä3¯éçÖs}%éYª¾U:ÄsᎯéÿÖí&¹éèéé˜éݽZ¯éôÖ˜¸ÏèééCéö”éÛÀb¯éüÖÃéýEéö[6·k㢯éüÖÃéýEéþØ2û©éé[¯éü䯯àéýÓ¯Àéûè´”§Úéÿà¯éÿ)¯éÿu¯é¯é¯é¯éÆ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/ÆÆÿ¾cýdx«!ÆÆÿ¶þx ÆÆë¶£DÄ®ˆ›³{›ÆÃq‚³ÆÿÁÆÆï¶¥Æˆ¶y#f³!û
ˆÆÁ+ÆÆç¶bjÆyK¢H2¦b¿xÆÆÿ¶í!žÆyÅÆÆƼ¡MÆÆô¶œ°ÅÆÆy9Æö~ƺ£SÆÆü¶¥Æýy:ÆöN.›[Á‰ÆÆü¶¥Æýy:Æþ·+ûÆÆNÆÆü””¾Æý³”£ÆûÅ™~ŽºÆÿ¾ÆÆÿ#ÆÆÿcÆ/Æ/Æ/ÆÆé¯é¯é¯é¯é¯é¯é¯é¯é¯ÿ¨WýYj—¯é¯ÿ¡þj¯é¯ë¡€<­šx‰žl‰¯¬|dsŸ¯ÿªé¯ï¡’¯x¡kZžû	x¯«&é¯ç¡V^¯kC€@,“V©jé¯ÿ¡í‹¯k®¯¯r¯¦ŽDé¯ô¡rŠœ®¯¯k2¯öo¯¥Ié¯ü¡’¯ýk3¯öE(‰Pªyé¯ü¡’¯ýk3¯þ¢&û¯¯Eé¯ü¬ƒƒ¨¯ýŸƒ¯û®‡o~¤¯ÿ¨é¯ÿé¯ÿXé¯é¯é¯é¯ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ#éÿØtýzŽÐ8éÿÅþÑé휒Գ Îé½ ÆÄˆÎéÚœ„£åéäË–…¡ÕééÓžƒšÍéÅÍÓ4méé¶’Æéû¡ à¥éÈ é[:«²-Úéˆbé«?éŹ¿-r鿇ééAéܰ’é[sªÏ"à\hé-ÐèȳéO¯éÑ©éÅï%àéèªäçœéé(—¥éÈZéé"é[EééÓ6:'-ée:€Ïéˆ2uÄéÅwz)Uéé„J
âé¡éðOééé[vééÕ]eÛuéâŸV @éæ«^*éÅÓé 	峊ÞéÑéö}`é[xéÕ&lÙÙ¡•é”ÇåÁ響äÕÚŘ;ézFÚeÞéÓéø5'“é[xééºné(‚éK`éÅñ¥éÑ@ÞéÓéöÏÅÙ¶éƯÍéêã®’œÅééÙ©’¦ØééÞ¯”ŸÑééá¯ý±Ääéõ⡯㯯æé¯¯äéúJR<ç9éû8*Dƒá;é#Æÿ·cýgx±8Æÿ¨þ²Æí…|´˜ˆ¯Æ¡ˆ¨¦t¯Æº…qŠÃÆä¬q‰µÆÆ³†oƒ®Æ¨®³,\ÆÆ›{m|¨Æû‰ˆ¾xÆÈzÆN2‘—&ºÆtSÆ‘6ƨ¢&aÆÄsÆÆ7ÆÜ–|zÆNb°l¾NYÆ&±Åª˜ÆD•Ʋƨï¾ÆÅÂÄ…ÆÆ"€ÆÈLÆÆzÆN;ÆÆ³.1!&ÆV2m¯Æt+c¦Æ¨eg#HÆÆq?ÀƉÆðCÆÆzÆNdÆÆµOVÛcÆÀ‡I6ÆÄ‘P$ƨ³ÆˆØyu½Æ²Æö	jRzÆNfÆÕ!\¹¹‰Æ~©Ã¤Ƈ¢Áµ
º¨†2Æg<ºU½Æ³Æø-!}ÆNfÆéž]Æ"nÆ@RƨñƲ6½Æ³Æö°¨¹›Æ¨”®ÆêÁ”|…¨ÆÆ¸|·ÆÆ½”~‡²ÆÆ¿”ý—§ÂÆõÀ‰”Á””ÄÆ•”ÁÆú?F3Ä9Æû/$9o¿;Æ#¯ÿ¢Wý[jœ8¯ÿ”þ¯íunŸ‡xš¯Žx•“fš¯¤udz¬¯ä˜qdy ¯¯Ÿwbtš¯”šŸ'R¯¯‰lan•¯ûyx¨|j¯Èl¯E,€†"¤¯fI¯€/¯”‹"V¯­f¯¯1¯Ü„nl¯EV€œ_¨EN¯"œ®–‡¯<„¯¯”隷®€¬®u¯¯q|¯ÈC¯¯l¯E4¯¯Ÿ(+"¯L,`›¯f&X“¯”Y[@¯¯d8
ª¯y¯ð;¯¯l¯EY¯¯ FLÛX¯ªwA0¯­€G ¯”Ÿ¯x¬‡kh§¯¯ö^Hl¯EZ¯ÕQ££yp¯o–¬‘¯w« ¤”rv-¯[5¤K§¯Ÿ¯ø(n¯EZ¯éŒR¯a¯8H¯”ñ|¯
0§¯Ÿ¯öœ”£‰¯•ƒš¯êª‚nu”¯¯£n}¢¯¯§ƒow¯¯©ƒý…”¬¯õªyƒªƒƒ­¯„ƒ«¯ú8>-®9¯û* 3b©;¯ÿéÿéÿéÿéÿéÿéÿéÿéÿéÿÿõ÷„˜Þÿÿ÷óÿéÿÿêù×ÿ÷''óÿéÿïêÎêGeÿý¯¯ûÿÿ×éÿïêºÔ>kÿöñÿ}éÿÿêõïÿöñã–éÿïêx‡4Kÿöñ¯ÿéÿïêÕÿÁðöñ·üéÿï꙯L
ûöñïbéÿÿêõ¨ÿöñÿ¦	éÿÿú¿óÁÕøÿÿý¿¿üÿõñÏéÿýo?béÿý±I.éÿéÿéÿéÿÆîÆîÆîÆîÆîÆîÆîÆîÆîÿåw÷{ŽÏîîçãîÆîÿÚùÉîç$$ãîÆîïÚÀÚB^î죣êîî¶ÆîïÚ®Æ:dîæáîuÆîÿÚõßîæáÔŒÆîïÚp~1Fîæá£îÆîïÚÇî´àæá«
ëÆîïÚ£G	êæáß
[ÆîÿÚõîæáî›Æîÿé²ó´Ççîîì²²ëîåáÁÆîýh;[Æîý¥D+ÆîÆîÆîÆî¯ª¯ª¯ª¯ª¯ª¯ª¯ª¯ª¯ªÿ£U÷Xe”ªª¥¢ª¯ªÿœùª¥¢ª¯ªïœ‰œ/Cª©uu§ªª‚e¯ªïœ|)Gª¤¡ªS¯ªÿœõŸª¤¡—d¯ªïœPZ#2ª¤¡uª¯ªïœŽª ¤¡z	¨¯ªïœfu3§¤¡Ÿ	A¯ªÿœõ
pª¤¡ªo¯ªÿ§󎥪ª©¨ª£¡Š¯ªýJ*A¯ªýv1¯ª¯ª¯ª¯ªÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿÿàÿ—ÿýßÏê$ÿÿÀÿ/ÿýT‹ ÿßÑܯÉòÇǃÇÇÒ몑µýÿò¯°ûÿÙ¯ÅÇ:_¯Ïÿü9Qÿë¨ÿÿ»8ôÿPñ¿LÿcÿüÅQÿæ¨ÿý–õ[~ÿíAÿÞDqÏâÿýqQÿè¨ÿÛ>?(=ÿÿÁ?ýÿÿT‹!ÿýdQÿø¨ÿÝjoó‡ÿÿ  ðÿÿT‹ ÿüQÿæ¨ÿþðí¬©ÿ×hSÿÿ}J¼äÿüQUÿæ¨ÿÿÅ„÷.¦ü;•ÿÏ“ÿüùA{ÿýê¿Åÿìö½Ÿ¬Ûÿì¿Æÿÿê¿ÇÿÿýÍ¿äÿü*â;ÿý@qâ=ÿîÿÑwÿîýÐÁÚ$îÿ³ÿ,îýN‚ îßÃÍ£¼âººzººÄÛŸ‡©ìî⣤êîË£¸º6Y£Áîü5Lîëîî¯4äîKwá²Gÿ\îü¸LîæîìŒåUvîÝ=xîÏ?iÁÓîýiLîèîÌ:;%9îî´;ìîîN‚!îý]LîøîÎchó~îî•àîîN‚ îü„LîæîíwàÝ¡žîÉ
aMîîuE¯ÕîüLOîæîî¸{ç+›ë7‹îÁ‰îüè=sîýÚ²¸îìæ°”¡Ìîܲ¹îîÚ²ºîî쿲Õîü'Ó;îýÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ0ÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿñQ‚1¡Q‚1¡ÿÿÿÿòÉBÐ:ÉBÐ:ÿÿÿÿü)Ø‘Œü)Ø‘ŒÿÿÿÿýåýåÿÿÿÿüÝÏAüÝÏAÿÿÿÿû¨i+×û¨i+×ÿÿÿÿòR½|’R½|’ÿÿÿÿñhx
hx
ÿÿÿÿñQ‚1¡Q‚1¡ÿÿÿÿòÉBÐ:ÉBÐ:ÿÿÿÿü)Ø‘Œü)Ø‘ŒÿÿÿÿýåýåÿÿÿÿüÝÏAüÝÏAÿÿÿÿû¨i+×û¨i+×ÿÿÿÿòR½|’R½|’ÿÿÿÿñhx
hx
?ÿÈáQ‚1¡Q‚1¡Q‚1¡Q‚1¡ âÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:"ü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œ$ýåýåýåýå#üÝÏAüÝÏAüÝÏAüÝÏA#û¨i+×û¨i+×û¨i+×û¨i+×!âR½|’R½|’R½|’R½|’!áhx
hx
hx
hx
 áQ‚1¡Q‚1¡Q‚1¡Q‚1¡ âÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:"ü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œ$ýåýåýåýå#üÝÏAüÝÏAüÝÏAüÝÏA#û¨i+×û¨i+×û¨i+×û¨i+×!âR½|’R½|’R½|’R½|’!áhx
hx
hx
hx
 ÉQ‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡ÊÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:
ü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘ŒýåýåýåýåýåýåýåüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAû¨i+×û¨i+×û¨i+×û¨i+×û¨i+×û¨i+×û¨i+×	ÊR½|’R½|’R½|’R½|’R½|’R½|’R½|’	Éhx
hx
hx
hx
hx
hx
hx
ÉQ‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡ÊÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:
ü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘ŒýåýåýåýåýåýåýåüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAû¨i+×û¨i+×û¨i+×û¨i+×û¨i+×û¨i+×û¨i+×	ÊR½|’R½|’R½|’R½|’R½|’R½|’R½|’	Èhx
hx
hx
hx
hx
hx
hx
ÿ'ÿÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿþ	þÿÿÿÿþA†	þ±6	ù)¾”ÁaÿÿÿÿþT«þü±6õ)¾O|ÿÿÿÿþT«쑹¼œ±¡ÈÈH€¹¹f)¾þ{|ÿÿÿÿþT«ì5±–Ò>žÄ
)¾þ{|ÿÿÿÿþT«ìR«¶Ñw±LÏ’ʰ°ÙP)¾þ{|ÿÿÿÿþT«ñÔ—|±gÞ	prþ)¾þ{|ÿÿÿÿþTÈXåÛB\Ü|±ÈG†¬ÖZ,`)¾X¨©Xÿÿÿÿÿ*€é2w<>X7‹ck›k	_	€ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿþ	þÿÿÿÿþA†	þ±6	ø)¾IÄ×(ÿÿÿÿþT«þü±6ô)¾=f³ÿÿÿÿþT«쑹¼œ±¡ÈÈH€¹¹f)¾þêÿÿÿÿþT«ì5±–Ò>žÄ
)¾þœ‚ÿÿÿÿþT«ìR«¶Ñw±LÏ’ʰ°ÙP)¾ý{­ÿÿÿÿþT«ñÔ—|±gÞ	prù)¾s±ÿÿÿÿþTÈXåÛB\Ü|±ÈG†¬ÖZ,`)¾dè^XXÿÿÿÿÿ*€é2w<>X7‹ck›k	_M€ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ&?ÿ@ÿ	>ÿ¿ÿ>ÿ?ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿþ9ü6/÷A'$FÿÿÿÿìÌ=5ÒÛ"W¹'Ù|”ÿÿÿÿí-ÖÓ6LÁÝqž.×ÿÿÿÿýyã‡ýžãbüÁâ?ÿÿÿÿýYþDý~û"ý£êÿÿÿÿîÝKÕ
-ØX¿LÃs›ÿÿÿÿí¬dŽËA¤iÛ$ÆEÿÿÿÿëV¹Ï:{•Ü p(Ù
ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ/?ÿ’ü7c.ÿ0HûF$ÿW+ìU¯vµ¥« ˆˆ~n¬.Ts+÷ã«4ùºSn
¸.ðç	«Ï«TŒO›9…B.ì
ÆW+
6ÎG	fB%¢$“†*ýºhþ` ú«š$fW)ý¸lû dùPw°Qn)þfúØï4ݽӧ	¸P¥´ 0ýþ
þÙ?ÿ'ÿ'ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿùQ‚1¡ÿÿÿÿúÉBÐ:ÿÿÿÿü)Ø‘ŒÿÿÿÿýåÿÿÿÿüÝÏAÿÿÿÿû¨i+×ÿÿÿÿúR½|’ÿÿÿÿùhx
ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿÿÿÿ>ÿÿùQ‚1¡(ÿÿúÉBÐ:)ÿÿü)Ø‘Œ*ÿÿýå*ÿÿüÝÏA*ÿÿû¨i+×)ÿÿúR½|’)ÿÿùhx
(ÿÿùQ‚1¡0ÿÿúÉBÐ:1ÿÿü)Ø‘Œ2ÿÿýå2ÿÿüÝÏA2ÿÿû¨i+×1ÿÿúR½|’1ÿÿùhx
?ÿØùQ‚1¡8úÉBÐ::ü)Ø‘Œ<ýå;üÝÏA;û¨i+×9úR½|’9ùhx
 ùQ‚1¡ùQ‚1¡ úÉBÐ:úÉBÐ:"ü)Ø‘Œü)Ø‘Œ$ýåýå#üÝÏAüÝÏA#û¨i+×û¨i+×!úR½|’úR½|’!ùhx
ùhx
(ùQ‚1¡éQ‚1¡Q‚1¡Q‚1¡úÉBÐ:	êÉBÐ:ÉBÐ:ÉBÐ:ü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘ŒýåýåýåýåüÝÏAüÝÏAüÝÏAüÝÏAû¨i+×
û¨i+×û¨i+×û¨i+×úR½|’	êR½|’R½|’R½|’ùhx
éhx
hx
hx
ñQ‚1¡Q‚1¡ùQ‚1¡ùQ‚1¡òÉBÐ:ÉBÐ:	úÉBÐ:úÉBÐ:
ü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘ŒýåýåýåýåüÝÏAüÝÏAüÝÏAüÝÏAû¨i+×û¨i+×
û¨i+×û¨i+×	òR½|’R½|’	úR½|’úR½|’	ñhx
hx
ùhx
ùhx
ùQ‚1¡ùQ‚1¡ùQ‚1¡úÉBÐ:	úÉBÐ:úÉBÐ:ü)Ø‘Œü)Ø‘Œü)Ø‘ŒýåýåýåüÝÏAüÝÏAüÝÏAû¨i+×
û¨i+×û¨i+×úR½|’	úR½|’úR½|’ùhx
ùhx
ùhx
ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ'ñQ‚1¡Q‚1¡ÿÿ'òÉBÐ:ÉBÐ:ÿÿ(ü)Ø‘Œü)Ø‘Œ	ÿÿ)ýåýå	ÿÿ(üÝÏAüÝÏA	ÿÿ(û¨i+×û¨i+×ÿÿ'òR½|’R½|’ÿÿ'ñhx
hx
ÿÿùQ‚1¡ÿÿúÉBÐ:ÿÿ ü)Ø‘Œÿÿ!ýåÿÿ üÝÏAÿÿ û¨i+×ÿÿúR½|’ÿÿùhx
ÿÿùQ‚1¡ÿÿúÉBÐ:ÿÿ ü)Ø‘Œÿÿ!ýåÿÿ üÝÏAÿÿ û¨i+×ÿÿúR½|’ÿÿùhx
ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ


ÿÿÿÿ×Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡ÿÿÿ×ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÿÿÿü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘ŒÿÿÿÿýåýåýåýåýåÿÿÿÿüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAÿÿÿÿû¨i+×û¨i+×û¨i+×û¨i+×ø¨i+×ÿÿÿ×R½|’R½|’R½|’R½|’R½|’ÿÿÿ×hx
hx
hx
hx
hx
ÿÿÿ×Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡ÿÿÿ×ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÿÿÿü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘ŒÿÿÿÿýåýåýåýåýåÿÿÿÿüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAÿÿÿÿû¨i+×û¨i+×û¨i+×û¨i+×ø¨i+×ÿÿÿ×R½|’R½|’R½|’R½|’R½|’ÿÿÿ×hx
hx
hx
hx
hx
ÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿÿÿ.ÿÿ0ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>?ÿ


‚Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘ŒýåýåýåýåýåýåýåýåüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAû¨i+×û¨i+×û¨i+×û¨i+×û¨i+×û¨i+×û¨i+×€¨i+×R½|’R½|’R½|’R½|’R½|’R½|’R½|’R½|’hx
hx
hx
hx
hx
hx
hx
hx
Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡Q‚1¡ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ÉBÐ:ü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘ŒýåýåýåýåýåýåýåýåüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAû¨i+×û¨i+×û¨i+×û¨i+×û¨i+×û¨i+×û¨i+×€†¨i+×R½|’R½|’R½|’R½|’R½|’R½|’R½|’R½|’hx
hx
hx
hx
hx
hx
hx
hx
Á?ÿÀ?ÿ


ÿÿÿÿþ0	þi 	ùqKe.ÿÿÿÿþT«	þ±6	ø)¾BŽsº˜ÿÿÿÿþT«ì<…w"±N~V
g`)¾þ
îÿÿÿÿþT«æY;KÖ±ÇOަÑU\Æ)¾U›ŠÿÿÿÿþT«æ9l¸]±jßi88½0)¾%‡ÄyÿÿÿÿþT«ò¸yJœ|±JÍ–§xý,)¾þãÿÿÿÿþT«åÞÀ|±‘ÔA¤)¾
àÿÿÿÿþTòØå2š¿Î­|±«ÄÎN‰Á¬È)¾~Í¿Í`ÿÿÿÿ
þþýþÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ&ÿ'ÿÀ?ÿ


€ÿ¿ÿ>ÿ	??ÿÀ?ÿ


ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ/ÿð?ÿ


	À?ÿ


'ÿÿùQ‚1¡0ÿÿúÉBÐ:1ÿÿü)Ø‘Œ2ÿÿýå2ÿÿüÝÏA2ÿÿû¨i+×1ÿÿúR½|’1ÿÿùhx
0ÿÿùQ‚1¡(ÿÿúÉBÐ:)ÿÿü)Ø‘Œ*ÿÿýå*ÿÿüÝÏA*ÿÿû¨i+×)ÿÿúR½|’)ÿÿùhx
(ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÀ?ÿ


ùQ‚1¡ùQ‚1¡ùQ‚1¡úÉBÐ:	úÉBÐ:úÉBÐ:ü)Ø‘Œü)Ø‘Œü)Ø‘ŒýåýåýåüÝÏAüÝÏAüÝÏAû¨i+×
û¨i+×û¨i+×úR½|’	úR½|’úR½|’ùhx
ùhx
ùhx
ñQ‚1¡Q‚1¡éQ‚1¡Q‚1¡Q‚1¡òÉBÐ:ÉBÐ:	êÉBÐ:ÉBÐ:ÉBÐ:ü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘Œü)Ø‘ŒýåýåýåýåýåüÝÏAüÝÏAüÝÏAüÝÏAüÝÏAû¨i+×û¨i+×
û¨i+×û¨i+×û¨i+×òR½|’R½|’	êR½|’R½|’R½|’ñhx
hx
éhx
hx
hx
Ñ?ÿÀ?ÿ


ùQ‚1¡ÿÿúÉBÐ:ÿÿ ü)Ø‘Œÿÿ!ýåÿÿ üÝÏAÿÿ û¨i+×ÿÿúR½|’ÿÿùhx
ÿÿ'ñQ‚1¡Q‚1¡ÿÿ'òÉBÐ:ÉBÐ:ÿÿ(ü)Ø‘Œü)Ø‘Œ	ÿÿ)ýåýå	ÿÿ(üÝÏAüÝÏA	ÿÿ(û¨i+×û¨i+×ÿÿ'òR½|’R½|’ÿÿ'ñhx
hx
ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿà?ÿ


	À?ÿ


	À?ÿ


	À?ÿ


>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>@ÿ ´ÐZh-4urwid-2.6.16/docs/manual/images/urwid_widgets_2.png000066400000000000000000002114531470350774000222360ustar00rootroot00000000000000‰PNG


IHDR@ßXÎzÄzTXtRaw profile type exifxÚmP[!üç=/]<ŽûhÒôøEÑdÝt‡tDàú~Þðj`RдY.9£C‹®.µ3¡vîp)#»ä䮨SíHCËiæÇ…©ºJ7#;Fa_E#²=ŒâY”ÖQÓç0*ÃH8
4j|s±íþ…ýÂ¥º¶ýÜëæÓ;“¿#Ì— ³ˆFÒ–‚TäÌ’¸©ÚYevâù7§	øS«YšÎ­=…iCCPICC profilexœ}‘=HÃP…OS¥¢U;XqÈPì¢"޵
E¨j…VL^úM’GÁµààÏbÕÁÅYWWAüqvpRt‘ïK
-b½ðxçÝsxï>@¨—™fuÅM·ÍT".f²«bà>„1€>„efs’”DÇúº§nª»(ÏêÜ÷gõ«9‹>‘8ÆÓ&Þ žÙ´
ÎûÄ!V”Uâsâ	“.HüÈuÅã7Ηž2Ó©yâ±Xhc¥YÑÔˆ§‰#ª¦S¾ñXå¼ÅY+WYóžü…Áœ¾²ÌuZ£H`K BA%”a#J»NŠ…Ç;øG\¿D.…\%0r, 
²ëÿƒß³µòS“^R0t¿8ÎÇØ5Çù>vœÆ	à®ô–¿Rf?I¯µ´È0¸
\\·4e¸Ü†ŸÙ”]ÉOKÈç÷3ú¦,0tô®yskžãôHÓ¬’7ÀÁ!0^ ìõïîiŸÛ¿=Íùý[³rY4¼
xiTXtXML:com.adobe.xmp

 
  
   
    
     
    
   
  
 

                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                           
J}óbKGDÿÿÿ ½§“	pHYs
×
×B(›xtIMEè-òóÚ IDATxÚìÝw\UåÀñÏe/‘%8.p ŽTÜ«,ýYd9*Ó†™#-M+-GYj®²´,µ´Ô4sÏÔܸâVP½×½¿?Îá²Ýñû~½x]¸gÞsžó|ï³4€!„B!„xJH$„B!„x*b¹B!„Bˆ§…@B!„B	€„B!„B !„B!„H!„B!$B!„B	€„B!„B !„B!„H!„B!$B!„B	€„B!„B !„B!„@B!„B!B!„B<áŒä”	µ3àT9ý|Õ[ày‡Ûx«¯g%y!„¢ŒÑÉ%x¬4÷»±î~w¢R?y/¸
„—Ô×§•1`d”²<¨SFR`|QÂòÊÀõ÷ÉÀµÖù¨|„½/àüžÇuõµÊ#þü†êýÊ´’¿!„¢¤hu@”\…ÇÀßÏé~ ݃*po³Î&`pì)¼WkçW ¢ŒŸk,ð*`ÌTÙ‚:¸×'€yE–›«S0ì	¼WÃ@?`±d3B!„å˃€ÍÕtCàe`ðpT-`ŠÂÚªÁQY ö¨i£u)çš©JmKXÞ0PjQP&g”š@!„B!ÊMJ3¯5:¬þôPד·Ø^ƒÒgÄþ.ŽY¥	™ímÖËÛ·í]~& .Å›¨Y5î¯*.¸]=ªàtçí~÷y—úÚ®„e퀃jTÚò‚ûÈŒo¹·H‡îÜ]@ Ú]~®¼ûïðÒ¹¹šŽk= ý	!„Bˆ'4*Í*àwµàøm	Ë€€$”¾C7Qú¼YÊþŒ€1ê:	(ìãÔ×N%H¿©ë]U×Þ/a¿­Ôõ&«¿ŸRÏ)¨@à6
8
¤Qú¹¤ß
ìËXÝWgõïsêß	À¾ë¨Ÿ·(`šº,\
"¢Púà
ª¨û]ªžg€úw0Œ¼‹ ­´ÈE
øv«³Ü.Z žKÍ"ë¢47KTÏ3	Ø|›à·ªzü$”þGQÀÀG=Æô¶qþ.¶bÔëùV‘õ6_«¿ÿTà^%rE–MEæÕB!„(óu-
¥æC£%y…ÊçQ¾qßQdýlµ oIáÚŽê²áE‚Ÿ<çš ®óy‘ubQš—ï”°3¥?”üäY ¾6z×j€ú:¶H0‘Nþ0ÔJØ.J
(Ú…Ò/«æ]—úZp ƒ¶(50é(µ[ûKX^pÛ;ù|“(\Ó“UàóõJÍTÑ!×£Qj󊪃Òÿls‘à§`Ú2G ãN$ªA­Y)ËeØl!„B°báþZ0¿LcçæÄݼñØÎ%6&’!½ýJ]¾iåþ˜;龓•IXÈE	€J9^^S¯¼>/uÕWcàÃ~šªË½Ô×
(5HWP¾µ¿+”~*Á(ýgŠÚ«¾Ö*aÙ­&%5F©¡Y‹Ò/BÝÿJu¹Û¸VµÔ ¥¤aÃ÷Ýâ¼ÏR|°”ff)\;v+;Õ×vêke”Z¬‚ÁÍ”&d5Ь»óöŸŒ*aÙŠO0f‡Òtñ%ϧt¼„÷òÒ–Q)i«Y‘´u;a(5{Õ45¥Ÿ˜¡dõB!„Èó×Âé¤$%`bbÊ™øàµÖÞ»õ±œ‹¹¹%íº¼Zêò´”d’cïû8aÁ|9¢×qu¸*(û$y5APš–•$C-CþÈ_×ïðx‘¥,,²^A1·¸f[Õs½ Qq(µ•Pú£XÜçu2U÷u’gNS¯aIçRÊ>ójÅî´°~Zý\
Qú(ìÿS0B]¦i(µD·ãz‹ëœ…RCW£ú_ÊþJÚO^Úê€ÒÌîviëNôAi69øDý‰Ei®9‰Ò'»B!ÄS¤Ã½ð¨Ý€Ê.ÕÙø÷¯<Óú9ýòËçOréì	*ÚØÓØ¯3¦fæ$ÄFsùü)ûuB£QÆ®:s"[{'\ªyÚXÈE´Ú\ª{*ßãž>¶;'\«+ß1Ÿ8´“ZuallŠ[º…¶½xæ8Á©U¯q±óNOKáðÞ-èt:5ïHÈÅÓxÖñÁÊZ©ÃHMIää¡]¤$'R¯‘®Õ”ïÁƒŽï#+3ƒC{6ШyŒMLËä½yÔ5@y£³],!GþB%ý¼]$ðq¾“`T}-møh§"ë݉×ÔàçW”&VÔBðòk€îWÞ<;Ž¥,7Sƒ’‡9©ªN
vQúµCi6P`£jà×e°3”fqÙw°ÿ¼û^ÒˆoÆ%%×os/]oqŒ‰·I[oÞÅu¹©:ÕÕôü‹úþXŠ7=B!ÄS.#=+NáäR]ÿÞº¿æñùà—¸vå+~›Éèwž#=-+k[–/˜Îº¿~àØíÌ÷>VÖÅörñ4¾Szxdge2éã×ùsÞdR’˜<úM
HŒ¿É´±ù½=Ö-û™ñÃ^!4äßO‘ýù5SÙY™|ú^WÖý5ËçNòÕÈ>Ìœ0ˆë×”©CƒÏ3¢_GöíX˵+çó~wîÚÀÕËgÉÎÊâbÐ1.#;;«ÌÞ“GYdƒ2@ÀÞ?§¾¶£ðHo¥ITþ5ÕÂð­æÏIBéâ‰Ò|ªhíÁ3êëÝLЙ׿gi	Ëž)e›¼`~ǹ¤uQú#Ô¥ÕÞXtàRÃÓ¥9^j‘ÏuP]v©À6w"X
¬R¼Žo	ÁyŠy«ASÑ¢’j¦­;mÜz§÷*¥ÏÚ”.¯ Lú*„Bˆ§Ü¤ßÀÈØ˜¸›QÔôjÈðqsHKMfé/“ùlÊ"|ži‹N§ãÃ¾íØ¸b>¯ôÿ'ýÊèw»PÙÕŸ¦~̈‰?ccW©Øþ4iÍ“?";+“s‡©ß¸5ƒŽ¡Ój9}|ž^
13·$91¿ø›ž–²ùSù|úêúú¡ÓjõÚ¹yZm.3~ÛÀ… £|ú^Wýòߘ@»ç{ÒçÝÑxû4gÑœ/iÞ®+Ýz¾ÇÉûè;øó2oU
PW”¾Ž(#ŠýQ`ÙuÙs(Ã5—ÄœÂÍÊæ 4¥›RÊg(øÞ"”&eYÇe41(>Ú×­ä
cÝ¡ÈûN%#O¨úÚä.޳H}Káù{ŒÈÐaÑ#€@™ÄÖ‹ÂÍßòìAé“õæ]@yçþY‘@\£~æ’|¯¦…ÉEîqc”áÁ‹:RcÕèy‹´ey‡÷ª¤¡¼A©ˤôæ‡B!„xÊŒüògf-þé·âàä·j-LXÈ%xðm¡|4|›µ'øâi¥@éR#¾aʧýéÜýu4i]âþmìqvuã\àa_'ª¸Õ$øâiî-q»ˆÐ+èt:êÔWºØk¨×¨¥~yðÅÓÔoÔJÿw
5ˆÊsþô®œdö¤aÌž4Œƒ»7qì¬Ì'êÞ<è ”!‰Qƒ7”~Ž@Js¡1E¶Ñ¢ŒÂve²Êj!:¥¿O# Js££ê6ÓÕmuÿËPú˸ Œúµ“üæHßo¨ÇuAmÎ¥G]5;tŸqZh¦œ·£ð)JÓ>‡¶Ù|€2Òsj!;Š’G.Ë3¥¯I”Á–©÷ë-”ÚŽ}Àò‡œ>N£Ô´xÜ"¸Éëä‰R;tä÷½e8é®êõùEMýÔû’HñþO?ªiaJ-Øn”¦o/ k>”Â#±å¥­C(Ã?«7NM[¾êþžG©É¥	_JSG”9‘t(#>¯¦½?Ôk…2T/5úC²{!„B˜[XaiUËéõö(†½Þš„¸Ñj›››ƒÆ ÿ»Ý ãû±²¶!4øÂ-Q¿Iî%ðè>7‡Ô”Dõï½þtF±õ5hµZ´:þ]­V‹ÚÝccR’ò;ÎÎÊ*Ô”M§ÓÑØ¯#žjß&€î½bhdôDÝ›]d¬`û©…EKµ@ü=JÓ¥oºJ/o”Ú¡·Q&¤Ü„Ò±¼Ê =ÈB™sæG”fb?©ëÏGùæþruÔ‚îfõ¼Vª…íÚ(Šö¿ËÏxD-€kP†ãÞ„Rõ¥7Ú¬L	À«êqÝæ89(#Œ-VøKÕß[«Ÿ÷YJ áAÒpò†½.ê ù}~¸³þ?yz¿©Ý
õÞTUÿN¡pÍ(´f£ô7z¥/Ô»äχTtî ójÚZ£Þë?¤­×mä×ú ¦O”ÑýZ£ôM¯¦å«(M*G«it»Ôz«ö8Éî…BQÔ¹ÀCØ;:S¡¢-UÝkallÂÙ“Êw¯¹9Ùœ8¸“š^
Ø·}-'ídö’=DG†²ñï_KÝoƒ&­9´g	q1¸Tó¤A“6ìÚò7±ÑÔ®[¼1‹KUOŒM8wê>ð
:¾O¿¼®¯§Žì!)A)®عžÜœü¢]úM•Áê6ÖÿÔôòÅÀÀûJΤ&'¡Õæ–ùû¡Q¹š2tN¦(ý{ìÔÂl%Ïõ“Çe8h{uý[V(µI(µ5÷s‡¬Ô *G
àrâ51SÙ¹(ýZ²ÊY¾`…R“r±”ùN|¨#}(}²W5mÙ£ô#½ÇkiR»i‡Rt•Âs	!„¢|Ó­(½úË­œ©åÝË
D߸NVf:}|Bûç•ùÛÖ-aÁwciÖö.œ>‚µ=“æ¬!6&’O<Ï3–QÓÛ—ðk—øt`7&|·Ï:>ÅŽ“–’Dß.uhÛå†}>­6—¾]êP»ncÆÍRŠC17®3¤WVìVfÙºf1¿ÍGëÎþœ<‚]%œ«¸1ä³Yèt:fŽÈ‘}ÿRÅ­Ö6ö„\<ÍØiRë!‘aÁ|5ò5LÍÌiд
ñ7£Hˆ‹áËþ`ÒÈ×IJŒÅÉ¥G}‹U›~áýýœ¸ÏØEW !n mhZ	eÞ&+”¦x1r™„Bñ¸ 3'Щ_ÇÛWªŒsUbëD„^áòùST´±§^#?Œ¹~•Œô4Üjxë×»~õ
úá­‹ºxævÎ88¹|!+k*»ºÅÅ3Ǩ۰…~›«—Ïr^
126!+3]?œ5@äõÐé@£apÏæü¶!H?CvV&gO$6&['¼}šcj–?vTlt7¯R»^SŒŒ%â8ƒÒìî8J?!/à=”þ:Ÿ¢ŒÈ&„BñØ 'Ùæâ^«>‰ñ7ÙüÏB7sY™9¿IúO˜CÀKäÏ•‹2õ`”A4„B!Ä=ÊÎÎfÍ’9hµ¹x7lA÷Þï—»Ï(5@âIe£þDPþúD	!„¢l+·5@eÔ‰§Y‚ú#„B!Ä3K „B!„xZH8!„B!îŽN.ÁcaîùÞI8!„B!]\4v=5’>$}•ÆÉÏ_Ò‡ä-ÿˆ
X-ùG9&ù‡ä3ÿx$z¼î+}ÈD¨’ùI_Bî¯äBHþ!ùÇ]Š
X-‰õ1pòó¿ï}H8É|„¤/!÷Wò!$ÿüC<5$’ÌGHúr%ÿBòÉ?„@’ùHæ#$}	É?„’Hþ!$’ÌG2!ÁüC!ù‡äB ‡#üÚezµ÷`åâÙètº;ZG§Ó²lÁt&êGbB¬d>O€¢÷L«ÕòÝWÃx÷å¦$Æß,ö·<Ü„Ü_É?ÊŠË×ÂñhߋًW–úœeçù"ù‡äB<Ðhí_óð÷sÂßω·þ×)cÞå¿M+HOKyÄKCíºñkßSSsÉ|ʈ‚é#ïçÓÝÑjsïúžIá¸üúoÓ
üýœx£‹7aÁÅ–kµ¹œ>¾ŸøØè;zÿv鱤㤥$óÁëíð÷sbʘwÑjµ’M`öâ•\ºöÀµbÓúã‡E<¶Ïœ«Õ²ÿøi¢cã%c¸ÃÿçKA÷]&¸—gÚ]¼™ðQV.žMØÕKüQÖÓïÃØi÷>ƒ±±säô±}ü0én„_¥Ï»£ÐhîaÈî;Ù¦È:†FÍÛKæSF½øúPìì0154ÅîÙ­
œü”_ÙY™ìÛ¾O¯F„_à\àa\ªzZ'2,„©c0~ÖRlíoûþí¤&Å|1¨Ðq®^$:"Tò2hpŸîT®dÇåká|÷Ûß,[¿µ?OÆÑÞöì?3+›µÛ÷ÑÈË“Áa<‡GU—û~LÝ‹°HŒ™ÊÒYãØç{ÝM™à^Ë#¿bï’ÙõürµZžyå}v,š‰M«bË7ì:ÀÖ½Gøá‹a÷}œóÁ¡Ô­áVþ v]zà^³Ͼø:£ßíÊþëy±ÏûXZYÆá}ÿr!èW/Ÿ¥QóötìÖ›jîµHNŠgëš?عŸ¦mhÜ¢VÖ…ö»u´Z-³¿þ ˜±`3*Ú1ûë©ääJÛçz°yÕïÝÇË­êàÞ£+ÎŽöÄ'%³xõÖíØO‡èݵ#ï|6…—ŸkË}{Ü['™O1žïI5Ú…Þ+éžÝIaùhÀvön[CDX0ÍÚt¡c×Þ8:WÕïÏÉ¥ºöfãÊ…œ=u˜o~Z‘‘±?eTDX0gN`Èg3”{»}-m:ûclb
@xè–-˜NjR,£ßy€1ÓþÄ¥ªG‰ï7nѱät0W™»Á¾r5|·âhÀvüÚwÅÀÀNGЉø¶è@\ÌBçW0ÿн|ˆöÍÑ»[Gj»WC«Õòá×ÊmäÛ½ùuÅzž£×èÓ­æf’<=º´£^Mw´Z-,-˜³t-Q±ñ8ÚÛ’Ãö€£¬Ù¾—Ë×ÂéТ¯<׎ÚÕØì4}?žÄ[¯<ϘAob Ñðó²µLûu³Ç
§[{?‚Ã"8pâ3>Âö€£¬Ý¾ÿÎm05ÉÏ7â“’ùcÍV6ì MS:´hŒµ•E¡ó‹Œæß}‡9t³—¯ÞuZ¹ÎôˈMJå¹wFðç´1tn̬ٔlåsnÛKpX]Ú4£w׎TuvÔﻚ‹½»vbáÊ>u–Õ?}ƒ±ÑÓ9õß–	š¶êL×ocïè|ÛgLIe­V‹…eÖ.C|l¶öŽädg«yÙ¯]¦Q‹´{îz{ÄÞqš”àçÉ4}á_¼þ¿Î89þò"'7—ë7bûùh4ôîÚSã’ËD©iÄÄ%Ü÷q’SÓèØo7ö¯*S÷ç¡öÒææ’›«ü“(‡JKMfûú¥8:W¡Y›.lYµˆiŸ$)!­VË?‹`ÉÏ_ãàè‚™™gO -%©PFv»uJ³}ý2fNL…жøÕ®Äì?V3mþ2´Z-Z޹KVóͼ¥Ô¨îŠ…™ƒÆÏ 4"Z2Ÿ2H§Ó±mýR¾ó6†FÆ´ìØ€™9aI	qúõvl\ÎŒñƒ‰‹¡ýó¯b``(ÁOvæäAŒMͨáåCc¿Žœ;yˆðÐ+úåq17ÈÉQÒ~·^ïóÖ°¯°³w*õýRÓ¡’LMÌhд5§ï!îféi)ìݶ¿v]õùVž¼ü«±³Ž.mš±hÕ~>¸„üügÃñÍT²³ÁÉÞ–1³°lÃö2Ó7¤¼äI)i܈‰ÅÙÞ»ŠÖèt:þ\÷/ý?›ŠV«£{Ç–lÛw„^ÃÇs1$Œf
½éßãy~[¹™“g/qéêu~X¼’nZÐɯ‰~¿OžÁÌÔ¯tôkÌ¡“ç¸^èôÃâøúç%¸8:`afÆ„ÙIJI+öÐ_º~;Uœï)­Üˆ‰Ó§é÷{uã«aoádo‡N§céúm¼=æ[ŒéÞ±%w0dÂÌBû^¾qƒÇÏ &.žWŸo¡Œyt«2Gíú¬þc6ËæOC«ÕÞñ3¦Pþ’DlÌ
lì±®¨Ü«×ýÉÔÏú£ÓjiÙ±;GömcÊðîw•&¥üñäzó¥çõNoý±QÉeÀÁü¶j3kwì'-=C(mÙ{­VyvDDÇòï¾#úmB#¢8yîr±}…FDqâl~3Ì£A8}1¿™÷“gˆR›Õ֫鎑aþ9»rßWmápàùâ_:gç°vÇ~–nØADt,‡N#2&¿O]Ff›vä·U›	º¢ÿ1¥Yêæ=‡Ø¼ç	É)e#-?ŒîÚòÇìäÈþmD…óÖ°¯03· º§ÓlÁH8+XÛðûã¹NNN»·®Â˧ÿ˜¥•5õ·dâ‡}ôûNˆ‹¾í:¥Iˆdøß3øc’S|8yö2GÏ‘’–NzF&k¶í¡•¯73>Š•…9~êÑçÉ’ù<`ÿm^¡/œV©^ƒF-:Ü}á'!–UKæÒ²“?ÃÆÎÂØÄ”MZ1nè«\:wßfJs‡äøXÞ:ží»>²<	~îMFz»¶üC
/+WA«ÕbljFЉ¸Õð ~ã–„‡^æÐ®õ´yÖŸš^
õÛ—ô~^SÊÒÒAnnÕ=ê`hl̵ËçpptáúÕK$ÆÇâY§驅αº§ûŒÇØXÉ:m¬+0þ‡ß	¾‰u~‚Ïõ¥I½:ÄÄ%Ð}à§ü±fk™¨*ùGÇ~#ô¿{¸:ñÃøquràf|"s—¬¢•¯7³Æ~€•…9í›5â¥AcXõïn>yïuÞyµ+›wdö¢¿©¨Þ¯¡oôÀÌÔD)À¦gðÏ–]øxÕ Je¥6ÅÌÔ˜'‚ðV›oDÇ%°jënZøx1û‹áX[YÒ²qýbÏ
/ÏêlY0ýžÓJËÆõ¹Îú]‡ð¶

½jè?§§–Ì;ScZ5iÀ«CÇqâÜ%Ú7ó 6>™ñCߦkûeªõBY’W&høL[ÒR’¹|ö$çž–BNvÖ-Ÿ1[tÔïgD¿üß\=øpü88¹’“UKæâíÛŠÆÎÂÜŠ·šÙÝUš”òGù7ÿïLúé^íÒŽcA˜¶à/6ý2k+>›ù+•ìhèUƒ…+7ñã’Õü5ªÎüðçjímhèU£Ðþ®GÝäã©s	øk¾˜Ž“½-[æK®VË›£¿áß…ÓÑêtôÿt
ç6-ÂÎÆšUÿîaÄ”¹¼ül–mر±Væú/{}ô%7nÆÒɯ	¿®Ø@ZzcÞƒ;¶$2&–W‡§ª³#5ݪ2ó·Œx«'o½üuj”Øä®\@ë–ÍÅÒÚßfíèÞë=š´ê¬Ï€ƒ/±çßUœ:²‡È°²2SÕ‚H.ÉI	¤¥$Ñ ik,,+PÙµ:líó¿U»ƒuJccïÌKn‰€–fxVwåBp(¹¹Zâ“’‰O¦ç±T)n®•±·­ ™Ï¶vÉúßûûòž ÄøX’ãcÙ¿}5û·¯.´,>6¿jÙÂÊšÚõ›Hðó¹ÀµËgéÿÁxŒŒMpr®Š{ͺìÚ²’NÝzë¿D¹·J6v•hؤ5§ŽîÅ·y{Μ{ÿ
|1€i£RÓ­Š~ßBÂ8{ùã?艱U¨[Ó•[vÑ»[',ÍÍHHJ&)%ÖMPÁRiöV½„gEÐÅ`V=„´ŸHl|2«·ïgõöý…–Å,ÁÚÊ‚&õkKðs›2A7%°4³°Äµº'¡ÁÐææÞñ3”>@ö•œ‰‰d˪El^µˆêuHJŒ#9>–Ž/ôÄÌÜ?H¸Ë4)å'ÛKƒÇbh¨Ô¾¾Ð¶9ßý ÐòÔô¾ùùO|=šŽ-¡ÓéèÔ$¿,_ÏÇïô¢M“ì9HC¯ì=H×¶ÍÙ{4ªÎì=XlMëÕ&":–ˆèXÒ22¨ì`Gtl²2Sùeúh~™>Z¿ÌÄÔ’ÐàÔªÛèž÷«t```@£æXüÓ×¼Ðã-ön_M¾``Xx}˜õtùx
½k²Lí”þçÚ­Œœús¡õ²³s5wÓêtJþñ€äõ¨í^•áßÌáÅN­©åVõÖj”tV°M{\b:FƒN§cßÑ@R3³=ýFOÿE¿ž¥©	‚CiT·V‰Ï ƒ"ÏŠ1±ô}Hi%/ïëÔÜ—¶Ï4,´¬ªs~³OccÃG–÷=©16ίi)ø?§ÏÈïPÕ½6s¾NëN/RÕ­V‘çƒæ®Ò¤”?ž|Nÿœº5Ý”gY	ÿ—®^''7—–êéËí›ùꛬµnÒ€å›vòæKÏ—”ÌØAo°xÍ¿tòkDTl|¡<)ÿ߈æ>Þì=HZF­›4àFL,'‚¸J›&
Šm—À›q´iš¿¬MSŽœVšÂ]¡¹o]}SZW§J¸¹VÖ¯{äôyíl6i¶úL‡ôÌL£b
Õx—û¨4	q1$ÄFâQ»/6v•ÈÎÎ"äÒýr«
±°²&èÄþ×k–ˆŽ#9>ö®Ö)Ienr«o‚*XameÁÁA¤¥g`iaNDÔMbã“%ó)‹ßÜÙ:PÁÖ3sž}ñ
ÌÌ•obµ¹¹¥¯…?÷'-%‰ÿ6­ÀÒÚžv]zè¿ÔHNŠçÀ8ut/5½}Ñh4ú~\¹9ÙE™’ß¿îµê‘’œÈºå¿3šu|ŠÝß3q	DÆ&з¶•ìlÈÊÎæÌ¥â¹JRJ×Âoà\ɞ؄$N»ŒmEk¬­,%ÿxÀrr•o0³²²•~4¶<DjzV愆Gu3žî[ah`Àù+ט±à/^ïÖ;›
Ì^ô­›øÐ¸^m’RÒX±é?ì­-éÑ¥>¨‰OJfÃØ{ô¾Þ5©¨>+œb@¯ÿQÁÒ‚°ÈèBÏŠ˜”Vò
Ù9ùµF¶6ØÛVÀÂÜŒ7^|V_S”›«•9ˆÊÀ3F«ö{ÎÎÊÂÆ®¶NDœØDjz£»N“ü<ù¬,Ì©x‹¼ßÀÀm‘ô”“›«ïÚªq}FN™Ë΃'hé[gx1lÒì>|Š
½KíSÔºI}ôvˆŒ‰eïÑ@·òúÿ:•x@¡Z¡\m~¾cbbDJjz¡mRÕ¾J:­Ž¾ÞthžÿEeÿ—»PÙÁŽŒ¬,	€¬+Úbcï̆ÐææLbÀÅÖÞ‰V^dÃòŸùþ«áxÖiÀñÿÚǬSRátÅmÎÍÑÞ–;µâçå9eŽÒ~{Ï!É|Ê(k{^éûó¦"#=ú[‘™™Fà‘½|4aö•œ%øy‚„\>Ëͨpüû¡×[#ôß~¦$'réÌ	vm^Éó/÷ê‚~„¦¿ÍƧIk}?²’ÞoجÝ¿’“+uê7eóÊù´êÜ'}ÿ!G”Pl+ZãloÂÈÍÕAl|b±}¥ff1jÊ\^y¾ǃ.Å´Q±03•üãøgË.Ž&*6ŽßVnÆÙÞߺµ°­X¡}{0bÊ\>úúê×ö`ÕÖݘ™óR§VddfñÃÿ0èµ±07gíö½Ì\¸œŸ¿ü˜³—CºÉ¾þŒx«—>
&&§pâÌ%VnÞE¿—ŸÇ©À³bøWßÓ Ž'ÿ8^ø9õ€ÒJÞ¥³ýMë&>Ô¨^…öÍ}ù ï+Œš6´ôZ5®OZf&{2gÂG8W²ªó’/ÄÆ®R~AðYÿB51êSÉÉU¿î®-ÿpúXq±Ql^ù6öÎÔªëK…ж|Ò·Ë=§É
EF”ü£ü©YÝSSNœ¡Cs_rrsùïà	}3´ÊvTuvä»E+ÖWéæQÕ…yË×Ó»kéÝZ7iÀ/Ë×ðÓ„Ä%&1cá
"¢oòÓ„ÅÖ··±¦ª³#»Ÿ¢×íÑétì9¨ïäç[Éó–rýFU*WbïÑÀB5—Ï4¨Ã± ‹Œz§wþ—ZŒ¨`iN\Bv6ÖeæÚ?Òaaœ\:fÕ=½8yxMü:3tÌLì+WÓG ¯öN¯wGLVF:#'ÎÅ«a³BQêíÖ)¨)GîìB0¬ß«|üvOÎ\!-=“Ï÷S–=¦ªhÉ|J§Ñhèü¿×ø|ÆRìœØºf1çN¦ug¬*T”àç	sü ò%†O“Ö…š~XZYÓªó‹ÜŒ
×××ómA¿¡¸qý*GöoÓ‘]Úûwô¿flL³6ÊðÙÍÚtÁPm
Süoõ]˜1f(^žÕÙsø$ýš0sÌPªU.\àôpubêèA\½~ƒëQ7ùvÔ@^}¡½äÈÜeë˜2o	»d`Ÿî¬˜ý%U]Ðh4ôz¡K¦ÅÄØˆ¿7í¤]3_Vþ8	¯nlÙsˆ¶íãÃþ=©Q½
®N|òÞl?x‚•[w±=à¨Zpð)”­­,y±s+£nræRï÷*£ßíEpXéYÌ8’f
½xZiá[	Cûqõú
¶í?‚©‰1†×þ×™¥3>ÇÉÁŽÅk¶røÔ9ü;·¦b«§>/Ù±a	ÿ,þNÿ“žzo#NÝÍ3fݲ¹,™7…“‡vѽÏ@¾œ½—ª´Ô¸ç4¹jÛžÇZ£'åGÃÜÌ”‰Ãßâí1S4a~½†`nj€W»ê×iÓ¤çƒCiÕ¤~¡¿[—Д-O½šîddeQÓ­
¦&Æ8W²ÇÈÐ7WgímJÜæ«áoóÉôy™øûÀ¼À—v
½jðö+ÏÓ¼ç ^ð	“~úƒÚîÕô5Pcõåf|"-z
æóï0lÒl:ôûPj²»whIaãyoÜ®†ß(åH@G±†©wnu@T™­s¿ÛÂiNN.Fª÷;ÍËŒc槃y½{ç§2óÑØõ¼¯è¯,§’Ýù¿ƒ?àäç_nÓÇÝÞß¼ùWœbó‚8ØV|lç^^ò¨€Õå²MWYJ+’È—cå5ÿx†¨€Õ¥.8qŸ:žúAYòdfesâÜ%šûxëß‹ää¹KØÙXÓÒ·ž~ôH€7ãˆæ™uˆKHâ|H-zß²¯Øé‹ÁXY˜ã^Eiq!$
j¹ç÷a8DÓ^ú@æJh§Î_Æ»†ö6ÖÄÄ%èGÀŠ!5-§JvÔíÚŸó¦àSÇPšÏ:y–°1ØU¬@3ïBó£ÝŒOäâÕë4¨í¡¯Yº¼ƒû‰]]¹í!y/…—QSçbnfBåJödçäð׆í8ÛÛз®|óòÔàGHáE!ù‡¸ÏtPJÙÑÔĸPðàQÕª%7ݯì`Ge‡üÉáíl¬KÝwAõkyú»¶{µâçØ¨ð€ežÕ\ð¬æ¢ÿ»’]~mѲ;ps­LFfS]F­êU
ÃÐÀ Øþ
r°­X¦¾ì1’ÌG-öj4tnÕ”-{òÇš-ØV´¦Çsíx©sk<ªºHæ#$ø‘û+…!„ä⩤AÃüÉÌÊ»†Ó?„Á“;Ì~¹€î5óÑh4¼Ð¶9/´m.™àGîï]300`öJþ!Ê|Zò|üCÜ­Þ];Ürà…'.–ÌG2!ÁüC!ù‡O‹rIæ#$}	É?„’Hþ!ÄmÓ¾d>’ù	~äþJþ!„üCÜu42ñ„’©£…B!„OEìc ×@!„Bñ´H!„B!B!„BH$„B!„	!„B!„@B!„B!B!„B„¤!éCÄcÎyPúÿñG¯*aT%ì¡ãAÍ.é£|ò÷s’ç‹xèÏ—‡Å­›õ8Ò‡]ÏûÞ‡4{‚ƒŸ*\—!„(sÁBQ–É%xòT箄˅BHð#„BHT¾¹q"äB!$øB!î4“àG< ¹¹¹Ì›7O.„àG!„HÜ/wB$ø)ã&OžÌ¸qã¸råŠ\!ÁB!¸ŸàÇ™H¹eX||<‹/&::šÁƒËü!„e”ô*ã<¦27äB”qƒ
âÒ¥Kû¬~Í›73~üx>ÀÇŒ±±1“'OÀ××—iӦѩS'ý6›6mbâĉ:t€‘#GbjjÊ7ß|€³fÍ¢C‡úm6lØÀ×_Íøè£°´´dÒ¤I4hЀï¿ÿžöíÛë·Y¿~=“'O&  €áÇcmmÍW_}@½zõ˜3gmÛ¶Õo³víZ¦M›Æ¾}û6l¶¶¶Lœ8€ºuëòÓO?ѦMý6«W¯fæÌ™ìÝ»€¡C‡R©R%Æ€——¿þú+­ZµÒo³jÕ*¾ÿþ{vïÞ
À!CprrbܸqÔ©S‡вeKý6+W®dΜ9ìܹSÿÌüì³Ïˆˆ(¿-[¾üv%Ú"ôùÈŸZM‡slçìí*<°c8r‘qß,gÛê/îyÛv2wÁVVÿ1ªÄåƒ?žOó&5y³wÛû:×ø„TSq¯îøXïe"§ûp]'ó=ž\Á‰[~'?ÿ‡=Ð}OTWÞçiÐétÌš5‹àà`–/_Î!ChÒ¤	ݺu+ןûA¥™ç¥|?ê<@’>D‰Ôy€ÚóÅÐÐèèh”ÿ+++ÜÝÝ9}ú´~zõêqíÚ5’““¨R¥
æ¶Û„††’””¤ßÆÀÀ€ÐÐPý6–º««+†††úm,--ñôô,´Mݺu	»å65jÔàÔ©S…¶¹~ý:‰‰‰úmŒŒŒ¸víZ©Ûx{{®ßÆÅÅcccý6Ô¬Yó¶Û˜˜˜põêUý6µjÕâäÉ“…¶‰ˆˆ !!A¿MåÊ•Ñjµóùòðÿ·˜ÈСïöíˆse[ý{cG¼Ì×3W1êƒîXZ˜>°Ù¹÷CFÍçìÁY÷¼—#Ø{àïöíXâòWûϤmKo†èr_çúÇò=ü½öë–~rïåe ûŠ]¤è	~DÙ	FŒÁùóçÙ¾};&L‹"žêàGˆ²ÀÞÞ{{ûBï5lذð³ÖÓ³Äínµ‡‡G±mìììÊÄ6îîîŶ±µµ}$Û]§èßnnnO]Ø¿3|
_«†õÜ024 =#‹­;NÑ©]}¬,Í8yú*Ù9¹4õUÒeй0ö:­Ýžk„…y~Ðt%$Ší»q¯îˆ‘¡a©çpàÈE\í¨VÅNǺÍGy¦q
œlÉÊÊaóöt¾	­-¨åéRðÛ¶ì8Éõˆ8:¶©Wl¿7âÙ´í8v6Vtn߀»ƒø_—Æ*½k®GIJcw:Ž.RÙц¤ätŽ
æFTk6ÁÔÔˆç;ù>–{#}€Ê \–àG!ÁB”#:ÿ¾ÓHLJÃÜÌ„Õ3hä¯\ºI'ÿ¯ôëNùn
]{MæÂ¥æ/ÞA£¶Ÿ”œ(5>
ÛŒbOÀ9¾½Ž/¾Y^ê1W­?Ä÷?oàTÐ5üûNgþâÿ8|O&,A£Ñpèè%FÿS¿]¿Ásx{èOœ9Æó¯~ù‹ùóO†\‹Æ·íhþYwˆõ[ñâëßâßw™YÙ¬ßrŒfư÷À9¶ï>o›Ñœ>JZz&WB¢ˆOLåàÑ‹;üØî…Ô•ÁàÇ‘h¹B	~„â	Ö¶Ûx}ˆ·gXøÃ BËçN—¦>å—EÛùiá¿|>òešúzr-š	Sÿæü¡ïp«V	€Îþ_1wÁV>ýð%Æ|µ”1#üùì#Þøñ	)%žC§v
ød‚ØìØsš—»=ÃŽ=§ùbTvì	¢S»úŶ9~*„¥+÷}q>v¶VŒõ
޵ÞÕ/ŸöÃ::¶©ÇÒ_‡ðÓÂÙ¹÷¹¹ZÞñ?~ûþÝžà³/—2qê߬\4’ž/µàﵘ2þõÇzo$*#4è¨Áe*#C!ÁB<á6üõ)>õª`bR¼ÈmiaÊß¿À§õ(ºtlȇƒ”Á“Žœ¸‚…¹	¦æ÷1ŠOáÌù0t:'O_eöԷ󃜶õ9~ªäÚ”VÍëpþR7c“Ù±;ˆ±#_¦×Û³HKÏdÇîÓŒú {±mNœÁ·;v¶VØÙZѨA~S¾!¼ýFþ@í[×Õÿ~-,†È¨Vm8ÄÚÍGˆãzD\™º7•‘à§&—pà¦\Œ'PÁAbcc™0aÂS1‚àG!Dé*X™cSѲPy¡¨½ÎSÑÚ‚à«Q¤¥gbanŠV«ÅÂÜ”÷ßz¶Ðºöj@¢ÑhÈÍÍ@"·”Á$ò‚¬fk°õ¿“œ<}•fkвYm6m;Á±SÁ´kU·Ø6Eö_ô¦¦Æ$§¤ëÿNNÉÐÿ®ÕêÐhàžm¨hm‘¿IÙ
9¤?â~ï¡FÃáÇ™3g7oÞdÖ¬Yy#Ø!ÁBˆ
ºÆØIËØ»éKêyUcèè…<Ó¨Ñ7•Qöš7©©ÿñt¯ŒF£¡‰¯'ÛvæXð÷’tlSŸ)ß­¡qCŒŒ騶>§þMƒºÕ±µ±,¶~_OÏ\#:F9‡˜›Iœ<}U¿¼mKoÖlµeŒÇ¿ÜIÙÄÀÀ \Ï´{ÿY7ôÐqg×¾3´lV‡¸„®„ÜÀï™Úúeaá±DE'ÐD;ôúM¿LVV^µ\ñ-Ð',<–}ÏãéîD-Og.ßП]TNN.ûžÇ§ž›¾ÆgOÀ9j×tÁ©REbã’¹v³Ð°Ý{Γ@ë^ÄŧPÑÚWge(öÜ\-ç.†cgkEÀá|0z!‘çÑoŸBÀáÄŧà^Ý‘Mkad”?\÷ùKáÄŧúüw\þxóIô˜‚ŸÚ\ÀŽûï&PÙ‘››Ëüùó8pàSñy%’àG !ä/=}22³™÷Û6Z6«Mй0fÌYO·ç3yÜk¦ü!¡>ÝÁ([

ŸšàGHð#„üE<¥å®GÄ2aêߘ›™0äÝ.¼×¯Óõ$zÄÁOÎcK¼\!„N„xH"""ä"”ñü%22R.üÊØØi_ö}¢?ƒ@ˆZjsA‚!„?B)­¶øÄŒY™d¤§ÊýBÈóE	€žÌàÇ›³üy8=bñqÑLøðöQ‡¿Ñöž1ý‹÷¸~õÒ=oÿõ¨7ˆ¹q½Øû;7¯`ÍÒ¹÷tÿã’øcí¿’„ò|B ÇüT Y.†‡Ó–›“]ê²´Ôdì*3õ—M…ÞÏÎÎ*¶î/«{¯¤õ²23nyÌ{ñÕ«qt®Vè3é´Z*’Tèþge—|ì\­­V—ø%¥ðÏÖ=’@„ò|â.H 	~„<œÊ¬¬ÌÏÄÕËgÐh [Ï÷hÖæyΜàïßg‘““ƒ©™9CÇÌbʧo1õ×M¤¥&3ÖX¯]ÆÄÔ”¬Ì†ŽùžªîµÒ«¿¬:ÆùÀÃ,ýu*èt¤¥¥àæéÍбßðã×ENV&Õ<½y÷£I·=ϽÛVsõòYú˺¿æ±}ýŸÌ^²—áW™?s,ŸÏXÂÄz2tÌ÷8¹TcëšÅü»ö4
®Žø¸ÙpòÜe&þø;¹Z-žÕ\9qö»þøNÇÌßþæpà9^hÛœ~þÏ1wé·„Òï“É´oæKÿ—»H¢BÈóE	€öÌÁ›³X‘"CÈÃé;~ð?²³2˜4g5I	q|úÞ4kó6Šw^lˆ¥Ö$;+­NKð…ÓÌ_{3sK>âODè\ªy2èÓé*YãÔÏÞ"òzÎUÜoyžu}[°fé\úKà‘=XW´'&*œSGöàݰ9ÙÙ€ŽÌŒtÖ/ÿ…™¿oÇÝ8Іô5šýÇ?|úÞë4óñâ÷U[8tê,GN_àjø
–7NÇ‹ƒÇâß¹5ƒ_{‰+¡,šú™$!„<_„H‚!§'ݱ€m4mõÖ6v88ºpõò¼¶À²B±mNÝC«N/`kïDu¯R‚–昙[àQ«W.âRÍ“½ÛVshÏf²23ˆŠ%èøþÛ@v•ÉÉÊ$>6ŠÔÔ$Ú=÷
G÷xt/þ¯)´îÙS©S¯	ž¦1T%œvÏ4$Wm
wâì%šÖ¯@ûæ¾ü²b=ëwp1$Œ~ŸLàf|"{RÇ£š$!„<_„èîœ<¼‹º
[`lbZê:çN¢Š[-*T´-üÔå–¤ÞrÿÁa‘äææRӭФ6!§»äè\ؘHýßq±ÑØWr!9)K«·q­^ƒáWÐjs‰¾Qòu62ÎÿŸ× ÓéHMIdíÒ¹Ìø};†Ì÷9%ô*I½Æ­øgñlêÔkBƒ¦mXöëTB.žÆ³vƒBë98º’¢¿ÿ‘1q8ÚÛàä`GL|Nö¶\½ž?I`u'œ+Ù1øµ—
íëjø
´:­$!ž¢òÇ>_¤ü!DéžúAöü»Šô´[1G÷o#>6ꮃ€³i÷AIiB‚Ÿ{ЬílYõ;Çì`ÅÂ88:ß¶ Ьuþ]³˜më–ðÓÔ166As‡v›’ž–ÊùÓGؾ~)çÜñ¹ú4mÃÖ5‹hд
ÎUܹr!êž^h
g³~îæ$ÆF²hõVÖlßÇ®Ã'õËz<×–¾ù‘å›v2åFýyûwnÍÒõÛùgë.„„²|ÓN¢bã©æìÄÍøD¶î=Ì•ÐI0B”óòÇÝ<_¤ü!ÄSÅD…|¾Ð{ÁÉHOãÔáÝ$'ÆóbŸAXZå7£	¹DÈ¥ b£‰»©|Û±[«zá8žÇ8~ô‰)ùWvN.‡ϳûð)²³s$e		~€jîµù|úΟ>Bå*î|>})NÎÕhѾ›~=s+ž{éM\«×ä«9«ÑææðÜKý¨PÑ—ªJ¶_@¥ÊUiÙáúí}›µ§º§&¦f|4a.ÿ­'-5™áã~¤¦w#Úuy»J¥žk½F-éñæp¼e³  IDAT}šðÚ€OèÖë=ýòNÿ{ZR¨JæM&!9…à°fþ-}ë0àÕ®ŒÒôŒL^ëÖ/Oìm¬ÙðË’RS™·|)iéX[Z`` aÁ×£¹~ƒà0	€„ÈñxÿFù#øB ™éÝKjJ¢~»ÜœlÎ&âðJ*g‡<1÷(22Rªxl4€N}½W:N÷pNN£au@Ô=mûã×’šš„¥UE¢#®1þ»åÓ·Km¼<ƒ[º´¾'Ó¿x±ÓÿT†Ñó6FFÆ›˜|¿öÝx¹ï0¦|Úÿ7†R¯ž}_¨‹_C/Ü\+³-à?~1Ÿ:ž,ݰƒ«×#135aëÞ#Ì3„º5Üøõ拉¥3¼ß+å9ùùó0¯ÿ}¦
ݽÞC	~Ê6'?ÿ2™>²³³Ø¿c-®Õjðß:nFG0òËyOÄýºB\BZŽÙ‹WÑÿå.tïà'éC”;þ~N$}ÜÏóï~ʺüQ»^ú½à…wÃTvuãXÀ6†ñ#žu|رa)©×q4Íx¤å‡Y6y@å!JÍ;Êe øØ(BCÎóíü-|7qA'àÓ´
ÙY™ô~gEÚåGE„’œϤ9«˜üI¿BËóš½ådg3êÞxyV§¦[6î>ˆOO^ëÖQù†&,33ÖîØOÝn’ÄD¹~Ê2CCCÒR’Ø¿c->MÛàóLÛºÿ©cÞF››[è½·†M¤²«Û}ß›
Vü»ï9¹¹L9€ZîUå†
ñ”—?òädgÓûQT÷ô¢Š[MîÞˆgúwkMUܤü!Ä](—ÐÙ“ñ¬ã£ÿ»VÝFœ=©d@¦fæ¸×ª_l›+çOâV£®þo·šù¿kÐáA0¸afj‚—guª9;²ëÒ~ýΖ¬ÛŽg5Ò3³ˆŒ¾)©KHðóòÂ+ï<´ýò͇vÿ«T®Äˆ·zÊMâ>¹ººþÄ—?
215£º§2ª¥£s5NÚEUÂ8¹s9£žÀò‡‹‹Ëcoª(žâ²ByüPµë5%äbþïËçOQ§~S}áH­Z-Ä˧—ÎæÏéŒò»	YX“„)™·<æœ%k˜ñé ¾þè]¼<ªñZ¥		~„Ü!Ämܸqã‰/ÜŽiT%ì‰-<®{$”Ó '«2cÜ{T°¶#"ô
õ·¾å6¶öNÔôöe‡=124&##
s#êrCro{ÌÆukñõÏRÅ©Gƒ.bb,S,IáW
¿BjJ"û¶­á9ÿ~·]72,˜ èÜýu´Ú\}¡`ßö5¸TóÄ£„o`ïDvvÆÆ&¥þ]Ôê¹£˜6ØŸ¨ØxÖíØÏ€žÝîøXyóHâ),›˜ÝrG¢± MÊB”Õ(##ƒÈÈHÜÝO$‚³³3fffå¸#&þÌõ«—HINཧèßÿdòo…Öüé¬+ÚðîG_‰‘‘1ó¾‰#GÌI糯áâèÀo“?Ño[¯–¿Ý€¯†¿ÃÁ“ÿgï¾ãkºÿ8Ž¿nnöÞ‘D!V{ÄV­R«KÑV‡¢ƒÖ¯­ÖlQEQJUÍji­ZµWì;vB"!‘=îýýqn®Œ;D|ž‡G¸÷œsOÎùúžïû~¿ç{Nž™Eß7:«tA¿Ø¬ÍÓ{NÇÓ:þ[fF:'î¡FH³».·oÇ„4nûPŸqüÐ.ÊûaimûPë‡Ø•Z˜[Xæ?—®^#=#SZùV/þ
ï
÷µ¬‘ÚsK%€,øsKÚwíÀÉ£{Q?têÛ­.ÓÿÞ‡ÚØ€÷:3{Õñ"ï§¥À“n³.lÿ ß¯"+;‡~ov’PŒìÜ@pýæúlȱƒaT¬…¥õo?òâLLLïù Ý¢\:wK+[ý,`¹R““¸qœ àrK GmLÿásý¹}ýý/qvõÈ·¾QTñ·&¨„¶?ŒÍdŒ(ùŠõëÅ™3gR½zuæÍ›Wè½yóæQ½zufΜYlŸ_¶\E}×s®‚ÿö«Tc¥Aóç̱„m\ΜŸ¾Â$-ŽàŠJ¥àë•Ò¨Ê}J;€µ•þcFF*Ѽ^Mìm¬©¬»ÑÝÅ	O7n%§Ÿ˜ïOJZ:¥ùø?Ö —–ÊöõKï¹Üúåóú3þ™÷×c~ÌxØÆå¤¦$êùÙ¾ÿ(+·ìz¨m^½Îã¦?·TVf;6.£~h{Ö.VwA_·l.Ñ‘çõÔèÈó¨ÕƘ™[’šœÄ‰Ã»9º;KçO&6:€Œô4þýkkþ™EjÊí"Îã
æLƆpûV‚>§¦$³ìi¬[>C»7‘ž–ÊÒù“Ù²v1YY™løw'²lÚçXß:‰µeþ²¶Ÿ~û“¨˜8ýk³—®E£Q×o&òïæ]¤¦¥³ã@8»gòü¥\¼¢LŸÀÏ–±q÷Aý:©iéüýßV¾™ô;“æ-!µ˜ë”ÒfÓª…ääܽ‡×–•$'%>Ôöwo^ÉÑ}ÛzÿNÝË•K…^¿Íßs&<ôvG
|CN~1{ØöǬŸ†ž–ŠoEej|oß,­lôëç^_JjûÃØL‹£¯<
D”|ÅÒ´wï^ÈáÇIMMeË–-…¦Cܺu+|òÉ'Ì;—ñãÇS·nݧz0Ú¾Ò‹È3‡èÝØ‹Jeë¶—oãøÙüóó·nX›V
kËñÎ:‚«»7¶öÊqOM¹Í­„˜™[pñìqjÔiJGÝ3\4šNÙƒ‡WyÒR’qu÷ÂÔÌœ×ßSzèÒR“IŒÃÜÒŠ+—ÏQ¥F=ý·¼É·9{âvÎ…fã)JîƒèœÜHJ¼É­„xùú£Õh¸|á4å*TæÅîïh—‚ʲ§/D’ià™P‰·“	?}žà*þ\ŽŽÍ7[Ϲȫ˜ããáÀÉó‘<ÁÁ¸8Øá­{ýyyá4N®¨ÕJUupçFªÔÂÕݛӿç….oóZŸÁ,ž=žþ_O&6ú;6,¡f½PÜ<}055#(¸!6¶ö¬\ô+_ïKLԦȠ‘¿úÌ䤵êÈ•Kgþiw~ü}½~[UjÔÃÜÒ
K+ŒML
nˆ¹…™iü>é[ÚµjÍ[­ƒ111f✿y§Ë„Ÿ>ÏŽƒÇhD/¾gÊ7¨ê_žÉó—òf‡Ö©‰¹~“E«7Ó®I]Ê•u'''‡†ÁA8ØÙvðfÿMÿ8q‘7ídòÌX´’ŒÌ,ºµkFdt™ÙÙXÊu
€kW/¡ÉÉÁÃ[y–’V«åâÙ㸗õåtø>*U¡ÛÛ1ÖõèåÖC¦æXYÙ`lbŠƒ3/t~Gÿü§gÂq÷*OĉCøW	Ö÷
eeeqâ †ÊÕëêËëÝ¿ÔIáæõkxxû‘••ÉňãøWQž9uùü)¼|ý©Ý¨
ffwêŽø8®\>‡ežgÇ€2;؉#»)W¡2	ñqxùúë÷!)1ž¸kQT¨T€¸˜(Μ8@ĉƒ˜šYP®Be),%@ÛWzqáL8!ÛÙcø°ÃªŸTûCÂxîгªŒ“=-¸Þsƒ‡Ñ³S9ÀÜJ¸ÁèÁ=©X“¨‹§©Tµ¯õÌù3áÌ7˜2žå¨Tµ¾ƒ˜4²?ãgoàzìUÆü¯Uƒ²üRW.EðõàíÀȯ3{•ò0¹i£âéS—2eùý§!L˜³	•‘ýö#ŽÎ®$ÄÇñëø/=c•ÁSó::œ};ÖÒ÷X¹xW.ä÷Ã9βù?óùw¿1kL?êín5™¤äTl­-9}!R¡Y¶a3­¤q­ªÌøk%¯Æ°ë¯©ddfñÎ×?àéæLNކœœ&~Õˆ‹Q$¥¤²óÐqªúû>w(êRD¾é¥«ÕnLø”ñ,Ç]ÞæäÑ=df¤“xó:®î^\¿¦4LLLqvõÀÜÂÿ*µôë7nÙ‘Æ­:¡ÑäðÞ+µÑjµ…Î}³v]9ut/ÉI‰¤§¦så"îe}161Á¿J°~œ±±‰~Û)É·ÈÉÎdÂÇݰ´(<¬T¥R1¼oŒŒTÄÆ'ðߎ}Tõ/oðw61VãUÆ…¬ìjUñàŸÿ¶ñr‹†ØZ[Ñ08ˆ¿Vo"=#“äÔ4’SÓ°07£}³zR¡èüðÕ;¨1R«I¹Ä“ÍÐþ]ª_`u¼ýó¿^LY†‰©Ã?펣s²23¸zùm:õ¤MÇ·˜ñã`>üâ<¼üø¦ß+7ÄÃÛ™ã¿ä«æááíÇúåó¸•p#µš9“‡òÅ÷³póð¹ë>fge1æË^LþcÇìà»Ï{0yávÊxø0ö«·™¶h7kþ™…wùJ„¶ëÆšf±cÃ2üƒjqþÔQÔÆÆú°4ydj„4cÅŸ¿p1â“lÃÖÞ‰ß&áö­xœ]=™7u$_ý0Ÿ˜+ÉÎÊâø¡ØÚ;K*!œÜ¨Õ U‘ï?Ê=¥O¢ý!áGHêÖ­KXX3gÎäÇ$44”¡C‡æ[føðáÄÄÄ0hÐ úôéóÔ„9éTáD±„Ÿ'íY:þûv¬#¸^sº½=­FÃ]CèÚë3ÒRn3äÇú§bìÛ¾†Æ­:Ññõ¾deeòf늷™™Î—cçʳ["N" ¨6ï~:Ь¬L¢#ÏÅÙ“‡ò5’
©R³>¿Oþ€SG÷Q­V#.Ÿ?IøþíT«Ó/¢0#€	·8s!Šu¿à¯~ÐogÞòuÌ17þÛ±S•ýÛ´û A}ùß{¯Ðã‹ï¹{¶Cذóz¼ò\VPfædfܪQ­Næþ<œØ˜HZ¾ø:§íçàîT®~7O¥Qjd¤­­VƒJuçþ¬Ì¿ÛŽv߯Ë7+;®_»rÏ{8<¹Š»«ÁðàéæŒ‘‘´¼Ü]Ù}䤌P‘;eSVvчS.ã`gíä^m߂Դt÷yÅk·2xÜ²²³™?îk쬭žë‹Ú•KgIKMfèO‹5èMΟ>Š_e’o'ÒgàhÜ<òßw1âff2€!};ÜvZj2}>³›Ž®ì[GÇ×ûÒ¾ë»hµZ¢£”!™»·®Î×cmˆµ­=¦fܼqð;m×ð;Hò‹Ç¿rp¡å×-›Ë˜™k°°´få¢_9°s=a›–ób÷÷mפÄxzµW¦PNŒ#êâi†Oþ€¥ó'³?l[uÂÜÂ’WzÐ3¢¤O¨#áG<‹Šõ >}úpôèQÞzë­Bï½õÖ[=zô©5¾G|ú*éi…ÂÏœ¥ÿ±týö‡Úæ­ä”BÝÌOSI>þ¹NÙ_ òÌ•‘žÞ¸tîPx¼t®ó§Ãõã£MLL)[Îßàrkêÿîêd愯˜4¢Û×-!9)‘ógÂ﹟–ÖØÙ;q!âÆ&&ÔiÔ–ðý;?°ƒ6µ+ä»8:AÕ€;ßîW¼sÿ…¨ýð¶ ŠwÕÛöeÛþ£ô<šžƒG£Õh¸óÜWPÞå+såÎÿ)¿@¢£ÎsîäÊûW%(¸!ý6ŽjµϲdçàBJòíú¼ÓÇàíW‰¶¯ô¢RÕ:ùî¿(¸=+²³²”	¸ÊÝ:O]¸Ll¼r?Ñ–½Gh^O)›Aþ¾œ8wI‚s9;Ø“”’ªÿw‡æ
qwqd@WÐã>|íeímQ©Œx³C+–LŸ·'{ŸxîË̉#»©˜ÿ9,'ŽìÀÞɵPø8&_ÿ ý¿‹z‹£Înyê”Ëʹ[ý'£½Áºes‰¹r‘÷Q§T­Õˆð;8sâ ]{Jø¥N)Xž“o'êë!€
yž3“·>´µwÂѹ LÊrýÚFîÉèÁ=9}l?I‰ò|º’ oû#¯ÿ–Î)t¯ëý†Ÿ§Õþð#$ÁÜܼÐd¾¾¾Ou².½>ÁÄÔsÒ	⸾ççæ­$o§<Ô6OŸdâì¿KÔ	.©Ç?W¥ªu¸xF™IK«Ñu^ÿ ·Ü!V¯§NBzZ*‘NÞ¸iâÍëœ:º—A#¥Gß!˜˜šr¿M¨V»1KP͆T«ÝˆC{6‘{™eó/W=°B¾Ññˆú¿W©X޳—®pðÄÆuƒšAÔ­Èܱ_2wì—,øqkWÃÒÂŒŒ¬¬ç¶‚òðò#;3SßT©TøW	ÆÕÝ•‘Õë4!êâƒÓÌÖiÆÉ#»ù¦_'Î>r_ŸW¹Z7âbñÙk|Ó¯>y†µêð&?|ý†~@ëo2ú“L÷õ=·àëMïÿ᥾$.>6Bèñrk>þn
¯ôû–ËWcõË7®]Ã'ÏÒ©ß7ì?všWÛ7çÔùËt0”žƒGÓí“aô6W?Aï/Çw#Ðz5Ÿû‹Z¥ªu¸ç9,çOÑ™b\TR­.yž½qâ á*Ã)wÙ‚©>·?‰{YßB÷\¥z&„mXŽ•µn>$܈åȾ­T¯Ó$ßrÖ6öh´ÒÓ”P|!âXž2{ç2ñ×cˆ¿®|qR¥f}ìœùrì\¾;—¯~˜¯ŸÑÔÌœœì,ÄÓm”të&)·8ü<­ö‡„ñ,+•÷…m\©™!Û²õ¿¿‰ºp†}‡så"a—Óµ×§l^½ˆª•2;Ï‘ðp¦þ±3S\í±·Un0Ž‹gÚÂ圾I—6M9}!’aý{°yÏaþZ½	{[kÞéò¾Þüòç
Ÿülÿ“À“’±·½3r«µhRƒôŒýL“®Nöü=iX¾íMüª©ié¤edâd¯LÕ>sÔçÄ'&¡Vaoc-
J/¡JeÄäQ02R“ž–JÅÊ5ɹËòå*bïè¨o“…Je„é=꡼*TªÎœŸ‡cemË™qrq¿¯õ«ÕåûÏ{ðæJˆ.P£û·á¤›ö8¯–/½ÁÈÏ^£rº?´S3嬯­;1idN…ï#>.Gç2›˜bkï„_ ã¾~—Ê5êw-ŠÊÕëQ·I;ªÕnÌÏߊP-Ú½Ò[
ÍShT¨TS35§Ã÷±ì©˜˜šaï誟°Å,î 3N+±í	?â™o_ZÝχ¥ÕÓc‡U*ËvÅ>ðz'ïâ¿es8bÃ?éNB|,ãçldýòydffðòkòQ·ºlšó#fª,ZôüŒùã¾&''‡îŸçãž]x»s;zM‡æ
iݨ6_Œ›Á•kq¬œ>š}á§™<	“¿îOâí>6u³Æ±÷è)~]´’YßñÄN [ƒNçñIJ ½×9Ôjµ;†»Wy\Ü<å¦ò„q”-§Üß“•Ååó'ñË3ôãê峨;¹ò¿÷Ú3ea˜ò-رýTªZ‡Ôä$â¯_ÃËWwíê%e›ƒ3éi)Ù»Ww/lìQãè\†KçNRƳœþ92ådgq*|?•kÔÅÇ(šôK»±²´ÀÃUyŽÃ±ˆø—óÂÌT	5ŽŸ!;'Gb¼Ü]ÈÊÊæÒÕk\ˆŠfÇpF}ú®þ3".Fq,â"U*–Ó?7H£ÑrâÜE,ÍÍñóö(1•‡[ƒNO¤|h49D?H¥j!%æw—‡ÜÞ•sÖþ]žHù(Ê¥s'ÈÉÎÖ×Z­¶Ðý~'Q1°*Ýg¯Ç^ÅŠ¾z›þC&ãR¦,ÏÇÃË3sÎ?@@ò%DR¢òm½»Wy4šŽ•ŠòþU¹•pƒ²å*rýÚ}ýR”³'ãî勵=7o\#ùö-¼}ˆ¾Œ™¹¥~ºØèËD^8C`µnÄ]¥\…;Cõ¢.F`ecËèÁ=7kþõÄø8ÂìÀÕÝÿ ZúÙ0c¢.’œD…ÀO¥ŒtjàöXÊGQ×?µZ}Ï)ζñ¨í¾Ýê1~ÎFT*ŸõlÁ×ãæ“““ÃðOºÓ¥çǼ׹C÷-±íû	?*Çn÷l›ùœ¢ÇÔþ¢Èº£TöÕaò¨df¤“™‘NµÚ9{âG÷o§û;ƒ°$S20%“°ÇhRŠ>JûiHu}#õÔùËÌó?T*o¼Ô’±3°bSvÖV,X¹Qi¬§¥~æ‚§GZǼ[YÛaemw§Â51Ñ7d2ÒÓ˜9á+<½ý8¼g3ÍÚvÕ/—;ÔÅÒÚ6ßMóÎ$fnaE½fííGîlH‰7¯gcïˆÚØ„ àw¿åò}+8«Wí €BŸ±yÏaö†ŸD«Ñ²mÿQ¾ÿ,ÿ=Xþ¾^øûz¸H¨Šœ1ìy`d¤–ðóŒ…+gÍSß¼á ·ž)8ÙIî´Ós§ŽÀÞÑ…ˆã±¶uÀ¥Œòÿ;÷þåÚr§ÖÖÞQ?m¿‘‘šê!MïÔvúm$'%’•™ï³-¬l03· bå;½ˆŽÎeò…¥‚3ɹyøè_³ÖõÄÇE³dÞdœ\Ý9¸{­:¼™o{'Wš´é\èø¸{•î:%::ºD¶?ò
?Fõ¦xú(_ôUiŠ=	”ɺXbÛ³ç'&FîsOO©@Æ&&xxùñß²¹TªBåê!Þ·•ȧ©\Á—*œÐ­£ÁX­Î÷­€‘ÚH÷õ’r+Izæ‹WFfå½i¬\áã鯙Ò(zÌÌ-èðêûÄÅDѺc|AéqXüûx4šüßvîù	.nžÜømT+K3rrrüÞ먌ä„>C$ü<áça´ïò.—ΠAèKúàò¸l^ý—~†¸\
[v¤jpÃGÞ¶£‹;-;¼AB|,í»ö)²ûyãæötp·öGÁP®ÉÉÎ÷Ì([u:$–ØöÇãöö´Î‘¥6rÃú’y“8bþU‚™6fAÕ‚	RÄ„;76©Sï¦Ïçrt,99¶ì9LUÿò¨Œ®âϯ‹VòBÓº,X±Aÿ¼.m›2yÞ^kßG{ÎGFcafŠoÙ2\¹v´ô,Ìͤt#ïò•ð._©X¶ýÞ 1ÅÖø551¡^uyîFI‘ž†V«ÅÜÂ’”ä[˜[Xz€eVV&ÙY™XXZ?ÒùÏÌÊ"#3+ß}>Å%áÖmìl¬Ñ¢%5-ý‰|æ³~@¹g,w†·Ç­ÃkÛ~«tÃî ªü§.ÁíJÕB
=s¬Z&ÌŸþ±Ñ—q͹¾=aÔóï\"ÛrÏ(mJí×ϵ´ÄÇ/Àj!˜[XQ¿QsÞlY]~‚«TÄX­ÆÆÊ’1ßcà˜i2›wº¾€»‹2¬aüÿú¢Ñjø~Æ4¬UowåÛŠºÕy³C+>ÿa:/ô̸Y‘“£ÁÅÑž.m›ÒgÈ8꺧Eé ßü—>ë–Íå¿¥ÊD‡}Ä¥³…§>°s=óùî‘ÏÿÆ]õË|åï»r,¢ø†¬tîÿ-ñ‰·Ðähxí³‘dßã>	?¢4òôô,1í:ZÓ¨åËú÷+V	F­6ÆÒʆ÷Žá÷1ý˜>et‰mWøñðð‚*žšR9	B¡‹3)TáÆ<Øàè¸x\ìHIK竉3iÓ(„—[4,Q'°4L‚ áçÙô¤&AÈ••…±nÖ¼¼r²³Pç=++“œì,Ì-î<T«Ñšz[?d2w˜£‘‘šQƒÞäµw?Çǯ2jccý·­»·®âÒÁuLøÚ}ÿB–ôŒŒ|E]³mÛöeì ÷þó\ªT,G—6Móïsv&ÆjƒÛLNMÃÚÒâ®ËefeajbBó·>eÑOCqq´gÂìÅ”-ãB·v¡O<ü¨»Iý!ŠTZ'Ax\×—’ÒþxØð#“ ˆNk\ÚÇ
?ç._á“ï§`¬Vób³ú´k"Eæ9#áçéÚ¾n	;7ÿ˸hŒTFÔnØJ#ñÖÿþfã¿ Õj	¨Z›|ÊȈÉ#û“”x&‡Š•kòZŸÁ,[ð3G÷oÃÔ̵±	ƒ¿ÿµK~''GC‡Wß`õß¿q5R¹Oãµ>_P#¤ÎÄsƒÛ¤gdÒ²÷@6Ï€©‰	IÉ©´ygÛÿ˜’/ŒlØy€©,ÇÒÂ*†öë™oR‹KW¯±vû^v:ÆÊͻֿ¦&Æüøûbbââ155áÓž]¨Y¹"?Íù‡³—¯p>*šæŽ×j IDAT†ÁA|ðj~øí/¢bâ06VÓ÷õŽ4¨Y…›‰I™4‹KW®akmEjz†þó^lVŸ/'Ì,¶$=?BÏõ¥$´?dØ›(ÍJu²&™Êœ|¨ðФNušÔ©^rÃsŽ”`	?¥ZŽ&‡‹góóŸ;161á›^¡Q«Ž¸¹ûð÷œ‰ŒŸ½33sÆ|Ù‹SÇöáâV–¸˜(FM[žo;þ]À”…;òõådçä›ìÂÂʆ±3×p=ö*£¿x‹—Bü¸É
ÌÍLi\«*«·î¡S«Æ,]¿vMêê‰	­WSÿ¬¦mûŽòǪïç9+å<ËЮIÝ|=@æ̡uÃÚ´kR—«±×~”÷ÒÏxU»akîÜH¾Cppvc`ïVÔoö"­_~[{'Z¼øŸ¼Jp½æ´éØo¿BŸS«~\Ü<1Öf`rãh¾÷{vjË—fÒ©UcþX¹_G*´«±7˜8ûo®Ý¸‰‘‘ЏøÄ{þ~«¶îæ|T4­ÞÀ¡“gIÏPf}jQ/X?SàÊ-»qwqdͶ=œ¹ÉÍÄ$¶í;§½”©àƒ*úâæä ß¶™©	ihµÚB7`Ký#„\_$ü	@¥ˆ
·©ÌI	?BÂO)=&ßßk„4ÃÕÝ‹øë×ô¯ßˆ½Š«»2Ôlàˆ$ÆÇ±iõŸLÙŸoÆ/¤ó[óB—w8¸k#>{•©‹vøe{eµ‘¤§ÜÂÑÎ&ßû•Ê{“““Ã’uÛ±³±Æ·¬{¡mLš·„—š7 Eý`v>Áÿ~üµÐ2êãÞ}<Ü1 ·ÁíåÁÍÇÃÏzw£Z@þç·xº9s=žHÏÈ$!)YÿÞ­älm$ü!×	?B”æ$áGHø)].Fcݲ¹XÙØsdïVzõ†¹…%Y™¬Z<g7O¶­[Â÷ÓÿåÊ¥³\:w¿@ì\±´²!#=-kX½.vŽÎXÙØ›V-¤rS6ØDpL
L¸Ð³c[>3•)ß|lp_-ÍÍØì4–fÌøë_ƒËT«äÇ¿›vâáêLÀ
¼Ú¾9C&΢ï/ceiÁ‰ˆ‹¼Ñ¡U¡õ^kßœá?Ïa@Î8ØÙpøäYzvjC›F!Œ¹kKVo݃Z}grψ‹QTõ÷•úG¹¾Hø¢´ [’䔄!á§	m×ÌÌn\8ɘ_W뇽}7mÛ×/%òü)FÏX‰ƒ“*•Š+—#8²w~ÕéóÙhŒIOOeå¢_)ãYŽA#f`d¤& jmý,EMZ¿BuOömYŽ¿oY¾|ÿ
Êyanfªß—Ƶ«aceI»&u
îëÀÞÝøcåFÖnßÇ wºsàø*–óÂD¨Ú5ÁÂÌ”}᧨àãI׶ͨX‘Åk6“šžA³4Vš›«MãÊ{³hÍ“nÓ°–òÜ—¦!Õq´·aÑêÍ´¨L ŸVºè®ÚDŸn/Iý#„\_$ü‘G©™ûy
?÷3Õ䣞“il%ü<¸'1
ö–µ‹‰ºx†·ú~óÔÏÿ¥«×˜¶p9}ÊÒ§Û‹%þü¤¥gðÕÄߘøåGO%üÈ4Øânž§i°ŸÇð#Ó`‹®tLƒmÇ-*qZz~„„ŸRÆÓÛKëqþ7ï9LEŸ²ôîÜî™8væfO-ü!žßð#ijà™@vÜ"SQ:/ÐÒøðó<ó¯R«Äœÿ·Ÿ‘à#õr}‘ð#D)@~ž
º¡BHãDê!¤~‘ð#„ ‡eO"•8-áçP\÷(‰§ëqN­,©„xœ¢££ŸÚgKø¹?111RP… á@œ‘ð#„ð#õ…¸¹=‘~Jþ9ÀHÂ4>„ð#¤þB€J Gnʰ7!„„©„¸+OOO©_Jxøñðð‚*$ÝOø	à*Jçý$Òøxöåää0cÆ9~¤þâ)»víšÔ/%8ü<és$Ä3€œˆ—ð#J¼Ñ£Góí·ßrþüy9~¤þBê	?B”ÔÿÏBøñ'BÂ(ј7oqqqôíÛ—uëÖÉA‘ƉÔ?B”pò˜!$•H~ijàÃ?äìÙ³ìÙ³‡Õ«WÓ¾}{90~¤þ¢„’G4!¨Ä’ð#ž…‹hHHÎÎÎ,Z´ˆ>úH.¬~¤þB!$	i|”N*•ŠÏ>ûŒÓ§O³qãF†
&EÂÔ?B!D	e$‡@BHø‘úG!„$¤ñ!„„©„B	@BBHø‘úG!„x6É=@ÒøH«Õ2qâD.\¸@||<Æ
£víÚ¼øâ‹rp$üHý#„B”0Ò$ñˆT*ûöícêԩܸqƒ‰'¢R©äÀHø‘úG!„(¤Hâ1øå—_8tègÏž¥^½zò 	?RÿñEGG³lÙ2ÆŽ@pp0cÆŒ¡uëÖúeÖ®]ËСCÙ·oƒ
ÂÄĄѣGP³fMÆGË–-õë¬Y³†ádzwï^ˆ™™ßÿ=Õ«Wgâĉ4oÞ\¿ÎªU«øî»ïؽ{7Ÿ~ú)VVVŒ5
€jÕª1iÒ$BCCõë¬\¹’Ñ£G³k×.>þøclmm9r$AAAL:•¦M›ê×Y±bãÆ#,,€àààÀðáèR¥
¿üòMš4ѯ³lÙ2&L˜ÀŽ;èׯ...:€ÀÀ@fΜI£Fôë,]º”I“&±mÛ6>úè#ÜÜÜøöÛo¨T©³fÍ¢aÆúuþùç¦NÊ–-[åÙy111RPÅS£´ºŸK[\Ͼ=yJHÂOÉ?€DI@⹿¾QdÝ!÷•ÒÆ‡xòÔjõs~ž	?Rÿ!„J4>„ð#õB!HHãC	?Rÿ!„€„4>„ð#õBñŒz,“ Èa,õe)Bʇò!¤|ˆg¨|q׺C*!„B!Äs€dœB!„â¹!H!„B!H!„B!$	!„B!„ !„B!„$„B!„€„B!„BB!„BHB!„B	@B!„B!H!„B!$	!„B!$	!„B!„ !„B!„$„B!„€„B!„BB!„BHB!„B	@B!„B!H!„B!$	!„B!„ !„B!„ !„B!„$
2jåŸãcà¥;°N% ª!„Bñ´i‹! ¼	ü¬‹€ïS
yæºýz…ß_­;ÿjù¯.„B!Ùçq @`¿n›ZàpÈÔý;øå	ÿ’ÍtŸýÓs€¢tûìiབྷóœ¯EÚÛ@`ý ÷tûÚWþ¿!„BHzœCà‚€CºFñ]cÛ¨‚ÒQØ´{ÆÚ5ÀxãÚç­ºŸM
¼×Èb‹x?X|ɺ×>Óƒ#òH!„BÀïa”{ˆóæ¥;oÊ\wü©„B!J¯Ç1®£n;°Ñ›ÛXþL˜r‡b% •³2°|°pB†–ª['˜ D¹F£ôZ侟˜çO‹<
è¿tŸ©ÍóçÐÁÀç—Ñ­¿Ø@Lªß1y¶µ›;÷ÏTؤåYþ,жˆ™û#€«ºå÷Ýãûé–‹(ðº=JïÏxà]Ý2Á–Y£{½Mž×&ëö£†@ýpK·N°QwÌŠWØ€2ÄN\G®XQ·¼¡!“ŽÀy>G«dXnqžò‘Zàüç†Æ²À?ºã÷üG¢B!„¥(û<®‰è~ÎF¹ßçAÌC™4á$ÊÄ	É@à”‰Zºð’·Ñî‰2œîº.lTD’ö)p¥[v£.(õA¹7iUží\Ðýôš¢Ü¯”€r/ÓûÀ
]#øß|;
÷Yê^­(¿é@o”á+Q†	jò¬ÓX§SQzbÜPîWY£ûþ4ðcQ†..£ô^ÜÍyàŠî8¹ëÂÜéñÙ¦( ƒ;¤û»Z·ÙÀNûQ°üÌЩãÀ, xUw®õlÙê›—®lFé}yeÆ8;!Ø¥7јì@é=z˜¦6ÿÓ-û·n;¡ôpíɳÜ0¶Lw,—ê~ÇkºÏhÍó=ÓŸB!DéMAa+tÛyý×k¤[ïx²J¯Œ¡ós'T˜Vàõ
Üé=Ê«wŸÁ’ü½F¹¼uÛ:Yàõ¢&Aø]÷úe”™Õr©Pz€´@û<¯«p”ž‰*‚A”.´XøŒX”ž‹1_·îky^Òó‘»¿×tç2Wîô`åeh„Üpw垯\ÆÀjÝò{€†ë^Vàu”­.åµP÷z§¯«u*SôrÝm„rº÷¶qÌdšx!„BˆR–}W/wx×¥\ï]ÝÏQ(÷•äÒCtÓ>E„¶!^;‡ÒÓar/ÑýJE~UP$JR ö¼›qB˜¥—‡
óv(½3QzžòJBúå	Ô7ðtáèAä6òóNtÐTXr÷w;ùïjZ`Ý»yGöÆ Ì—+¥—ÎÞ@Š.ˆå•L1°¼—.ÀmGé¹É+¥ÑÃC
IÖ{3ݾ¤‘:B!„¢ty\Cànê~Ú?àz¹ÏÙià½s(CµÊ£|»Ÿ“ç½+y>3¯º0æ†2÷ýò>BéÑðæÎð6Û<Ïm…±_èö+Wn¯ð‰ut?QîÊëaf_Û¢ûÙL÷Ó垟ó,³è
TÓ}F³ëÞMn¸Ûkà½C(Ãáò2GéÅ:^ 0å]§ Êy‚‰¡cæšç˜Ý(½SQzî£UÜIÑ<!„B	@D ôø=àz¹Ã¸bŠx?eÈ™ùg•K.bùÜ{…ä¡—!º†¿
eÕZ”¡Z9(CÖj“Ú½$ßç~åÞ_Òè\ĶÒu© ëqŽ. «@™x –n¶@ ôü„£QÌ." ”ûŒ¡8ïåNRwh ³î˜5›ž¡ß1÷˜5B¹¯ª¨cæøÇåm”{ƒú¡·¨;ÿ3Q†æ¥J5!„BQz<®!pgt??àz¹¡Æ­ˆ÷Ýt
ð¸b<?ë‚@}”aiýotßÈbüÜÜÐ×eˆ]Q†?ÆÏÜš'à4Ó“íyÞÏ×¨‰ÒK´e˜Úýþ>E
?,8Uuî4Ún÷T†>cü=ŽÙ+pLP&•ðÕýÞSQî#ú\‚„B!„ Bæ¡LIÜ…ÂÓ(”wšì³ºŸµ‹h{ ÜWô(Ñrg¥3teƶ3r¥¾ßåQœÒýlöÏwÞÔåÞ£ø<ïk0]
-°Î½äΪWÃÀ{•)Ü‹–2̱|a'ô1³»ÿ‚û³
¥'¨†n½NÈDB!„€ˆE™”@,*"мHþûYæê~~Náç}«Û¿¹¸o‘w	Y9(Cœ4Â?âáÌy¿VéÂÝ;(Ãð14Õôã@ítán›e¶£»ûàPîylà\)bI(“Œ-ð{¡ÌÞVД‰)ê¢]3ÄŒü³ÐÝíüÛbxÀL”¡t©ÈDB!„¥Êãl\ÿ‚r#|`°eHÕ5]ƒ6å¾syÖÙÈgílA™^9徘.(Vðˆûuå~–ºÏÛ§kà.ÐíË2”ÙÈ–sP&Xxè2@b:ö)(3Ü­Ö…ŽŸuû–Š2éB}”¡\Þ<Ø„w“{P¹»„›Ü!q~(3¤í¼Ïmï×…ßîºãÀAݺÍP¢zå~²ê(ÏzU·¹ûue¢ƒuçT£_
uen¾îõ”É)ºêÂÑ$©"„B!JícÞ^(p@×øÕæùsøÂ÷|˜ Lœ‘gÙ`	†ofϤð³yrýª[¿Z×Qf÷ŠÖm[´Õ½g§ky÷õÊ0°iºç
w¯ç6×D÷ÞÞ«ŒÒ£)°7tÛ4¿Ïϸ_óò|†k¡8Y÷þ®"¶aè9@¹ëNÕ§ÜÏ8ŽòŒ¦Ó~(½EãtË%ê‚K/]hÒßXÇå±YŽY
ÊMËX¾¡îÇå9Îåui?Êð·¼ÛIF™RÛXª!„BˆÒ—}´Å´q5ÊвFºÐ ºÇòÆ(=Eux°Y×/]èqJ'Ãe↺¼ú.X–ºãúÛxWW6ß½Ë2&ºpÛ¥÷Ñì!?Ëå™L¡(=@–R7!„BH¢8
-ö(ÃÓtÁT!„BˆÇ€dˆxÚÂP†4îG™’:å>Ÿ2À(”{–„B!„x|)HxЦ¢Ü›“{ÿM6ʃu{È¡B!„Å‘}$‰’ÀåÞ'39B!„BB!„BD±•Ø]ˤ|Ü…k¥¬gzÿåú"íâ,ÆÅøûiïã=•®’[¸„”!„~„´?J[û£¸ö>‚ª$!)\BʇB	?BÚ¥¯ýQHkà§¶@ð1„TR¸¤q+•”!„~„´?ž¥T0ôh”÷O‰
AR¸„”!„~„´?Joûãq ‚áG“çBQž?%"IáR>„BHøÒþ(ÝíÇ5
vÞð£r€lÝöM3À\÷Ó4Ïçæ$ý09FÃO#ðî+u¸•pC
×sÔ¸Õj5ü9ëGF}Þ“[‰ñO­,HùâÙ®;„x­–gýIÏÏGŸxËà2ç._¥|hw&Ïû­öÞ“‹i4Œü‰:¯¼Ë„[~ž÷2öíiÓ¾=æð£MKIfÿÎõê½Û×™F^qpr5X­l¨X šõ©Û¤^å*ÊI(á22³Øsä8›w"ìà1ìm¬¨V©MêT§YÝš¨T†˜¨€ZUðvwÃÂÌì¾?oÆ_+øvòœ|¯Õ­À²_¾bCY$üÜ¿ä¤DöïÜÀþ°õDœ:‚w9j7lE½¦mqt.S"÷YÚ%;éƒPjr’ö—qÿS‡mX¢ªÚ&­;qþÌ1Vü5ƒÚ
[U﨤p•þÆm^^닽£+dž1eT®]½Äkï~ŽJ¥"¸^h¾oL¤òyþÊÇËoôÃÑÉ
S33ž¡Ç†‰§¤`Ýq/1QûU†N\˜/å­£]Êpõò9þžýWþÉèé+.+J†Ô´t¾Ÿ>Ÿ™¯¡¼§Z7A¥RqàØiªø~rËOh½à{”±¢ßë÷ÆË¸99`ff*᧺•pƒI#?æðžÔmö/¼Ò‹èÈóÌýyëWÌç£gQƳœ„Ÿç¨ýñ¸`×ÖÕFa–¨zöÆKÝú 66F«Õ’xó:VÖ¶dgeq`×Fvl\ÎÕËç®ßœfmºà]>Àà†5
ß}Ñ“«—Ï1î·5ØØ9 ÑäðÕ‡¸}+~[…¥5“¿û{G†¾Èæµsæø‚ë†Ò¦cÎg×–UÄFGÒºÃ4kÛcSêiÂðÝd<Ý\èܦ)s–®%ì@8­ÕáíÎíqwu`ïÑ“üóßVö>AåŠå­Lûfõ±±²”ÂõšµíŒoÅ Z¿ü_¼Ûž›VòòkèÏãñû?k-6vŽ…ÖÍÊÌPÊφåDG] n“¶´hÿ*®î^Rù”’òѼ]·BõF£aòwŸàæáMËö¯²úŸß9ytßÿ²Œ›×¯±/l=gŽäÒ¹“ץŋ¯âí _ïAë†']ÖÄ£É=Ϲu‡ƒ3'îeëÿpâðÊU¬Lp½Pê7kOB|Îú‘”¤x¾x§
_[@<_ÒåÖQK+V,œJB|,N®w½†;¸“QƒzЮKoÞúð+T*#Vü9?gŽãão'Ó ôE9YÅdͶ=Ìü{
½:µáË÷ßÄÞÖZ_6²²sôÿä»Éx{¸ñjû–üþÏjö=É’©ßñùØ©ì>|œµ³Æãì`GBÒmæ/_Ǫ-»hR§:Íë×ÂÖÚð5¿[»æ”÷.T&]¿²²Ù¸ëË7îàÜå«4¯L—6Í(ïÍÌE+=}kg# ¼7‘ѱ´}{ kgýˆg".FÑöíA|Ó¯'ƒ¿n%'ý~¨Z-Û7,çðžô0’ö]ÞÖ·OkÖeòÈYõ÷,Þî?Œ?fŽeբ߸á·5øøUàBÄ1¾|ÿe^éÑn½?%;+³ÈkCQתï¦.)´_q1Q¯]^åüÙù[Þ\¸‚¿&
#¤Z ûÃOÑýãaô«3Ÿöî&íGô؆œegej·¬ùÛÈÎÉF-_Fml¬ÿf%÷[3­VËú0öË^h5¶èÀþ°
ý¸;Q#yVüñ3c‡¼‡ƒVÖv,™÷ï½R‹5KfSÖ§7ãc™9ákN…ïÏ׸ýsåFú›€ƒ
UÊ3yþ2Æýö'†¨˜XzþžC'"èúB(ff,[¿cc)\ÚhÉÉ!'Gù=ŒŒŒî«Û°r!?|õ6jc¶èÀ®-«™0ì#’oJø)eåÃM«1~h_n^'´]WŒŒÔ¤¦ÜfãÊ…¸º—¥n“¶ü·t.ㆼŸ¯L
®Š«‡7{¶ÿÇí¤jÕo¡듇ôëŸ>v€šu›Ü×µÁеªPýRĵ+ðÖ:Ú4
`çÁchµZ´Z-»œ ´^Mi<Ž}}\;%å67®Gcïà„ƒ³Á…’ãYúÇ4*×lDÿ¯'baiMpÝP¾ú°#ÛÖ/å÷?òŽôûr<5ê4áêås~¿&&¦øz".eÊâëÄè/z`ue=Ôj£_'&>‘Iß|LÓÜNNåÈÉsì?Erj™Y¤gdѨVUÞíö–æfÜNIÅÜÌT
×CÚúßíÞÂþˆ½zÞFbnauÏKsËOÖðõDLLͨV»ßöëÊÙS‡õ—„Ÿg»|l^»Xß -ëSàúÍõïÝNˆçí~C©Ú^?¤ÅÇ/gý‡±‰Òȱ±µgΔ¡Üˆ»Šµ­ýÕ
ÑW.RµVÃ'VÖD15x23ÈÊH§j­F¼Ôí]ÌÌ-IM¹©™9U´>*•ªPoMÜÍD–®ÛFýêLþæcl­­hX«*¯}2Üàç/^»Y?®‚OYš×/<œ.>1‰i,¥QÍÊLüº?Ö–„Ö
¦ã‡_±tý6ÞîÒï2N:ÁÍêqâìE:´lDzF'Ï]¦Q­j<~ϲöTð-#'ý¤¥&“œ”ˆµ­=¶F–˜™[R®Beömÿ”ä$ÊU¨Œ»—/»·¬¦YÛ.h5vn^‰O…Êx—¯tÏkCͺ¡¯U†z‹ºv]»A%?oê׬Š;x§«²èY¹B¡ÞFi<ݺƫ2µá°·ã¹O‹ºana¥»¸xãàìFø0^{g*£‡ï”²wrÇÓÛO)HöŽXÛØáR¦,N.î8:+ªÜîð\îNöT,W+Ksü|<9s!’œ
>eèÿVg¦Ì[¾ðS¼×½ƒÁÊM
×ýû÷ÏiXÙ:Q³n3:tÚZ¡R©î€n%(ågçÆeìܸ,ß{	ñ×%ü”’ò±âŸõï5`D¾dimK@ÕÚùÆó_ˆ8ÎöõK9º;1QÉÌH ''ç놜ì¬'VÖDñ)ãáCç·ú³dÞN…ï£C÷÷ò•£{éðZ_œ\܉¿ÃKç²vé\|ÊW"éÖÍ{^Ã\Üôx¹¾Þ°µ¶¤vÕ€"ïJLºMRr*ëTÓu÷ñ,ƒ“ƒÁåþc…þï#ô*"Ý">á6Ý^h•…†½=Ýpsv ì@8ŸöîNýšAlÞ}þoufËžC¼ÓõE²³sX´fÝÛ7'ìèAZ4©Š£ƒµœôÇQX°´²¡ùÝY<ç'â¢#ÉÎÉæÜÉÃô0sKâb¢îëÚ`èZUÐÝ®]f¦¦toß‚OFMæô…HÔj#.FÅðý ÷h¢i<dai­µ´²áö­n'%¾YTûh3—&'‡ô´”B¯«ML0ÑßÏ
aV6vú»k¨Â
nÏÄD©É.rcµQž÷Œù¸WWÚ6©Ëò
;8z*ukòˈAØÛXKáz†gqºŒ­+@5뵤FHÓ|ï¹=†û2$ü”“l/òž@µ‰	Æy†ŸÆ_¿Æ¨A=¨X¹C'þ‰ƒ“+ëV,`úØT7¼HwYÅüÿÁÄ„®½>¦n“¶ìذœ©£X£.ƒFü‚µý=×ϽÀË7€©ßLã–/ãUÎÿž×°Üû^s%ݺ‰V«½kCH<+Kìm­‰‰‹'>!	wW'æÿø
ié´{gÁk¾ñ}cÏûe®Ñ]Îßö“îý­ü=Ú?jµkWgãΜ<{‰KWc(ïåAVv6§/\æjúY.^ŽcÈ ®÷5d\ä	5–ÖØÚ;qýÚ’oââæ©/#=•KçNbãिW½j­†ü1cgO%;+3sªÔ¬ÿ@׆‚תB¸Àµ«½Ó9¬XÇÀ±ÓõËÔ­ˆ“ƒ
»ÃÌÄs3êU¯,í€T&¦fªF-:hü2ÊhØzZ¾ôºþ?iVfÆ&¦Ø;ºààìÆñÃ{HOKÁÂҚث‘$܈¥Q‹©
ö˜[X’|û©)·±±s þF,·â±´*ümLQ™ì"ü!~±´ôÔj#ý|¨TÞ;†N™CäÕXì+YKáz‚윱qpÂÜÂ’Ö/¿‰¹…¥>ßÏs$ü”>‰7¯“Cù€Ø;º••Éų'WRÐ-β&Š_FzFj5>~x—¯„s¦%öj$Ö•ìõãñs{üîþ…[¶î:–y_×°ËçOó׬ñ´xñ
lìY2w2Õk7& ¨–œ˜bbjbL§VMøfòlþÝÆ;]^ÄØøá§Î·³±ÆÖڒ݇ӧûKØXYG|Âí‡Þ¦‹£=nÎì9|œ”´t¬--ˆ¼Kì:´h„ÚȈÊÊ‘ž‘ÅÞ£'175£Œ‹#99Ì­Ôl
;@P ·œðdbjF“Ö™5ñköl]Ã]z£V+“ Ú³…¸èHZw졟x©¬OkÔåäÑ=¤¦Ü&(¸îeË=ÖkCÞk׎gÉÌÊæÄÙ‹ù–qur k»ælÜy###Ú4®‹—»›´?JXB¥RÑ´Mçœ=ÛÖýöÓ7?¼ߊUH¼ySáû5u	6vtîÑic>cÊwŸR> *ÛÖ-ÅÄÌœF-;eddD`µ:ìÚ´œeL%(¸!»·®&1>Æ`*ºqûpߘ\ºC¿?Ѧq–æfüóßVÜìq´·•Âõ„ÙÚ;Ñ¥GfŒûœô´TªÖjDFF*áûwðé°©ù¾Õ‘ðóœ”	;ìÜYµxšœ¢£.p+!¾Ä–5ñèVÿý;öŽ.ú7nÝ©PÏLÌ•Kü4¢!Û`fnÉÖÿþQ&2°W8N®ÊÐÇ¿uá¤à½f[ÿ[±ƒ»¸ËÚfcï䎕š÷¼†ef¤³dþ^~ýC,,,Ù±q‹~ŸÀ Ó±´¶‘XT*Z7aÓîƒ|;y‡NDP#°"7o%‡¯—ûmÏÍÉ—[6bú¢U|;_œÈ¥ÓèØk˜p…û@éë²æÇÉœº|ƒ—o°n׿T37axïNԯ퀂›ßw¾Þí*š9{»ê–¬þa2S—þùNuÊ€.m01ÐãàéKì;vž¶MêÑ¿Km« ¥¡Nûf
ømÛAêת‰©S> @÷šì=t/FtD]MEdv)ÐÑ5àÛ¹¿Ë6B=¼{=ÖvÎÿrZv.¶ª£‹;¹¹9Ò¹<ÎnïýÝ`hbÁꙣX½ý—nø2¢OÜ]é=q–\<;kKª™““KÇò3—°2´?$¼X· ”ôŠ}±´E!Pðü/ïùÿ:SO4nßCÑïÿNoëƒ^±åÖ_H”Q>Þžwª.?9<š˜|°òQXX€D"æB|Êå#Öë`¥õG›œŠöÀàPNšÇØ=øfx¿rÑRIÚ…ïcèeõâmôÂù6ÐÛOOY—wDù”¾º”‹œâG Ä ’¶?ü‚pîš;œBSCžm›	ñó¾Ë{l¼A’—PÁs¤öR˜hÜŠÆ­?¢|@ˆÑþ(giéì=vŽ®­š0¸{{¬-ÍEû£œ
 ÿA<Fá[Qùˆò!!~å­ýÑ¢+W÷­í
"€øQó²[\áK/	 ÷*‚DãV ʇ@ „øˆö‡h”¥ú_ñóòqIñ^Äyç™Å¢p	Dù?Ñþí!€JA%/é³(\Qùˆò!BüDûC´?>(b[s@ @ð)P(Ö@ ŸB	@ !€@ @ @ @ H @ „@ !€@  ,y¡Š}„*7Q>¢|Dùˆò!åCPYÊÇû@ôŠ-—wÆ£‰I¹µ­"üF&&Tæòñ1±$K"*¬ýƹHôû¿—´b½ŠQ	1iâ!Ê‡àƒ—ÂÂÒµq%I…*[&M<*ܳ`ÒÄãòG´?²}*\à!~ÞZü@ TT„„øâG @@ˆ!~@ $„ø@P!)(Èçä¡¿…Büñ#AågÿÖ_Ùµa)1O
$„øâG ‚ÊKZj2NìãÙÓÖ-Ÿ.@ âGˆ@ |*œ9²“¨Ç¡o¿°°ü¼ÜWWÖ-ŸFtD!Þx{H (oܸ|¢ÄðÁ¾$%Ä|°ïŽ#"<€{·xö4᯽}íAg6¡–ä/ËËÏ'":NnO„°ˆhñÄ'0DˆŸ¸éÌ‚ÕïßG¹  ‰³ß9…«·]ªk#câyU´?ņ}ÿqøœ×{ýObã™ðÃJ<½ýÙ|àx¥ªž¥¥ãu;°Ô×gçärþúíׯ	
}Ä¡3žÜ¹VìÞfç=#™YÙ:ãÉ®‰J[ ¨@øÝ¼HR›ïôäÑ~˜4@v|ç¿Ì_a~oaa!vŽntî3m]}ºGá'º]¬@‚rÍ©CÛ^þ ȯtÞ‹Ý»áõ"æúy®]üŸkçHˆ}òʸ;Ö/!,¤Hìlÿ}ª‘²ã€ûá¸öÅèYËi>äkb’dçî†>â¯ÊWô¼Œüddf›ðôý¿($<òÝÄst|"·ƒîc]Õ¬T׿tCg¯ÈŽÛ5v£~­šïõwæäæ@£:Nì>zN®Ñ^щŒ‰ç×­ûK}}rjWo{mœƒg<¹âðR™Ì¢×„Ùº—î#9sÕ[.þ”ekD¥ý‰àéíOzf'.ßx­Ð>tÆ“Cg<åÂÓ3³äÞ×ý‚8tÆ“ˆè¸rkwAA!¹¹yen÷‡$åY"û¶¬äÒ©äËÂSŸ=åðîux?B~¾ô7߸|œ¸˜Hlû
߸qù‘îs`ÛoÜõ“v€¤$'òï®5\¿t\–^b|4×.ãÚ…ÿضf‚}yö4þþ•K§¼véø?É:OØ,›·ãwóÑ‘áoõ[%	=}A×¾#©¢­ÏÀQS©ß´½@AYNFz*ñ1‘ÄÇHECfFšì—Ù+7Ò竹üsò’,<.1™•[ö1xòv9#{!ï_ÍeÈä…l?\4¬Ÿ’–Á/›÷â1q6ûŽ_ûŽ›þÁ|½ð7¾ZðÞÏGãbŸ2÷·Íü²y/ÇL'-#Sîš¿¢O‡²ãGQ±Ìÿ}æ/áè…«H_V3V¬g÷çèýå\¯ÝNbr
ÏÒÒÙqäûN\`øô%ø†p+àÁaHxúŒU[ö3ðÛÙqø´L´ü¶íûN\à³)?%÷=‘‰±Ñ³–ÓkÂlæüúW‰£RJŠŠ´hà¿g=+]=—ˆ—O€ìžË/éÜô.¶ýÓg©\¾u‡ì윢N‘àÐb£³ÏÒÒ¨ëh‹KMéó¿ãÈÌM8»õv¯œË÷?o °°u5Uzµk†¢‚x¥~*,߸[ÖA3eé«…oJZz±‘ìGÎàæ1†¾_ÍeìœäÈÎýsêW}Ë¥Ýý'ͧNÏQô˜0«Xüm÷‡äÀ¶ß±¬nϣР–~?€¤„fŒëŠ‘)QB™=Ñ€ªÕk¢¥­K-·¦˜[ÚPÕÚ]j¹5ÅĬ±Q˜5¡'Æf–„…øËF‹âc"ùsñ·D<ÁÚΙeßdë?`ae‡·×iþÛÿ×+í»~ùÂIINdÇÚ%\:%mCìÞ´U5ñ0
$¨h\>u€K'ÿ`Ýòé¬Y6€K§àuî0& 7'›™ã{pþØþúuÁþE=W;Ö/aÍÒÉx?ÂâiÃØ¹þ'iÃ0"Œù“úsõüQÖ-ŸÆ¡~Äøh|®¼‘Ý®
K	
ö£  Ÿéc:èãÅã° ®ž?Bjr)ɉ„ß Âç¦yå®Í/(àªoÍë»HÅ”{wrÛù”ÄÀi/o<:´`Ý“ùeó^Y£t⫨ëdÇ–Ÿfö˜#ç¥îc
jÕdï¯óY·`2'.ß ð4?~Ù¼—ÂBؾb6÷ÃåϳWmbÆØ!Ìübßÿ¼ž¬ì2³²Ù°÷6–æ^³
5ù—Œÿ½PìªW•³‚6ê²òû‰lþç8W}¥£7îáu;€­Kg`l Ç¼ß6££¥Éîíèש[—~›³=aÑDÅIÅø¼ß6£«­ÅÆES¹áÌΣg¼ΡӞ¬šõ[4dùÆ]¨«ª²ðÛQZ½VîuXºaW‰÷Ò®zUî>xT©êðÈh&-þã—oÐyô4Yú_ÿç³)‹8vñ:FM“‰Å}'.0hòÎ_¿Íw?­–¥óëßûñº {f|ûùyùÅë,ošÕsA"‘P×ÉŽä”TžÄ&ˆÊ¼ö˜7|Éx>JÒÑšë~AÅDvI$%§púÊ-¼|Èɕ֩:Zšôj×L&Ì¬ÙÆù¿WqþïUÄ&<åÌ•[4tuÄþ¥z¥<Ù
°jæWìZ9·Øµïbwy Y»^4jÕ•¡ãg~¬Ìtn^>IËŽ}iÚ¶'}?ŸDNv‰qQ˜[Ö@CC{çz›Yb^µUtô±w®‡±9×/ m·Á4nÕA£§‘œGJr"F¦Ué÷ù·4k×#SKÜ·£QË.ô4ž;·.¿Ò>×ú-ð»y‰;·<é6`·½ÈÊL'#-#3ñÐ
$¨h¸ÔoÎ[—ÉÏË%==…¬Ìtòrs¹së2.õ[ÈÅ
¸íEU+;ÆO_Á¬åÛÉÉ–Vòùxž>ļ•{5i!µëUÖçŽï¡“Çç0›)7àyö_

òqoÞ	[Wzýk»ZolïÓ„XHèÔ{½‡~Mç>#1³¬…•};4gÎж¨«©Ê]“œ’F

ôuµ°­fAbrŠ?oHÓºÎÔs¶ÇPOwnÜ	&áé3<Šäν0Öî>B~~>ÿ>w'SVVbÍΙ´è¢ãe
‹7|Õ·šêjŒèÝY–þ‘sW0ÒÓaÿÉ‹ì;qeeÎ^õÀÜØží𡬤ˆ‚‚Dή°ÈhªšÉDTaa!-¸b «Íàní8~©H íÙ*šïÕ¯Û¯mŒäåçsÍï.Ÿõl–†:£úvåÄKi
îÞC=º´lÈ;ÁhkirÍ÷.S—­åïC'¹x³d×Ъ¦Æ<ŠŠ­TuHlâS¶/ŸÅ‚oFÒ¹ECŽ_º.@ûÿc×/s˜÷åp†ôhÇ¡3ÒÆÅÖƒ'Ù¼d:s'g@—Ö²t>÷èÄÖC§8ëåM=g{Ù3û2‰OŸa]ÕTvlSÍ‚„äg¢2¯Ä$$ÑcüL6ì=Šw@ÿž“Ö)ë÷aüü•\¸áKŸ¯çñêyÞ!ž²ŸpîÚí;|ƒ`f¨¹±ÊÊJ4®ëŒ×;Œœ”•ÝÒ:Ñ Ræ½±™¥ì³®1	±QDE„Ê…›˜U#ê
ÜÍþ÷:#SK™G‹‰…•,\³Š6¦Ï5«èžšüÿ¶•îܺH£–])(ÈÇçêYœë6î; $nàcaçìÆƒ`_‚îܤ¦s=$

ùß <Ä›š.rqC¼±u¬SÔðp~މ|ˆ±9’ç®'ֶβ]ün\äÁ]_ÎÛ#­ØôŒHŒ‹.µ½Æætè9”y_÷AG߈£¦âP»d`D`Tìš*šê¤fd‘™…†ºQñ	TÑÒâç
ÑÕ®"û¬ª¢BNN.99¹h¨©ÑÔ­H¼jªKGh¾Zð+3ÆfDŸÎüõÏ1ž>“ºXæ ð¼Œ(*õûdåäRÝÂT–VS·ZT33&=3ëµ/{C}ⓞafd@~~¾,m%Eòó‹FdnQ	…RPXðÊt_xaI$YZy/¥¥§­õ<\Q~üÒuÎ]óaú˜AèëhS³ÓÐbn_ñIÉèVª:ÄÑÆ
ee\mØ{ü[4DII	çeÂÍÙž¹¿n&¿ €'q	˜I󵎃­,æõ]˜µj‰É)l;|šñƒz–ø}ÚZróÒ¢ãÑÖÔ•y%àÐiO:·h(—÷ùì;qã—¡¤¨È‰Ë7ØòÓF*1[þÁ´¨ï˜þÝPRT”=wr":9Enî UÕÿw1Žò`weÆ÷úyš´îNZj2ÉIñT­n‡[ãvœ;¶›Vú‘•IXÈk»“‘žJFFšìZ=CÒÓ‹:5ë5nÇMÏ4iÝÌŒ4"…`ëX‡@ŸRÛWþ6Âxö4+Gœê4fÏ_?3hÌÛ/a]XXÈ‘Ý눉zDjJ»7-ÇÖ¡Î'9HŒ	>ŠŠJT«áÈ¡«qmÐ×-øwç¬le‚æ¶NuäþÙ̲I	1²	Š!w‹*™ÚnÍhÓu ß/ÝÊ÷K·2ûç™VEUMƒÜÜœRÙܱ×0Vþ}ž#§°ù·¹X¡ZÌeàQQVƦš÷Â¥Ëa„„ãd[]ˆŸwÀÜÄC}ž¦¤R×ÑŽÚöÖ(+)‘—ŸOLÂSjÙ× 5=ƒÏ-2Ðʽ[ž ;'—­OÊÂûvlÁ
ÿ`Lô¨çl™‘´WöÿÃÙÆŠðˆ(,ÍŒ)((àº_É©iì|öÅLé³^·1ššÚ̞Ћ¹_õ¡ßçߢ¤¬Œ¶®>Õm™ó¥Gö¬Ç¼š
ššÚÌùÒƒ³GwRǽ%‰³'z0ÿ›þ3Åw²O¢ €ƒ+fU­‘H$¸6hAÄÃj¹½ýD"á~Çÿù‹”ä$Žì^‡Dòi>ób¨S‘÷˜yKýæl[³€©7 ‘HXŠO¬D"¡[¿ÑÌß#SR’“04± m·Aü±ä[ÂBüÑ74%Øÿ&SnÀÑÅ-¿ÏcûÚÅ4nÕ×7²5øÎ
.œÜ•#¡A~4­çŠ%4«_›U[ösÓ?˜‰ƒ{»nÚèAŒø~)훳ÿä¶-Ÿ%ÄÏ £­)7Ϧ†¥zR·¤ßç|ÅöÃgX¹y*ÊJŒî×
›jæ|?v0C&/DKCÁÝÛ¡ô|´ç»ÏûñÇŽƒô?“ÁÝÚÉV\³01bîÄá,Z»GOb01ÔgÉä1¨©ªPÛ¾Æ+mØ­-«¶ì§çsÿø?ç}Ëú=GŒ¦çV4­W[×ÉÆ
/çP×Ñ–ù_}@“ºÎ\¿Ä ï0e䪙c¨«Àü¯>gÓ¾ÿè?i>Ý[7aH÷vÔ´¶D»JÑhCƒÚîÖ–Ù«6Ñuìº·nB×–Šý†3W½ÙòÓŒJUêiWaøŒ%8ÛVçܵÛl[&m¼ëÙ‘ÁSÒÐÅ‘ÓWn±zþ$†öhÏç3~¢•{®ùÝ•KkP×¶¸ôÅìñŸ½òûFôéB·qß“ž‘Éùë·ùzXÙ” bÓ¸®3Þ÷¨çl/s´±B·Š&MCYéÿoÄêjk±dòXòòóY°úoö;OŸŽòîÜŽ6VfÓÇÈ»tôîЂÞŠ¿¸õtª0gÂ0Ùñp޲ÏÍêÕ¦ÙK‚å‹¿óJÛjÙY“—ŸOdL³Æ-–Îw#úËo^"u0Ò×eÝ“‹Å732`Ñ·£ðºH]';õt*MhmaÊöå3ÑPW#8ì1ÓÆ–5ö¾Ôƒ¾ZâÊ”Qdn=ƒ»·£u£ºø=`ÜÀîóB	ñóQѪ¢‹S]ÙñËó|^ôš¼@"‘àÖ¸m±4NÜJZj2	qQ„û1{ŹóN®Š]£¬¢Š½s=²³2IN”ß»@IEEne•ªÕ튾nMJ¼ÿºÚ2·™~[²çØyFôîŒM5sjZW£¦u5¹kî‡ãJë†u…ø©€¬_0‰äõ^ÄÕÌMdïIýZö¸»8Tªû¯¡®&s'­nQ¼#ÃPO‡¶ÝJ†/æé:HçTy†°aïQviƒîóyVî.\¸îËŸº¶j${Ö{¶m*—fjzëöap·¶âÁ¨ ˜›rò¯åxÝ$1ù-H=&éŰ^8wÕG+º:°è»Ñ²y‚/:#wkËMÿ`róòÙõóôuµ‰OJ&'/ˆè8,ÍŒX2y^·x–šÎÂoG¡¥!íLxúŒ”´ŒrkwKwWÜœìÙGºÌ‹¹L¥±[ðqé;ütô1µ¨þÉÞ	Pøüi)<èU>WòhbByµí}‰“&ì7z41áËF™”Œ´îß½¦¶¶uÞêZ¿›—¸zþˆ\XÕêötë?¦RŠÏ÷)~$úýßKùˆõ:(ÞF•“&¦|‡=æiJ*î.Žb/Ÿ
V>J;ê&‘H(‹²•–‘ÉOëw°pÒ¨×ÆÝyäwCÑ·cKê8Ú»_eYW¾»Mšxð.ùSÚ‚×¾Wí"F€*°øHÑÐÒÆÕ½e©®}±øÂ§pÿÅÈ@P25ª‰› ø hi¨ÿ¿âƒŸÏ÷vÑÕ%Ä@ˆ@ !€¢ñ-âG  ²!\àDãûƒóÜWS (‘çsQ>@ ?•cäGLB¬|ùkìûbQ>%ò¾:>Ä"BØ
¹”Ïw¥@]8·7!~ʳø@4ÒEû¤4”‡‘yÂ~È_!~@ í“O¥ý© 2OØ/ù+Ä@ ¢}ò©  2OØ/ù+Ä@ ¢}ò© $2OØ/ù+Ä@ øÔ¨h1ˆ…#¢}"?‘¿BüA©©H+š4ñ¨p+"
Á&Ú'å‘yÂ~È_!~@ í!€Dæ	ûBü@ í!€Þ¸è>,ñÜË'Ê]æåçåâ}õL‰çîú]#-%¹R>
2Ê…¯*‚}IJˆù`ßFDx÷nñìiÂ_{ûÚ9‚ÎlB-É_.<'W^T„EDsèŒ'>!Büoɳ´t¼n–úúìœ\Î_¿ýÚ8A¡8tÆ“;÷ÂäÂsóò)((”gfesèŒ'ÿ]¸&2F ñ#Pq"†äw½Äsk–M-uæÍX±žÈ˜ø÷nonnŽï-ñÜ¡^ªÂç}fûO^,§…¢{ÊG£üÔ¡m¯äWª4ýn\äèÞ
¯1×Ïsíâø\;GBì“WÆÝ±~	a!Ebgûï³P͈”/Û¸‹ú}ÆaÕzé™Yò":ôýs\ˆàmß%1ñüºu©¯ONMcáêm¯sðŒ'W|dÇ—oÝ¡éÀ/©Ùñ3ž¾\,þ”ekDÆ!~>5TP/7ºó È—œliƒ/æÉC23Ò°u¬Kýfeq23Òð»y‰ÌŒ4¹´
	ô½J\tÑ‘ád¤§Ê2Ï ó>×ý‚d=p	OŸáxï€{x¿eozäÃûäæd.UHKI&>ö	*ªjôö¼]·½Hˆ*1­@RŸ=%>&Rîš`ÿ›d¤§bI¦y¹PdorjZ¹*5+“ —ó5>&RvÏ23Òˆyò€Ác§Ë]ì³DÑõ˜ ¿ë¤¥&;ÿ È—”ä$Ùñ½@oîÜ"$ÐûFvZuꇙe
òrs	ô½Ê­+§INŠ'-5™oé_ 7fyá(“G—
1Ò×`¸G'®ï[ƒN-ùûliF“ºÎBüï@T\"^>r#2©éÜô¦°P>üé³T.ߺCvvNQ§Hp¨\¼°ˆhž¥¥P×Ñ—šÒçßÅÁ†Ó›WгmS¹4ÕÕTéÕ®Š

"C?ŸšRPPä§#ÈÎÊ$!6ŠécºÈÜÇ~ž3ŽÜœ¼¯žáø?à}õ³'zpçæ%~žû…,´Ôd¦îÄÕóGÙ¸r&‹¦|FØ=,‰àä¾µŒ›÷oúÒç빦3W/ãÏ%ßùð¾,5K§°mÍÎÛÍÂ)ƒ9²g´ñìË“úãsõ,kçÀó¿-äææò02š‡Ob¸â@Rrjùi‡1qeò]—OàÒÉX·|ºlðÒ©x;À‚ɃÈÍÉfæøœ?¶‡¿~C°ÿ
Y:;Ö/aÍÒÉx?ÂâiÃØ¹þ'©ÀŠcþ¤þ\=”u˧qhÇŸD„ß#1>šŸ+oäF·kÃRBƒý((Ègú˜Îúxñ8,ˆ«çšœDJr"á÷ˆð9†iÞÃb×›è•û†‘?‚ŠHxd4“ÿÎñË7èë]n
ƒÔ ¬Ì¾Ï¥~sï^GûCHOOA"‘—›Ë[—ñ2Q.nÀm/ªZÙ1~ú
¾èÛŽ:zž>Äê=W‘((°kã2¢‡pîø:y|N£–](,,dꨎôôîÍ;qÿ®½‡~ýVö>Mˆ‰„N½GÈ•e+;úvhNϺF²âGPQ‰M|Šç®ßQQVfÕ–ý¿t½;ó×þÿ8õ×
4ÔÕøûÐI¹ÌÔQÙzð$›—LÇÌÈ€}'.°v—´£åsNl=tЦnµ9ëåM=g{ôuµÅ
BüôfÔ~.(ã£éÿù·ü¶ðîx_Æ¥^³bqCƒ|±¶«€ƒKQx°5j;ÚÚ`LAaDÅ%2zÖ2Ù¹êUMßÍ^·fìÚ°”€º©]¯Ü%4Ø´”§š˜“žöL÷^ 7nÚ ¨¨$³=üA ÕjÔ,²ÉΙè©ð»q‘œäH. uÔÕÖ"-#³Ü„ó~((³ï´svãA°/AwnRÓ¹‚üoâÍKùà­cÙ±ƒôsLäCŒÍ‘<a±¶u–	 ¿yp×—óÇöH~‰qÑ¥¶×ÀØœ=‡2ïë>èè1pÔTj7@ƒŒˆ*žâGP‘q´±BEYWGö¿@ç
QRR’Ô¸9Û3÷×Íäð$.3#ê8ØÊÒi^ß…Y«6‘˜œÂ¶Ã§?¨§¸¹@ˆ!€Þ—úÍ9ºwJJJXXÙa`dƹÿöеïÈbq­íkó(4ˆöµ	)r]sªÓˆ;·hiKç4¬W—‘]ÜiêV[.-
u5Ò“ËMA(«y?/£¨¨DµŽÚ¹š®}G"QPàßk°²q”	šØ:Õá¦ç)Ùqxˆ?-;öÅ̲I	1äç硨¨DÈ]9q[ÝΙæíå7c¿HnnN©lîØk{
#øÎ
6ÿ6—Öc¨VPl…7!~‚ϽðrsóPVVÂ?$œzÎö˜ê““›KfV6êjªø= KM05Ô'6ñ)&zø‡vK$umÃÊÍ{y[®ææ	?@™‘•‰um©‹’kƒl\5‹)Ö‹Û¶Û þ\ò
šu $Ðç%Õ‚S‡¶±zÎg¤'Eafd€ŠŠ2JŠŠéÑž¡ÓÓºa’SÓÑש˜þÝh^ß…Ek¶áælÏÔQßZ´=º‹ê¶ÎèèòÛ‚¯˜¶xS±xõš´çÇo’ÉãÐ`4µ¤.ªjê4kÛ‹Ùz¡¥­KzÚ3ôM±$‚‰Í™ÿÇšÔõG§Š&!ᬜù%ê83ræRròréש¶Õ,>Z!(Ëy?%Ýûmk0uá$	‹§eÄ×?‹WÛ­»7.gãÊÙ$ÆGD"k¸të7šYã{bdjAJr†&²òõÇ’o	ñGßД`ÿ›L]¸Gw¶ü>íkÓ¸UWl\ßÈÖà;7¸pr?V6Ž„ùÑ´ž+–DЬ~mVmÙÏMÿ`&îU캓—op檙YÌ^µ	7';†öì Ä@ðŽèiWaøŒ%8ÛVçܵÛl[6€a=;2xÊBº8rúÊ-VÏŸÀÐíù|ÆO´r¯Ã5¿»ri
êÚ—£˜=þ³W~_dL<¿þý7ýƒ‰ˆ‰ç¦0?M+2âÄÓÛŸºNvhª«qâò
:5w/1^vN.Ç/IW¾íÕNê	“_P@DtƆr¹×ý‚ˆŽO¤ž³=–fÆåÎîܼ|"câ06ÐCó¥¹pea·@ˆŸr/€¦-Þ„¦fš·÷ FMÔÔ5¨Û°5N®
poÞ	‡ÚîÜõ½J÷ce‹
¨©k°îçŸÈ¸±.FMŵ¦
ãõdhÏœ½êƒC
+Õq`ÜÀôl׌°ˆ·wqê1ðš·÷@"‘ ohÊ’µG°¶¯ýÜM¾™ûºúF,ÿë$Þ^giß}¹¹9™V`À¨)têý9yy¹ìÛ²’ÖnvÒèlÏ¡?qùÖR32èÓ¡%vVœÜ¸Œ€û1Ðùx¾æe=ïçiÓuŽ.©°xíÌ,­eç§/Ù€ŠªË6žàöµsšX ¤¤Lé<œ.}GѲS_ÒSS8lÆf–˜W³añšÃÜõ»ÆÓ„X>ÿj>UtôøuûEÂï oT²eÖ](È—N‚î?b2z†Æ¨¨ª“——G|l$ã{ÐÒZêzÓ·cKZ7¬Ë½ð”Ÿ»ã<‰K@SCUem¬¨¢©Ç󗈞4¿Ó22‰O*ûQÀŠ$~â¢#ÈHO¡ºmézä“ãˆ}‚S]QÛW2¬-LÙ¾|&êj‡=fÚ˜Á(+)ðÅ ôíÔ¿àP¦Œ€’¢4|p÷v´nTß ŒØGObeéii¨cb¨Ç€.mä¾'éY*É)ièjk¡«­…G»f²gY¢ ‘5h£bŠ­8'([²²²ˆŽŽÆÚÚZ.<<<333ÔÔÞßË7îfåÌ/©aiÆ”¥k^)$RÒÒY°úoæLÀÝ={9Væ&DÅ%Ò¹ECfŒ,‹ÿÏ©Kääæ~0!QZ»Ó22é8r*5ª™›€]õª¬ýá»·²»,ó§$rs²IJˆÅļš\xlÔcô
MPVQâ§Ð4Ì/ùóAP5ë¢ù0ZÚr]}ùyÚºú4jÕ€šµêË2ï—ÅÓ±«^•Ë·îЮI}¹^-
õb˘êcj¨ÿÖöêêÉÙåàRT!(**ÉÙ¯¨¨„{óŽÅÒ8¸ý$	‘ï“q—^“ºÊÎ)(Hhé^|”AC]
w‡V?ƼŸÿE«Š®Üý}yž€Ãó‘DŽö¸5n[,“·’–šLB\¡Á~Ì^±Ck£b×(«¨bï\ì¬L’åG¿”TT002“W­n'û\Ë­I‰•‹®¶Ìm¦_ç–ì9vž½;cSÍœjæ&T37)fCÀýpü‚Ciݰìçmä'Ð÷*á÷J-€Âïàyö_!€*!êj8ÙV ºEñŽC=Ú6v+nfd ›¤ë ]šÞ;0„
{2°Ktµ‹–«wwqàÂu_®øеU#´4ÔiâV«Xš©é¬Ûs„ÁÝÚŠŒùHlذ+V0xð`æÍ›'wîï¿ÿfçÎL™2…1cƼ6à°ÇÄ$$á^ÛA6,=3‹€pÔv@á¹è}IÉ)x† ©®FýÚÒ¶Ž–¦lÅÂÔˆ3›FC]ܼ|šœÈОí±01¢¡«#§®Ü,ÕïÿÐvki¨se÷²øFOÃ'07gû7²û}åOi9}x;‡v®¦EûÞ5EîÜ…ã{¹tú½O }Ï„øùÀâGÓ° \äRyͼ‰Ÿyö8Š]Ú`PVãiÛm0‚nÓ¥±3­jô¯Êǘ÷ó!hÞÞƒûwoãêÞ’qS–¾ÝKÃÿ&WÏ‘«ZÝžnýÇ”ºryÓ•ý¹:ÑÈÕIˆŸ7àÉ£ûd¤§2I	1<{š [¤¨ÇèOb)(”V´y¹¹D>ºOuÛ¢ûýðA U­ìQz>j'øtÑTWc¸GGÜ]åÂÛ5®G»ÆõþÿN´*Z,œ4JÜÈÀõëי꼘·E[WŸží+ŒÏåÇœ÷ó¾ÑÐÒ.uÅáÚ ®
ZTŠÊ¥²ŠŸÛ×Ά†–6;Ö-fÞÊ=HØðËLâc"Ð30!âaóVîAUMë—à…•¡Á~T­n‡¢’¿ÌÇ¿@WßˆÄøh–Ïû®ˆ¤‡ÕÄMøÄ9tÚ“Î-Ê­˜_PÀ¾8¾qJŠŠœ¸|ƒý'/0mô Ó¸åL‹ú.ŒéßMævù:7çå›vS¿–vÕ«V»?Ÿ¾„»1¢Og™;· ôdefp/à驨kjaeㄪš:y¹¹„Üõ¦¦s=•¤tñ±OPUU'6êšZXXÙ½²}±.ZêDDÇ¡  ÁÂĈÔô’ž¥bU‚WJY‹ŸM¹@}ÂVE²ÿcÏûù+ÄÏÛ   (›¶|ÖhîzcimOàm/Vm»ÀÚåÓð½qúMÛsåÌ¿ü¹ç*‰„Í¿Ï#-%‰DB›®ƒ8{t'}†}ùÿvÓ¶Û $‰(ÜA¦aÆxzzÊ\¬Z·n]ÌÅê‡~ ::úµ.V—oÝốòÞ÷ŸÄ¨™Ò­7
KóWÚ2 K–nØIÓ_ÒÒÝ•ïÇyeÜMûqûî}þ^öý;ýþ²¶ûàŸ¥›Oú“—oÐ¥e£2ÉŸÒbïìÆ’µGd.VµÝšs±ÚÃ
ž&Æ~¸ÌŒTã¥sו””06«Fàm/voZNízÍÙüë<›A݆­ùoï}¯bmçLRBF¦Œ›²´ÄöÉÞãç±45bHöLYºee%¶/ŸÅ¾ÉÊÎfB	6}ñó1ó§LÐí¿SÍÚúMÛóóܱ|þÕrs,^Î<ïÀjÙY£ª"U¼FNÁÌÈ€ÓÇc¤¯KvN.î8Èã¨8zµoF+w霑Gϲïøyºµn¨¾]ÞÉÞA¾\>}€_ÿÈ©·¡¢ªF«NýJŒõˆªŠqXšH™óßÂÝЇŒîÛ•Íð$6ž‹7ïð02šá±0‘Î5
{ÌÂ5ÛPVRbó’éïýžÿ·o#§o—5WÿôÙÙYLž÷ö„ÐeôºµjÌ—Ÿyˆ7¨?oM\pÙ¹½¼JŸ£+A~×ÉH“_¡¦s=îú^ÅÄÜ
S‹ê2acëPßÒg m×AÌßϾä‰},^sXn ’0f̆Jttñ…†
ÆôéÓ_;ɾq]g¼îQÏÙ^æhc…nM6.š&[`ãuèjk±dòXòòóY°úoö;OŸŽÅ=¶<ÉIÏl[6K¶Ui)K»_ ¨ @U#2²²Ë,Þ•ö=>£U§~$%Ä;תszýê£,‚ g`‚clëʶì8õï6ÆMYŠƒ+ÁMÛsx÷:ê6l-mX×dÂŒ_øfHtÓîb©U|´®EW¶wïDZ‹×èЬL9Ô¨ÆÏ3&0ø»äžÛ;×CQ±({4ëD~^®lÞÏÐí±«nY&€G‚Òòòaá!þtê=‚jÖl[³H~?È—ZnM¨jeGÌ“‡²ð°Ùç*:zÔ¨éÂÖ?¤†}-tôÅÍ­˜44‚7CMM­Ø
c@‰aÿKïöÍñýR>‰ÁÊÜc=<Ú7gP·v›¶ˆVîÒ­7tµݯk‰iü¶íÙ99èëhsÍ÷.˧/çÆ`¾ÿy=½;´`ö*évCz´“Û˜÷m(+»¯ú²ãðì¬,|ð‡‘,ŸöE™åÏû@YEµØ
c@‰a“{·°¶—Îkµu¬Kÿ
Ù9Û—:쬉	8S£â‹ü4¨U“¯þÆu¿ Ü]ÈÎÍã¦0Áaq~¾xLy?+ÊÜÎóÌ!ŒÍª¡¢ªÆ©-ó	¼íEnn_êˆæÛ%¢£­Åú'Ë]›’–Á¡3ž„œÜ†ºš*G±ëèY¹ž÷̓ _”UT12­Ê­+§9}x;©)Oq¨Ý€/tçÌ©#(+)qé¦ÓÇ÷¯íÞº	Ý[7áªïÝ2½ÏvNuå&Œ7hÖáù¼ŸÏ+ÜöefËA¯Xñf.Š>²$Ñ/›Å>23Óùyî84«èƒ£‹;

ŠØ9ÕaéÌ‘™sÿ®Ã&ÌFIY÷æX0y0–Õí	¸í…•MÑÄöΟóýÝùñ¢Vb½Š› „íÇÜÄ“-Çëv ‰ÉÏhÑ@ÚМ8¤ÃzuàÜUm¬hø|ášE߯ÜXºšà/ŽÁÝÚrÓ?˜Ü¼|vý<}]mâ“’ÉÉË#":K3cì¬,Øÿ›ü$îªÏ;Fž>#%-£\Ú]¿–tÅÚˆè8:4k€£•Ì†ÒØ-(BUMƒÜœ¢Ñ4[Ç:<

ÂÚ®a!w°w*Z!ìy‡ %<|p—ºŽƒKÊJØXš³n÷aÆ
ìAvn.n?Hm{ë2qý.s~>ººàMNv)wOðä{VÝdÞN IDATΣ °€¤äTŒ
t±¶4cå÷e#@/ó$63#}ÔÕ¤Ã`N¶V\¾uçƒÚóä!ªj²mõ–¬ÿ
Í*¨ÄÞ¢ŽA}:´@[KóÝî>4bÞ?ïSü”/ö
KO}FjJ2_L]&;7aÆ/DG†“”ÃèoÊÂ?ÿj>BƒxšKï¡_‘šR4*l`lŽ…•µÝšŠB(Šñb;ƒ—©¢©AÏçËA¿ –]ѨEƒÚRq`¨§Cç
åâ©«©Òʽëöaá¤QèéT)q)u€Sž7QUQƾÞÚne%E×q¦qç÷j·j¹5eÛê…ÄF=¢sï´ï1”Õ?M¦~“öÜð<Éà1ÓdqCƒýØõËW¤'<¦®£z:U^™n‹®,Û¸‹M‹§“—ŸÏçÓ—°dò‡ß¸¹"ˆŸ"€Ìˆ¢ºI>?Üç§
;ñh×L®7áUääæÉÄ€†š¹¹yeb³D"AßÈŒM«fÓ³Mcú7´*Æêò°ß@ˆŸÒð¿û†«KªZcVµ¸û„•£läG[WÚÛyÓóÿîZÃÐñ³D!e‚–†ú/‘>¸{;a÷'ˆKýæÌÿm/BƒPÓÐĽyG\ê7'Àç
=O@M]CwäÔ2MõÔq|½ÛäîíhêVUeTQæÈÚ%Ø|àÕ•+Šø‘¶ËUr0'=Îlù몦L_±ŽÕ;ýÿÂÉȀЈ(òò¥»È…>ÂÔH¿ÌlŸ»r7½Û7ãÆÅ£xLœ-F@U–ý~„øù´ÄÏ{¡WÑaä7?Ò°EgQ¯åIl<Ë7í7B ”	šZ:8¹6BM]ºO”šºõ›¶—?UHŧ4u«-?yùùÄ%&Ëý½XÂ\W[KN$Õu²C[KCˆŸç”é%¨ õsŒŒ‰§Š–ƒº¶ÅH_—#ç¼°©fNpxD‰.pƺÔu´eëÁ“ôhÓ„ƒ§/3¦·2±=';åXoú¹[еöHÚ~þùX[šqÝï.ù(*דÙ9¹qn©ߪ™_’“—‡ºêëí°®jÊòéãQRTü +æýˆÊKˆ t8@ Ú•Wü¼wÔ¬]/šµëõ^3ïM'l©ª(£ªR´£²ŠæÿŸ)–Öö|1mù{-|êjª¨óÿïV«¤¨ˆŽ–æ»ežjaÉ
W1ïGT^Bü¥F¸À	Ñ~¨¼â>à*p3óT4е̯ԅOIµ}ëâK€W¶y?±Qyø ø&²………ܸ|òƒ~·ÏÕ³äåæ’Ÿ—‹÷Õ3o|]Jr"Ag6qïjÑʆ9¹¹<|CA¼h=sÕ›CgëWL/uºk—M%1.êµq6ÿ>¬Ìtrss¸p|ï+ãe¤§ò˼¢IˆÙ=ùgÏÙñé+·h5t³~ÙH«¡“Ø~ø´ÜõkvþKXd´?Ay©wž»À	?•OüÀXAˆŸ#~ âÍû)(ÈçqØ=ªÛ:pÿîmªÛ:¡¬¢Jtd8zÆØ;»‘cë,'6îß½­£«\Z………Üõ½Š±Y5òrsÐ50F]Cº\dVf:ïR³V}$

$'Ås/À›{ÞÆÇ`ïìöúr¥ªFŸaE+÷%'Ær÷6U´u±¯Uȇ÷		ô&$Ð;í,Œ‰ÃÂÄvëÒµ÷/íø
%EEâ“’é0r
ý:µBUE™vë±íÐ)!~‚r„pû40iâ!ìñó	ŠŸ÷.€„øùxâ§"ÎûQPPdÉôaü±ë
I	±ÌÛ•éKþ½y'VÌÿïç¦çI¢#òÙ3¹éyŠÝ›–S§AKï^+K'õÙS~ün 5k5 6ê!QaLüþœ\qdÏz|<©nëÌŽõK˜0ã2ÓSÉHOá~ ±Oý¿(3#³ÇòÇî+x{æàŽÕ¸7ëH`B4ÚºúD<¼GVf>Ç0®¦‰žN¹ë
õtdŸÕTUÈÈ̦°°°\å…?àS£"‰\“&N”Á&ÄÏ'!€„øùxâ§"Ïûq¨Ý€`ÿ›ÄþæA/wnyâR¯y±¸¡÷ü©n#u—³q(ÚïéA/ÖvRÑ#‘Hd.uá!$%İôû‘,™>œsÿí"';ëìíÜg$‘ï3a@c6ÿ6—¬Ìt,x‚ÿ¿ˆ˜½j:Zš|3¼¯?@ ñ#ÄÏÇj[¿¯âç㈠Bï÷ãÒ %'nEAA3ËèêqáÄ>º[,nu[g‡ßÃÊÆ‘ðY¸SFûߤiÛžä|çy|'4µt˜¶h#ŠJÊri©©i››óÖöêšòåÌ•äåæ²fÙB.l£Q+{òó__çý¾™Œ¬l~™1Aˆ@ „øâ§¢ !~>žø©è™XžúŒZnM¥‚¨~s¶ü>Ÿi‹6‹Û®Û ~_ô
õ›¶'$À[^ǽgŽìäç¹cyš‘)**j(*)Ó¶ë@MF÷V¤¥Iñ#?Bü$ÄOy?i¥lôþìK¢"ÂiÓuÚºúouíѽëIIN”ëè1k»Z¥*?Væ&,œ4ê¾û›a}Êì	ñ#?Ÿºø©PHˆŸ‚VZÈ~s-û¾Qå½|XXÙaaeWªkQ)Ê?@P6xzûS×ÉMu5N\¾A§æî%ÆËÎÉåø¥ëôj×Lî\^~>…… ¬¤Àu¿ ¢ã©çl¥™±¸ÉBüñóQ¨F
ñó1ŠEQu	ñ#>,ùùù¬[·N܈
Âò»‰Mx
À”¥k^/%-«ÿ.ž›—OÇ‘S:u‘\ø?§.½r/9h?ñó		 !~>žMÏI•—?ÿK\°²x«	ï‘%K–0wî\BCCÅÍøˆ‡=æÂ
_22‹öŒKÏÌâº_ÿ×Þ‡EY-pÿ‹,"¨¸!Šˆ;¦¹d¥––æVšz3õšš[¥]5µí¶¹g7¯–Z©uK³rÉ,×rESqÅ7Â}Ùfî3L²hšˆ¿Ïóð$ó¾óÎyÏ{ óã=ç¼½ Gl|"wî#héæß¿e\Jç¹ûóÑ’ïhÕ4ç0ëü©S½ª.‚ú
?…ÑÏUøQøù‹2ÝñeZæXDäÆâââøòË/‰ŠŠbĈ¬_¿^•RÈ"¢cúÆLjûT¥r…r\¾CßÎíYðívì! Vu¦.ü†YFàë])ßc=ÅäÙŸÑ­}kbâqvr¤J>à?Æ™?.ñ|÷Ž„ž¿ Ê¿©ÿ¢T$ÃOvRø¹£2ÝñŸVE–ø4Œøsœ2$‹ó©à—Á£·~ƒŠ€áÇj^Ö÷îݬ]»–Î;«b
Ѫ;x¢m†÷ýsÅÐ,£‘ïÖmá—Ϧcgk˺í{ø~ýÆ¿Ð7ßcì;r‚¶Í2¤wìlÍ£Ç¯ÄÆçØ'ËhäÍ1÷ÍW8ýÇEUü}L}—âÞ/+Æáç¾NžE#üè!wüW†L8Z¬ÂOaÉÈÌ"hûžº§ç´?„̬¬;VbòÕ-_øå(Žž>˜ÿ
rÝ𹿙L&š7oÎÈ‘#)W®cƌѳîíûÓ¼¡Ž×ކžçò•XOšÎ€	Søæ§Í¤^K¿á1ž}²I)WiÝgãg|J\BRž}¾ZµJå=	=Çc¡Ä&$|¿IÉc4f±~Õ—
@
?
?%íÙC…Åž8Š+I
?ÃóãßgÇþ#ëa±k·ìæ›5›î¸Q1ñ|ôå÷Ö¿èÞ®)¾æÈ©³–kënâ¶=ì:x”ÖoàK‘|þýÏúá+"cÇŽeÔ¨QxzzòöÛoÓ¥KUL!kÙ$€à“9^ó¯éƒ»ki>{øèIö=EðÑS9þ{ðøibâ­ßÇÆ'r1ò
ᗣؾïpžrÌûfÏwï˜ãµs.s6ü²õ{£ÑdîÌ$$±mï!22Ì?OñIɇœ2=EFf{tÊñàÛ¨˜xŸ<›ãøGNåZZ:;÷!öº²ffe|ô¿ý~€´ôü¯G—G[±jãvk]ˆÈ_ëùØCü°~“>\Èüe?²rãvìíléۥϟùË~dÚÂ¥|öÝÚãã¯V0ãóe,Y¹žÝåYäàÑMx}D^ÑŸþݧ¦weõ|âo—{cP0ϼô—¢bðòÏ€V·S"¢cuq%‡ä¤x¶¬ûŽ„¸hæÏ˜ ¤ð£ðS¬PD8ß~>óž}¾#×hÀœHUøù›‚CNYB@Q1qlÚÌSÃ'±9(˜.C_cËžƒÖ@óä	lܹž£ßä‡õÛÈÊÊâløeÂ/G±sÑñ‰„]Ф÷+o³æ· ^›9Ÿ¿ZÀæÝûy~üÞüx'Ïå3¸qç>Ú4m`ý~àkS™þÙ2¦¶”ç'L -=§G¿ÉÐ7g±uï!ž2Ã'ÏŸDL|"!¡çع?„ÌÌL&~¸ó#xkÎ"Æ|ð_¾^³‘.Ã&Z‡Æ
˜0•ÿžÍæ]ûé9úMvî?ÀÚßv±vË.Ÿ8C÷‘¯óû¡ãyÊkogK-Ÿ*8~Zÿ§¹E•½Ê±þ‹ty´•½ÊÑ®eSFþ£;ÞýËyвq=úw{€÷Ǿ@å
ž,šbîDöëÒžúµkPÞÃ¥³Þ a]_Ò33	¿•ç3ë×®Á›#Ÿ·~—pÛÃc¨_—¿4€reÝørÚD붯¦OƳŒ›.®ä0Æx.‡›ÿèvêh0ÁAKd=Ü‹ (ü¨L­^£ïÙFg®Rc”"]áçèÑ‘Ÿ/ã¥þ=˜²àfŒ‘u|éÐê8—ÿÄ#ÍóíÏ¿2v`/:>ÔœðËQ}sOwlË#Í—`}ÿŒÏ—ñ\×ÇéÚ®&“‰ŽƒÇ1¢Ÿy³­µs½ôŒb“pwsÌ«7edd²xªùá¹}ƼÃÑÓçñ­Z‰øÄdfNŽOe/ÖÝÉwë¶ðîËÿ¤¶Oz>Þ–VMr;%õ›wígÇ7sxã?_ðëîýÖår‡>û6ªG³úuYóë.Z7m@·mèÖ¡
ç/FàY¶+7n§E#ÿ<å®^¥"§Ã.äùL¹¹ü~f\K;Ó-×2Öõk×ø3„4ð³2<ѶEŽýœx¤yc滆÷^œc››‹3õjU·~¿aÇ^JÙS§º÷-—×ÝÍÅúû©”½½µ,Íúé‚J&“‰ÚþMq-ãÉÎÍ«xâéRR§Þó¤ð£2Ý
W"/²ù§oè3x\¡~®ÉÔãvº½Ÿ¢rÒÚYhR¯{›ï~ì:xŒ—<€w¥
\ŠŒÎwxØÖ=‡Ø{ä„u®Lù²î\ŠŒÎÑÉ--=ûëæþì:xŒFþ5­ß7
¨Í®Gñ­Z	wW|*›—Lmì_‹y߬ºéù8Š¿o5ë÷uØuð¨55ñ¯m=§°Kæ;F¿ý~€O–®¦¦weÃ
ç8”*ŵ´t¤htHfÏžÍÙ³g‰‰‰áí·ß¦Y³fšTL¸8;å	>7Ò頻0¹«]û¾ÈŰPïÝVè}¤ûÉ=§ð£2ݵt†À¹‘HG~î’Æ~µ8yöŸˆ u`}zuz„%Ó&²dÚD¾žõ:Õ,ÅÞ.ÿ¿¹–vÆÎÎÎúPÄÔµ~À¡ãg¬á)>)™Wóžfõëàì蘣ÙùÕäÄuCî?#ˆyËóéÒy{Ô@¦¼:”F~µn¸Dãù‹ÔòÑ‹J‡dÏž=Ì;—èèhfÏž!¿‹/""×VøQø)Ž
{\Yâ¨ËIl(OT¾ßÂ@ÿî3fÊ\kÕŒõ;ö0ah?ž}òQ&ÎZÀÁã¡ü¶ûŸîÀƒêññ—?€ÁÀÓ?DŸ'Ûñòûs8v:ŒÊ<Ù}ðŸ0þ/?·eãz:y†–hPÇ—ÌÌL^yÿ¿˜0‘‘•I#¿š¤^K£ŒKiÆNK³úuùeÛ>|mmš5à£Åß³÷È	Föëž#\µnRŸ¦àS¥"»eÌ ^7-KÓ€:Ìüâ[ü|«±/׊U×;q6ŒÆ~õ¸"â“O>aÿþý„††òàƒê@""w™ós^îäÏM¦È •÷eø1xôævËVÔ‚Æíœãí–ÉàÑSìrƒGï;!jºsq
s\9¢©M(†bòȤ[
?–¡Þ韪oúûcï‘9¤^#hmàäè`}=##“-{ñ@ƒº¸»ºX_O½–FHè9|½+ãénž¼çð	.GEX¿.U+–çJl÷2?͟¾#'y´Ecœ­ûÇÄ'rò\84ðãÔùpjT©hÝv)’‹Wr¬|ôMëÕÆ`0’z?.Eâ_Ó“ÉÄÞ#'IHN¡yC?Â.FÒ°®oŽsعÿ?ý¶‹)¯½§íÈ«Ui%å™8ï½÷sæÌ!((ˆš5kÿ·ù.×=kƒážõþîÏSQ*ov™ïäúDûÐÃLów1,”)òße;‹dù{´òºÓöa*ô;@ºó£2J²»ÛÈ‹H|9[âÂOaÉ=7§´“#µn–g?{{;k˜çu'G‡<ÇÈ=1¸¼‡ûMËàç[æ
üÈÌʲ>¨AßüÛƒgY:?ò`ž×=Ýݬ“«®›ôàSÙ+Oø
´ïË>gÿš>ÖNÁõåÏ`ןCL|£û?­ÿÃ1'N¤|ùò%"üˆÈ½a2™X³l>—ÂHJŒeÙç3¨åטf­+quQ¨s€~T¦ÂRCà*s‰šœQø)^èÕù¦B-UÊžƼp_”µk»VÖåy¥è°µµeذaª¹k¡Ç÷óË_Ëšeó)©S-)üÙ2i6n>ªñÕ9_lÎGáç;¯66ùÞ™¹Ÿ7JÞæÑu	lõX‰¬‡B	@
?EºLöúu‘SuÎS•
?"""R¤¸¸ºóH§^”)[Ža㦕Øz¸ës€~Š|™ÊC1êíß¡ª\ÀƒXÒøs¾“uÜõÿÍï5…¹—žð2eÊ–£b•ê%¶
d85¥bßFPûµQûµQûbÐ>Lj """""RR˜lT"""""RR(‰ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ(‰ˆˆˆˆˆ(‰ˆˆˆˆˆ(‰ˆˆˆˆˆ(‰ˆˆˆˆˆ(‰ˆˆˆˆˆ(‰ˆˆˆˆˆ(‰ˆˆˆˆˆ(‰ˆˆˆˆˆ(‰ˆˆˆˆˆ(‰ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆHq	@®À›À! Hó‡Ã=>׿@ðÖ_¼–ŸGLà›ÔáÀ|œÏv;à”¿ÏÌ60O«¹ŠˆˆˆˆÜÈØ
üh”Ü€ÀPàWËëEU°%À´ìsm+4²„”Ö€m®íU€šÀ ¶ˆœo[Kà›¬QÊkÐ8
tÊÎ@= ?°ÞŠªd`¿%ÌÔʵíÀXc9ßò¹¶g‡¦@°Çòý¿ÕüDDDDDŠ^2ZÎpàG HŽÿº`¾RTm˜‡³µÊµ­-p˜ƒùîσ¹¶?ŒyxÜV57‘¢€L˜ç´€ùNÉí|öó–`$¿/ðç0³N˜ïš¼<‚ù.J*°›?‡š9cž{t̲-ØhyoAÚn9ׇsCÌw‡v[>»íuÛí0‹K³l‡Ï*k	Q—,j%8Þˆð_àr®ýo4gÈì-ï9Œ 簽ɖkb¼g9g“¥n,׿_˜çz]µ¼~Är
Üô#%"""%AUP´˜
à/X:Ýû€',¡äf쀥–÷˜r}eå
@ß[:ÚÙûì±tЀ-ùÄyˈë>óNAó\ŸD ôº€VóbïYþZKˆËUk¹^Ëï3K[öÉ}Àò|Mi`o>û§ß泿-æ;qùÕ·øôºÜÉ7¨Ïì4íÇ1#õã$"""%ÉÊ HÛ•A‘.ª‰¢•}
"Ù3,ðìŽûQ`мµt¢/c¾TÎÒ©o†yÕ¸‡s ,Ìw;|r%ì1–ã„,ôòÀKYï
@6˜ïŒ¤µ-¯µ³¼¿£åû‰˜ïŠdÏzÆR¾Yñ™ÿ²ìwó¥R@e`¦åÜsšq–×b¾ÃT
¨d	'ùí??çµ\,ïie	^™–ÏÍv£Eì,×5èj9Ž3æù_Ó1ß})QHµP2P¶j˜‡«-N\×*\$¶YBÀCq¼ì´™¼Ãõl0KËóyïlËg- æ;=Æë:úoaöçaùþ!Ëñ²‡®Í±”¿ëM>3{ík€_®Ï3«sÌÃØ®uóÙe>ûÿŠyˆaå|ÎÉÏê¦ÞBʇ-aSDDDD¤H ‚~êÀ€>–Žuuà;ÌÌ´tÐí0¯¤v?çÅü•Íä]EΨ„[:ä¹ýl	wµ
ðü¶YŽÙÖ²|v¼eûAÌËe·Å|×+÷üŸüØYêé,æUôr'Ô
ùìïcÙÿL>ûoÌgÿ:˜ï°ýa	6Y×…Ó㘇ôU»…ó7b¾ûäŠyÎÕ'˜¾h‚Æ¾ŠˆˆˆH`s—Ã<,ìqò>CçV¥Ü'õµ×r.­1Ïÿ	Ä|7Æx]9÷cNVðB€˜»‘^ocC³'~ IDAT_[K@±µ|ÙX¾®-·zmöXÕ Ìö~¶Á@ýH‰ˆˆˆHI@`¾ó½`óˆÓ˜h~ÇÍÎcžãÓ Ÿí,ŸwºÏ%ó]žü9fûuÛ–@ÔÐòùüùüŸ¿:_òÞ­2X‚cîýÃ0?\Õ7Ÿý;ä³ÿià
æ¡z†|õÊu7k˜‡ÕMúbžå,$ï|/‘b€æ[¾ž°tÌ,áàÌ«˜9b^r9ÃÒ¹þŸ¥£¼ø‡¥cîŒy„OɹÔô–ã,ż à‰yQQ˜ççüR€õe´{ÌCÁ21/"p½–rŒåÖžÿcVa^”à[Ìw—ì1/j0¼Ka1Ï*…y®UöƒV+bž£Ô-Ÿý—XêåGK rçÏ¡t]0SlÝ{â,ïkeÙ7[)à7àEÌ}u²Üö˜~¨¤$""""÷»‚Xa%ù/‹œýOΈÚóçòη²öË7ø\'KÀ(Œe°ÉU&æe¿swø=,ÁË„y¸œW®í7Z{·¾¶LþË`gï}²sã嫳ÈùÜ$GÌsŒr/ƒíf¹–ùÃ|¤'¹Ÿ³OAÝ‚ùY@?a^Ú:ÓÒa>ŽyùꦘW‚Ë–yèÔPÌ$c^,à€åX[nñsS'·1¯:—†yéë_1Q›w*mås±œSîám	˜
æ%££oá˜)–2ˆÀ¼ÂÛ~ÌÃÒÖæ³2æ;9Ÿ‘–ý÷YBÒ^KI¸nÿ,ÌA}ó"	±–º:‹ùîÓÓ˜šÈv
ó¹í–²e‡ä4Ëç.°œ[ª¥,{€Ñ˜ï¼‰ˆˆˆˆÜß)HUPlØb^©.óp4É•}€Š¦Às˜—¯¶Ç¼ìøbÌw6Q8\ˆˆˆˆˆ(I¡ø‘Ï·ÒrÔ"""""
@ÅJ`pó⑘@h ªQ‘€4ODDDDDDJ QQQQQQQQQQ ‘âÊîN°2(Ò¤j,¾z´ò2¨}ÈÝj""""…Mw€DDDDDDHDDDDDDHDDDDDDHDDDDDDHDDDDDD¨ F>z÷%^èù	qÑy¾—’íNÛÇ­ì¯6'"""RtØäÁbÙ¶a%»¶üŒÉ˜…_ÃhÔì!¶ÁÆæÞe­ÕËæ³øã7(íæI-ÿFÔoÒ’mŸÀ»zmµ‚Br¿¶Qºm™,žó;7ÿH».}p+S–#Á;qrv¡a³‡î‹“íÚwå+r1ì4ß-úˆMk–2åÓÕ”õ¬ –p—…ö!""""
@·,!.š¿o¡M‡nyå]llmé=hi×R1ÌÏJÌHO#x×fvþú¡Çà×ð:uïO€@>~ÿ¼*W£Cç>¬ýþŽÚ߬ìÚÄöM«¸vš¦-ÛñHÇg¨æ[÷¶ËøH§§©Q»>F£çÒ®¬þf.q1‘”õ¬@ÔåpöìØÀÉ`Ο>FÓ¥}—>T«Q£ÑxÃòÙÙÙ«ñöq³koýA±µ#êò–/þˆã‡÷ÐîÉgéÐ¥/ŽNÎù3#=Í\®«¸~–m;Ѿs*TòVc¹‡
lÜ‘ƒ£Î¥]9uìçNÃd2akk‡siWL&¿¬X´‰ƒH½šB‡§ú’”‡})ë16¯ý–Yo .ö
>Ñ6üø?¦MˆÉh¤uû®ìݱ‘·^~–ðs§þvY¯&'s%wÏJ¸•ñ0¿–’Ħ5ßP¡RUZ´íĺK˜ñú0ãcoR>[µ bÐ>nåÚÇEG²hÎÛ”q÷ÄÉÙ•ÏgObÓOK1™LyŽg2™Ø¸æ¦Oú'¶vö´nß• ßÖòáÛ#sSDDDD
_ÝrqsgÀÈ×™õæp&½Ø]ûñDTµÌ±Iˆ‹æÇå©Û cÞšCi7ŒFcŽc$ÅÅðÏQoÑòÑÎâ¢Yñõ<ê5iÃèɳqrv¡i‹G™4¼;[7¬àC'ÜVÇhoý·W_^yk弪àSÓŸ™Ÿ¯ÃÎÞ|GÇÕÍÅsÞ":ê".nîù–OŠGû¸•kðüÈ×ñ«ßŒö]ú0aXWÖ¯úŠ]úRÊÁ1ÇñãcXñõ–¤¸Ú=ÑËúWÿì‰ïÙ]g7ê6hf
	ñ1$ÅÅÐþÉÞ8:•¶—j”-çÅá};è;ø_ncò|×¾#ð,_‰˜+—Y·b	¿¬X‚¯îå9{*„mVphï6.‡Ÿ#=-€¬¬,ëûs—OŠGû¸•kïZÖ“
«PÖÓ¿ú=°›«)‰yPBœ¹\;7­d禕9¶ÅÅ\QC).À»zmŒ|ƒÖí»òÁø|óÙš<ˆ	óP¡›…[{{ìì®+’é/>ì6sHö ïu™ûÁË<Ô¡~
à½õ§v½Æ¼5{)e=+°~õÿøtÚ«7/Ÿùös%â–®}VFFŽán&“	[ûüçeŸK“;иùÃ9¶yiˆˆˆÈ=U`s€’∋‰²vb«×ô§rµš¤¦$“™™‰‹«;Î.n	ÞIjJ²¥i$%9ñ†Çt÷(OÙr^„ØÍµTó_å#/þA\t$
[ßÑcV&ééÄÇ^!>æ2¾uàîQžŒŒtÎ…Uë(@÷kû¸Õk59‘ˆ‹a€y>ÐÉ£ûqqqù´[Þr•-‡kYOœy¼Ûstí3”®}†Ò¥×`ê5j¡Æ """rØíŒ˜+LÒ™&-ÛQ˯!ÎsúØzö…siWœœ]hߥ?,™Í‡ÿE@ãœ=ÊÃ{ҤţùÓµLYžî?ŠySÇ2çý1øÖmÀÖõ+°wp¤M‡î†|'¡ßÈ–u?p$8ˆØ˜H~ù~îž•¨Ð{ûR¸{Vâ§åŸcÌÊâRøYâbÔ:
ÐýÚ>ÜÊ”½¥kŸž–¼©ãx¸cOŽì"&âz¾:
G§<ŸáæîÉ3ýG3Æ8®¥^¥A`ÒÒ®rxïvƼ=—ò–yg""""Rø
ìG¹
®Á``eP¤®ÒMôhåuWë¿ D­Ô…*dáxèwöî&Þªh)™HDîü2¨ÀYU„ˆˆˆH>4H¤˜…QQø … Qø‘ÛdŠ]n0Å.W[HD~DDDJତ$"
?"""%…zVžˆ(üˆˆˆ”& 0ª*€DDáGDD¤$¸fðèmR5ÜŸô T…) –à£ðsÓ … QøQ… ¹×.]ºÄ”)S¤k×®,[¶Œ´´4UŒÂˆˆˆ{6OÑÒÙE-©~ï$%%ñâ‹/bkkËŽ;øä“OX¸p!K–,Ád2ýå{ضm›*RáGDDDÔT%¥¤ø½uôèQ¶lÙB·nÝprr¢J•*ôïߟ¹sç’’’BXX^^^ÜÜÜ>|8áááF^{í5Ž;ÆÃ?L@@IIIªP… uÀï_qqq”)Sëkžžž$''“’’b}íÛo¿%22’2eÊ0{öl¦NJ½zõغu+GÅÕÕUªð#"""7aŠ]®áj%=©~o•-[–„„’““­¯ÅÄÄàââBéÒ¥­¯ùûûãääD£Fغu+F£R¬ð#"""÷C(ÒÃS‹XRüÞ
 mÛ¶¬^½šÔÔT.]ºÄW_}ň#rÔÿ‰'HMMåðáômÛlll°³³ãêÕ«ªH…¹ƒ£*(AHð{ËÕÕ•ùóç“‘‘Áƒ>È/¼ÀàÁƒ0`ß?‹½{÷ÆËË‹ØØXÆŒƒ
ÎÎÎŒ1‚‘#GÒµkW²²²T¡
?"""r4$®h¥TÓ¦USö"/^dñâÅ,_¾œ*UªðÜsÏѳgO	£yóæDEEáêêJß¾}™x”óÂÁÁƒÁ@ùŠUINŒ'ìÌ	L¦¼IÆ`óç©—óªLrbùîwVìŒ`eP$žê{Gua4Y½z5:u¢Q£F4ˆÐÐâ³â\Dt,ÿYòퟅþÿz—U·‘–~ïﺔ«™©ß"""""EH
3¬Y³†O>ù„Ë—/Ó´iS&MšDíÚµïZá	ÙÄò/>$6:’VíºÐö±ÔôkÈðñÓXöÅ,B‚wÒoغõ–ïqjù7bÈØ÷X0k)É	tèÒZ?FB|t¾û×®×ÿ†0¤{3ÒÓRòlŸùÆJ9”¦mÇžôøJž;Pµü1hô[,œý!Á;IOKaÊüµø5hö·ë",,Œ±cÇòý÷ßS¯^=BBBÈÌ,óä«©Œ›:—ê±vátâ“õö‡$&_¥÷Žy“ªwÅ
\³è/}«ûå§‚_aáw~~>Þå	YxWê.)9•{Of
£m«zú'"""%žó@/ÃÃd2™8wî:tÈÑwvvÆßßÿïÎ``eP¤®ÒMôhå…ÉdâÔ©S<ôÐC,^¼˜6mÚàêêš#œþòË/,\¸_ý•wÞy‡W^yå–êÿÛ€)2hå 8ä$½F¿É†ÅR˧
Ë~ÚħKW³vátb“è8p,1‰æ0ºþóéx–-ÃSC&ðÝœw¨Qµ2߯ûO—®æø9ó0³éã†1 G'Â#¢¬û9::XSÚ¡µiÆ#Rµby¢bâèš28Ç1jûVâË·2{Þ3/ ñé¬6èq23³n¸-Ëhä§uûX¸d#ñ	)Œx¡OwmÉØÉ‹˜÷ù&êÕ­ÄîSquq*˜_½¢}ˆˆˆˆ&S
ËÈÈ 99™ˆˆÒÓÓ	´†£ÑÈÚµkéÞ½;nnn|ôÑGªú\ŒÆ,^y¾½uuº­¼8´wÛ-¿ß××—>ø€¡C‡R¥JFŽIXXû÷ïçÅ_dÒ¤IÄÇÇßRø¹ŸÄ'%ãæâLigGëke˸‘’šÊÕkiÖ׿¼>š‹Û¿§±Î»Ž‡Ožaêü¯YðÞxÿø9þ5ªÞôóæÿ{‡×.&9%•í{PÁ³,{W|Æ™_—ññìç÷æÂ/FÿeÙýjWáø©DDÅó{ð)bb‰º’@Ðîã´~Лœù!øÐYÞø`)˾x•KÇçÓ°^•<Û–~>–KÇrnÛðc'/â?S³ò˜öÑJ~ßÊÔ·úS¯n%¶þô6Gwý§ÀˆˆˆHQU`Cà®ï€'$$пÆµþÃ?°bÅ
lllT󹨨ØòÑ—ép;;;L¿~ý

e̘1|ýõ×Lœ8‘°°0<<ó
Ëœ¢ëçí9Á¯ûö´~
{6½OOç[*»£ƒ=]:5cÖÜÕ´¬Cƒz>Ìúïjìlm¨^­Bžý+Wò 6>™°ð+\‰NäðѰ|·EE%䨿]¥ujVàtðÇc¾Å»œAÿh‡Á€­
WSÓôC""""R¨8wÀÿލˆp=Õ˜ðó¡9þ}³ýî„““Û¶m£[·nâííMŸ>}hÚ´)óæÍãÝwߥL™2En¢‹³3_IFfþù*c>˜C¿®Ó§s»|@È­aÝšLú^˜4†][çݎǺ֠uóºÔhô"Í;L&*æê-½Ï`0ЬqM¶ï
%À¿>Þ幓È5Å¥´cžý›6ôå‰}yvÐL*ù
µÎõɽ­r½a9¶6®Éô?ϘI_P¦ÚsI×ö­oé=÷ãCN¯D'Ògð,^ügGzukuo~yh)zL6ÈËË‹wÞy'ßm¶¶¶<õÔS<õÔSŶ&S¯&ó¯>Áð	өߤ%*z³hÍAkÈ‘{'Ëhäñc9~™†~5éñx[Ú·
,Ðð“•e¤yûqì?üçµÞ´òuÚ?ܰàÎÃò¡g"lìKŸžñD‡¦ºÀ""""·Á®(Þh4¼k3ëWý#û¶[Ÿõc4Ù³}ëV}ÅÕä$Ú=Ù›GŸìEB\4cv$%1†R¥©S?ß:õ9zp7•ªÖ`àÈ7ð¬P‰¨ˆðû=Üéiz
|…ò^Uˆ‹‰bÜÎ$ÅÅP§~ O>=æudÉÜw¹ôÇ)ÞÙÊÕêð¯w?å±ÿ°ÞÊÈHç¥~mò/÷ùìݱõ«¾"9)ÎÏ¢MûnØÚÙ©¥Þ[6yûÃþnçέ­
Á[fÝÝó(„Ï)îŠôŠgNfÞÔqôø2Ë~=m}Ðééã™?sƒ_ú7“§/æ×Ÿ—³wÇFëûƽÿ9ó–qùâyÜ=*ðîœïˆ½Â‘ýA9Ž?ö|¹î(Î.n¬^ú)&“‘²žølÅ^–ýz†¾ô6Ÿü6±W"0ò
*W«Ã»sW1wÙvJ»ºå)o~ÇËq>'±ð£7úêûLž¾˜ï¿œÃñ#{ÕJïûqØ›ˆˆˆˆ”ðtåò\ÜÜ©^«ß§y	7w*TöÆÍÝߺ
¸~“	ìíKQµzmJ»¸âäTšêµëáà茻GyÒÓRs¿Z:888Q£v!ûƒ0f‰‹ŽdñÜwÞ»c´#&⮦$ÝRyó;^Žó‰¸HLÄïÕ‚¸pî8Îx½………áííÍñãÇsü[~DDDD€î‚‚ꀗ¯X•äÄxÂΜȱ@9¯Ê$'Æuù‰	qœ=y„ÊÞ¾d/v}XÊ^ò8÷Ý€çCIO»ÆùÓÇhÒ[N„ìcïö
Lÿl-Ó?[Gi7OL&ó1mlmI¿–zÃòæw¼ë•óªŒW_>ùîwVìŒ`eP$žê«Vªð#""""¤HO.©é×áã§±ì‹Y„ï´Îªå߈!cßcÁ¬I¤$'СK?hý	ñÑ·uü™o¡”CiÚvìIï¯`0ØP»^ü>ÀîÍHOK±îëàèDçg1oÆ||ý2æ½[:Þõjù7bÐè·X8û
B‚w’ž–”ùkñkÐL-UáGDDDD
@-ƒ};ÂÂÂhÓ¦
6lÀÙÙÙúoÿœ…»GË`ßéÒÔ…){ì°°0š7oNTT{÷î¥I“&¬Y³†˜˜ÈóÏ?Oÿþý‰ŠŠºåú§˜-ƒ­ðS€¿<´¶ˆˆˆ=¦†——ƒƒÁÀ¾}ûÈÊÊbÕªU<ùä“´iÓ†ššªj¿‹–,YBFFÍš5#88˜Ñ£G3sæLV¬XÁ—_~ÉO?ý¤JRø)±
|Ü’%KèׯvvvìÙ³‡Ñ£G³~ýzÊ•+GÏž=)[¶,Í›7¿¿;Ä×=ç()UªØY–ÍÇÃÃ5jààà@“&M

åPËWø)‘
t„›uÀË•+gí€ßî9¹jóç%õöö&66–óçÏè]»vöÐ6……‘§Àïݨ^®\98@Û¶mÕ/$üç?ÿáå—_&>>žAƒѹsg®\¹¢ð£ð#"""¢¤xÑåããCxxxŽ×lmméÙ³'={ö¼é¾¹ß§ð#""""ÅÕ=Yî–wV+J²W»[õO1[Ná§Û‡V‘¢Çd£:’BáGDDDD€DáGDDDD€¤h	ÃÛÛ›ãÇ«2~DDDDän uÀEáGDDDDJLQø )4éééÔ«W777†n]Þ:""‚š5kâææÆã?Ί+0„……áåå…Á`À`0°oß>…QR¼èøöÛo‰ŒŒ¤L™2Ìž=£ÑHÅŠ9sæ			̘1ƒq¯^}±sIDATãÆåxöÏ’%KÈÈÈ Y³f
?""""¢¤xÑáïï““5bëÖ­F._¾Ìk¯½†ŸŸ7æìÙ³$&&PªT)±³³+ç¯ð#""""…€Jzü^;qâ©©©>|˜¶mÛbccî]»X³f
AAAìÙ³‡
*äl6Åc$¤Âˆˆˆˆz*ÉðûAïÞ½ñòò"66–1cÆ`ccCóæÍiݺ55jÔ yóæDEE»óVø‘[Qà·]z÷î««+}ûöeòäÉy:àIIIªõ»ÀÇLJÈÈÈ|·U­Z•°`Á‚<Û®Ѝð#""""Å0Yþûw™L&ÓÝ)œÁÀÊ H]¥›èÑÊ‹»YÿwØ6L‘A+~Šã/ÞÑ>DDDD
“IcϤÈRø‘Û¥$
?""""¢$¢ð#""""
@"
?""""¢$¢ð#""""
@"
?""""¢$¢ð#""""
@"
?""""¢$
?"""""
@¢ð#""""¢$
?"""""
@¢ð#""""
@"
?""""¢$¢ð#""""
@"
?""""R4“å¿—IÕXìÛjr—Ú‡ˆˆˆHa2©ƒ*"""""%&iœˆˆˆˆˆ”
@"""""¢$"""""¢$"""""¢$"""""¢$"""""¢$"""""¢$"""""¢$"""""¢$"""""¢$"""""¢$"""""
@""""""
@""""""Eœ0©DDDDD¤$ø? 5è‚IEND®B`‚urwid-2.6.16/docs/manual/images/urwid_widgets_2.xcf000066400000000000000000006232021470350774000222310ustar00rootroot00000000000000gimp xcf v011@–B³üB³üÓW
Unnamed #12ÏDOÜC´DOÜC´DOÜC´DOäC³DOäC³DOäC³Unnamed #11È?C´P?C´P?C´P?C³4?C³4?C³4DOádC´zoDOádC´tßDOádC´oNDOä-C³7§DOä-C³7§DOä-C³7§Unnamed #10·>àC³°>àC³°>àC³°Unnamed #9A	Cx°Cý@Cx°Cý@Cx°Cý@C‡@Cý@C‡@Cý@C‡@Cý@Cx Cý8Cx Cý8Cx Cý8Unnamed #8=Cx€Cñ@Cx€Cñ@Cx€Cñ@C‡8Cñ8C‡8Cñ8C‡8Cñ8Unnamed #7;Cx€Cñ@Cx€Cñ@Cx€Cñ@C‡(Cñ@C‡(Cñ@C‡(Cñ@Unnamed #6/	C‡ÈC÷ÈC‡ÈC÷ÈC‡ÈC÷ÈC‡¨DôC‡¨DôC‡¨DôCiDäCiDäCiDäUnnamed #5-C‰ÀCíC‰ÀCíC‰ÀCíC‹PCî@C‹PCî@C‹PCî@Unnamed #4'	C‡ÈCñhC‡ÈCñhC‡ÈCñhC‡°DC‡°DC‡°DCŠ(CøPCŠ(CøPCŠ(CøPUnnamed #3!	C~DÜC~DÜC~DÜC‡˜DÜC‡˜DÜC‡˜DÜC‡¨Cý¨C‡¨Cý¨C‡¨Cý¨CŠ0Cö@CŠ0Cö@CŠ0Cö@Unnamed #2Cw`CåØCw`CåØCw`CåØC‡ÀCåàC‡ÀCåàC‡ÀCåàUnnamed #1	C‡ÀCÚ0C‡ÀCÚ0C‡ÀCÚ0C‡¸CýÈC‡¸CýÈC‡¸CýÈChCèàChCèàChCèàUnnamedCPDäCPDäCPDäC€CûøC€CûøC€CûøËgimp-image-grid¬(style solid)
(fgcolor (color-rgba 0 0 0 1))
(bgcolor (color-rgba 1 1 1 1))
(xspacing 10)
(yspacing 10)
(spacing-unit inches)
(xoffset 0)
(yoffset 0)
(offset-unit inches)
gimp-image-metadataã

  8 8 8
  540
  832
  1
  2
  90/1
  90/1
  1
  1

1v?R#Ã(-‚25µ:Ç?TDaI1RV¯\Na5Overlayÿ!?€	"
 
ºâ%$ÿÿÿÿ#ÿÿÿÿgimp-text-layerø(text "Overlay")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)

M5
i5
$$$õüáû/ù&£çûí±7üáû/÷/íÿè¾Þþ÷Hüáû/Ƕÿ¸
—þÕÍþYaþÅ”ç÷ÈBáÇ@Ñôáû/B´äøÒVÉþrqþÄõü>Îøû/tþ©±þk²ÿäÂøó+áûóâ°
áû/\÷ÍËþø+fþÄÁþg-úøÎäþM÷íñô.ûålþ‡áþ–áû/
ºÿaíø"÷ñ-úøòäþM¸ýPXþ®Wþÿá§áü8áû/bÒ÷þÿÿg˜þrlþ¦ôü>ñøû.]þ ¨ýSXþí¸¦{áû/áû/8üê4¬ÿg6ûüýIµÿ·	—þÔîÜåè/ûç
áû/áû/bÿÆÙÿgÉôóß-ìÿç½ÞþöGÚ¢üþ—®ÿí¼Æò>áû/áû/@üýÉåðÿgfþÿ‡ù&£çüí²7üGýü=á‡Þúï°áû/áû/‡ñô Cûgøú,0üdýÃ.ûfÄûûK/üúÓUm=	GridFlowÿ!?€	"
 
2â%$ÿÿÿÿ#ÿÿÿÿgimp-text-layerù(text "GridFlow")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
”=°=ÈÄÄÄüÇë-üPþÁüáû/ø‚Ô÷ñÒ„ü„¤úPþÁ¹ÿúóáû/øÖÿúÉÈïðNþÁ¹ÿи¸¯áû/ß¡ÿÚ&áÇ@Ñôáû/'ÁöÏxüÁ¹ÿWäáû/
‘æ÷ÎW·þWDýýFTþ²ñýLéáûóâ°
áû/ÑÿêÅöþÁ¹ÿWŒáû/²ÿçÂøüUsÿ–ùü€•þm,ú÷ª¸¸²áþ–áû/7üò!tþÁ¹ÿи¸†áû/.ûñdþË4ûѽÈкÒú++ú÷ìÿÿ÷áü8áû/XþÆ7ûÁ¹ÿŹáû/VþÈ%úôáø'ð–ì'øÛõü:ø÷áû/áû/XþÅ4ûÁ¹ÿWÇáû/WþÈ%úô	¢ý{ûU]û~þ™¸ÿ¹
ø÷áû/áû/9üðmþÁ¹ÿWÇáû/+úñdþÌ]þÅù")úËþT0ïÿæ»Îþ÷áû/áû/ÔÿéÃóþÁ¹ÿWéáû/ªÿçÁøý[øûÝçüõá*§êüóÒ”áû/áû/+ÆøÌ?îÁ¹ÿWéáû/ŠãùÓ\Îÿ§¸ÿÄ23ListBoxÿ!?€	"
 
ªâ%$ÿÿÿÿ#ÿÿÿÿgimp-text-layerø(text "ListBox")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
-3I3aüüüÔüÇë-'ý¹ÿ_ü„¤óz3¹ÿÿýîÁSý¹ÿ_òPýg¹ÿÒ¹Üþú3ý¹ÿ_óáû/tÜùæ¦Nåÿዹÿ_Ñÿa
‘æ÷ÎW{þлþ•¹ÿ_Ñáû/Cý÷Áàï{èÿÔ¸d¹ÿ_&äü=²ÿçÂøüU	ÑþmTýá¹ÿ_íáû/QþÔ¯ÿg¹ÿîú~.ûñdþË>úëáýTý¹ÿ_×áû/
Äþõ$¯ÿg¹ÿÒ¸Òþè0VþÈ%úô
™ÿÿ®ý¹ÿ_×áû/ZÌþî%¯ÿg¹ÿ_§ÿ—WþÈ%úô	´ÿÿÃý¹ÿ_×áû/ºÿd«ÿp¹ÿ_«ÿ +úñdþÌ[ýÞÏþmý¹ÿÞÌÒXáû/[÷É¿øü?‡ÿ츆¹ÿÒ¸ÐþüMªÿçÁøý[äýS=ûí ¹ÿÔoáû/5Ê÷úØj»ùð{¹ÿÿþ÷ÊUŠãùÓ\ÿ¸¡ÿ¯ÿ,Frameÿ!?€	"
 
"â%$ÿÿÿÿ#ÿÿÿÿgimp-text-layerö(text "Frame")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
W,s,‹pppÝÿ¹ÿþó$ù¹ÿи¸¯$ý¹ÿWØáÇ@ÑôB´äøÒVáÐiØöÌ?“äô¨”ç÷ÈB¹ÿW®áûóâ°\÷ÍËþø+áÿûÆðÿÿðÅûþ{²ÿäÂøó+¹ÿи¸†áþ–
ºÿaáÿ€Pýü?ÿ¬.ûålþ‡¹ÿà¹áü8bÒ÷þÿÿgáü>*úðeÿ²Wþÿû§¹ÿWááû/8üê4¬ÿgáû0(úècÿ²Xþí¸û{¹ÿWØáû/bÿÆÙÿgáû/(úècÿ²/ûç
¹ÿWØáû/@üýÉåðÿgáû/(úècÿ²®ÿí¼Æò>¹ÿWÜáû/‡ñô Cûgáû/(úècÿ²‡Þúï°ÜPileÿ!?€	"
 
šâ%$ÿÿÿÿ#ÿÿÿÿgimp-text-layerõ(text "Pile")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
:RôôôløÇë-áû/ï¹ÿÿü褄¤áû/÷¹ÿÒ¼íÿÉüáû/¢¹ÿ_"ùù!áû/áû/”ç÷ÈB¹ÿ_)úùáû/áû/²ÿäÂøó+¹ÿÒÀóÿ¸áû/áû/.ûålþ‡¹ÿÿýä–áû/áû/Wþÿû§¹ÿ_õáû/áû/Xþí¸û{¹ÿ_ìáû/áû//ûç
¹ÿ_ìáû/áû/®ÿí¼Æò>¹ÿ_ðáû/áû/‡Þúï°|=Columnsÿ!?€	"
 
â%$ÿÿÿÿ#ÿÿÿÿgimp-text-layerø(text "Columns")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
!n=!Š=!¢ÄÄÄüáû/(ùœæøÞŸ$üáû/'ø ãÿçÂèö!üáû/&¿¦ÿ»4
‘æ÷ÎWáû/ìú(óù!áÐiØöÌ?“äô¨áÊQÖøÎ:tÜùæ¦ñü;ȲÿçÂøüUáû/ìú(óù!áÿûÆðÿÿðÅûþ{áüöÆâÿÞCý÷Áàï,ú÷È.ûñdþËáû/ìú(óù!áÿ€Pýü?ÿ¬áÿ ùù!QþÔ-ú÷
ÈVþÈ%úô
áû/ìú(óù!áü>*úðeÿ²áü<êú(
Äþõ$öû4€ëWþÈ%úô	áû/ìú)÷ù!áû0(úècÿ²áû/èú(ZÌþî%¾ÿ«+úñdþÌáû/æýDCüù!áû/(úècÿ²áû/èú(ºÿd:ôþã½ÒôªÿçÁøý[áû/µÿÚ™æÿù!áû/(úècÿ²áû/èú([÷É¿øü?6µðüé©
ŠãùÓ\áû/#½øè†£ù!áû/(úècÿ²áû/èú(5Ê÷úØj17LineBoxÿ!?€	"
 
22%$ÿÿÿÿ#ÿÿÿÿJgimp-text-layerø(text "LineBox")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
%ß7%û7&LLLäüÇë-+ý¹ÿ_ü„¤ø¹ÿÿýîÁSý¹ÿ_÷¹ÿÒ¹Üþú3ý¹ÿ_Íáû/áÊQÖøÎ:”ç÷ÈB¹ÿ_Ñÿa
‘æ÷ÎW{þлþ•¹ÿ_Íáû/áüöÆâÿÞ²ÿäÂøó+¹ÿ_&äü=²ÿçÂøüU	ÑþmTýá¹ÿ_éáû/áÿ ùù!.ûålþ‡¹ÿîú~.ûñdþË>úëáýTý¹ÿ_ñáû/áü<êú(Wþÿç§¹ÿÒ¸Òþè0VþÈ%úô
™ÿÿ®ý¹ÿ_ðáû/áû/èú(Xþí¸ç{¹ÿ_§ÿ—WþÈ%úô	´ÿÿÃý¹ÿ_Óáû/áû/èú(/ûç
¹ÿ_«ÿ +úñdþÌ[ýÞÏþmý¹ÿÞÌÎXáû/áû/èú(®ÿí¼Æò>¹ÿÒ¸ÐþüMªÿçÁøý[äýS=ûí ¹ÿÐoáû/áû/èú(‡Þúï°¹ÿÿþ÷ÊUŠãùÓ\ÿ¸¡ÿ¯RBoxAdapterÿ!?€	"
 
ª2%$ÿÿÿÿ#ÿÿÿÿIgimp-text-layerû(text "BoxAdapter")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
*&R*J-vR*j,Ó(üPþÁù¹ÿÿýîÁSü¤ÿýMüPþÁõz¹ÿÒ¹Üþú3ûíõþ¦üNþÁÝPý¹ÿ_Ñÿa
‘æ÷ÎW{þлþ•Zþãî¢'ÁöÏxüÁB´äøÒVááFÖö´Nåÿ¹ÿ_&äü=²ÿçÂøüU	ÑþmTýá²ýLžþ\ÑÿêÅöþÁ\÷ÍËþø+áþñÂìÿ´{èÿ¹ÿ€•ú~.ûñdþË>úëáýTôîQý´7üò!tþÁ
ºÿaáþn0úø¯ÿ¹ÿÒ¸Òþè0VþÈ%úô
™ÿÿ®iþ°ðõXþÆ7ûÁbÒ÷þÿÿgáû7äü7¯ÿ¹ÿ_§ÿ—WþÈ%úô	´ÿÿÃÀÿ€¼þkXþÅ4ûÁ8üê4¬ÿgáü9åû7¯ÿ¹ÿ_«ÿ +úñdþÌ[ýÞÏþm#ùýÕÔÔáþÂ9üðmþÁbÿÆÙÿgáþu0úö«ÿ¹ÿÒ¸ÐþüMªÿçÁøý[äýS=ûí wþÆ$ùù%ÔÿéÃóþÁ@üýÉåðÿgáÿùÂëÿ®‡ÿ¹ÿÿþ÷ÊUŠãùÓ\ÿ¸¡ÿ²ÐþrßÇþz+ÆøÌ?îÁ‡ñô Cûgáúyß÷¯»3üáû/;üáû/;üáû/ˆhhhYÿ3ÿgÿÇ‹”ç÷ÈBáÇ@ÑôÔ¸d²ÿäÂøó+áûóâ°g.ûålþ‡áþ–gWþÿû§áü8úgXþí¸û{áû/ñp/ûç
áû/ñ츆®ÿí¼Æò>áû/ñùð{‡Þúï°áû/\)
:AttrMapÿ!?€	"
 
"2%$ÿÿÿÿ#ÿÿÿÿHgimp-text-layerø(text "AttrMap")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
/ž:/º:/Òˆˆˆ%ü¤ÿýMýz3ýz3û¹ÿýLû øÿèûíõþ¦ýPýgýPýgû¹ÿÿ™ûmþÿèöZþãîNåÿý‹Nåÿ“‹áÇ@Ñô
¹ûßá¿èôèB´äøÒVááFÖö´²ýLžþ\{èÿÔ¸d{èÿÔ¸dáûóâ°¹ùžü;÷óè\÷ÍËþø+áþñÂìÿ´ôîQý´¯ÿgɯÿgáþ–¹ú\ûˆjüN÷è
ºÿaáþn0úøiþ°ðõ¯ÿgÕ¯ÿgáü8¹ú1ÞÓ¼ã÷èbÒ÷þÿÿgáû7äü7Àÿùþk¯ÿgɯÿgáû/¹ú,–úGö—÷è8üê4¬ÿgáü9åû7#ùýÕÔÔáþ«ÿpš«ÿpáû/¹ú,Iý×üE÷èbÿÆÙÿgáþu0úöwþÆ$ùù%‡ÿ츆‡ÿ츆áû/¹ú,
ëÿä	÷è@üýÉåðÿgáÿùÂëÿ®ÎþrÍÇþz»ùð{»ùð{áû/¹ú,©ÿ™÷è‡ñô Cûgáúyß÷¯1üáû/5üáû/5üáû/x#Fillerÿ!?€	"
 
š2%$ÿÿÿÿ#ÿÿÿÿGgimp-text-layer÷(text "Filler")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
4&#4B#4Z¼¼¼”ôÇë-áû/áû/þ¹ÿòó„¤áû/áû/ø¹ÿи¸¯øáû/áû/ü¹ÿWááû/áû/áû/”ç÷ÈBáÇ@Ñô¹ÿWÀáû/áû/áû/²ÿäÂøó+áûóâ°¹ÿи¸†áû/áû/áû/.ûålþ‡áþ–¹ÿð¹áû/áû/áû/Wþÿû§áü8ü¹ÿWñáû/áû/áû/Xþí¸û{áû/ü¹ÿWèáû/áû/áû//ûç
áû/ü¹ÿWèáû/áû/áû/®ÿí¼Æò>áû/ü¹ÿWèáû/áû/áû/‡Þúï°áû/²>
ScrollBarÿ!?€	"
 
–%$ÿÿÿÿ#ÿÿÿÿ•gimp-text-layers(markup "ScrollBar")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
8N>8j>8‚ØØØøáû/áû/ù*´ñòÎðáû/áû/¹ÿÿýîÁSø
àþØÈòÂïáû/áû/¹ÿÒ¹Üþú3½:üß	’äúápáÇ@Ñô
‘æ÷ÎWáû/áû/¹ÿ_ÑÿaB´äøÒVáÇ@Ñô&ùøb¢¸ÿëÁìeáûóâ°²ÿçÂøüUáû/áû/¹ÿ_&äü=\÷ÍËþø+áûóâ°ŽþþÖR1ûó"áþ–.ûñdþËáû/áû/¹ÿðú~
ºÿaáþ–õSÑþþ†WþÉÔáü8VþÈ%úô
áû/áû/¹ÿÒ¸Òþè0bÒ÷þÿÿgáü8÷güõXþÈÔáû/WþÈ%úô	áû/áû/¹ÿ_§ÿ—8üê4¬ÿgáû/Å)4ñù"8üò !áû/+úñdþÌáû/áû/¹ÿ_«ÿ bÿÆÙÿgáû/Å[þèÁÞþÅËÿë½ã‡áû/ªÿçÁøý[áû/áû/¹ÿÒ¸ÐþüM@üýÉåðÿgáû/í,³èú眨ïû×Láû/ÛŠãùÓ\áû/áû/¹ÿÿþ÷ÊU‡ñô Cûgáû/99Paddingÿ!?€	"
 
2%$ÿÿÿÿ#ÿÿÿÿFgimp-text-layerø(text "Padding")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
<ã9<ÿ9=tttúüPþÁøPþÁÇë-ø¹ÿÿüè¤
üPþÁøPþÁ„¤÷¹ÿÒ¼íÿÉüNþÁüNþÁ€è¹ÿ_"ùù!B´äøÒV'ÁöÏxüÁ'ÁöÏxüÁáû/áÊQÖøÎ:'Â÷ÎOøÁ¹ÿ_)úù\÷ÍËþø+ÑÿêÅöþÁÑÿêÅöþÁáû/áüöÆâÿÞÎÿæÄöþÁ¹ÿÒÀóÿ¸
ºÿa7üò!tþÁ7üò!tþÁáû/áÿ ùù!6ûñtþÁ¹ÿÿýä–bÒ÷þÿÿgXþÆ7ûÁXþÆ7ûÁáû/áü<êú(XþÈ6ûÁ¹ÿ_Ì8üê4¬ÿgXþÅ4ûÁXþÅ4ûÁáû/áû/èú(XþÈ3ûÁ¹ÿ_ÌbÿÆÙÿg9üðmþÁ9üðmþÁáû/áû/èú(8üñhþÁ¹ÿ_Ì@üýÉåðÿgÔÿéÃóþÁÔÿéÃóþÁáû/áû/èú(ÔÿåÂõþÁ¹ÿ_Їñô Cûg+ÆøÌ?îÁ+ÆøÌ?îÁáû/áû/èú(,ÈùÌ{þ¿0ÿ
ý{ÿª0øߨÁÆûüM0ù’áûöËXsCScrollableÿ!?€	"
 
š–%$ÿÿÿÿ#ÿÿÿÿ¨gimp-text-layerû(text "Scrollable")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
AvCAšDUCAºD2øáû/áû/üáû/üáû/ù*´ñòÎøáû/áû/üáû/üáû/ø
àþØÈòÂøáû/áû/üáû.üáû/»:üß	’äúápáÇ@Ñô
‘æ÷ÎWáû/áû/B´äøÒVáúhÖö´áû/”ç÷&ùøb€„¸ÿëÁìeáûóâ°²ÿçÂøüUáû/áû/\÷ÍËþø+áþòÄñÿ´áû/²ÿäÂŽþþÖR1ûó"áþ–.ûñdþËáû/áû/
ºÿaáþn9ûøáû/.ûåSÑþþ†WþÉÒáü8VþÈ%úô
áû/áû/bÒ÷þÿÿgáû7åü7áû/Wþÿ÷güõXþÈ€Äáû/WþÈ%úô	áû/áû/8üê4¬ÿgáü9æû7áû/Xþí¸¸)4ñù"8üò !áû/+úñdþÌáû/áû/bÿÆÙÿgáþu:û÷áû//ûç[þèÁÞþÅËÿë½ã‡áû/ªÿçÁøý[áû/áû/@üýÉåðÿgáÿùÄðÿ±áû/®ÿí¼,³èú眨ïû×Láû/ÖŠãùÓ\áû/áû/‡ñô CûgáÖXãø²áû/‡Þú@;;;èÈBøó+lþ‡ÿÿ§¸¸{
Æò>ï°!
<Opt ...ÿ!?€	"
 
rÁ%$ÿÿÿÿ#ÿÿÿÿ²¥gimp-text-layer‰(markup "Opt\n_\nSc\nroll\nbar")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
G<G*<GB°°°(ú7Â÷ìœøì¿9ñ®­ùwmþP§øAýÿþÜ>Úÿÿù‡ü9Žû2AýšÐÕùwmþP§øAýZž÷
ùwí¿8ñ¯Aý˜ÏÖø†í:ÄøîŸAýÿþÝ?¯ûìýAüSýAüSåÿ÷ÿÿJúïøáL
úDûbeS
üNýˆú‹ëþ¹õ	ªüþÚAgüj
ø\ñÖø	õC€äºhühó1ËòúÎ5Žìþ¹aúAüSAüS
úAüSAüS
úAüSAüSïAýÿÿg’îô®AüSAüSïAý¤	jüU/ô™AüSAüSïAüW–øÙÃBüSAüSïAüSjüS-ô™AüSAüSïAüS•ðö±AüSAüSRýAüSýAüSýAüSÛAýÿþÜ>3ûþð”AýÿÿgAýšÐÕ^û@Aý¤	ðAýZž÷
5ÐúÿÿZAüWðAý˜ÏÖ”ù*iý[AüSðAýÿþÝ?;Þþÿÿ[AüS?…Scroll Widgetsÿ!?€	"
 
p%$ÿÿÿÿ#ÿÿÿÿ´˜gimp-text-layer|(markup "Scroll Widgets")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
KØ…LQèQô…L,N²Q¯ÀÀÀ÷
~ÑôñÒ™=ø6û6ûýAü¦þÎÿþû/ø6û6ûüñßö|þÛN	)\´-ø6û6ûü¿ú)ü´üD%ø6û6ûýþcý¯ü8	ç^ÁïóÉdHýo@Ä÷È
~Ö÷ð»Hø6û6ûôDý¢dþÌI
ý§þÿóýEHý¬÷ÿÿÊ	Àÿôüj6û6ûðòÛ‹úÿûÕŸPÛfþç^4™?Hýý”qþÛDwúò6û6ûúÁú%Bóo¨ØýþÆÂû=üHý½üÃû3ôþf6û6ûû„þ_÷©þ¢æßýHýzüäÞôEýŠ6û6ûûFý¼øôèæßýHýoüäÞôEýŠ6û6ûûóØïø÷ñÂû;ýHýoüÄû2ôþf6û6ûáÃù_û™™I#@ÅÿÀfþå[
4˜?HýoírþÙBtùò6û6û	ú†þÔ÷¯ÿùð:ªþÿúýEHýoþ	Áÿôüj6û6û	òHýÿÌc³æûöÖ“"ôdÇóøÎbHýoù×øñ½Jø6û6û	üôÿ’@ÀÀÀüÄÿ’ùÖõ6ûý_þS$ûóýÍùùÈ6ûý_þSý>ü{ûFû°÷ýXþŒý_þSý>ü{û„ñCúPý—ýMý_þSý>ü{ñÀ¿êŽÒö6û÷“æöÊL_þS÷”çöÊL_þSõaÅòöÏj{ÿç÷tÑöñ„µÉøÉ6ûÉÿøûªþSËÿ÷ú©þS¤þÿûþ˜{ÿÂ÷‰þÿÿûGyöTþ6ûtþÑ<ŒýþSuþÐ;‰ýþS`ýÊC:Éü<>ü{çÕîNô=ûL“ýN6ûÄø(÷±þSÅø'÷¯þS¾óø(úˆ>ü{çÁÒÆ
ðŠÎö6ûäÖøkþSäÖìjþSäþüüýýþÿÿ¦>ü{è6ßÝ—‹¾Æ÷Ê6ûäÖøkþSäÕújþSçÿú«>ü{üBxNûƒôcþŽø6ûÄø(÷±þSÅø&÷®þSÃêý<ü}ÿûGûÕýOË6ûtþÑ;‹ýþSvþÎ9…ýþSdýÃFL«C+ú·¿h*	ûôÿöù6ûÊÿøûªþSÍÿ÷úªþO þÿúýHÞÿý÷ðÿûÇÿËý6û÷”ç÷ÍP_þS÷—è÷ËMuü@÷UºíøÝ¯Pö9Âôþ÷6™Ôö"üÂ÷6÷‹\( þ¼6ÿ²ÿþð37ù&Ÿåúéª-Zrrr'û÷Ó~ÿúk
0Š_ãi1§çÓ3ÛÎOðçÿÿþóÉkB!<Opt ... #1ÿ!?€	"
 
Â%$ÿÿÿÿ#ÿÿÿÿ«gimp-text-layere(markup "Opt\n_\nSc\nroll\nbar")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
TŒ<T¨<TÀ°°°(ú7Â÷ìœøì¿9ñ®­ùwmþP§øAýÿþÜ>Úÿÿù‡ü9Žû2AýšÐÕùwmþP§øAýZž÷
ùwí¿8ñ¯Aý˜ÏÖø†í:ÄøîŸAýÿþÝ?¯ûìýAüSýAüSåÿ÷ÿÿJúïøáL
úDûbeS
üNýˆú‹ëþ¹õ	ªüþÚAgüj
ø\ñÖø	õC€äºhühó1ËòúÎ5Žìþ¹aúAüSAüS
úAüSAüS
úAüSAüSïAýÿÿg’îô®AüSAüSïAý¤	jüU/ô™AüSAüSïAüW–øÙÃBüSAüSïAüSjüS-ô™AüSAüSïAüS•ðö±AüSAüSRýAüSýAüSýAüSÛAýÿþÜ>3ûþð”AýÿÿgAýšÐÕ^û@Aý¤	ðAýZž÷
5ÐúÿÿZAüWðAý˜ÏÖ”ù*iý[AüSðAýÿþÝ?;Þþÿÿ[AüS?:Scrollable_ ...ÿ!?€	"
 
/Ô%$ÿÿÿÿ#ÿÿÿÿªƒgimp-text-layerg(markup "Scrollable_\ncompatible")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
YB:Y^:YvpppQúAüSAüSýAüSýAüSúïøáLúAüSAüSýAüSýAüSúDûbeSúAüSAüSýAüSýAüSüNýˆÓ‹ëþ¹Aýÿÿg’îô®AüSAüS3ûþð”AýÿþÜ>AüSíôŸ
Ì	ªüþÚAgüjAý¤	jüU/ô™AüSAüS^û@AýšÐÕAüSiûH.øwø\ñÖøÞAüW–øÙÃBüSAüS5ÐúÿÿZAýZž÷
AüS–ÿÿ¨ÎC€äºhühAüSjüS-ô™AüSAüS”ù*iý[Aý˜ÏÖAüSjúHÌ1ËòúÎ5Žìþ¹AüS•ðö±AüSAüS;Þþÿÿ[AýÿþÝ?AüSìþÿorþ÷ÿÿœúAüSAüSýAüS,úAüSAüSýAüS(ýùwýAüSýAüS‰‹ëþ¹’îô®AýÿþðÒõä:AýÿþÜ>3ûþð”	Úÿÿù^üSAýÿþÜ>AüSíôŸ
güjjüU/ô™Aý•8û¼ð AýšÐÕ^û@ùwAüSAýšÐÕAüSiûH.øw–øÑ–øÙÃCüVø‚ä«AýZž÷
5ÐúÿÿZùwAüSAýZž÷
AüS–ÿ‹¨hühjüS-ô™AüSøå«Aý˜ÏÖ”ù*iý[ø†AüSAý˜ÏÖAüSjúHŽìþ¹•ðö±AüSøå«AýÿþÝ?;Þþÿÿ[¯ûìGüSAýÿþÝ?AüSìþÿoýAüS6ýAüSZTOriginal_widgetÿ!?€	"
 
©Ù%$ÿÿÿÿ#ÿÿÿÿ©Ÿgimp-text-layerƒ(markup "Original_widget")
(font "Sans-serif Bold")
(font-size 14)
(font-size-unit pixels)
(antialias yes)
(hint-style full)
(language "en-us")
(base-direction ltr)
(color (color-rgb 0 0 0))
(justify left)
(box-mode dynamic)
(box-unit pixels)
(hinting yes)
^ýT_!a„T_A`ðLýAüSýAüSýAüS
ýAüS÷Þ7Â÷ìœýAüSýAüSýAüS
ýAüSöÞì¿9ñ®ýAüSÔÞmþP§øAýÿÿgAüS
§øÿÿ¯AüSAýÿþæ@3ûþð”AüSÏŽë
æ¦ØAüS
§øÿÿ‡ü9Žû2Aý¤	AüSküU6ø¯AüSAýœí¨ù^û@AüSÅMú;Öð6Þ›AüSküS5ømþP§øAüWAüS“øä¯AüSAüVÛ²5ÐúÿÿZAüSÅõओùZAüS“øäí¿8ñ¯AüSAüSküQ6ø¯AüSAüSÚ²”ù*iý[AüSÅÄó®aôøAüSküQ3ø:ÄøîŸAüSAüS¨øÿÿ¯AüSAüSÚ²;Þþÿÿ[AüSð…ÿr'úÐAüS¨øÿÿý-ñ˜9ú÷þò¯þ÷ÿÿPðððÿ¯ÿ¯ÿ¯
Éùw¯
§øÿÿ¯íôŸÚÿÿù¯küU6ø¯iûH.øwùw¯“ø䯖ÿÒ¨ùw¯küQ6ø¯júHø†¯¨øÿÿ¯ìþÿo¯ûìý-ñ˜
ú÷þò¯*@urwid_widgets_2.pngÿ!?€	"
 
%$ÿÿÿÿ#ÿÿÿÿb¤@bà&R&^&j&v@f˜pyRƒ„†Ìˆ(‹UŒÿ!Žwãš$¢/¬¨µ¦¾UéÏ
Ú¡ßÀä"êÀòÎúí¬Æq|a¨#*Q.Ý0û6ÀDÒJŒN"OtS”Xê]8^ÿekænHsvt€x„z|â}܃‡iŠcŽšŸ¨¼ªÊ·ç½IÇEÍÖ›æÒö$¾ÆêdÄÀ„5å!A#%#Ü@>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ%ÿÿÿ
#ÿÿÿ"ÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿýÿÿÿÿÿ	ÿÿÿ	ÿÿÿ"ÿÿÿþÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ.¯þéÿ-¯éÿÿ,¯éÿÿ+¯éÿÿ*¯éÿÿ)¯éÿÿ(¯é@>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ%ÿÿÿ
#ÿÿÿ"ÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿýÿÿÿÿÿ	ÿÿÿ	ÿÿÿ"ÿÿÿþÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æ@>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ%ÿÿÿ
#ÿÿÿ"ÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿýÿÿÿÿÿ	ÿÿÿ	ÿÿÿ"ÿÿÿþÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ.éþ¯ÿ-é¯ÿÿ,é¯ÿÿ+é¯ÿÿ*é¯ÿÿ)é¯ÿÿ(é¯@ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ˜ü‡gF!%ÿÿÿÿþµ3#ÿÿôÿð/Q³ÿÿo"ÿÿýÿðüRþö$ý	ý(úÿýÿðýžÿ®ù­óÿÿä^ýzäÿþ×DøŸñÿÿãhÿýÿðý-ÿá÷%êû˜_mÎÿxìÄÿÏxZyÅqÝÿ³duÛÿÿýÿð÷
ÿþ
¶ýCöÌôpÿ®úÿœýÝÿýÿðùéÿ,,ÿ±øZÿcâúýæùþhÿýÿðøùÿWÿ¶pùÿŒ
ÿ×ýÿ×þ+ÿýÿðøÿðyÿú÷ûž+ÿ»ý(ÿ¾þÿýÿðøbÿÊ`ÿxýÿÔýÿÕþ)ÿýÿð÷éÿ]7ÿ¸	ýåùýéøþ_ÿýÿðüKÙÿ²üÈÿRø	
zÿŸúŠÿ…ýÔÿôÿú  §Çéÿû™à+òýšZIg™ð4ÐÿÁhIc°oæÿŸSeÐÿÿÿÿûðÒ´%ý°üÿýò©ý	“ùÿþäKø¶þÿÿ÷~ÿ"û,>#û*="ú7)ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ?áÿ5ÿÿ5ÿÿ5ÿÿ%ÿÿÿ
ÿ
ÿ	ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÓÿ¯.éÿíÿ¯.éÿíÿ¯.éÿí	ÿ¯.éÿí
ÿ¯.éÿíÿ¯.éÿíÿ¯.éÿí
ÿÿ¯.éÿíÿ.éÿíÿ-éÿíÿ,éÿíÿ+éÿíÿ*éÿíÿ)éÿíÿ(éÿíÿ'éÿíÿ?áÿ5ÿÿ5ÿÿ5ÿÿ%ÿÿÿ
ÿ
ÿ	ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÓÿ6ÆÿÍî5ÆÿÍî4ÆÿÍ	î3ÆÿÍ
î2ÆÿÍî1ÆÿÍî0ÆÿÍ
î/ÆÿÍî.ÆÿÍî-ÆÿÍî,ÆÿÍî+ÆÿÍî*ÆÿÍî)ÆÿÍî(ÆÿÍî'ÆÿÍî?áÿ5ÿÿ5ÿÿ5ÿÿ%ÿÿÿ
ÿ
ÿ	ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÓÿé.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®	ªé.¯ÿ®
ªé.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®
ªÿé.¯ÿ®ª.¯ÿ®ª-¯ÿ®ª,¯ÿ®ª+¯ÿ®ª*¯ÿ®ª)¯ÿ®ª(¯ÿ®ª'¯ÿ®ª?ÿáþ'/5þþÔü5ý¶ÿþÔü5ý¶ÿþ%ý
+ü%ý¶ÿ
þ
ý
!	õ<ð‡<Ñüÿ(uÓÿïö‘
ðûÿñððÄÆìø‹ìÿÿé€öð¦$Æúÿõsÿ•á@ÿÄò¬uˆÏ·zZl¾ÿºxØÿ…xxbÔü÷ÅÿÄjmÌÿ·öÿÑå¬p¨úÿXøý/@ÿÿdÿý¢ÿBý¶ÿþÔüð_ÿ¹	ÆÿOÿÿrýHÿËùÿ’@ÿÔý?ÿtý¶ÿþÔüýÄÿ4÷Fÿ´ÿèý÷ðùÿ¸@ÿ¥÷]±Ðæèëÿ”ý¶ÿþÔüýê÷ø
ÿÚÿ¾ýÅÿùÿÔ@ÿö¾ÿØ›‰€•ÿžý¶ÿùÔüÿàùðöÿ²ý¾ÿ
ùÿº@ÿý^ÿšýAÿžý¶ÿþÔüþìöøþÜÿ²ý¾ÿ
ùÿ•@ÿýÿ@ýwÿžý°ÿ#þÔüýÇÿ+ø=ÿ·ÿ²ý¾ÿ
ùÿ7@ÿýƒÿ‚üëÿžý’ÿWþÔüñhÿ¦¹ÿXÿ²ý¾ÿ
ÿ¢ý@ÿö5üðImåÅÿžöIÿ枀iÔüñÐÿ²Y]¾ÿÃÿ²ý¾ÿ
ÿý@ÿ÷añÿÿõ’,ÿž÷mÖóÿÒÔüø¡üÿÿû–	ýÿ²ý¾ÿ
ü3/ü2/Óÿ?Ùÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿ	ÿÿÿÿ	ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿ ÿ
ÿÿ+ÿÿÿ6ÿ7ÿ:ÿçÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯?Ùÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿ	ÿÿÿÿ	ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿ ÿ
ÿÿ+ÿÿÿ6ÿ7ÿ:ÿÏÿîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æ?Ùÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿ	ÿÿÿÿ	ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿ ÿ
ÿÿ+ÿÿÿ6ÿ7ÿ:ÿÏÿªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'é?ÿÙý0	*ý˜ƒü˜˜ù|˜¨ÿ(	äüíýü[ÿÿgø	ùõ¨ÿ(	äý¯ÿNüšøì¦øBÿ»	äýoÿŽüÚÀ®æý‚ÿ{	ù!ääþ

ýý/ÿÎúÿpÿ&øÂÿ;ð%÷óÿö·ää÷Áüÿéˆ/ðŒù«óÿÿåaéíýYÿB2ÿeøõ¨ÿ(÷§ÿÍmkÆÐíäöèÿ›^và½ÿ–ø"éû™`lÌÿð¯ÿM™ù	ñ¥Aÿ»ñ¨ÿ(6ÿË	¿ÿäýÿtüêÿ–ý²þFþÈðoÿŒØÄ¶ä€ÿ{ø¨ÿ(›ÿOý?ÿäýîíøÿ–(ÿµÿVð.ÿÌÿ„xÿ$Àÿ:ø¨ÿ(ÀÿøþäÿÆøOÿ–Sÿ¹pÿðíüWÿE:ÿc÷õø¨ÿ(Ûÿùëä(ÿµø:ÿ–uÿû÷ò®ÿK—úö£?ÿºø¨ÿ(ÂÿøýäÿÌøUÿ–\ÿ|ûnÿŠÖÇû¾â~ÿzø¨ÿ( ÿFý6ÿäýãöø–ÿ–3ÿ¼û.ÿÝÿˆû€ÿÜÿ:ñ¨ÿ(Aÿ¿±ÿäðtÿš3õÿ–ÅÿVûìÿÿHûBÿÿôý¨ÿ(÷´ÿ»US²ÝðãöËÿÏ«õ“ÿ”ø(ñþœ[Ig˜ü®ÿü
ü
úÿºý¨ÿ(÷žüÿÿÏää÷	‘×î¹YDÿyý®üÿÿó ý9
ýpÿXû+>$+ÿüæò6øÕk\…ëÿw7ùžíÿÿü×U:ý Ïÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿ?ÿ<ÿ<ÿ<ÿÿ0ÿÿÿ+ÿýÿÿ+ÿÿÿÿÿ+ÿÿÿ1ÿÿÿ-ÿÿÿ/ÿÿ/ÿ	ÿ+ÿÿÿÿ+ÿÿ+ÿÿ8ÿîÿ¯é¯ é¯!é¯"é¯#é¯$é¯%é¯&é¯'é¯(é¯)é¯*é¯+é¯,é¯-é¯.é?ÿ<ÿ<ÿ<ÿÿ0ÿÿÿ+ÿýÿÿ+ÿÿÿÿÿ+ÿÿÿ1ÿÿÿ-ÿÿÿ/ÿÿ/ÿ	ÿ+ÿÿÿÿ+ÿÿ+ÿÿ8ÿîÿÆ?ÿ<ÿ<ÿ<ÿÿ0ÿÿÿ+ÿýÿÿ+ÿÿÿÿÿ+ÿÿÿ1ÿÿÿ-ÿÿÿ/ÿÿ/ÿ	ÿ+ÿÿÿÿ+ÿÿ+ÿÿ8ÿîÿé¯é ¯é!¯é"¯é#¯é$¯é%¯é&¯é'¯é(¯é)¯é*¯é+¯é,¯é-¯é.¯?ÿý<ýrÿ^<ýrÿ^<ýrÿ^ü	$&0üµ÷ÿõðû"±þÿþßF+ù|Z´ÿ©xö
Ôÿ§p[w¿f+ùörÿ^ý,ÿ´ÿ+ùÿgrÿ^ýý¼1ùÿrÿ^ú»ÿÞˆ<-ù÷¢rÿ^ø|çÿÿì„/ýrÿ^úC™ìÿ™/ýlÿg	ý"ýí+ù	Nÿ›þýöá+ìï8ñõ¯€Xä‡ZCpÇÿ„+þª÷BÅìÿÿ8Îýÿýñ…8û.@)	îÿ?Àÿéÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯?ÀÿÆÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/ÆÿÆ?Àÿ¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé?ÿÀÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ?ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8¯'é¯(é¯)é¯*é¯+é¯,é¯-é¯.é¯.éÿí¯.éþíÿ
¯.éýíÿÿ¯.éÿíÿ¯.éÿíÿ
¯.éÿíÿ	¯.éÿíÿ¯.éÿíÿ?ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8?ÆÿÍ=ÆþÍî<ÆýÍîî;ÆÿÍî:ÆÿÍî9ÆÿÍî8ÆÿÍî7ÆÿÍî?ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8é'¯é(¯é)¯é*¯é+¯é,¯é-¯é.¯é.¯ÿ®é.¯þ®ª
é.¯ý®ªªé.¯ÿ®ªé.¯ÿ®ª
é.¯ÿ®ª	é.¯ÿ®ªé.¯ÿ®ª?ÿÀÿéÿíÿéÿíÿéÿíÿéÿíÿéÿíÿéÿíÿéÿíÿéÿíÿéÿéÿéÿéÿéÿéÿéÿéÿéÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆîÆîÆîÆîÆîÆîÆîÆîÆ¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ª¯ª¯ª¯ª¯ª¯ª¯ª¯ª¯?ÿÀÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿéÆ¯?ÿÀÿé¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/Æ/ÆÆ¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é?ÿÀÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ'¯é&¯é%¯é$¯é#¯é"¯é!¯é ¯é¯é¯ é¯!é¯"é¯#é¯$é¯%é¯&éÆ'é¯&é¯%é¯$é¯#é¯"é¯!é¯ é¯é¯é ¯é!¯é"¯é#¯é$¯é%¯é&¯?ÿÀÿéÿíÿéÿíÿéÿíÿéÿíÿéÿíÿéÿíÿéÿíÿéÿíÿéÿíÿ
éÿíÿéÿíÿéÿíÿ
éÿíÿ	éÿíÿéÿíÿéÿíÿÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆÿÍîÆÿÍî
ÆÿÍîÆÿÍîÆÿÍî
ÆÿÍî	ÆÿÍîÆÿÍîÆÿÍî¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª¯ÿ®ª
¯ÿ®ª¯ÿ®ª¯ÿ®ª
¯ÿ®ª	¯ÿ®ª¯ÿ®ª¯ÿ®ª?ÿÀ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿQ.¯.¯.¯.¯.¯.¯.¯¯ûYWWn¯þœ¯¯úK]C
y¯þ¥Z¯¯ú¯¯[.¯ô‹fu¨¯Ÿ{®¯¯„•¯ô’¯¯‚”¯¯~¤©z¯¯ë¯§8E¯¯aG¦«$`¯|Ÿ¯ñq-¯ª8¯£§š¯¯a¯ì(§¯¥€¯®[¯˜x¯ñŸª3®v(¯š¯¦¯í„¤<1¯ˆª¯¯O9¯¯sT¯òSg.ŒHV¯š¯Œ¯í¯¯“ŸŠª¯¯L:¯¯G6¯òKS%•\^„¯š¯¯ì¯¯j¬§
€¯¬_¯sž d¯òy¯Š«¯š¯§
¯ë'1n¯¯j>%©–
Н”Œ¯ò¥A¯­
1¯¯š¯¯h¯ü„ƒƒ–¯ù•q€¬¯—¯þ“”¯ö‰›¯¯—¯¯ª„¯.¯.¯.¯.¯.¯‘.é.é.é.é.é.é.ééÿvtýäÍ&ééþ]týäÉ
ééþ»éþÉ
éñ»ˆšßéå¡çéÏ£éé˹éö˸ééµ¼éé°Òéþ»éìÉ
éé…	^+%Ûé¹émÇé^téö¯#éé1éæÈéæXéÉ
éÞ¦éè,uéX{é0LŠé!²éöä	ÏÅ2Û·é鿝ÚÚàéÉ
é¹áéémHé•>Ù«MÎ
äé÷A’ˆoW¡zZééþ»éíÉ
é»áééiJéјYáCé÷~UK­”d<—ééþ»éíÉ
éà§éå'zéé%(–é;(€é÷»âÐ!ÓéQ.Æ.Æ.Æ.Æ.Æ.Æ.ÆÆûdcc}²Æþ±ÆÆúUiL‰ÆþºfÆÆúŸÆÆf4Æôžt„¾Æ³‹ÅÆÆ•¨Æô¥£ÆÆ“§ÆÆº¿ŠÆÆëŸÆ½?NÆÆnQ#!¼Á(mÆ´Æñ3ÆÀ@Ƹ½®ÆÆmÆì-½Æ»ÆÅ"fƬˆ’Æñ³À’:Ć.Æ®Ƽ	Æí•º¢D8ÆšÁÆÆY@ÆÆ‚_Æò!’^t4žRbƮƞÆíŸÆÆ¦´œÁÆÆVBÆÆP=ÆòU^*¨hj–ƮƠÆìŸÆÆxý‘ÆÂkÆ‚³$qÆò‰ Æœ"ÁÆ®ƽÆë,7}ÆÆxG*¿ªœÆ§žÆòºJÆÄ7ÆÆ®ÆÆvÆü•””©Æù¨€ÂÆ«¢Æþ¦§Æö›¯ÆÆŸ«ÆÆÁ–Æ.Æ.Æ.Æ.Æ.Æ‘.Æ.Æ.Æ.Æ.Æ.Æ.ÆÆÿdcý®!ÆÆþOcý«	ÆÆþŸÆþ«	ÆñŸtƒ½ÆÃ‰ÄÆ°ŠÆÆ¬Æö¬œÆÆš ÆÆ–³ÆþŸÆì«	ÆÆqP$ ºÆÆ\©ÆPcÆö•ÆÆ*ÆÄªÆæ	KÆ«	ƽ	ÆÅ%cÆKiÆ(@uÆ—ÆöÁ°¨+º›ÆÆæ•ºº¾Æ«	Æ¿ÆÆ\=Æ5¹‘A¯	ÂÆ÷7|t_J‰gLÆÆþŸÆí«	ÆŸ¿ÆÆY?ƲK¿y9Æ÷kH@“~U3€ÆÆþŸÆí«	ƾŽÆÃ!hÆÆ "Æ2"mÆ÷ŸÀ±³ÆQ.é.é.é.é.é.é.ééûvtt“ÑéþÐ#ééúd{Z¢éþÛxééú»ééy=éô¹ˆ›àéÓ¤èé靯éôÃÀéé­Äéé¨Úá£ééë»éßJ\éé‚	_)'Ýä0€é¥Ôéñ–<éâKéÙÞÍéééì5ßéÜ	ªéè(yéË ¬éñÓâ¬Eç6éÍéÝ
éí¯Ú¾OBéµ	ãééiLéé™oéò'¬o‰=º`séÍéº	éí»ééÄÔ¸ãééeNéé^Héòeo1Æz}#°éÍé¼éì»ééåÞ
«éä$~éšÒ*„éò¢%é¸(	äéÍéß
éë4A%“ééŽ	S1áȸéĺéòÛWéæAééÍéé‹é¯ÿÇéùÆ—ªäéÉ¿éÄéö¶Ïéé»Éééã°é.é.é.é.é.é‘.¯.¯.¯.¯.¯.¯.¯¯ÿYWý¬š¯¯þFWý¬—¯¯þ¯þ—¯ñft§¯¬y®¯œz¯¯˜‹¯ö˜Š¯¯ˆ¯¯„ž¯þ¯ì—¯¯dG ¥¯‹¯R–¯GW¯ö„¯¯%¯­
–¯æ

B¯—¯§}¯®!X¯B]¯$9h¯†¯ö«œ”&¥‰¯¯æ„¤¤¨¯—¯‹©¯¯R6¯p/£€:š¬¯÷1nfTAy[C¯¯þ¯í—¯©¯¯O8¯rC©k2¯÷_@8‚oK-q¯¯þ¯í—¯¨~¯¬\¯¯q¯-`¯÷ªœŸ¯?ÿÐ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ@.¯.¯.¯.¯.¯.¯.¯¯þeR¯þª’¯¯þ[D¯þ¯æ‰g[D¯¯¦po¡€£¯¯¤th”¯¯dxxª¯æI&D¯ª"K$‡¯‘8Iw¯D	RR¥¯鄯®D¯i¯¯ˆ‡¯1V¯¯s¯¯鬯¯ED¯GI¯¯¬‡¯	3AA;ª¯𪯯CD¯L@¯¯§‡¯f‰ý­¯ð¯¬D¯w¡¯n‡¯0D¯û®¯£	­¯æD+D¯®<#1‹¯– N=-¯¯3%<£¯ûr–𔝖÷Ÿ¯¯ª€m~¨¯ýœ„«¯¯ûf‡x/1"¯¯ûa75Y§"¯.¯.¯.¯‘.é.é.é.é.é.é.éýæÙéþVéþæ¾éýètàéþ’AéþÙéáè äééÀ‰³’AééäšÏ²Ðééៈ½ééž  Ùéáæ×é—	[05Aéè;#g* šéÐ%BgˆétmmÏéäæ×é–éé8A饿éΚéZZéé³äÙéóæ×ÔÔéévAéyGéô šé%;WWVÉÙéóæ×ÖÑéésAé;éûšé$t¶ýáÙéëæ×é’éè5Aé¹Í鬚éZAéûçéæ×é@.Æ.Æ.Æ.Æ.Æ.Æ.ÆÆþr\ÆþÀ¥ÆÆþfMÆþ£Ææ›t fMÆÆ¼~¶¸ÆÆºƒv¨ÆÆqˆˆÀÆæ	S!+MÆÀ"'U)™Æ¤@S†ÆM
\\»Æ镯ÄMÆw ÆÆš™Æ7bÆÆ‚Æ£ÆéÂÆÆNMÆQRÆÆÃ	™Æ
:JJCÀ£ÆðÁÆÆLMÆVHÆÆ½™Æ	t›ýÄ£Æð’ÆÃMƇ¶Æ|™Æ6MÆûÄÆ¸
ÄÆæM1MÆÅD(8Æ©$YE3ÆÆ9*D¹Æû¢ª¯¨Æôª©ª´ÆÆÀ{¾Æý°•ÁÆÆûs™ˆ58"ÆÆûn>B"ééû‚IFwß"é.é.é.é‘.¯.¯.¯.¯.¯.¯.¯ý­£¯þvA¯þ­¯ý®W¨¯þn1¯þ£¯á®x«¯¯g‡n1¯¯«tlœ†œ¯¯©wfޝ¯wxx£¯á­¡¯qE$(1¯®-N t¯œ1Nf¯WRRœ¯ä­¡¯q¯¯*1¯|­¯št¯DC¯¯‡¬£¯ó­¡ŸŸ¯¯Y1¯[6¯ût¯-Aü—£¯ó­¡¡¯¯V1¯_-¯ût¯W‰ý©£¯ë­¡¯n¯®(1¯‹š¯t¯C1¯û®¯­
¡¯?ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿÿ'¯éÿÿ&¯éÿÿ%¯	éÿÿ$¯
éÿÿ#¯éÿÿ"¯éÿÿ!¯
éÿÿ ¯éÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ0ÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯þš‘¯û§€¢ÿÿÿÿ
¯õC8C-¬¯5U–ÿÿÿÿ¯ôž%¯¯?€¯¯¯ÿÿÿÿ¯ôtH¯¯eV¯®¯¯ÿÿÿÿ¯ø›'¯¯A~¯¯ÿÿÿÿÿ
¯ù=ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ0ÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆþ¯¤ÆûŸ½·ÿÿÿÿ
ÆõK@L2ÂÆ<`©ÿÿÿÿÆô³*ÆÆG‘ÆŸÆÆÿÿÿÿÆôƒRÆÆraÆÅÆÆÿÿÿÿÆø¯,ÆÆJŽÆÆÿÿÿÿÿ
ÆùEDR,ÁÆÆÿÿÿÿþ÷ÞÿþÞ÷ÿÿÆþ¦™Æÿ™Æÿÿÿÿýš&€ÿý€&šÿÿÆÿÿÿÿûÈ;
?û?
;ÈÿÿÆÿÿÿÿûÈ;
?û?
;ÈÿÿÆÿÿÿÿýš&€ÿý€&šÿÿÆÿÿÿÿþ÷ÞÿþÞ÷ÿÿÆþ7¾ÿÿÆþ—Äÿÿ'é¯ÿÿ&é¯ÿÿ%é	¯ÿÿ$é
¯ÿÿ#é¯ÿÿ"é¯ÿÿ!é
¯ÿÿ é¯ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ0ÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéþÎÁéû»ßªØÿÿÿÿ
éõYKZ;äéFqÇÿÿÿÿéôÒ1ééT«é	»ééÿÿÿÿéôš`éé†réèééÿÿÿÿéøÏ4ééW§ééÿÿÿÿÿ
éùQOa4äééÿÿÿÿþ÷ÞÿþÞ÷ÿÿéþÄ´éÿ´éÿÿÿÿýš&€ÿý€&šÿÿéÿÿÿÿûÈ;
?û?
;ÈÿÿéÿÿÿÿûÈ;
?û?
;Èÿÿéÿÿÿÿýš&€ÿý€&šÿÿéÿÿÿÿþ÷ÞÿþÞ÷ÿÿéþAàÿÿéþ±æÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ&éÿíÿ%éÿíÿ$éÿíÿ#éÿíÿ"éÿíÿ!éÿíÿ éÿíÿéÿíÿ?.¯þÍÿ-¯ýÍéÿ,¯üÎééÿ+¯ÿÏéÿÿ*¯ÿÏéÿÿ)¯ÿÐéÿÿ(¯ÿÐéÿÿ'¯ÿÑéÿÿ&¯ÿÒéÿÿ%¯ÿÒéÿÿ$¯ÿÓ	éÿÿ#¯ÿÓ
éÿÿ!¯þ°Ôéÿÿ ¯þ°Õéÿÿ¯þ°Õ
éÿÿ¯þ°Öéÿÿ¯þ°Öéÿÿ¯þ°×éÿÿ¯þ°×éÿÿ¯þ°Øéÿÿ¯þ±Øéÿÿ¯þ±Ùéÿÿ¯þ±Ùéÿÿ¯þ±Úéÿÿ¯þ±Úéÿÿ¯þ²Ûéÿÿ¯þ²Ûéÿÿ¯þ²Üéÿÿ¯þ²Üéÿÿ¯þ²Üéÿÿ¯þ³Ýéÿÿ¯þ³Ýéÿÿþª¯þ†¢¯þ³ÞéþÇÄéÿÿþA¡¯úR‘¯¯³Þéþ„zéÿÿ欯¯ŽŽŸ¯•§¯‘Ö¬Úéé䳪äéé„zéÿÿæ"¯8éÿÚÓÿÞéÿÿýš&€ÿþ€&¯ýž€ÁéÿŠ^ÿ¤éÿÿûÈ;
?þ?¯þ¶â*éÿÿûÈ;
?ú?¯¯·â+éÿÿýš&€ÿø€&¯·â鉗éþÚ½éÿÿþ÷ÞÿøÞ÷·ãééw‡éþ²Léÿÿ&ÆÿÍî%ÆÿÍî$ÆÿÍî#ÆÿÍî"ÆÿÍî!ÆÿÍî ÆÿÍîÆÿÍîÿ?/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/ÆÿÿþÀÆþ—·Æþ©¦ÆÿÿþJ¶Æþ\¤ÆþqgÆÿÿæ£ÂÆÆ¡¡³²Æ¨½ÆŸ¾’ºÆÆÂ˜ÁÆÆqgÆÿÿæ&²Æ@DJ(iÆ>šÆ@[!žÆz^e(¢ÆqgÆÿÿï&²¬1ÆÆ=iÆ>次{_Æú©m_ÆqgÆÿÿæ&²„UÆÆbiÆ>𯯯”DÆ…7UN8ÆqgÆÿÿæ&²³+ÆÆ7iÆ>𯯯”DÆ*¯Æp7ÆqgÆÿÿæ&²ÆU69/oÆ>𯯯”DÆUG^27ÆqgÆÿÿæžÁÆÆ±µ?¡Æ¥»Æ™ÆÆº¦ÆÄ”޹¢Æ±¯Æÿÿþ÷ÞÿþÞ÷ÆüamE6Æÿº³ÿ½Æÿÿýš&€ÿþ€&Æý³ŒªÆÿuPÿ‹ÆÿÿûÈ;
?þ?/ÆÿÿûÈ;
?þ?/Æÿÿýš&€ÿþ€&Æþt€Æþº¡Æÿÿþ÷ÞÿþÞ÷ÆþesÆþ—@Æÿÿ&¯ÿ®ª%¯ÿ®ª$¯ÿ®ª#¯ÿ®ª"¯ÿ®ª!¯ÿ®ª ¯ÿ®ª¯ÿ®ªÿ?.éþËÿ-éý˯ÿ,éüʯ¯ÿ+éÿɯÿÿ*éÿɯÿÿ)éÿȯÿÿ(éÿȯÿÿ'éÿǯÿÿ&éÿƯÿÿ%éÿƯÿÿ$éÿÅ	¯ÿÿ#éÿÅ
¯ÿÿ!éþèįÿÿ éþèÃ¯ÿÿéþèÃ
¯ÿÿéþè¯ÿÿéþè¯ÿÿéþèÁ¯ÿÿéþèÁ¯ÿÿéþèÀ¯ÿÿéþçÀ¯ÿÿéþ翯ÿÿéþ翯ÿÿéþ羯ÿÿéþ羯ÿÿéþ潯ÿÿéþ潯ÿÿéþ漯ÿÿéþ漯ÿÿéþ漯ÿÿéþ廯ÿÿéþ廯ÿÿþ©âéþ²Øéþ庯þ–“¯ÿÿþW×éúmÁéé庯þd[¯ÿÿæÀäéé½½ÓÑéÆÞé·²¤¯¯¬‡€«¯¯d[¯ÿÿæ-ÑéKPW/{éIµä8PŒ¯lSY$¯d[¯ÿÿï-ÑË:ééH{éI±¹
¯lT¯ú–aT¯d[¯ÿÿæ-Ñ›eéés{éH¯¯¯‚<¯u1KE1¯d[¯ÿÿæ-ÑÒ3ééA{ã:ˆ¯¯¯‚<¯%š¯c1¯d[¯ÿÿæ-Ñéd?D7¸7ˆ¯¯¯‚<¯K>S-1¯d[¯ÿÿæºäééÐÕH•¯‘¥¯‡¯¯¤“¯®ƒ~£¯œš¯ÿÿþ÷ÞÿþÞ÷éür€O2¯ÿ¤Ÿÿ§¯ÿÿýš&€ÿþ€&éýÒ ¯ÿhGÿ{¯ÿÿûÈ;
?þ?éþâ¶*¯ÿÿûÈ;
?ú?ééá¶+¯ÿÿýš&€ÿø€&éᶯgq¯þ¤Ž¯ÿÿþ÷ÞÿøÞ÷ᵯ¯Yf¯þ†9¯ÿÿÿÿÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿÿ'ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿ'ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ¯þ©¯ÿy
¯ÿšÿÿÿÿ¯þ¡A¯ÿ3
¯ý
;Èÿÿÿÿ¯ß‹¡¯¯¥–|¬‘¯¯­‚œ“©¯¯¦–ž‚­¯¯£~
;Èÿÿÿÿ¯ß 'K.V¯‚3["¯š"O2‘¯¯…6K ®¯K]Lšÿÿÿÿ¯àjY¯®«‚¯¯"¯d_¯ª‘¯¯…¯¯8ˆ¯¯ªÿÿÿÿ¯à@|¯¯1‹‚:¯¯"¯A€¯¯"‘¯¯…:¯¯Nq¯J;Rÿ¯âh[¯®ª‚=¯¯"¯j[¯§‘¯¯…:¯¯NqŸ!¯îÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æÿÿ'ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿ'ÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&ÆÿÿÿÿÆþ¿Æÿ‰
ÆÿšÿÿÿÿÆþ¶JÆÿ9
Æý
;ÈÿÿÿÿÆß¶ÆÆ»ª²Â¤ÆÆÄ”±¦¿ÆŸÆ¼©³”ÄÆÆ¹ŽŸ
;ÈÿÿÿÿÆßµ,U4aÆ”:g²'Ư'Y9¥ÆÆ—=U$ÄÆUiVšÿÿÿÿÆàxdÆÄÁ”ÆÆ²'ÆqlÆÁ¥ÆÆ—ÆÆ@šÆÆÁ£ÿÿÿÿÆàHÆÆ7”AÆÆ²'ÆI‘ÆÆ'¥ÆÆ—AÆÆY€ÆTC\ÿÆâufÆÄÀ”EÆÆ²'Æxfƽ¥ÆÆ—BÆÆY€´%ƪÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éÿÿ'ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿÿ-ÿÿÿ'ÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿéþá©éÿ¡
éÿšÿÿÿÿéþ×WéÿD
éý
;Èÿÿÿÿéß¹×ééÜÈÑ¥äÁééæ®ÐÄáé»éÝÇÒ®æééÙ§»
;ÈÿÿÿÿéßÕ4e=ré®%EzÑ.éÎ.iCÂéé±$Hd*çée{ešÿÿÿÿéàŽvéçä®ééÑ.é„éãÂéé±ééKµééãÀÿÿÿÿéàU¥ééA¹®MééÑ.éV«éé.Âéé±Mééh—écOmÿéâŠyéçâ®QééÑ.éŽyéßÂéé±Nééh—Ô,éÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿ¯/é¯0é
¯1é¯2é¯3é
¯4é	¯5é¯6éÿ?ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜+ÿ?&¯ÿÌé%¯ÿÌé$¯ÿÌé#¯ÿÌé"¯ÿÌé!¯ÿÌé ¯ÿÌé¯ÿÌé¯ÿÌé¯ÿÌ é¯ÿÌ!é¯ÿÌ"é¯ÿÌ#é¯ÿÌ$é¯ÿÌ%é¯ÿÌ&é¯ÿÌ'é¯þƒ¥
¯ø§¯ÌééÉÁ$é¯þ/
¯ýEÌéþ‹téþÎ_é¯þ/¯žë¯¡’¯®¯‘ÆééÁ»‹téé٫ݾéøÅÊéé¤,¶Ëéû'®¯/¯Þ{E¯T®‹:¯.ÊéWIW/té˜1nD#éâE`]Zé~{¨éû6‰¯/¯à¦¯+>]hÌ5ÊÕ0ééLté:Êé¡#éŸqééFÎÆ9éû*f¯/¯á(/sa/¯é5Ê£]éé{téééÍ#é`2HHE¬Æ9éû/f¯/¯çV3[¡/éé5ÊÒ1ééOtéCÄéš#éšiéþÐ2éÆÿ?ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜+ÿ?CÆþ”º
Æþ’½Æþ«¤'Æþ6£
ÆþN²ÆþvcÆþ¯QÆþ6£Æ³ëƶ¥ÆÄ¢Æ¥ÁÆÆ¤ŸvcÆÆ¹‘¼¢Æø¨¬ÆÆŒ%›¬Æû,ÅÆ6£ÆÞ‹NÆ`ÅžAÆ-¬ÆJ>J(cÆ*]9ÆÀ;ROLÆkiÆû=›Æ6£Æà¼Æ1F¢ivÆ-¬µ(ÆÆ@cÆ1¬Æ‰Ƈ`ÆÆ<¯¨0Æû/tÆ6£Æá- 6‚m5ªÆ-¬ŠOÆÆicÆÆÆ®ÆR+==:’¨0Æû5sÆ6£Æça:g¶5ÆÆ-¬³*ÆÆCcÆ9¦ÆƒƃYÆþ±+Æé/¯éÿè	é0¯
é1¯é2¯é3¯
é4¯	é5¯é6¯ÿ?ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜+ÿ?&éÿ̯%éÿ̯$éÿ̯#éÿ̯"éÿ̯!éÿ̯ éÿ̯éÿ̯éÿ̯éÿÌ ¯éÿÌ!¯éÿÌ"¯éÿÌ#¯éÿÌ$¯éÿÌ%¯éÿÌ&¯éÿÌ'¯éþ¯Û
éø¬ßé̯¯—‘$¯éþ?À
éý\Ñ̯þhW¯þšG¯éþ?ÀéÒëé×Ãéç¿éÂÆ¯¯‘hW¯¯£€¦¯ø”˜¯¯|!‰˜¯û4èé?ÀéÞ¤[épè¹Mé.˜¯A7A#W¯r%R3¯ª4HFC¯_]~¯ûH¶é?ÀéàÝ"é:R¾{‹Ì(˜ $¯¯9W¯+˜¯y¯wU¯¯5š•+¯û8ˆé?Àéá5¼?š>¯¯(˜zF¯¯]W¯¯¯š¯H&663•+¯û>‡é?ÀéçrEzÖ>¯¯(˜ž%¯¯;W¯2“¯t¯tO¯þœ&¯ÿÿéÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯ÿÿÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯þ®¢¯ÿ¡¯éÿÿÿ¯þ"¯ÿ¯éÿÿÿ¯ÿ¬¯ý¥¯«¯ÿª¯ÿª¯éÿÿÿ¯è©K+,x¯„/+2ž&¯¥A&,&’¯¯‡-éÿÿÿ¯èwF¯¡¯‚	¥¯"¯qN¯ž‘¯¯…éÿÿÿ¯èDy¯¯-‚3¯¯"¯B~¯¯ ‘¯¯…6éÿÿÿ¯èZf¯¯¢‚=¯¯"¯]f¯­
‘¯¯…:éÿÿÿ¯è‘}Y/¯‚=¯¯"¯” jD’¯¯…:éÿÿÿ¯éšUq«¯™v¯¯¦i¯¯£h‹¬¯_¯šuéÿÿÿ¯ûªoŠP<¯éÿÿÿ¯û­eO~­¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯þ¤h¯÷–j¯­­¯¯s¯éÿÿÿ¯þYs¯÷˜r¯\d¯¯Iv¯éÿÿÿ¯èª¬¯f®¯h•¯‚•›y¥15]˜Ioae¥éÿÿÿ¯èŒ/¯¯3£V¯,™„<ªHN‰¥I"rY;éÿÿÿ¯èoG¯¯a_wV,©¯„<¯\d¯¯I_¯­éÿÿÿ¯èvA¯¯0HŒ.vF¯„<¯\d¯¯Iv¯¯éÿÿÿ¯è”(¯¯®$®Zt¯„<¯kY¯¯Iv¯¯é¯è®$£¯¯<:¯‰¡¯„<¯›/,‹Iv¯¯Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/ÆÿÆÿÿÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆþÄ·Æÿ¶ÆÿÿÿÆþ²'ÆÿÆÿÿÿÆÿÂÆý»ÆÁÆÿÁÆÿÁÆÿÿÿÆè¿U12ˆÆ•519³+Æ»J+2+¥ÆƘ3ÆÿÿÿÆè‡OƶÆ”
»Æ²'ÆXƳ¥ÆÆ—ÆÿÿÿÆèM‰ÆÆ2¢”:ÆÆ²'ÆKÆÆ$¥ÆÆ—=ÆÿÿÿÆèftÆÆ·”EÆÆ²'ÆjtÆÄ¥ÆÆ—BÆÿÿÿÆè¥"d5Æ”EÆÆ²'Ƨ$xM¥ÆÆ—BÆÿÿÿÆé®`€ÁÆ­†ÆÆ¼wÆÆ¹u ÂÆlƯ„ÆÿÿÿÆûÁ~œZDÆÿÿÿÆûÄrYŽÄÆÿÿÿ-Æÿÿÿ-ÆÿÿÿÆþºuÆ÷©xÆÄÄÆÆ‚ ÆÿÿÿÆþd‚Æ÷¬ÆhqÆÆS†ÆÿÿÿÆèÀ ÃÆtÄÆu¨Æ“¨¯‰º7:CÒ3éÜW3:2Ãéé³<¯ÿÿÿéèŸ]é×!é®ÜéÑ.é–géÒÂé鱯ÿÿÿéèZ¡éé;¾®EééÑ.éX¨éé+Âéé±G¯ÿÿÿéèxˆéé%Ø®QééÑ.é|ˆéæÂéé±N¯ÿÿÿéèÂ(¦v>é®QééÑ.éÄ*ŽZÃéé±N¯ÿÿÿééÍq—äéÌééÝŒééÙŠ¹%äééΛ¯ÿÿÿéû㔸jPé¯ÿÿÿéûæ†i§æé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿéþÚŠé÷ÇŽéææé陼é¯ÿÿÿéþvšé÷˘éz„éébé¯ÿÿÿéèâ%åéˆçéŠÆé­ÆÏ¢ÛAF{Ëb”‚†Ü¯ÿÿÿéèº?ééD»Ùré:̯Oã`g¶Ûb.˜vO¯ÿÿÿéè”_éé~žr:á%é¯Oéz„ééb~鿝ÿÿÿéèWéé¿@`º=]é¯Oéz„éébéé¯ÿÿÿéèÅ6ééè0çx(šé¯Oéwéébéé¯éèè0ÙééOMé¶×é¯OéÏ>:¹bééÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ¯.éÿíÿ¯.éÿíÿ¯.éÿí	ÿ¯.éÿí
ÿ¯.éÿíÿ¯.éÿíÿ¯.éÿí
ÿÿ¯.éÿíÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿx=¯þÌé<¯ýÌéé;¯ÿÌé:¯ÿÌé9¯ÿÌé8¯ÿÌé7¯ÿÌé6¯ÿÌé5¯ÿÌé4¯ÿÌ	é3¯ÿÌ
é2¯ÿÌé1¯ÿÌé0¯ÿÌ
é/¯ÿÌé.¯ÿÌé-¯ÿÌé,¯ÿÌé+¯ÿÌé*¯ÿÌé)¯ÿÌé(¯ÿÌé'¯ÿÌé&¯ÿÌé%¯ÿÌé$¯ÿÌé
¯þ£¬
¯þ£­¯ü©§¯Ìé
¯þ/
¯þ(˜¯ýhWÌéþ׌
éþ¯ª¯þ©¬¯þ/
¯ø«®¯¯¬®hféÿãéúæçééÆ9
éó-#C¯¯C1-K¯¯/¯ÞrR¯a¯—3¯,˜¯h"#Eté¹:7Z)éèv@@Šé\H‰éó§¯+•¯¬¯¯8–¯/¯à"¯3+šh]¯(˜¨©Ê7téK²é‰#é³Véè7ÛÆ9
é¯õMr¯o82j¯/¯áž(hl:‹¯(˜~CÌéwtéçéÊ#éf0HH#žÆ9
é¯õNq¤ ­¯:´ÖZ=H:éNAJDã
é6ÆÿÍî5ÆÿÍî4ÆÿÍ	î3ÆÿÍ
î2ÆÿÍî1ÆÿÍî0ÆÿÍ
î/ÆÿÍîÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿx‹Æþ¹Ã
Æþ¸ÄÆþ¿½'Æþ6£
Æþ-¬ÆþvcÆþ¶wÆÿÁÆþ¿ÂÆþ6£
ÆøÁÅÆÆÂÄvcÆÿÁÆÄÆþ¨0
Æó2(KÆÆK82UÆÆ6£ÆÞ]Æn!Æ«9Æ2¬Æv'(CcÆ1/M#ÆÅd66uÆN=tÆó½Æ1¨ÆÃÆÆ?ªÆ6£Æà²'Æ90¯viÆ-¬¾¿Ä/cÆ@—ÆtƘIÆÅ/º¨0ÆõWÆ~@9xÆ6£Æá!³.u{BÆ-¬KÆÆecÆÄƬÆW(==†¨0ÆõY€º$ÄÆDsÆ6£ÆçTUZªEÄÆ-¬¥6ÆÆPcÆ*³ÆÆqlÆþ«/ÆõY€¿—ysÆ6£Æß‰Æ?ÆÆ-¬Æ#u‚cÆq9|<ƶ¥|³Ä‰¨
Æõ£ÆšUn”Æ~µÆß»c¾Æƒ–ÆÆz¹Æ¼he–”ÆÆ¤tˆGÆÆ¹tNr¾Æ£n‹/Æû¡‘2„#Æÿ@=ÿ«Æü®eb¥£ÆÿÁÆÿÁÆü\Ƴp
ÆÄ"ÆûÂÆÆÆüfÆ¥4
ÆþnjÆÐ‚Yx·Æ”gv	i}Áx›\·rÆ¥4Šb­ÆÆtÄÆ´„½:9iªÆ³jÄÆ½~Wh½ÆÐtž~(Ư—¡¬›¤º	N”¼Æ¥W‰# ÆÂÆŸ9ÂVS›¹“2iJÆf` †¹
ÆÿÅÆÒ½ŽpªÆÂÆÆÆÆº½ÆÆÆ¥
ÄÆvcÆÂÆŸ9ÆnjÆÆKu››¿\R¨ÆÿÄÆðÀ!t‘+’ÆÂÆÆÆÆºÆëÆ¥$ÆÆFÆÁÆ›9ÆnjÆÆ)Ziú¶Áx3/º
ÆÿÄÆð”Dƾ’Æ(¶ÆÆÁƺÆâÆ¥³ÆX~Æ=Æb9Æ_ÆÆkoÆÆÂÆÂÆÆM‰
ÆÿÄÆðÂ.8*/’Æv1EÆk1QºÆâÆ¥#+.EºÆŒ"2G9Ƴ52™¶L4=2ÆB7?9Á
Æé.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®	ªé.¯ÿ®
ªé.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®
ªÿé.¯ÿ®ªÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿx=éþ̯<éý̯¯;éÿ̯:éÿ̯9éÿ̯8éÿ̯7éÿ̯6éÿ̯5éÿ̯4éÿÌ	¯3éÿÌ
¯2éÿ̯1éÿÌ¯0éÿÌ
¯/éÿ̯.éÿ̯-éÿ̯,éÿ̯+éÿ̯*éÿ̯)éÿ̯(éÿ̯'éÿ̯&éÿ̯%éÿ̯$éÿ̯
éþÙå
éþÙæéüáÞé̯
éþ?À
éþ5Êéý‹t̯þ¡i
¯þéãéþáäéþ?À
éøäèééäç‹f¯ÿª¯ú­®¯¯•+
¯ó;/YééYB;eéé?ÀéÞ˜né‚&éÉDé:Ëé‹./EW¯‹+)D¯®Y00h¯E
6g¯óÞé:ÆéåééJÈé?ÀéàÑ.éD9΋{é5Êà%áÊ)W¯8†¯g¯‡A¯®)¥•+
¯éõf˜é”KC%é?Àéá'Ò6ŠN¹é5ʨY̯YW¯®¯˜¯M$66w•+
¯éõh—Ú*æéO‡é?ÀéçcdjÈQ!çé5ÊÃ8¯¯GW¯%ž¯€¯d_¯þ—)
¯éõh—᱇é?Àéß¡¨é#Jéé5ÊÌhsW¯d2n5¯¡q‘nž®y•¯éõ©Àéµd‚®¹é”ÕéßÜtàéš°é龯¦\Y„ƒ¯¯‘gx>¯¯£fEe¨¯a{¯éÿ̯ûŽ€-u¯éÿKHÿÉéÿ̯üšYV’¯éÿÌ%¯éÿÌ&¯
éÿãéÿãéü^¯žc
¯­¯
éûäéééûÌ[¯‘.
¯þa^¯éКiØé¯z‹¦{“ã¶^¢e¯‘.zV™¯¯f®¯Ÿu§32]–¯ž^|®¯§oM\§
¯éЉ¹”/éβ½Ê¶ÁÚQƒ¦¯‘My¯¬¯3¬LI‰£‚-~]A¯ZUw£
¯ÿèéÒÞ§„$ÈéäééééÚ§¯¯¯‘®¯hW¯¬¯3¯a^¯¯Ch‰‰©QH”¯ÿæéðâ'‰«3¬éäééé鿯믑 ¯¯~>¯«¯‰3¯a^¯¯$P]ú¡ªj-)¤
¯ÿæéð®Oéà
¬é/×éé"ä̤¯â¯‘ž¯No¯6‹¯V3¯qT¯¯_b¯¯¬¯¬¯¯Dy
¯ÿæéðä6B17¬é‹:Qé~2G¤¯â¯‘&(=¥¯|-?3¯Ÿ/,‡¡C.6,¯:183ª
¯ÿÿÿéÿéÿéÿéÿéÿéÿéÿé@é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é
¯÷—•¯¯®¦~¦é¯ö9<>3¯«3Vœé¯õ“0¯¯3Œ«˜¯¯é¯øhT¯¯Ya«¯é¯ø2¯¯6‰«¯îÆîÆîÆîÆîÆîÆîÆîÆ@Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ-Æ
Æ÷«¨ÆÆÅ ¼Ž¼ÆÆö@DG:ÆÁ
9b±ÆÆ÷¦6ÆÆ:žÁ¬ÆÆøv_ÆÆenÁÆÆø£9ÆÆ=›Áƪ¯ª¯ª¯ª¯ª¯ª¯ª¯ª¯@¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯é¯
é÷ÉÆééè¼Ý§Ý¯éöLPSEéäDsЯéõÄ@ééEºäËéé¯éø‹oééw‚äé¯éøÀCééG·äéÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿé@
¯þ{­¯þ‚¦¯þ‘–
¯þ™¯þ¥ƒ¯þ8ª¯þHš¯þXg
¯þf}¯þ‘.
¯ä3¯Ž®¯¯‹‘œ ¯’ª¯¦€¦¯¯ª„‚¬¯¯Xg¯Å§•¯ª‰¯¯­š¡¯¯Ÿƒ.¯¯«€ ¬¯¯¡‹¯¯™‰©®/AR-§€]©6¯¯+h¯+”®›¯a_¯¯®”W_¯Xg¯Æ#OCf†?¯Pn¯¦¯b.¯Tn¯¡¡Ÿ.¯¯]r¯¯©jW¯¯Kh¯+”®¯¯wH¯l2M>=¯Xg¯ÆPo(˜8Xm¯Pn£¯¯†.¯1¯¯¡q665d¯¯©“1¯¯%h¯+”®¯¯wH¯¥¯W<¯Xg¯ç33¯'(œ¯Pn¯§¯d.¯[j¯œ¡œ(¯þ¬Æ@
Æþ‹ÄÆþ“¼Æþ¥ª
Æþ¢­Æþº”Æþ?ÁÆþR®Æþct
ÆþsÆþ¤4
Æä9Æ¡ÅÆÆ¥°µÆ¥ÀÆŸ¼‘¼ÆÆÁ–”ÃÆÆctÆÅ½¨ÆÁ›ÆÆ£Ä¯¶ÆÆ³” 4ÆÆÁ‘µ¢ÃÆÆ¶žÆÆ­›¿Å6JD%vÆ1§Ä=[¨Æq`c&¬ÆctÆÅ¸!Æ¨ÂÆ[}Ær-Y04Æ£&\8¶ÆUF\3½‘	i¿Ÿ=ÆÆ0vÆ1§Ä¯ÆmlÆÆÅ¨clÆctÆÆ(²YKt—GÆ[}Æ!¼Æo4Æ`}ƶ¶³4ÆÆiÆÆ¿xcÆÆUvÆ1§ÄÆÆ†RÆ{9WGEÆctÆÆ[~.¬@c{Æ[}¹!ÆÆ—4Æ8¢ÆÆ¶==¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ=¯þÌ<¯ýÌé;¯üÌéé:¯ÿÌéÿ9¯ÿÌéÿ8¯ÿÌéÿ7¯ÿÌéÿ6¯ÿÌéÿ5¯ÿÌéÿ¯þª¯þ†¢
¯üÌéÇÄéÿ¯þA¡¯þR‘	¯ûÌéé„zéÿ¯þš‘¯â§€¢¬¯¯ŽŽŸ¯•§¯¨¤¯¯¬‡•äéé„zéÿ¯ÜC8C-¬¯5U–"¯8Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>ÆÿÆþÀÆþ—·Æþ©¦ÆÿÆþJ¶Æþ\¤ÆþqgÆÿÆþ¯¤ÆâŸ½·£ÂÆÆ¡¡³²Æ¨½ÆŸ¾’ºÆÆÂ˜ÁÆÆqgÆÿÆÜK@L2ÂÆ<`©&²Æ@DJ(iÆ>šÆ@[!žÆz^e(¢ÆqgÆÿÆä³*ÆÆG‘ÆŸÆÆ&²¬1ÆÆ=iÆ>次{_Æú©m_ÆqgÆÿÆÛƒRÆÆraÆÅÆÆ&²„UÆÆbiÆ>𯯯”DÆ…7UN8ÆqgÆÿÆø¯,ÆÆJŽÆÆæ&²³+ÆÆ7iÆ>𯯯”DÆ*¯Æp7ÆqgÆÿÆùEDR,ÁÆÆæ&²ÆU69/oÆ>𯯯”DÆUG^27ÆqgÆÿÆþ¦™Æÿ™ÆæžÁÆÆ±µ?¡Æ¥»Æ™ÆÆº¦ÆÄ”޹¢Æ±¯Æÿ!ÆüamE6Æÿº³þ½!Æý³ŒªÆÿuPþ‹>Æÿ>ÆÿÆþ7¾Æþt€Æþº¡
ÆÿÆþ—ÄÆþesÆþ—@
Æé'¯é(¯é)¯é*¯é+¯é,¯é-¯é.¯@Àÿöîm77«°77hó7öín77ª±77fõ7öëp77¨³77eö7öêq77¦´77cø7êès77¥¶77bù77ûÜÏÏëìÏÏÛüÏöûÝÏÏëíÏÏÛýÏöûÝÏÏêíÏÏÚýÏöúÝÏÏêíÏÏÚþÏôúÞÏÏêîÏÏÚþÏÏ€ÿ@>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ=éþÌ<éý̯;éü̯¯:éÿ̯ÿ9éÿ̯ÿ8éÿ̯ÿ7éÿ̯ÿ6éÿ̯ÿ5éÿ̯ÿéþ©âéþ²Ø
éü̯–“¯ÿéþW×éþmÁ	éû̯¯d[¯ÿéþÎÁéâ»ßªØÀäéé½½ÓÑéÆÞé»à¬Úéé䳕«¯¯d[¯ÿéÜYKZ;äéFqÇ-ÑéKPW/{éIµéKk&ºéaY$¯d[¯ÿéÛÒ1ééT«é	»éé-ÑË:ééH{éIµé
Àéoé̯–aT¯d[¯ÿéÛš`éé†réèéé-Ñ›eéés{éIµééé®PÌu1KE1¯d[¯ÿéøÏ4ééW§ééæ-ÑÒ3ééA{éIµééé®F¯%š¯c1¯d[¯ÿéùQOa4äééæ-Ñéd?D7ƒéIµééé˜<¯K>S-1¯d[¯ÿéþÄ´éÿ´éæºäééÐÕJ½éÂÜé´é̤“¯®ƒ~£¯œš¯ÿ!éür€Q@éÿ̯ÿ¤Ÿþ§!éýÒ¤ÈéÿÌ¯ÿhGþ{)éÿ̯ÿ(éÿ̯ÿéþAàéþ‰—éÿ̯þ¤Ž
¯ÿéþ±æéûw‡ééÌ	¯þ†9
¯ÿÿéÿíÿéÿí ÿéÿí!ÿéÿí"ÿéÿí#ÿéÿí$ÿþéí%ÿÿí&ÿX&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿõ7æt77£¸77`û7öåv77¢¹77_ü7úãx77 ÿõÏùÞÏÏéîÏÏÙþÏöùÞÏÏéïÏÏÙÿÏúùßÏÏéÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ0ÿÿÿ¯ÿÌéÿÿÿÿ¯ÿÌéÿÿÿÿ¯ÿÌéÿÿÿÿ¯ÿÌéÿÿÿèÿ¯ÿÌéÿÿþ¯ÌéÿÿÿÌéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿèÿéÿÿéÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéþíÿÿÿéýíÿÿÿÿéüíÿÿÿÿÿéÿíÿÿÿÿÿéÿíÿÿÿÿÿéÿíÿÿÿÿÿéÿíÿÿÿÿÿÆÿÍîÆÿÍ îÆÿÍ!îÆÿÍ"îÆÿÍ#îÆÿÍ$îþÆÍ%îÿÍ&îX&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿõ7æt77£¸77`û7öåv77¢¹77_ü7úãx77 ÿõÏùÞÏÏéîÏÏÙþÏöùÞÏÏéïÏÏÙÿÏúùßÏÏéÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ0ÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿèÿÆÿÿÆÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿèÿÆÿÿÆÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆþÍÿÿÿÆýÍîÿÿÿÆüÍîîÿÿÿÆÿÍîÿÿÿÿÆÿÍîÿÿÿÿÆÿÍîÿÿÿÿÆÿÍîÿÿÿÿ¯ÿ®ª¯ÿ® ª¯ÿ®!ª¯ÿ®"ª¯ÿ®#ª¯ÿ®$ªþ¯®%ªÿ®&ªX&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿõ7æt77£¸77`û7öåv77¢¹77_ü7úãx77 ÿõÏùÞÏÏéîÏÏÙþÏöùÞÏÏéïÏÏÙÿÏúùßÏÏéÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ0ÿÿÿéÿ̯ÿÿÿÿéÿ̯ÿÿÿÿéÿ̯ÿÿÿÿéÿ̯ÿÿÿèÿéÿ̯ÿÿþé̯ÿÿÿ̯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿèÿ¯ÿÿ¯ÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯þ®ÿÿÿ¯ý®ªÿÿÿ¯ü®ªªÿÿÿ¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿéþ»éíÉ
éé	R0ßéécÒéy½é÷ç=éé%'éééþ¯Þéþâ±éüÇ—©äéùѳééׯèéø¼ÈééÂÃéé.é.é.é.é.é‘.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿÿÿýùìÿþÆD
ÿÿþfýúôÿþ¾)
ÿÿþÍÿñ¯øö¯óÿÿÏÊÿÿÓ˜©õÿûܘ¹½)ÿúÆáÿþ°æÿþÍÿèþéÿ^\ÿÚÊÿ‡qE'ñÿ¾ZC:)ÿútrÿÏ‚ÿã`þéÿô)™0ÿÞßÿÿD‡ÿ8†ÿÿ[)ÿú¸/ÿŒS?ÿïÀïïõþéÿÿÔKÿÿ©_÷1\þÊÿÿŸ)ÿúôëIÄÿþÍÿôþéÿÿ—)õÿ§ÇøÚþ	Çÿÿœ)ÿû>¤ûVÿþÍÿóþéÿÐÕUaÿÝÇÿøýÿ=‚ÿÿW)ÿû,Lÿ™ÿþÍÿèþéó&žÿñ"£ÿ”Pq?˜ÿÆR;E)ÿûÄÿÜÿþÀóÿü¿úèÅÿøâËÿÿá«¥Îÿûâ¦ÉïÊÿûû¿îÿÿ.ÿ.ÿ.ÿ.ÿ.ÿ@ÆþŸÆí«	ÆÆ{F(½ÆÆT³Æf¡Æ÷Ä4ÆÆ!ÆÆÆþ•½ÆþÀ—Æü©€ÁÆù²˜ÆÆ¶•ÅÆø ªÆÆ¥¥ÆÆ.Æ.Æ.Æ.Æ.Æ‘.î.î.î.î.î.î.îîÿxwýèÜîþ¹?
îîþ_wýéwäîþ±&
îîþ¿îñ£çæ£ãîîÁ½îîÅŽžåîûÍŽ­°&îú¹Òîí¤×îþ¿îèíÙîXVî˽î~i@$áî±T?6&îúljîÁyîãZíÙîä&-…îÏÐîî?~î4}îîU&îú¬,îƒM;îï³ßßåíÙîîÆFîîžY÷.Ví½îî”&îúäÛD·îþ¿îôíÙîî&åøËíºîî’&îû:™êPîþ¿îóíÙîÂÇO[îκîøìî9yîîQ&îûx)Gîîþ¿îèíÙã#“îá ˜îŠ
Ki;Žî¹
M7@&îû·…îÍîþ³ãîü²éÙ¸îøÓ½îîÒ šÀîûÓ›¼ß½îûê²Þîî.î.î.î.î.î@¯þ¯í—¯¯l>$§¯¯Jž¯[ޝ÷®.¯¯¯¯¯þ„§¯þª…¯ü–q«¯ù‡¯¯¡„®¯ø–¯¯‘’¯¯.¯.¯.¯.¯.¯‘.ª.ª.ª.ª.ª.ª.ªªÿVUý¦ªþ„-
ªªþDUý§U£ªþ
ªªþ‰ªñu¥¤u¢ªªŠ‡ªªeq£ªû“e{~ªú„–ª©u™ªþ‰ªè©›ª?=ª‘
‡ªZK.¡ª<-'ªúMLªŠWªã

@©›ª£f _ª”•ªª-Zª%Yªª=ªú{ª]7*ªï€ŸŸ£©›ªª2ªªq?÷!=©‡ªªjªú£1ƒªþ‰ªô©›ªªe£ªo…ø‘©…ªªhªû)m§9ªþ‰ªó©›ª‹Ž9Aª“…ªø©ª)Wªª:ªûV3ªfªþ‰ªè©›¢iª¡mªc	5K*eª„	7'.ªûƒ_ª“ªþ€¢ªü§›ƒªø—‡ªª–rn‰ªû—o†Ÿ‡ªû§Ÿªª.ª.ª.ª.ª.ª@/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿáæ×é 	T+>Aééh30'ŸéÕ-"eW)éé\*OÉéöé¯äé鯗ÁÔ¿éôÎÃÚºé鿝’¤ÛéýÕ¯Ýééú°¨L+çééû‘LEoØé.é.é.é‘.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿÿþºPÿþi¡ÿþîÞÿÿþØ›ÿþT“ÿþ¢MÿÞÿù°þçÁÿÿþ¶šÝT“ÿÿå›­ò¯üÿÿã Ÿçÿño5¯¶ÿÞÿ¿'ÿ°7ÿÿ]o&+“ÿÞH`Aõÿ²c[ÒçL$wƒÿàÿ|kÿ°7ÿÍíÿí“ÿiYÿÿ–õü®ÿÿxSÿ¢Mÿ÷õ9®ÿ°7ÿœ4ÿì5“ÿ8šÿÿ×õÝ\__D(ÿ¢Mÿ÷«îÿ°7ÿž1ÿó2“ÿ?ÿÿÊõÛ¸ÇüÎÿ¢Mÿç,4ÿÿ°7ÿÒéÿê“ÿ~=öþrõü“ÿüýÿ¿7ÿßwÿÿ°7ÿÿjg'0“ÿð6(`ùÿ»>tNjÿú$CfÿúÁèÿÿìÍÿôÁªçÕäÿÿüÓå«ÿûí³¡Âûÿþ×Åÿÿú÷ËŸ,xÿÿúútKT—ýÿ.ÿ.ÿ.ÿQáĶƈG$57ÆÆY+)!‡Æµ&VJ#ÆÆN$D«ÆöÆ”ÂÆÆ¨€¤´¢Æô¯¥ºžÆÆÄ”|‹ºÆýµ•¼ÆÆú{–@$ÄÆÆû{@:^·Æ.Æ.Æ.Æ‘.î.î.î.î.î.î.îîþ®Kîþb–îþÞÏîîþÊ‘îþN‰îþ—HîÞîè¤íØ´îîíªÎN‰îîÖ‘¡â£ëîîÔ•”Øîáh1£ªîÞî²$î¤3îîWh#(‰îÏCZ=åî¦\UÄØG"ozîàîtdî¤3î¿Ýî݉îbSîîŒåë¢îîpMî—Hî÷å5¢î¤3î’1îì1‰î4îîÉåÎVYY?%î—Hî÷ Þî¤3î“.îó/‰î;„îî½å̬ºüÀî—Hîç)1îî¤3îÄÙîÚ‰îv9æíjåë‰îüìî²3îßoîî¤3îîc`$-‰îà2%Zèî¯:lIcîé"?_îú´ÙîîÜ¿îô´ŸØÇÕîîëÅÖ îûݧ–µêîþɸîîú熽”)pîîúélFNìî.î.î.îQá­¡¯x? /1¯¯N&$w¯ "LA¯¯E <—¯ö¯ƒ¬¯¯•q‘Ÿ¯ôš’¤Œ¯¯­ƒn{¥¯ý „¦¯¯úl„~9 ®¯¯ûm93S¢¯.¯.¯.¯‘.ª.ª.ª.ª.ª.ª.ªªþ|5ªþFkªþŸ”ªªþgªþ8bªþl3ªÞª¦u©šªª©yg“8bªª™gs¡u¨ªª—kjšª¡J#uyªÞªªu%ªª>Jbª”0@+£ªwB=Œš3OWªàªSGªu%ª‰žªžbªF;ªªd£¨tªªP7ªl3ª÷£&tªu%ªh#ªì#bª%gªª£“=??-ªl3ª÷rŸªu%ªi!ªó!bª*^ªª‡£’{…ü‰ªl3ªç#ªªu%ªŒ›ªœbªT)¤©L£¨bªü©ª%ªßOªªu%ªªG
E bª $@¦ª})M4Gª§-Dªú›ªª‰ªôqšŽ˜ªª¨™rªûžwk§ªþƒªªú¥`‡jPªªú§M28e©ª.ª.ª.ªQ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ¯õsy¯oR¯–Z¯C²ÿÿ¯õ„<¯0¬f`¯×ÿÿ¯õ«©*M‡7޹ Ýÿÿ
¯ö0aR|V¸ä!Ýÿÿ
¯ö^§>äé!Ýÿÿ
¯ö‘-ª¯KšééSáÿÿ¯þºåéÿÿ¯þºåéÿÿ¯þ»åéÿÿ
¯þ»æéÿÿ¯þ¼æéÿÿ¯þ¼æ	éÿÿ
¯þ½æ
éÿÿ	¯þ½æéÿÿ¯þ¾æéÿÿ¯þ¾ç
éÿÿ¯þ¾çéÿÿ¯þ¿çéÿÿ¯þÀçéÿÿ¯þÀçéÿÿ¯þÁèéÿû¯¯Áèéÿü¯ÂèéÿýÂèéÿþèéÿÿéÿÿéÿÿéÿÿéÿÿéÿÿéÿÿéÿÿéÿÿéÿÿéÿÿéÿÿéÿÿéÿÿé>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÆõ‚‰Æ~\ƪfÆL¿ÿÿÆõ•DÆ6#ÃsmƼÿÿÆõÁ¿/W˜>¡Æ¼ÿÿ
Æö6n]ŒbÅÆ¼ÿÿ
Æöj’½BÆÆ¼ÿÿ
Æö¤3ÀÆP†ÆÆG¿ÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆÿÿÆ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿéõš¡é”méÈxéZØÿÿéõ¯Pé@)å‡€é ¬ÿÿéõäá8f³I½ß¦ÿÿ
éö@‚n¤s!Þ´¦ÿÿ
éö}¬ÞJ´¯¦ÿÿ
éöÁ<âéZy¯¯>©ÿÿéþÞ³¯ÿÿéþÞ³¯ÿÿéþݳ¯ÿÿ
éþݲ¯ÿÿéþܲ¯ÿÿéþܲ	¯ÿÿ
éþÛ²
¯ÿÿ	éþÛ²¯ÿÿéþÚ²¯ÿÿéþÚ±
¯ÿÿéþÚ±¯ÿÿéþÙ±¯ÿÿéþر¯ÿÿéþر¯ÿÿéþ×°¯ÿûéé×°¯ÿüéÖ°¯ÿýÖ°¯ÿþ°¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯ÿÿ¯>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>éã³HOe‡éÏe=‘iéé§O_ÂéIH—éÿÿëÞ!ÄÁ‡éH–ÜR6é³E×Ç0é²Léÿÿë¢`ééW‡äèé­6édT{{%»²LéÿÿñždééZ‡æçé¥6é]{¶ýÚ³LéÿÿêÜ#ÊLJéQËI7é±CÔéÆãÔ0àéÿÿéé«=F| éÞQ„UéèŸI3Qàé„EéÿÿéûÏäÙSš#éÿÿéû«?Cã#éÿÿ.éþôÿ-éýôÿÿ,éüôÿÿÿ+éÿôÿÿÿ*éÿôÿÿÿ)éÿôÿÿÿ(éÿôÿÿÿ'éÿôÿÿÿ&éÿôÿÿÿ%éÿôÿÿÿ$éÿô	ÿÿÿ#éÿô
ÿÿÿ"éÿôÿÿÿ!éÿôÿÿÿ éÿô
ÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿéÿôÿÿÿ€ÀÿéÆ˜=CUsưV4{YÆÆŽCQ¥Æ>=€Æÿÿë½§¤sÆ=»F.Ƙ;¶©)Æ—@Æÿÿë‰RÆÆJsÁÅÆ“.ÆUGii Ÿ—@Æÿÿñ†UÆÆMsÄÄÆ.ÆOi›ýº˜@Æÿÿ껬©sÆEm¬>/Æ—9´Æ¨Á´(¾Æÿÿ鯑4+E¾Æp:yÆÿÿÆû°Á¸Gƒ#ÆÿÿÆû‘69yÁ#Æÿÿ.ÆþÚÿ-ÆýÚîÿ,ÆüÚîîÿ+ÆÿÚîÿÿ*ÆÿÚîÿÿ)ÆÿÚîÿÿ(ÆÿÚîÿÿ'ÆÿÚîÿÿ&ÆÿÚîÿÿ%ÆÿÚîÿÿ$ÆÿÚ	îÿÿ#ÆÿÚ
îÿÿ"ÆÿÚîÿÿ!ÆÿÚîÿÿ ÆÿÚ
îÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿÆÿÚîÿÿ€Àÿ鵇6;Kf¯œL.mO¯¯~;G‘¯76q¯ÿÿë§”‘f¯6q¥>(¯‡4¡–$¯†9¯ÿÿëyH¯¯Af«®¯‚(¯K?]]†9¯ÿÿñwK¯¯Df­®¯|(¯F]‰ý¤‡9¯ÿÿ꥘–f¯=a˜7)¯…2Ÿ¯•ªŸ$¨¯ÿÿ鯀.5]x¯§_=d@¯®w7&=¨¯c3k¯ÿÿ¯ûœ«£>t#¯ÿÿ¯û€/2kª#¯ÿÿ.¯þ¬ÿ-¯ý¬ªÿ,¯ü¬ªªÿ+¯ÿ¬ªÿÿ*¯ÿ¬ªÿÿ)¯ÿ¬ªÿÿ(¯ÿ¬ªÿÿ'¯ÿ¬ªÿÿ&¯ÿ¬ªÿÿ%¯ÿ¬ªÿÿ$¯ÿ¬	ªÿÿ#¯ÿ¬
ªÿÿ"¯ÿ¬ªÿÿ!¯ÿ¬ªÿÿ ¯ÿ¬
ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ¯ÿ¬ªÿÿ€Àÿ
ÿÿ¯àŸ#Q1P¯‚=¯¯"¯ .>,–¯¯…:¯¯Nq®Y@ÿ¯õ®š¯¯¤“¯¯«Œ¯ï”¤¯¯‡¯¥’¯¯— ¯§wÿ¯û¨C^-\
¯ÿ¯ý‘{¤¯ÿ&¯ÿ&¯ÿ&¯ÿ&¯ÿ&¯ÿ&¯ÿ&¯ÿ&¯ÿ&¯ÿ&¯ÿ&¯ÿ&¯ÿ8ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ&ÿ€ÀÿÿÆà³(\7[Æ”EÆÆ²'Ƶ4G2ªÆÆ—BÆÆY€ÅeHÿÆõÅ’¯ÆÆº¦ÆÆÁžÆï§º ÆÆ™Æº¥ÆÆ«µÆ½†ŸÿÆû¾Lj2h
ÆÿÆý¤‹ºÆÿ&Æÿ&Æÿ&Æÿ&Æÿ&Æÿ&Æÿ&Æÿ&Æÿ&Æÿ&Æÿ&Æÿ&Æÿ8ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ&ÿ€ÀÿÿéàÓ/lAké®QééÑ.éÕ=S:!Èéé±Nééh—è%wUÿéõè¬ÎééÚÄééäºéïÄÚ%éé´éÛÃééÉÕéÞž»ÿéûàZ};z
éÿéýÁ¤Úéÿ&éÿ&éÿ&éÿ&éÿ&éÿ&éÿ&éÿ&éÿ&éÿ&éÿ&éÿ&éÿ8ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ&ÿ€Àÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿû f¯/¯ß„Н&Zéé5ÊéOO^)té¨0XE)éâ=[‡XÉé-g¤éûš¯§¯ê¬ƒ­ÌÄÏéé¼âééµ±ÑÌééäÄ¢eéøÃ–Áééç¼Æé¯Ÿü¬¯¯Ì
éû§po0¶é¯ÿIGýš¯Ìéüã±®æé
¯ÿÌ3é	¯ÿÌ4é¯ÿÌ5é¯ÿÌ6é¯ÿÌ7é¯ÿÌ8é¯ÿÌ9é¯ÿÌ:é¯ÿÌ;é¯ÿÌ<éþ¯Ì=éÿÌ>é?ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜+ÿ€Àÿû$sÆ6£Æß–œÆ%LÆÆ-¬ÆDDP#cÆ)K;#ÆÀ4NsK«Æ&X‹Æû®²Æ¢½ÆêÂ”ÄÆ¦°ÆÆ ÀÆÆš—²­ÆÆÁ¦‰UÆø¥¤ÆÆÄ ¨Æÿ´³ÿÃÆûŽ`^)›#ÆÿRPÿ¯ÆüÁ—”Ä Æ?ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜+ÿ€Àÿû+‡é?Àéß°¸é&C¯¯(˜¯<¯?ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜+ÿ€Àÿ
ÿé¯þq^¯é¯þ­‡¯é¯é¯é¯ÿ¨é¯ÿaé	¯ÿ«¯ÿ¬¯þª§¯û®¤¯¯­é¯èq6"$X¯¯o+*Q®¯A31V¯^/58¯dé¯òn©­*”«§®6…š+¯þ¬¯ÿaé¯ènB¯¯Zb<¯¯jR¯<"[¡¯\N•¯aé¯èn/¯¯Gw—)¯¯Vh¯¯Ÿ\2¯¯¦s¬aé¯ènhs§¯$cw“s’l<¨g‚"­aé¯ènObTŸ¯¯©kYž¯žWCf¡¬_C[–¯ˆé¯þnQ¯é¯þ‰w¯é¯ ¯ÿÌÿ¯þÌéÿ¯ýÌééÿ¯ÿÌéÿ¯ÿÌéÿ¯ÿÌéÿ¯ÿÌéÿ¯ÿÌéÿ¯ÿÌéÿ¯ÿÌéÿ¯ÿÌ	éÿ¯ÿÌ
éÿ¯ÿÌéÿ¯ÿÌéÿ¯ÿÌ
éÿ¯ÿÌéÿ
¯ÿÌéÿ¯ÿÌéÿ¯ÿÌéÿ
¯ÿÌéÿ	¯ÿÌéÿ¯ÿÌéÿ¯ÿÌé€ÀÿÆÆþj#ÆÆþĘ#Æ-Æ-ÆÆÿ¾ÆÆÿmÆ	ÆÿÁÆÿÂÆþÁ½ÆûÄºÆÆÄÆÆè='(cÆÆ~0/\ÄÆI97aÆj5<@ÆqÆÆò}¿Ä/¨Á½Ä=—®0ÆþÂÆÿmÆÆè}KÆÆfo’DÆÆx\ÆD'g¶Æh X¨ÆmÆÆè}6ÆÆQ‡«/ÆÆauÆÆ³h9ÆÆ¼‚ÃmÆÆè}u‚½Æ)p‡²¦‚¥{D¾t£”'ÄmÆÆè}Yo`´ÆÆ¿yd³Æ³cKt¶ÂlLg©ÆšÆÆþ}\$ÆÆþ›‡$ÆÆ ÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆÿÆ€Àÿ¯éþ–}é¯éþæ³é¯é¯é¯éÿà¯éÿ¯	éÿäéÿäéþãÞéûçÚé鿝éè–H.0uéé”98lçéVDAré}>FK鄯éò“áæ8Åä#ßçG±Í9éþ!äéÿ¯éè“Xééxƒ¬OééméP.zÖéz%gÆé¯éè“?éé_ŸÉ7éérŠééÓzCééÝšå¯é蓊šßé0„ŸÑÄšÃPà‰À®.æ¯éè“iƒpÔééávÒéÒtYˆ×äZzÇ鵯éþ“lé¯éþ¶Ÿé¯é éÿÌÿéþ̯ÿéý̯¯ÿéÿ̯ÿéÿ̯ÿéÿ̯ÿéÿ̯ÿéÿ̯ÿéÿ̯ÿéÿ̯ÿéÿÌ	¯ÿéÿÌ
¯ÿéÿ̯ÿéÿÌ¯ÿéÿÌ
¯ÿéÿ̯ÿ
éÿ̯ÿéÿ̯ÿéÿ̯ÿ
éÿ̯ÿ	éÿ̯ÿéÿ̯ÿéÿ̯€Àÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ¯ÿ ¯ÿÌéÿÙéþäÜéþßÖéþÖÙé
¯ÿÌ0é¯ÿÌ1é¯ÿÌ2éü§¯¤¬¯üª¦¯Ì
éþãÜéüØçæÜéü_¯3Œ¯ývIÌéûŸ`饼éû+Ôä5åéö­¯3Œ«®¯¯vV
éþçßéûŸ`éj•éÿäéüÝééäéùãé+Ôéw“éïa¯3A&&¯Šbá%ãéÛ"äéÚ¤9CDÃéŸ`É!/HÈÎZAG¸éyT1lÔ^AE¯é×S3A!ÔéÃ<éð_¯3J¯š!Ìbéb˜é‰kéÚåééªhéŸ`éj•ééi éÎEéu:æéw’é×:é‡ué½Ôéâéð_¯3}¯¯%ÑbéÁ:é.ÈéÙÊYAC-éŸ`éj•ééFHHáu}éé*CHH×H¹ééÔééèéñ_¯3j¯Ëãbéé7–=éñ[¦é¯'éŸ`ém“éé<ÈéúuŠééI»é÷c éáÔéÚ!éñ_¯3'”lOébéé–›éÚeq°H'éŸ`é›M­Ý”D®¹ŽéuŠéé¢<ª»Šéª3¬a	Ôé´Néñ‡¯q jåéÄ¥ééÓäéëØ}g¼ˆéĤéä¤wÑéÄnd¤é¯¹éòÉrbéé¼e¤ŠßéZ³é¯ÿÌéýÉ_„(éþàZéþ¯Ìéý‡„å1éÿÌ~éÿô=éþôÿ<éýôÿÿ;éÿôÿ:éÿôÿ9éÿôÿ8éÿôÿ7éÿôÿ6éÿôÿ5éÿôÿ4éÿô	ÿ3éÿô
ÿ2éÿôÿ1éÿôÿ0éÿô
ÿ€ÀÿÆÿµÆÿ¸Æþ»Æþ½¶Æþ¶¹ÍÆü½ÆºÃÆþÁ¼ÆþÁ»Æü·ÄÄ»ÆükÆ:žÆþ†SÆû‡RÆ Æû$´Á-ÃÆöÄÆ:žÁÅÆÆ†S
ÆþĽÆû‡RÆZÆÿÂÆü¼ÆÆÂÆùÁÆ$´Æe}ÆïnÆ:I++ƆS¿ ÁƺÂÆÚ‹099¥Æ‡R«(=ª¯M7=œÆfG*\´P7;•ƶG+7´Æ¥3ÆðkÆ:TƯ%ƆSÆSÆt[ÆÚÃÆÆYƇRÆZÆÆYˆÆ¯:Æc2ÄÆe|ƶ2ÆscÆ¡´ÆÀÆðkÆ:ÆÆ$²†SƤ2Æ'ªÆÙ¬K79&ƇRÆZÆÆ<==¿cjÆÆ$9==¶=ÆÆ´ÆÆÅÆñkÆ:xÆÅÁ†SÆÆ/4ÆñNÆ•!ƇRÆ\}ÆÆ3ªÆúcuÆÆ>ŸÆ÷TˆÆ¿´ÆºÆñkÆ:,\DƆSÆÆ„ÆÚV`–=!ƇRÆ„A“¼~9”žxÆcuÆÆ‰3ŸuÆ+’R´Æ™BÆñ™Æ€›Z{ÃÆ¦ÆÆ³ÂÆë·jX tƧŒÆÁ‹e²Æ§]U‹Æ•žÆò«aS†ÆÆ U‹u½ÆM˜Æý«Qp(Æþ¾MÆýspñÆÿÚ=ÆþÚî<ÆýÚîî;ÆÿÚî:ÆÿÚî9ÆÿÚî8ÆÿÚî7ÆÿÚî6ÆÿÚî5ÆÿÚî4ÆÿÚ	î3ÆÿÚ
î2ÆÿÚî1ÆÿÚî0ÆÿÚ
î€ÀÿéÿÕéÿ̯ÿ£¯þ¬¥¯þ§¡¯þ¡£¯
éÿÌ0¯éÿÌ1¯éÿÌ2¯üßéÚåéüãÝéÌ
¯þª¥¯ü¢®­¥¯ü~éEºéýb̯ûwH¯|¯û Ÿ«(¬¯öæéEºäèééV
¯þ®§¯ûwH¯Pp¯ÿ¬¯ü¦¯¯¬¯ùª¯ Ÿ¯Yn¯ï‚éEV33©éŠI©ª¯¥¬¯Ú{+23’¯wH—#6–šD16Н[?%QŸG14„¯¡>&1Ÿ¯’-¯ð~éEcéÎ,ÌvI¯Ir¯gP¯Ú¬¯¯€N¯wH¯Pp¯¯Ox¯š3¯X,­¯Yn¯¡,¯fX¯ŽŸ¯ª¯ð~éE¦éé%vI¯‘,¯"–¯Ù˜C12"¯wH¯Pp¯¯566©X^¯¯ 266¡6‹¯¯Ÿ¯¯®¯ñ~éEŽé˪vI¯¯)q.¯ñE}¯„¯wH¯Rn¯¯-–¯úXh¯¯7¯÷Jx¯©Ÿ¯¤¯ñ~éE4”Q<¯vI¯¯qu¯ÚLU„6¯wH¯u:‚¦o3‚‹j¯Xh¯¯y-€h¯€&IŸ¯‡:¯ñ´é— Pl¬¯“|¯¯Ÿ¬¯ë¢^Nf¯”|¯«{Y¯”RK{¯„‹¯ò—VIv¯¯K{h§¯D‡¯éÿ̯ý—Gc(¯þ¨D¯þé̯ýfc¬1¯ÿÌ~¯ÿ¬=¯þ¬ª<¯ý¬ªª;¯ÿ¬ª:¯ÿ¬ª9¯ÿ¬ª8¯ÿ¬ª7¯ÿ¬ª6¯ÿ¬ª5¯ÿ¬ª4¯ÿ¬	ª3¯ÿ¬
ª2¯ÿ¬ª1¯ÿ¬ª0¯ÿ¬
ª€Àÿ
ÿé¯ù3AC.®«¯éÿô
¯úŒ¯¯®‰¯éþôÿ¯éýôÿÿ¯éÿôÿ¯éÿôÿ¯éÿôÿ¯éÿôÿ¯éÿôÿ¯
éÿôÿ¯éÿôÿ¯éÿô	ÿ¯
éÿô
ÿ¯	éÿôÿ¯éÿôÿ¯éÿô
ÿ¯éÿôÿ¯éÿôÿ¯éÿôÿ¯éÿôÿ¯éÿôÿ¯éÿôÿ¯þéôÿ¯ÿôÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯€ÀÿÆÆù:JL4ÅÁÆÿÚ
Æú¢žÆÆÅ›ÆþÚî*ÆýÚîî)ÆÿÚî(ÆÿÚî'ÆÿÚî&ÆÿÚî%ÆÿÚî$ÆÿÚî#ÆÿÚî"ÆÿÚ	î!ÆÿÚ
î ÆÿÚîÆÿÚîÆÿÚ
îÆÿÚîÆÿÚîÆÿÚîÆÿÚîÆÿÚîÆÿÚîÆÿÚîÆÿÚîÆîÆîÆîÆîÆîÆîÆîÆîÆîÆîÆîÆîÆîÆîÆîÆ€Àÿ¯éùEWZ=èäé¯ÿ¬
éú¾ºééè¶é¯þ¬ªé¯ý¬ªªé¯ÿ¬ªé¯ÿ¬ªé¯ÿ¬ªé¯ÿ¬ªé¯ÿ¬ªé
¯ÿ¬ªé¯ÿ¬ªé¯ÿ¬	ªé
¯ÿ¬
ªé	¯ÿ¬ªé¯ÿ¬ªé¯ÿ¬
ªé¯ÿ¬ªé¯ÿ¬ªé¯ÿ¬ªé¯ÿ¬ªé¯ÿ¬ªé¯ÿ¬ªéþ¯¬ªéÿ¬ªéªéªéªéªéªéªéªéªéªéªéªéªéªéªéªé€Àÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ橯A3/&n¯+”®¯¯wH¯?EP)<¯Xg¯Æ¨a¯I¯¯Pn¯`*T(.¯˜)A.¥¯G:dNx¯D@‰®¯¯š£-š¯Ž¨¯ˆ¯¯¡–¯­€€¢“¯š¯ñŠ£¯‘¯¯˜Ÿ¯¯˜z£¯ý’$¯ùŸq‡­¯¯–¯üP`:9¯ÿ£Ÿÿ¨¯ûœI\)i¯ý›{š¯ÿaGÿ‚¯ü®|§˯€Àÿæ¿ÆJ96+|Æ1§ÄÆÆ†RÆGNZ/DÆctÆÆ¾mÆSÆÆ[}Æm/_-4Ƭ/J4»ÆPAqYˆÆMH›ÄÆÆ¯¸2®Æ¡¾ÆšÆÆ¶©ÆÄ‘·¦Æ®²Æñœ¹Æ²¥ÆÆ¬´ÆÆ¬Š¸¢Æý¥²)Æù³˜ÄÆÆ©Æü[mA@Æÿ¸³ÿ¾Æû°Rh/wÆý¯‹®ÆÿmPÿ”ÆüÅ ½ËÆ€ÀÿæáéWD?3’é:Äçééž`éT\j7Péu‰éÆàéb#éék“é€8o5=éÊ7W=Üé^M…h éZU·çééÎÙ;Íé½àéµéé×Çéæª«ØÄéÍÑéñ¸ÙéÑÂééÊÔééÊ£Ù¾éýÃÑ0éùÓ–³æééÇéük€MLéÿÙÓÿàéûÏaz7ŒéýϤÍéÿ^ÿ®éüè¼¥ßËé€Àÿ
ÿÿf¯ÿÿÿÿ‹¯ÿÿÿ¯ÿÿèÿ¯ÿ¯ÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿèÿ¯ÿ¯ÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿÿÿ¯ÿúè®77iñ7¯ÿûìÏÏÛüϯÿ¯ÿ¯ÿ¯ÿ¯ÿ€ÀÿÿtÆÿÿÿÿÆÿÿÿÆÿÿèÿÆÿÆÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿèÿÆÿÆÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿÿÿÆÿúè®77iñ7ÆÿûìÏÏÛüÏÆÿÆÿÆÿÆÿÆÿ€Àÿÿˆéÿÿÿÿ¹éÿÿÿéÿÿèÿéÿéÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿèÿéÿéÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿÿÿéÿúè®77iñ7éÿûìÏÏÛüÏéÿéÿéÿéÿéÿ€Àÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿ¯Þsy¯oR¯–Z¯C©¯‡6;Kf¯¶e=‘iéé§O_ÂéIH—éÿ¯à„<¯0¬f`¯¦§”‘fÌH–ÜR6é³E×Ç0é²L
éÿ¯à«©*M‡7ޝ¦yH¯¯Aväèé­6édT{{%»²L
éÿ¯ç0aR|V®¯¦wK¯¯O‡æçé¥6é]{¶ýÚ³L
éÿ¯à^§:¯¯¦¥˜®‡éQËI7é±CÔéÆãÔ0à	éÿ¯ß‘-ª¯Gv¯¯>©¯€6F| éÞQ„UéèŸI3Qàé„Eéÿ¯ÿÌéûÏäÙSšéÿ¯ÿÌéû«?Cãéÿ¯ÿÌéÿ¯ÿÌ éÿ¯ÿÌ!éÿ¯ÿÌ"éÿ¯ÿÌ#éÿ¯ÿÌ$éÿ¯ÿÌ%éÿ¯ÿÌ&éÿ¯ÿÌ&éþí¯ÿÌ&éýíÿ¯ÿÌ&éüíÿÿ¯ÿÌ&éÿíÿÿ¯ÿÌ&éÿíÿÿ¯ÿÌ&éÿíÿÿ¯ÿÌ&éÿíÿ?ÿöîm77«°77hó7öín77ª±77fõ7öëp77¨³77eö7öêq77¦´77cø7êès77¥¶77bù77ûÜÏÏëìÏÏÛüÏöûÝÏÏëíÏÏÛýÏöûÝÏÏêíÏÏÚýÏöúÝÏÏêíÏÏÚþÏôúÞÏÏêîÏÏÚþÏÏ@ÿ€ÀÿÿÆÞ‚‰Æ~\ƪfÆL¿Æ˜=CUsưV4{YÆÆŽCQ¥Æ>=€ÆÿÆà•DÆ6#ÃsmƼ½§¤sÆ=»F.Ƙ;¶©)Æ—@
ÆÿÆàÁ¿/W˜>¡Æ¼‰RÆÆJsÁÅÆ“.ÆUGii Ÿ—@
ÆÿÆç6n]ŒbÅÆ¼†UÆÆMsÄÄÆ.ÆOi›ýº˜@
ÆÿÆàj’½BÆÆ¼»¬©sÆEm¬>/Æ—9´Æ¨Á´(¾	ÆÿÆß¤3ÀÆP†ÆÆG¿Æ‘4+E¾Æp:yÆÿ%Æû°Á¸GƒÆÿ%Æû‘69yÁÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ=ÆþÍ<ÆýÍî;ÆüÍîî:ÆÿÍîÿ9ÆÿÍîÿ8ÆÿÍîÿ7ÆÿÍî?ÿöîm77«°77hó7öín77ª±77fõ7öëp77¨³77eö7öêq77¦´77cø7êès77¥¶77bù77ûÜÏÏëìÏÏÛüÏöûÝÏÏëíÏÏÛýÏöûÝÏÏêíÏÏÚýÏöúÝÏÏêíÏÏÚþÏôúÞÏÏêîÏÏÚþÏÏ@ÿ€ÀÿÿéÞš¡é”méÈxéZáé³HOe‡é¶L.mO¯¯~;G‘¯76q¯ÿéà¯Pé@)净é!ÝÞ!ÄÁ‡Ì6q¥>(¯‡4¡–$¯†9
¯ÿéàäá8f³I½é!Ý¢`ééWv«®¯‚(¯K?]]†9
¯ÿéç@‚n¤s!èé!ÝždééOf­®¯|(¯F]‰ý¤‡9
¯ÿéà}¬ÞNéé!ÝÜ#Ê®f¯=a˜7)¯…2Ÿ¯•ªŸ$¨	¯ÿéßÁ<âé^ééSáé«65]x¯§_=d@¯®w7&=¨¯c3k¯ÿéÿ̯ûœ«£>t¯ÿéÿ̯û€/2kª¯ÿéÿ̯ÿéÿÌ ¯ÿéÿÌ!¯ÿéÿÌ"¯ÿéÿÌ#¯ÿéÿÌ$¯ÿéÿÌ%¯ÿéÿÌ&¯ÿéÿÌ&¯þ®éÿÌ&¯ý®ªéÿÌ&¯ü®ªªéÿÌ&¯ÿ®ªÿéÿÌ&¯ÿ®ªÿéÿÌ&¯ÿ®ªÿéÿÌ&¯ÿ®ª?ÿöîm77«°77hó7öín77ª±77fõ7öëp77¨³77eö7öêq77¦´77cø7êès77¥¶77bù77ûÜÏÏëìÏÏÛüÏöûÝÏÏëíÏÏÛýÏöûÝÏÏêíÏÏÚýÏöúÝÏÏêíÏÏÚþÏôúÞÏÏêîÏÏÚþÏÏ@ÿ€Àÿ
ÿéÿíÿÿÿÿÿ
éÿíÿÿÿÿÿéÿíÿÿÿÿèÿéÿí	ÿÿÿ
éÿí
ÿÿÿ	éÿíÿÿÿÿÿéÿíÿÿÿÿÿéÿí
ÿÿÿÿÿéÿíÿÿÿÿÿéÿíÿÿÿÿÿéÿíÿÿÿÿÿéÿíÿÿÿÿÿéÿíÿÿÿÿÿéÿíÿÿÿÿÿþéíÿÿÿÿÿÿíÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ7æt77£¸77`û7öåv77¢¹77_ü7úãx77 èÿõÏùÞÏÏéîÏÏÙþÏöùÞÏÏéïÏÏÙÿÏûùßÏÏéÿ&ÿ&ÿ&ÿ&ÿ&ÿ™ÀÿÆÿÍîÿÿÿÿ
ÆÿÍîÿÿÿÿÆÿÍîÿÿÿèÿÆÿÍ	îÿÿ
ÆÿÍ
îÿÿ	ÆÿÍîÿÿÿÿÆÿÍîÿÿÿÿÆÿÍ
îÿÿÿÿÆÿÍîÿÿÿÿÆÿÍîÿÿÿÿÆÿÍîÿÿÿÿÆÿÍîÿÿÿÿÆÿÍîÿÿÿÿÆÿÍîÿÿÿÿþÆÍîÿÿÿÿÿÍîÿÿÿÿîÿÿÿÿîÿÿÿèÿîÿÿîÿÿîÿÿÿÿîÿÿÿÿîÿÿÿÿ0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ7æt77£¸77`û7öåv77¢¹77_ü7úãx77 èÿõÏùÞÏÏéîÏÏÙþÏöùÞÏÏéïÏÏÙÿÏûùßÏÏéÿ&ÿ&ÿ&ÿ&ÿ&ÿ™Àÿ¯ÿ®ªÿÿÿÿ
¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿèÿ¯ÿ®	ªÿÿ
¯ÿ®
ªÿÿ	¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿÿ¯ÿ®
ªÿÿÿÿ¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿÿ¯ÿ®ªÿÿÿÿþ¯®ªÿÿÿÿÿ®ªÿÿÿÿªÿÿÿÿªÿÿÿèÿªÿÿªÿÿªÿÿÿÿªÿÿÿÿªÿÿÿÿ0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ7æt77£¸77`û7öåv77¢¹77_ü7úãx77 èÿõÏùÞÏÏéîÏÏÙþÏöùÞÏÏéïÏÏÙÿÏûùßÏÏéÿ&ÿ&ÿ&ÿ&ÿ&ÿ™Àÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ
@Àÿ
@Àÿ
@Àÿ
@>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ
@>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ
@>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿþÿÿ
ÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ	ÿÿÿÿÿ
ÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿ	ÿÿÿ	ÿÿÿ"ÿÿÿþÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ.¯þéÿ-¯éÿÿ,¯éÿÿ+¯éÿÿ*¯éÿÿ)¯éÿÿ(¯éÿÿ'¯éÿÿ&¯éÿÿ%¯	éÿÿ$¯
éÿÿ#¯éÿÿ"¯éÿÿ!¯
éÿÿ ¯éÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿþÿÿ
ÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ	ÿÿÿÿÿ
ÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿ	ÿÿÿ	ÿÿÿ"ÿÿÿþÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿþÿÿ
ÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ	ÿÿÿÿÿ
ÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿ	ÿÿÿ	ÿÿÿ"ÿÿÿþÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ.éþ¯ÿ-é¯ÿÿ,é¯ÿÿ+é¯ÿÿ*é¯ÿÿ)é¯ÿÿ(é¯ÿÿ'é¯ÿÿ&é¯ÿÿ%é	¯ÿÿ$é
¯ÿÿ#é¯ÿÿ"é¯ÿÿ!é
¯ÿÿ é¯ÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ>ÿÿø\Œ·Ìª‚!ýÿÿö)ËÿÿìÕúÿü—ý@ÿÿÿõ,÷ÿŸ7»àý@ÿÿÿýÀÿyÿLþ
ý#ý@ÿÿÿüPÿß
øræÿÿï—ñêȶöÿùއôÿùðýCÿýŠÿ~÷£ÿÕrfºÿÔñúãÒ½t™ôÿ|CšÿÄxý!ÿý±ÿZ
ö<ÿÔ¨ÿsýúÿ–ý$ÿïý@ÿÿÿýÔÿ:
ý ÿXý$þØýúþøÜÿ@ÿÿÿýÂÿJ
ýÆÿùåúúâø¡ÿ+@ÿÿÿýœÿm
ýâÿùÌÿúÖøšÿ.@ÿÿÿýuÿª
ýÈÿùãûúÖøšÿ.@ÿÿÿüðþ2ú£ÿOýýÛþúÖøšÿ.:ÿ™þÿülÿÛ4øLÃDÿÄý™ÿ|þúÖøšÿ.ÿÍÿÿõ‘þúª}e‘ÇÿÛ÷°ÿÅaV«ÿÞ
þúÖòšÿ.Ñý¼†€$ÿýAÌøÿýñŠøˆùÿÿý­þúÖýšÿ.ø)³æýÿHÿû'<ü,5
ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿþ#ÿÿþ!ÿÿþýþÿÿþýÿüûüýþÿÿþüýüûùøùùúûüýþþÿÿþúýüúøööõùöøúüýþþÿóÿþþýûùöúüýýüûøüúüýþÿþûýüúøýÿüþýüüÿþüüú÷þ*ÿûþýüùø+ÿûþýû÷û+ÿûþüúöý+ÿûþüúöü+ÿûþüúöù+ÿúþýû÷õþ*ÿúþýûùôû*ÿþûüúöóþ)ÿþûýûùõó)ÿøÿþþýûøõô(ÿÿþúüûøöôü&ÿÿóþýüûù÷öøúüýþþ ÿÿþõýüûúùùúûüýþÿÿþýüÿýþÿÿþýþ ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ<ÿ(ÿÿ)ÿÿ)ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ	ÿÿ	ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿþÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿ	ÿÿÿÿÿÿÿ	ÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÓÿ¯.éÿíÿ¯.éÿíÿ¯.éÿí	ÿ¯.éÿí
ÿ¯.éÿíÿ¯.éÿíÿ¯.éÿí
ÿÿ¯.éÿíÿ.éÿíÿ-éÿíÿ,éÿíÿ+éÿíÿ*éÿíÿ)éÿíÿ(éÿíÿ'éÿíÿ&éÿíÿ%éÿíÿ$éÿíÿ#éÿíÿ"éÿíÿ!éÿíÿ éÿíÿéÿíÿ¯ÿÿ<ÿ(ÿÿ)ÿÿ)ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ	ÿÿ	ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿþÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿ	ÿÿÿÿÿÿÿ	ÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÓÿ6ÆÿÍî5ÆÿÍî4ÆÿÍ	î3ÆÿÍ
î2ÆÿÍî1ÆÿÍî0ÆÿÍ
î/ÆÿÍî.ÆÿÍî-ÆÿÍî,ÆÿÍî+ÆÿÍî*ÆÿÍî)ÆÿÍî(ÆÿÍî'ÆÿÍî&ÆÿÍî%ÆÿÍî$ÆÿÍî#ÆÿÍî"ÆÿÍî!ÆÿÍî ÆÿÍîÆÿÍîÆÿÿ<ÿ(ÿÿ)ÿÿ)ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ	ÿÿ	ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿþÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿ	ÿÿÿÿÿÿÿ	ÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÓÿé.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®	ªé.¯ÿ®
ªé.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®
ªÿé.¯ÿ®ª.¯ÿ®ª-¯ÿ®ª,¯ÿ®ª+¯ÿ®ª*¯ÿ®ª)¯ÿ®ª(¯ÿ®ª'¯ÿ®ª&¯ÿ®ª%¯ÿ®ª$¯ÿ®ª#¯ÿ®ª"¯ÿ®ª!¯ÿ®ª ¯ÿ®ª¯ÿ®ªéÿý0"<ýÿ¶(ý
˜Šýÿ¶)ýãÿý)ý£ÿZü(þý"
þýcÿšý¨ïÿþÜNýðª÷”ð/~çÿÿÇ#ù$½÷ÿÿÛHù”ð/‚åÿÊý#ÿÚö>ê–kZƒêÿNýÿ¶öžÿ¶åƒzÛÿÖ÷;ôõŒ\sÝþYùžÿ¸ìqyøãÿM
ýïÖýÿ¶ðžÿáÇÿKÒö+öãä
žÿé
ú£ÿYø«ÿ	ÿ¶ýžÿlø€ÿpLÿ‘øzÿCžÿwëcÿ˜ÌоÜèèöÿ(ÿ¶ýžÿ>øEÿ‡wÿ¤pø¡ÿlžÿGë"ÿØü6øù·“ƒ€Ëÿ2ÿ¶ýžÿ2ø>ÿŠ™ÿù÷úžÿ2øâÿKÿÊû2ø­ÿ2ÿ¶ýžÿ2ø>ÿŠ€ÿX	ýžÿ2ù¢ÿW‹þùÔ÷ßÿ2ÿ¶ýžÿ2ø>ÿŠWÿ˜	ýžÿ2øbÿ–ÊÓïû÷nÿÿ2ÿ¶ýžÿ2÷>ÿŠßû6ùžÿ2ì"ÿßü”žÿÊjN˜õÏÿ2ÿ¶ýžÿ2ý>ÿŠñDùøŒVLk¤öžÿ2
úâÿÿT°ÿöà=–ÿ2ÿ¶ýžÿ2ý>ÿŠý*Áþÿøìžžÿ2
÷¢ÿÿ=û
1; Ùÿÿ	ÿ#ÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿ	ÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ	ÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿ ÿÿÿ+ÿÿÿ6ÿ7ÿ:ÿçÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ'¯ÿ&¯ÿéÿ%¯éÿ$¯éÿ#¯éÿ"¯éÿ!¯éÿ ¯é¯ÿ/¯ÿ/¯ÿ/¯ÿ'¯ÿÿ	ÿ#ÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿ	ÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ	ÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿ ÿÿÿ+ÿÿÿ6ÿ7ÿ:ÿÏÿîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ'Æîÿ/Æÿ/Æÿ/Æÿ/Æÿ'Æÿÿ	ÿ#ÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿ	ÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ	ÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿ ÿÿÿ+ÿÿÿ6ÿ7ÿ:ÿÏÿªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ'éªÿ&éÿ¯ªÿ%鯪ÿ$鯪ÿ#鯪ÿ"鯪ÿ!鯪ÿ é¯éÿ/éÿ/éÿ/éÿ'éÿý0		þ(-#ü˜˜&ùt˜#œÿ4	þØðýüOÿÿsøóûœÿ4	þØðýfÿjüŽüè²ø6ÿÇ	þØðýfÿjûÎÌ¢ïývÿ‡	ù!Øðþ	 þýfÿjûýdÿ2ø¶ÿG’ð0÷…ñÿ÷¼Øð÷»ûÿë%ð—ù¥ñÿÿèiüªöÿöðîÿN&ÿqñûœÿ4÷›ÿÓphÀ׿ðöâÿ¡_tÚ½ÿ¢ñäýžajÆÿˆU®ÿ°xôýè±5ÿÇñœÿ4,þÓ
µÿðýÿ€üâÿ¢ý¦ÿQ÷¾ú%fÿjõ Ъîtÿ‡øœÿ4ÿ[ý3ÿðýâô
øuÿ¢ÿÁøJÿsfÿjõlÿ0´ÿFøœÿ4´ÿ$ø÷ðþÒøCÿ¢Gÿ¿pø†ÿœfÿjöQ.ÿoðûøœÿ4ÏÿùßðÿÁø.ÿ¢iÿû÷ú®fÿj÷î¯3ÿÆøœÿ4¶ÿ"ùõðûØøIÿ¢Pÿˆ	ýfÿjû²ìtÿ†øœÿ4”ÿRý*ÿðý×úøŠÿ¢'ÿÈ	ý`ÿsûtÿÜÿF÷œÿ45ÿÉý¦ÿðöhÿ¤,ðÿ¢ý»ÿbù
Bÿ§ÿû6ÿÿúýœÿ4÷¨ÿÂXP«ãêïöÃÿÓ‘¨ó’ÿ ì íÿ¢]Ie”ìD
ë÷²‚€LüôÿÆýœÿ4÷•ûÿÿÔ$Øð÷‹Ôð¼`8ÿ…ý§ûÿýõ® ù;Áêþÿ"/ý
8
ýdÿdû)>%+ÿüß÷6øË¡l[‚çÿƒ7ù•êÿÿüÚ]:ý Ûÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿÿ9ÿ7ÿ7ÿÿ7ÿÿÿÿ-ÿ
ÿÿ+ÿ+ÿÿÿÿ+ÿÿÿ1ÿÿÿ-ÿÿ+ÿÿ,ÿ	ÿ+ÿÿÿÿ+ÿÿ+ÿÿ8ÿîÿ¯.éÿí¯.éþíÿ
¯.éýíÿÿ¯.éÿíÿ¯.éÿíÿ
¯.éÿíÿ	¯.éÿíÿ¯.éÿíÿ¯.éÿíÿ¯.éÿíÿ¯.éÿí	ÿ¯.éÿí
ÿ¯.éÿíÿ¯.éÿíÿ¯.éÿí
ÿÿ¯.éÿíÿ.éÿíÿ-éÿíÿ,éÿíÿ+éÿíÿ*éÿíÿ)éÿíÿ(éÿíÿ'éÿíÿ¯ÿÿ9ÿ7ÿ7ÿÿ7ÿÿÿÿ-ÿ
ÿÿ+ÿ+ÿÿÿÿ+ÿÿÿ1ÿÿÿ-ÿÿ+ÿÿ,ÿ	ÿ+ÿÿÿÿ+ÿÿ+ÿÿ8ÿîÿ>ÆÿÍ=ÆþÍî<ÆýÍîî;ÆÿÍî:ÆÿÍî9ÆÿÍî8ÆÿÍî7ÆÿÍî6ÆÿÍî5ÆÿÍî4ÆÿÍ	î3ÆÿÍ
î2ÆÿÍî1ÆÿÍî0ÆÿÍ
î/ÆÿÍî.ÆÿÍî-ÆÿÍî,ÆÿÍî+ÆÿÍî*ÆÿÍî)ÆÿÍî(ÆÿÍî'ÆÿÍîÆÿÿ9ÿ7ÿ7ÿÿ7ÿÿÿÿ-ÿ
ÿÿ+ÿ+ÿÿÿÿ+ÿÿÿ1ÿÿÿ-ÿÿ+ÿÿ,ÿ	ÿ+ÿÿÿÿ+ÿÿ+ÿÿ8ÿîÿé.¯ÿ®é.¯þ®ª
é.¯ý®ªªé.¯ÿ®ªé.¯ÿ®ª
é.¯ÿ®ª	é.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®	ªé.¯ÿ®
ªé.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®
ªÿé.¯ÿ®ª.¯ÿ®ª-¯ÿ®ª,¯ÿ®ª+¯ÿ®ª*¯ÿ®ª)¯ÿ®ª(¯ÿ®ª'¯ÿ®ªéÿü"'9ý¬ýÿþâO7øËÿ­rZu»q7þÿÀÿ7ýúÇ<ú¯ÿâŒ?9øwãÿÿïŠ
9ú?”éÿ¥<üùö6ÿýñì7øè‹[BmÂÿ7þËüÿýôŒ8û-A+
úÿ ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ¯ÿÿîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿÆîÿ/Æÿ/Æÿ/Æÿ/ÆÿÆÿÿªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿéªÿ/éÿ/éÿ/éÿ/éÿéÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿ¯.éÿíÿ¯.éÿíÿ¯.éÿí
ÿÿ¯.éÿíÿÿ3ÆÿÍî1ÆÿÍî0ÆÿÍ
î/ÆÿÍîÿÿLéÿè·é.¯ÿ®ªé.¯ÿ®ªé.¯ÿ®
ªÿé.¯ÿ®ªÿÿÿ¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ¯ÿ®'¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ¯'ÿé'ÿé'ÿé'ÿéÿÿÆÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/ÆÿÆÿÅ'Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/ÆÿÆîÿÆîÿÆîÿÆîÿÆÿÿéÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿéÿç'éÿ/éÿ/éÿ/éÿ/éÿ/éÿéªÿ¯ªÿ¯ªÿ¯ªÿ¯ÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿ¯éÿÿÆÿÿé¯ÿÿÿ¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿ/éÿ¯éÿ¯éÿ¯éÿ¯ÿÿÆÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/ÆÿÆÿÿéÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ/¯ÿé¯ÿé¯ÿé¯ÿéÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿ7éÿ6éÿ5é	ÿ4é
ÿ3éÿ2éÿ1é
ÿ0éÿ/éÿ.éÿ-éÿ,éÿ+éÿ*éÿ)éÿ(éÿ'éÿ&éÿ%éÿ$éÿ#éÿ"éÿ!éÿ éÿ¯+é¯,é¯-é¯.éÿÿ7Æî6Æî5Æ	î4Æ
î3Æî2Æî1Æ
î0Æî/Æî.Æî-Æî,Æî+Æî*Æî)Æî(Æî'Æî&Æî%Æî$Æî#Æî"Æî!Æî ÆîÆÿÿ7¯ª6¯ª5¯	ª4¯
ª3¯ª2¯ª1¯
ª0¯ª/¯ª.¯ª-¯ª,¯ª+¯ª*¯ª)¯ª(¯ª'¯ª&¯ª%¯ª$¯ª#¯ª"¯ª!¯ª ¯ªé+¯é,¯é-¯é.¯ÿÿ8ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯éÿí;ÿéÿí<ÿþéí=ÿÿí?ÿÿ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿ
ÆÿÍ#îÿÆÿÍ$îÿþÆÍ%îÿÿÍ&îÿÿ'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé¯ÿ®#ªÿ¯ÿ®$ªÿþ¯®%ªÿÿ®&ªÿ'ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿÿ/ÿÿ.¯ÿÿ.¯ÿÿ.¯ÿÿ.¯ÿÿ.¯ÿÿ.¯ÿÿ.¯ÿÿ¯ûYWWn¯þœ¯ÿÿ¯úK]C
y¯þ¥Z¯ÿÿ¯ú¯¯[.¯ô‹fu¨¯Ÿ{®¯¯„•¯ô’¯¯‚”¯¯~¤©z¯ÿÿ¯ë¯§8E¯¯aG¦«$`¯|Ÿ¯ñq-¯ª8¯£§š¯¯aÿÿ¯ì(§¯¥€¯®[¯˜x¯ñŸª3®v(¯š¯¦ÿÿ¯í„¤<1¯ˆª¯¯O9¯¯sT¯òSg.ŒHV¯š¯Œÿÿ¯í¯¯“ŸŠª¯¯L:¯¯G6¯òKS%•\^„¯š¯ÿÿ¯ì¯¯j¬§
€¯¬_¯sž d¯òy¯Š«¯š¯§
ÿÿ¯ë'1n¯¯j>%©–
Н”Œ¯ò¥A¯­
1¯¯š¯¯hÿÿ¯ü„ƒƒ–¯ù•q€¬¯—¯þ“”¯ö‰›¯¯—¯¯ª„¯ÿÿ.¯ÿÿ.¯ÿÿ.¯ÿÿ.¯ÿÿ.¯ÿ/ÿ/¯é.¯é-¯é,¯é+¯é*¯é)¯é(¯é'¯é&¯é%¯é$¯é#¯é"¯é!¯é ¯é¯é¯ é¯!é¯"é¯#é¯$é¯%é¯&éÿÿ.éÿÿ.éÿÿ.éÿ/ÿÿ/ÿÿ.Æÿÿ.Æÿÿ.Æÿÿ.Æÿÿ.Æÿÿ.Æÿÿ.ÆÿÿÆûdcc}²Æþ±ÆÿÿÆúUiL‰ÆþºfÆÿÿÆúŸÆÆf4Æôžt„¾Æ³‹ÅÆÆ•¨Æô¥£ÆÆ“§ÆÆº¿ŠÆÿÿÆëŸÆ½?NÆÆnQ#!¼Á(mÆ´Æñ3ÆÀ@Ƹ½®ÆÆmÿÿÆì-½Æ»ÆÅ"fƬˆ’Æñ³À’:Ć.Æ®Ƽ	ÿÿÆí•º¢D8ÆšÁÆÆY@ÆÆ‚_Æò!’^t4žRbƮƞÿÿÆíŸÆÆ¦´œÁÆÆVBÆÆP=ÆòU^*¨hj–ƮƠÿÿÆìŸÆÆxý‘ÆÂkÆ‚³$qÆò‰ Æœ"ÁÆ®ƽÿÿÆë,7}ÆÆxG*¿ªœÆ§žÆòºJÆÄ7ÆÆ®ÆÆvÿÿÆü•””©Æù¨€ÂÆ«¢Æþ¦§Æö›¯ÆÆŸ«ÆÆÁ–Æÿÿ.Æÿÿ.Æÿÿ.Æÿÿ.Æÿÿ.Æÿ/ÿÆÿÿ.Æÿÿ.Æÿÿ.Æÿ/ÿÿ/ÿÿ.éÿÿ.éÿÿ.éÿÿ.éÿÿ.éÿÿ.éÿÿ.éÿÿéûvtt“ÑéþÐ#éÿÿéúd{Z¢éþÛxéÿÿéú»ééy=éô¹ˆ›àéÓ¤èé靯éôÃÀéé­Äéé¨Úá£éÿÿéë»éßJ\éé‚	_)'Ýä0€é¥Ôéñ–<éâKéÙÞÍééÿÿéì5ßéÜ	ªéè(yéË ¬éñÓâ¬Eç6éÍéÝ
ÿÿéí¯Ú¾OBéµ	ãééiLéé™oéò'¬o‰=º`séÍéº	ÿÿéí»ééÄÔ¸ãééeNéé^Héòeo1Æz}#°éÍé¼ÿÿéì»ééåÞ
«éä$~éšÒ*„éò¢%é¸(	äéÍéß
ÿÿéë4A%“ééŽ	S1áȸéĺéòÛWéæAééÍéé‹ÿÿé¯ÿÇéùÆ—ªäéÉ¿éÄéö¶Ïéé»Éééã°éÿÿ.éÿÿ.éÿÿ.éÿÿ.éÿÿ.éÿ/ÿ/é¯.é¯-é¯,é¯+é¯*é¯)é¯(é¯'é¯&é¯%é¯$é¯#é¯"é¯!é¯ é¯é¯é ¯é!¯é"¯é#¯é$¯é%¯é&¯ÿÿ.¯ÿÿ.¯ÿÿ.¯ÿ/ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ0ÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ¯þeR¯þª’¯ÿÿÿ¯þ[D¯þ¯ÿÿå‰g[D¯¯¦po¡€£¯¯¤th”¯¯dxxª¯ÿÿåI&D¯ª"K$‡¯‘8Iw¯D	RR¥¯ÿÿ脯®D¯i¯¯ˆ‡¯1V¯¯s¯¯ÿÿ謯¯ED¯GI¯¯¬‡¯	3AA;ª¯ÿÿ絛¯CD¯L@¯¯§‡¯f‰ý­¯ÿÿﯬD¯w¡¯n‡¯0D¯û®¯£	­¯ÿÿåD+D¯®<#1‹¯– N=-¯¯3%<£¯ÿÿúr–𔝖÷Ÿ¯¯ª€m~¨¯ýœ„«¯ÿÿÿ¯ûf‡x/1"¯ÿÿÿ¯ûa75Y§"¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿéÿíÿÿéÿíÿÿéÿí ÿÿéÿí!ÿÿéÿí"ÿÿéÿí#ÿÿéÿí$ÿÿéÿí%ÿÿéÿí&ÿÿéÿí'ÿÿéÿí(ÿÿéÿí)ÿÿéÿí*ÿÿéÿí+ÿÿéÿí,ÿÿéÿí-ÿÿéÿí.ÿÿ
éÿí/ÿÿéÿí0ÿÿéÿí1ÿÿ
éÿí2ÿÿ	éÿí3ÿÿéÿí4ÿÿéÿí5ÿÿ.éÿÿÿ.éÿÿÿ.éÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ>ÿÿ>ÿÿ>ÿÿ>ÿ0ÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.ÆÿÿÿÆþr\ÆþÀ¥ÆÿÿÿÆþfMÆþ£Æÿÿå›t fMÆÆ¼~¶¸ÆÆºƒv¨ÆÆqˆˆÀÆÿÿå	S!+MÆÀ"'U)™Æ¤@S†ÆM
\\»Æÿÿè•ÆÄMÆw ÆÆš™Æ7bÆÆ‚Æ£ÆÿÿèÂÆÆNMÆQRÆÆÃ	™Æ
:JJCÀ£ÆÿÿïÁÆÆLMÆVHÆÆ½™Æ	t›ýÄ£Æÿÿï’ÆÃMƇ¶Æ|™Æ6MÆûÄÆ¸
ÄÆÿÿåM1MÆÅD(8Æ©$YE3ÆÆ9*D¹Æÿÿú¢ª¯¨Æôª©ª´ÆÆÀ{¾Æý°•ÁÆÿÿÿÆûs™ˆ58"ÆÿÿÿÆûn>ÿÿ>ÿÿ>ÿÿ>ÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿ
ÆÿÍ îÿÿÆÿÍ!îÿÿÆÿÍ"îÿÿ
ÆÿÍ#îÿÿ	ÆÿÍ$îÿÿÆÿÍ%îÿÿÆÿÍ&îÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ>ÿÿ>ÿÿ>ÿÿ>ÿ0ÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿéþ†méþâÃéÿÿÿéþyZéþÀéÿÿå·‰¼yZééÝ•”תÙééÚš‹Åéé„  âéÿÿåb'2Zéâ(.d0´éÁKbžéZmmÜéÿÿè¯éç!ZéŒ%ééµ´éAsééš éÀéÿÿèäéé\Zé_aééå´éEWWOâÀéÿÿïãééZZéeUééߴ鈶ýæÀéÿÿï¬éåZéŸÖé’´é@ZéûçéÙæéÿÿå	Z%:ZéèO	/B¹éÇ!*hQ<ééD1OÙéÿÿú¿˜ÈÎÅéôÈÇÈÔéé⪑¨àéýϯäéÿÿÿéû‡´ >B"éÿÿÿéû‚IFwß"éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ
¯ÿ® ªÿÿ¯ÿ®!ªÿÿ¯ÿ®"ªÿÿ
¯ÿ®#ªÿÿ	¯ÿ®$ªÿÿ¯ÿ®%ªÿÿ¯ÿ®&ªÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ0ÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ%¯ÿ¯ÿÿÿ$¯þ̯ÿÿÿ#¯ýÌé¯ÿÿÿ"¯üÌéé¯ÿÿÿ!¯ÿÌéÿ¯ÿÿÿ ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ
¯þ™¯ÿy	¯ÿÌéÿ¯ÿÿÿ
¯þ}f¯ÿ¯ÿÌéþaÍéÿ¯ÿÿد¯­¯¯‹©¯—¦¡š¯¯¦€¦¯¯®„™–¥¯Ìß²çéÕ.š¶éé¯ÿÿÿ¯Ü¨›v¯$¡nQ¯|"R3¯£$K5ƒÌ…Fs:Žh{éé¯ÿÿÿ¯àAmCH£"¯nQ¯8‹¯€¯rQ¯¯™ç.äé¤pé:Äéÿ¯ÿÿÿ¯çnQ>”&uP¯nQ¯¬¯£¯Oq¯¯9¯¾Hühé:Äéÿ¯ÿÿÿ¯æœ"&¯93~¯nQ¯6¯‚¯yK¯Ê¯å%äéþE½éÿ¯ÿÿÿ¯ÝC¯h§¯nQ¯y"W1¯§3E:)µé€A‚r„é‚Btéé¯ÿÿÿ¯Ý‘œ¯¥Š¯¯Ÿ˜¯¯¡w¤ˆ¯¯ÌÇÝ%äééÝšªâééѯéé¯ÿÿÿ¯ûÌZBiéÿ¯ÿÿÿ¯ûÌéÅ£Ö
éÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯û]{¯<œ¯éÌéé¶âéé/HÔéâwY©éé:†éé¯ÿÿÿ¯øNq¯‰©¯¯Ìéû0ÏéééôVQŸ¶éé‹téé¯ÿÿÿ¯áNq¯MŸ¯i>9¡¢@OéééÛàé{]éé‹téé¯ÿÿÿ¯áNq¯&™¡NçåÓé0ÏééééÀéé¥>éé‹téé¯ÿÿÿ¯áNq¯&²Ò"oÀéé0Ïééééº(éé¯9éé‹téé¯ÿÿÿ¯áNq¯-ÌéÞ7é0ÏééééÓåé†Qéé‹téé¯ÿÿÿ¯ðNqÌ3ÌÍÚé¥XéS§éééô6†Ô5šéé‹téé¯ÿÿÿ¯ðg–éaÓ¯64WËé»ZAééôÍC+xèéé‹téé¯ÿÿÿ¯ÿÌ
éýWÖéú˜4téé¯ÿÿü¯¯sGÿƒéýÛÚçéúâÚâéé¯þ’ÿÿ¯ÿÌéþï¯ÿÿÿ¯ÿÌéýïÿ¯ÿÿÿ¯ÿÌéüïÿÿ¯ÿÿÿ¯ÿÌéÿïÿÿ¯ÿÿü¯¯Ìéÿïÿÿ¯ÿÿý¯Ìéÿïÿÿ¯ÿÿþÌéÿïÿÿ¯ÿÿÿéÿïÿÿ¯ÿÿ0ÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ%ÆÿÆÿÿÿ
Æþ­¢Æÿ‰ÆÿÆÿÿÿ
ÆþsÆÿ
ÆþR®ÆÿÆÿÿØÆÆÄ¢ÆÆ¿Æ«¼¶¯ÆÆ¼‘¼ÆÆÅ–­ª»ÆÆ½—ÄÆµ'‚›ÆÆÆÿÿÿÆÜ¾¯†Æ(¶}\Æ'\:ƹ)U<”Æq~¯¯¯ô(eŸ(t¯¯hW¯¯éÿÿÿéð‰–¯IŸƒ('A˜¯C1¯¯ôš2 Z®¯¯hW¯¯éÿÿÿéÿÌ
¯ý
A¡¯úr'W¯¯éÿÿüéé™^ÿƒ¯ý¥¤®¯úª¤ª¯¯éþÃÿÿéÿ̯þ­éÿÿÿéÿ̯ý­ªéÿÿÿéÿ̯ü­ªªéÿÿÿéÿ̯ÿ­ªÿéÿÿüéé̯ÿ­ªÿéÿÿýé̯ÿ­ªÿéÿÿþ̯ÿ­ªÿéÿÿÿ¯ÿ­ªÿéÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ?/¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ=¯þÌ<¯ýÌé;¯üÌéé:¯ÿÌéÿ9¯ÿÌéÿ8¯ÿÌéÿ7¯ÿÌéÿ6¯ÿÌéÿ5¯ÿÌéÿ4¯ÿÌéÿ3¯ÿÌ	éÿ2¯ÿÌ
éÿ1¯ÿÌéÿ0¯ÿÌéÿ/¯ÿÌ
éÿ.¯ÿÌéÿ-¯ÿÌéÿ¯þ¬|¯þ…£¯ÿÌéÿ¯þ§<¯þ5НýÌ™•éÿ¯Þ“ª¯–¯£š­¯¯®„›5Н¯“‰¢š¯¯®‡¥Ìäeb¶Ù
éÿ¯ÞHw¯#A¯Xm¤¯¡%K0НF6G&M¯‹/U=ßEB{Ç
éÿ¯àvI¡2%¯*›¤¯nV¯¬	Š¥¯¯FM¯DˆÌà3é‚|éÿ¯à£u_@Œ¯¤¯Gy¯¯*Š„<¯¯fM¯>HHCÝ‚|éÿ¯ç#3Žn3H¯¤¯lX¯­
Ѝ¯¯AM¯L®éþŒvéÿ¯ßQ®v¯¤¯Ÿ!Q3НX+7,RÌ·7q€ZéÄ&pÅ
éÿ¯ßŸŽ¯¯‡¨¯¬Š¯¯¬y–‘¦¯¯ œG“ééçªÓééß³Ø
éÿ ¯û^_C/ãéÿ
¯ÿ±¯ý¡}©éÿ
¯ÿÓ¯ÿÌéÿ¯þÅé¯ÿÌéÿ¯ý¹çé¯û‹N¯io¯ï˜´ééi?£éÎoÎééÁEJéÿ¯öd¯¯E>±ß1}¯û„;¯˜š¯ôi†ééP®ééœs%µéþÒ,éÿ¯öŠg¯¯zwÓ鎳¯ï„;¯ty¯{6)Q¹%*H¿P®éþ:µéþÒ,éÿ	¯ÿÅé¯ï„;¯\c®¥¯µéx†ééP®éþ:µéþÒ,éÿ¯þ¹çé¯ï„;¯\c¯0B‘çéx†ééP®éþ:µéþÒ,éÿ¯þ±ßé¯ï„;¯\c¯®”[8éy†ééP®éþ:µéþÒ,éÿ¯ÿÓé¯ä„;¯\c©®éß"æšcæéP®ééâÓ5¤×ééÒ,éÿ¯ÿÅé¯ìY¯qvÀC0G¤éÛm=»P®éé®:ûUééÒ,éÿ¯þ¹çé¯ÿÌ	éýPA©éýÄO,éÿ¯þ±ßéGÿP¯ÿÌ
éýàÚãéýåÚÞéÿ¯ÿÓé¯ÿÌ&éþí¯ÿÅ	é¯ÿÌ&éýíÿ¯þ¹ç	é¯ÿÌ&éøíÿÿ¯¯±ß
é¯ÿÌ&éÿíÿü¯¯Óé¯ÿÌ&éÿíÿý¯Åé¯ÿÌ&éÿíÿý¹çéþ¯Ì&éÿíÿþß
éÿÌ&éÿíÿÿé?/Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>ÆÿÆþÂÆþ—¸ÆÿÆþ½DÆþ<œÆþ‚ÆÿÆÞ¦Àƪ²Æ¸¯Ä¢ÆÆÅ•¯<œÆÆ¦›·®ÆÆÅ™»ÆÂVS›¹
ÆÿÆÞR‡Æ(IÆc{ºƶ*U6œÆO=Q+WÆž5`;{½:8i©
ÆÿÆà†S¶9*Æ/¯ºÆ|aÆÃ
œº"ÆÆOWÆMšÆ¾+ÆnjÆÿÆç¸!„lHž!ƺÆQ‰ÆÆ/œ–DÆÆtWÆ=ü9¼njÆÿÆç(:¡}:RƺÆzcÆÄœ¾ ÆÆIWÆJ”ÆþwdÆÿÆß\Ų†ÆºÆ´%\9œÆc0>2]Æ›/`mLƧ!`¨
ÆÿÆß´¡ÆÆ˜¾ÆÃœÆÆÃ‰©¤¼ÆÆµ±QÆÆÄ†³ÆÆ½˜·
Æÿ ÆûjkK.ÁÆÿ Æý¶¤Æÿ>Æÿ>ÆÿÆûXÆw~Æï¬¯ÆÆY6ŠÆ¯z^¯ÆÆ¤;?ÆÿÆöq#ÆÆNGÆÆ*jÆû•CƬ®ÆôfrÆÆD”ÆÆ…bšÆþ³%ÆÿÆöœtÆÆŠ†ÆÆx˜Æï•Cƃ‰Æ‹=/\³ $=¢D”Æþ2šÆþ³%ÆÿÆï•CÆhpÅ!»Æ¯ÆfrÆÆD”Æþ2šÆþ³%ÆÿÆï•CÆhpÆ6KÄÆfrÆÆD”Æþ2šÆþ³%ÆÿÆï•CÆhpÆÅN/ÆfrÆÆD”Æþ2šÆþ³%ÆÿÆä•CÆhp¿¨Æ½ăTÄÆD”ÆÆÀ³-Œ¶ÆÆ³%ÆÿÆì¢dÆ€†º9)=ŒÆº\4ŸD”ÆÆ”1ûHÆÆ³%Æÿ"ÆýD7Æý§C%ÆÿÆPÿ[Æý¾ºÁÆýú½Æÿ=ÆþÍ<ÆýÍî;ÆüÍîî:ÆÿÍîÿ9ÆÿÍîÿ8ÆÿÍîÿ7ÆÿÍîÿ6ÆÿÍîÿÆ?/éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ=éþÌ<éý̯;éü̯¯:éÿ̯ÿ9éÿ̯ÿ8éÿ̯ÿ7éÿ̯ÿ6éÿ̯ÿ5éÿ̯ÿ4éÿ̯ÿ3éÿÌ	¯ÿ2éÿÌ
¯ÿ1éÿ̯ÿ0éÿÌ¯ÿ/éÿÌ
¯ÿ.éÿ̯ÿ-éÿ̯ÿéþä¥éþ±Ùéÿ̯ÿéþÞOéþF¸éýÌsp¯ÿéÞÄâéÈÑéÙÎæ¾ééè¯ÏF¸ééÄ·ØÍééè´Ü̬LI‰£
¯ÿéÞ`Ÿé/Véu‘Ú$éÖ1e@!¸é]G_2fé¹>q=l§31]–
¯ÿéàb×C1é8ÏÚ$é’réå¸Û(éé]féZµÌ¨&¯a]¯ÿéàÙ&›Uº'éÚ$é_¢éé8¸°Oééˆfé>662¦a]¯ÿéç/E½“E`éÚ$éu鿏à%ééVféL‚¯þiY¯ÿéßlèÑéÚ$éÔ,lD¸éu9I:n̉)U`C¯”T”
¯ÿéßÔ½éé³àéå¸ééå¢ÇÁÝééÕÐ_“¯¯®€vŸ¯¯§‡¢
¯ÿ éû}~Y/ª¯ÿ
éÿçéýצ©¯ÿ
éÿÅéÿ̯ÿéþÓ¯éÿ̯ÿéýß±¯éû¹g錔éïÊ´¯¯O/z¯šlS𝝑48¯ÿéö…)éé[Sç¹%^éû¯OéÊÍéôie¯¯<‚¯¯uVˆ¯þž!¯ÿéö¸‰é飞ůj‡éï¯Oéš¡é¤H7l¹ 6<‚¯þ,ˆ¯þž!¯ÿ	éÿÓ¯éï¯Oéz„è'Ü鵯Ze¯¯<‚¯þ,ˆ¯þž!¯ÿéþß±¯éï¯Oéz„é@X‘®¯Ze¯¯<‚¯þ,ˆ¯þž!¯ÿéþ繯éï¯Oéz„éè”E*¯[e¯¯<‚¯þ,ˆ¯þž!¯ÿéÿůéä¯Oéz„ᮯ§­tJ­¯<‚¯¯ªŸ(|¡¯¯ž!¯ÿéÿÓ¯éì¾vé—À2$6|¯¥R.<‚¯¯‚+û@¯¯ž!¯ÿéþß±¯éÿÌ	¯ý<1¯ý”;!¯ÿéþ繯^ÿkéÿÌ
¯ý¨¤ª¯ý¬¤§¯ÿéÿůéÿÌ&¯þ®éÿÓ	¯éÿÌ&¯ý®ªéþß±	¯éÿÌ&¯ø®ªªééç¹
¯éÿÌ&¯ÿ®ªüééůéÿÌ&¯ÿ®ªýéÓ¯éÿÌ&¯ÿ®ªýß±¯þéÌ&¯ÿ®ªþ¹
¯ÿÌ&¯ÿ®ªÿ¯ÿÿ'¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ¯ÿy¯þ‹¯ÿÿÿ¯ÿ3¯þEy¯þ€c¯ÿÿÿ¯ö–¥¯šš¯¦—¯¯ö‡–Ey¯¯˜†£–¯ù‹¡¯¯Y<‰¯ÿÿÿ¯ßYg¯31¯h]¯¯ª)F4y¯T/L)<¯–/R:_¯<)]¯ÿÿÿ¯à‡9¬()«:‹¯¯~E¯¯y­®¯V<¯Tw¯®#­rM¯ÿÿÿ¯à¬ƒOP‚®¯¯Xi¯¯:y•+¯¯w<¯%0663šrM¯ÿÿÿ¯3é~38¯¯¯|G¯¯y®¬¯Q<¯Rr¯þyH¯ÿÿÿ¯àa¨©f¯¯¯©$K8y¯e'<.B¯•)Pb:¯¢S¯ÿÿÿ¯ê£Š¯¯‹¤¯¯‡¯¯®}‘•¢¯¯¤™Xn¯ù…tš¯¯ª‰¯ÿÿÿ¯ûh\H%¡¯ÿÿÿ¯ý¥€‹¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿ'¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ,¯þ¹ÿÿ¯ÿy¯þ‹¯ý±ßÿÿ¯ÿ3¯þEy¯þ€c¯ýÓéÿÿ¯ö–¥¯šš¯¦—¯¯ö‡–Ey¯¯˜†£–¯ù‹¡¯¯Y<‰¯üÅééÿÿ¯ßYg¯31¯h]¯¯ª)F4y¯T/L)<¯–/R:_¯<)]¯û¹çééÿÿ¯à‡9¬()«:‹¯¯~E¯¯y­®¯V<¯Tw¯®#­rM¯þ±ßéÿÿÿ¯à¬ƒOP‚®¯¯Xi¯¯:y•+¯¯w<¯%0663šrM¯ÿÓéÿÿÿ¯3é~38¯¯¯|G¯¯y®¬¯Q<¯Rr¯ûyH¯¯Åéÿÿÿ¯Þa¨©f¯¯¯©$K8y¯e'<.B¯•)Pb:¯¢S¹çéÿÿÿ¯ê£Š¯¯‹¤¯¯‡¯¯®}‘•¢¯¯¤™Xn¯ø…tš¯¯ª‰ßéÿÿÿ¯ûh\H%¡	¯éÿÿÿ¯ý¥€‹¯éÿÿÿ&¯éÿÿÿ&¯éÿÿÿ&¯éÿÿ'éÿÿÿ&¯éÿÿÿ&¯éÿÿÿ&¯éÿÿÿ&¯éÿÿÿ&¯éÿÿÿ&¯éÿÿÿ&¯éÿÿÿ&¯éÿÿÿ&¯éÿÿÿ¯þ˜¯þ®y¯éÿÿÿ¯þ{h¯þ¬
¯þGœéÿÿÿ¯ß­¯¯Šª¯–§¡›¯¯¥€¤¯¯®„𖦝¯§‡®¯Ÿ uéÿÿÿ¯àª˜y¯"£lS¯z"R2¯¢$K4…¯b6V,–Péÿÿÿ¯à>‚jCK¡$¯lS¯6ޝ~¯pS¯®…­"­¯xV¯)–éÿÿÿ¯çlT<–'sR¯lS¯®¯¡¯Lt¯¯.…Œ6üP¯)–éÿÿÿ¯æš$&¯73€¯lS¯3¯€¯vN¯­…¬¬¯þ1‘ÿ'Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆÿ‰Æþž²ÆÿÿÿÆÿ9ÆþN‰ÆþpÆÿÿÿÆöª»Æ®®Æ¼«ÆŸÆö™ªN‰ÆÆ¬—¹ªÆù¶ÆÆeD›ÆÿÿÿÆßdtÆ98ÆviÆÆÀ/O;!‰Æ`5V/DÆ©6\AkÆD/iÆÿÿÿÆà˜@Â./ÁBÆÆNÆÆ‰ÄÄÆbDÆ`‡ÆÅ(ÄWÆÿÿÿÆàÔY[”ÄÆÆcwÆÆB‰¨1ÆÆ†DÆ*6==9®WÆÿÿÿÆ:éŽ:?ÆÆÆQÆÆ‰ÅÃÆ\DÆ\Æþ‰RÆÿÿÿÆàn¾¿sÆÆÆ¿)U@‰Ær,D4Kƨ/[oBÆ·^ÆÿÿÿÆê¹œÆÆºÆÆ™ÆÆÅ¤¨·ÆÆº­c|Æù—ƒ¯ÆÆÀ›ÆÿÿÿÆûuhR*¶ÆÿÿÿÆýºžÆÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿ'Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆÿ‰Æþž²ÆÿÿÿÆÿ9ÆþN‰ÆþpÆÿÿÿÆöª»Æ®®Æ¼«ÆŸÆö™ªN‰ÆÆ¬—¹ªÆù¶ÆÆeD›ÆÿÿÿÆßdtÆ98ÆviÆÆÀ/O;!‰Æ`5V/DÆ©6\AkÆD/iÆÿÿÿÆà˜@Â./ÁBÆÆNÆÆ‰ÄÄÆbDÆ`‡ÆÅ(ÄWÆÿÿÿÆàÔY[”ÄÆÆcwÆÆB‰¨1ÆÆ†DÆ*6==9®WÆÿÿÿÆ:éŽ:?ÆÆÆQÆÆ‰ÅÃÆ\DÆ\Æþ‰RÆÿÿÿÆàn¾¿sÆÆÆ¿)U@‰Ær,D4Kƨ/[oBÆ·^ÆÿÿÿÆê¹œÆÆºÆÆ™ÆÆÅ¤¨·ÆÆº­c|Æù—ƒ¯ÆÆÀ›ÆÿÿÿÆûuhR*¶ÆÿÿÿÆýºžÆÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿ'Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆþ¬¢ÆþʼnÆÿÿÿÆþ‹uÆþÃ
ÆþP°ÆÿÿÿÆßÄ¢ÆÆœÀƪ½¶¯ÆÆ»‘ºÆÆÅ•®©¼ÆÆ½˜ÄÆ´$…ÆÿÿÿÆàÀ¬‰Æ&¹z^ÆŠ'\9Æ·)U;—Æo=a2ªŸZÆÿÿÿÆàF“xKU¶)Æz^Æ=¡ÆŽÆ^ÆÅ—Ä'ÄÆˆbÆ/©ÆÿÿÿÆçz_D©,‚\Æz^ÆÄƶÆVƒÆÆ4—ž=ü[Æ/©ÆÿÿÿÆæ¯)+Æ>:‘Æz^Æ:£ÆƆYÆÄ—ÂÂÆþ7¤ÿ'éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿéÿ¡éþ¹ÑéÿÿÿéÿDéþ\¢éþª„éÿÿÿéöÈÜéÍÍéÝÉé»éö´È\¢éé˲ÙÈéù¹×ééwP¶éÿÿÿéßv‰éDBé‹{ééâ7]E&¢ép>e7PéÇ?mM~éP7{éÿÿÿéà³Lä67äN¹éé¨\é颿 çésPépŸéè/æ˜féÿÿÿéàå¯ik®!çééuŒééN¢Æ:ééžPé1@HHD͘féÿÿÿéEé§©EJééé¥_éé!¢è"åélPém˜éþ¢`éÿÿÿéà‚àá‡éééá0eK"¢é†4O=XéÆ7kƒNéØ!oéÿÿÿéêÙ¸éé¹Úéé´ééè¦ÁÆØééÚÌu’éù±šÎééâ¶éÿÿÿéûŠz`1ÖéÿÿÿéýÛª¹éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿ'éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ,éþßÿÿéÿ¡éþ¹Ñéýç¹ÿÿéÿDéþ\¢éþª„éýůÿÿéöÈÜéÍÍéÝÉé»éö´È\¢éé˲ÙÈéù¹×ééwP¶éüÓ¯¯ÿÿéßv‰éDBé‹{ééâ7]E&¢ép>e7PéÇ?mM~éP7{éûß±¯¯ÿÿéà³Lä67äN¹éé¨\é颿 çésPépŸéè/æ˜féþ繯ÿÿÿéàå¯ik®!çééuŒééN¢Æ:ééžPé1@HHD͘féÿůÿÿÿéEé§©EJééé¥_éé!¢è"åélPém˜éû¢`ééÓ¯ÿÿÿéÞ‚àá‡éééá0eK"¢é†4O=XéÆ7kƒNéØ!oß±¯ÿÿÿéêÙ¸éé¹Úéé´ééè¦ÁÆØééÚÌu’éø±šÎééâ¶¹¯ÿÿÿéûŠz`1Ö	é¯ÿÿÿéýÛª¹é¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿ'¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿÿ&é¯ÿÿÿéþË¿éþè¢é¯ÿÿÿéþ¤Šéþå
éþ^ϯÿÿÿéßæ¿éé¸âéÈÞÖÏééÜ«Úééè¯ÍÇÝééÞ³çéÔ+œ¯ÿÿÿéàâË¡é-Ùoé£.mCéØ0eE#±éƒGr:È»j¯ÿÿÿéàR­ŽYdÖ0éoéG½é§é•oéè±æ.æé sé7ǯÿÿÿéàoPÇ4šméoéçé×éešéé=±ºHHGké7ǯÿÿÿéæÎ03éIE«éoéEÀéªéhéæ±ä%äéþAÁÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿ?¯ÿÌ é¯ÿÌ!é¯ÿÌ"é¯ÿÌ#é¯ÿÌ$é¯ÿÌ%é¯ÿÌ&é¯ÿÌ'é¯û–’¯ˆ 	¯ó~†ÔééåÌèééÇ™·é¯ûeZ¯VŒ¯ïT¯¯"Ôé½0%ÑéÇoUéÿ ¯äeZ¯–¥¯«„{Ÿ£.g‰È'×éé;šéoeéé©UéÿޝäeZ¯=‚­"Y]f– Flà'×éæ×é¯+éé©U靸eZ¯=‚œ"¡¯ï;šéé'×éÖééÓéé©Ué¯äeZ¯=‚¯h /‡¯E¯éé'×éãßé¹%éé©Ué¯ûeZ¯=‚¯ì‡&ÌY©éé'×éé%¸éOéé©UéÿŒ¯äeZ¯=‚‡GfCaé•9tß'×éé›j¹éé©Uéþÿž¯÷œš¯“¤ªƒnªéðÖ¯ä'¼âéé™ÏééÝ”Uéÿÿ¨Ÿÿ£¯ÿÌéý|eÆéý±e–éÿÿ€Gÿc¯ÿÌ.éÿ
¯ÿÌ.éÿ	¯ÿÌ.éÿ¯ÿÌ.éÿ?¯ÿÌé¯ÿÌ é¯ÿÌ!é¯ÿÌ"é¯ÿÌ#é¯ÿÌ$é¯ÿÌ%é¯ÿÌ&é¯ÿÌ'é¯û–’¯ˆ 	¯ý~†ÔéÿãéýÇ™·é¯ûeZ¯VŒ¯ïT¯¯"ÔésÞééÇoUéÿ ¯éeZ¯–¥¯«„{Ÿ£.g‰È'×ééÒæÞéþ©UéÿޝïeZ¯=‚­"Y]f– Flà'×éþÞéþ©U靸eZ¯=‚œ"¡¯ú;šéé'×éþÞéþ©Ué¯ïeZ¯=‚¯h /‡¯E¯éé'×éþÞéþ©Ué¯ûeZ¯=‚¯÷‡&ÌY©éé'×éþÞéþ©UéþÿŒ¯äeZ¯=‚‡GfCaé•9tß'×éé¢^ZŒéé©Uéÿÿž¯÷œš¯“¤ªƒnªéøÖ¯ä'¼âé˯ûÂéÝ”Uéÿÿ¨Ÿÿ£¯ÿÌéý|eÆéý±e–éÿÿ€Gÿc¯ÿÌ-éÿ
¯ÿÌ-éÿ	¯ÿÌ-éÿ¯ÿÌ-éÿ?¯ÿÌé¯ÿÌ é¯ÿÌ!é¯ÿÌ"é¯ÿÌ#é¯ÿÌ$é¯ÿÌ%é¯ÿÌ&é¯ÿÌ'é¯ü}«¬|	¯ø¡†¥ééãÉçéý¥™Øé¯ü£§<¯î¥=¯¯š3¥é¤#D'éé¥0¶éÿ‰¯ä£­¯¯Ÿy†¬‰‰®°NééÕåéÔâééH¶éÿ^¯ð£¤¯|+aN”llŸ°NéùÜáééH¶é¯ð£¤¯T_®¯¯¢"éé°NéýèOzéþH¶é¯ð£¤¯CN§½&éé°Néþd^éþH¶é¯ü£¤¯÷«Bß#éé°NéýhZèéþH¶éÿ?Æû©¥Æšµ	ÆóŽ‚´ÆÆÃ­ÅÆÆ©‚›#ÆûrfÆbžÆï` ÆÆ!x´Æ¡( ²Æ©_HÆÿµÆärfƪ»ÆÁ–‹´¹4t›Â!¶ÆÆ2‚Æ_UÆÆHÆÿ¡ÆärfÆE“Ä&dis©$Oi¾!¶ÆÄ¶Æ•$ÆÆH#ÆørfÆE“°'¶ÆïC•ÆÆ!¶Æ¶ÆÆ³ÆÆH#ÆärfÆE“Æv$6™ÆC•ÆÆ!¶ÆÁ½Æž ÆÆH#ÆûrfÆE“Æì˜+ÆKÆÆ!¶ÆÆ œÆxDÆÆHÆÿžÆärfÆE“˜QtK^Æ0c½!¶ÆÆ„ZžÆÆHÆþî³Æ÷±®Æ¦ºÁ”}¥Æð¶•Â! ÀÆÆ¥‚¯ÆÆ¼~HÆîÿ¾³ÿ¹ÆýjV¨Æý—VÆîÿ‘Pÿp5Æî:Æî9Æî8Æî?FÆû©¥Æšµ	ÆýŽ‚´ÆÿÁÆý©‚›#ÆûrfÆbžÆï` ÆÆ!x´Æb½ÆÆ©_HÆÿµÆérfƪ»ÆÁ–‹´¹4t›Â!¶ÆÆ³Ä½ÆþHÆÿ¡ÆïrfÆE“Ä&dis©$Oi¾!¶Æþ½ÆþH#ÆørfÆE“°'¶ÆúC•ÆÆ!¶Æþ½ÆþH#ÆïrfÆE“Æv$6™ÆC•ÆÆ!¶Æþ½ÆþH#ÆûrfÆE“Æ÷˜+ÆKÆÆ!¶Æþ½ÆþHÆþîžÆärfÆE“˜QtK^Æ0c½!¶ÆÆ‰PMwÆÆHÆîÿ³Æ÷±®Æ¦ºÁ”}¥Æø¶•Â! ÀƬ”û¥Æ¼~HÆîÿ¾³ÿ¹ÆýjV¨Æý—VÆîÿ‘Pÿp4Æî9Æî8Æî7Æî?GÆüÁÂ	Æø¶‚ÆÆÁ«ÄÆý‚·#Æü ¸½DÆî»EÆÆ–+Æ‹9!xÆÆ(›Æÿ›Æä ¸Ä¢ÆÆ³‰—Û›©–BÆÆµÃÆ´ÀÆÆ=›ÆÿjÆð ¸ºÆŒ1mY§{i‡–BÆù»
¿ÆÆ=›#Æð ¸ºÆ_lÄÆÆ·!ÆÆ–BÆýÅDgÆþ=›#Æð ¸ºƲLX½·!ÆÆ–BÆþUPÆþ=›#Æü ¸ºÆ÷ÁK}½ÆÆ–BÆýYMÅÆþ=›Æî?éÿÌ ¯éÿÌ!¯éÿÌ"¯éÿÌ#¯éÿÌ$¯éÿÌ%¯éÿÌ&¯éÿÌ'¯éûÇÃéµÕ	é󧆟¯¯¬™®¯¯–s‰¯éû†xésºéïp¼éé"jŸ¯Ž$¯–T@¯ÿÕéä†xéÈÜéä°¤ÔÙ=‰¶È¡¯¯-s¯TK¯¯@¯ÿ½éä†xéQ­æ-v{‡Ç*]l¨¡¯­¡¯„ ¯¯@¯éø†xéQ­Ï.×éïO𝝡¯¡¯¯Ÿ¯¯@¯éä†xéQ­é‹*?´éE„¯¯¡¯ª§¯‹¯¯@¯éû†xéQ­éì³2ÌC¯¯¡¯¯Нj<¯¯@¯ÿºéä†xéQ­³_ˆYa¯p+W§¡¯¯uP‹¯¯@¯þªÒé÷ÐÍéÄÚ㯓ª¯ð¡„¬ª¯¯‘s›¯¯¦o@¯ªÿàÓÿÙéÿ̯ý]L•¯ý…Lq¯ªÿ«^ÿ„éÿÌ.¯ª
éÿÌ.¯ª	éÿÌ.¯ªéÿÌ.¯ª?éÿ̯éÿÌ ¯éÿÌ!¯éÿÌ"¯éÿÌ#¯éÿÌ$¯éÿÌ%¯éÿÌ&¯éÿÌ'¯éûÇÃéµÕ	éý§†Ÿ¯ÿª¯ý–s‰¯éû†xésºéïp¼éé"jŸ¯V§¯¯–T@¯ÿÕéé†xéÈÜéä°¤ÔÙ=‰¶È¡¯¯ž­
§¯þ@¯ÿ½éï†xéQ­æ-v{‡Ç*]l¨¡¯þ
§¯þ@¯éø†xéQ­Ï.×éúO𝝡¯þ
§¯þ@¯éï†xéQ­é‹*?´éE„¯¯¡¯þ
§¯þ@¯éû†xéQ­é÷³2ÌC¯¯¡¯þ
§¯þ@¯þªºéä†xéQ­³_ˆYa¯p+W§¡¯¯yGDi¯¯@¯ªÿÒé÷ÐÍéÄÚ㯓ª¯ø¡„¬ª¯˜ƒû‘¯¦o@¯ªÿàÓÿÙéÿ̯ý]L•¯ý…Lq¯ªÿ«^ÿ„éÿÌ-¯ª
éÿÌ-¯ª	éÿÌ-¯ªéÿÌ-¯ª?éÿ̯éÿÌ ¯éÿÌ!¯éÿÌ"¯éÿÌ#¯éÿÌ$¯éÿÌ%¯éÿÌ&¯éÿÌ'¯éü¦ää¥	éøÖ†|¯¯ª—®¯ý|s¢¯éü%ÙÞOéîÜQééš&|¯{3j¯¯|$‰¯ÿ·éä%Ùæ¾ééÓ¢²å·¶®„:¯¯ ¬¯Ÿª¯¯6‰¯ÿ}éð%ÙÚ$é¤:hÄlw„:¯ù¥©¯¯6‰¯éð%ÙÚ$éoçééØ"¯¯„:¯ý®<[¯þ6‰¯éð%ÙÚ$éÑZ gÞ½¯¯„:¯þKG¯þ6‰¯éü%ÙÚ$é÷äX§¯¯„:¯ýND®¯þ6‰¯ªÿÿ
éþÿÿÿ+éÿÿÿÿ*éÿÿÿÿ)éÿÿÿÿ(éÿÿÿÿ'éÿÿÿÿ&éÿÿÿÿ%éÿÿÿÿ$éÿÿÿÿéþ´léþç:é	ÿÿÿÿéþªUéþæé
ÿÿÿÿéèªOe<–ééÓxCäé©ãf—éÄæÝE¾éivÞeéÿÿÿÿéæªEééZ¤Õ{{cHéÉ}M,zé)ÙéÆéé
ÿÿÿÿéøªUééažÏ$¶òÁé6®Þ|ké&ÛéÉéÿÿÿÿéçªUééažé;¥çãÆåÚÑ-kéf{âjéÿÿÿÿéç¹zé郱éÎr4:žé—5A‡‹éØl0ŠMéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿ
éþÿÿÿ¯éÿÿÿÿ¯éÿÿÿÿ¯
éÿÿÿÿ¯	éÿÿÿÿ¯éÿÿÿÿ¯éÿÿÿÿ¯éÿÿÿÿ¯éÿÿÿÿ¯é	ÿÿÿÿ¯é
ÿÿÿÿ¯éÿÿÿÿ¯éÿÿÿÿ¯ÿé
ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯þ™
¯þŸŠ¯ÿÿÿÿ¯þnQ
¯þ~B¯ÿÿÿÿ¯ünQ”Нþޝú˜‡|B«¯ÿÿÿÿÿ¯èn 8B/®¯J5F)¨¯T.I&B®Ÿ¯œÿÿÿÿ¯èn"¯¯:‡¤¯¯Gx¬®¯NB¯_\¯Z]
ÿþéÿÿ¯ènE¯¯]^}?¯¯nN1¯¯rB¯¢­ ÿÿ¯èn$¯¯<„¢ ¯¯Iu«¯¯PB¯¯>N<¯
éþÿÿÿ¯ènçécF]7àép=b3Xè&ÔéÐ#ªÿÿÿéè“-ééM´Ú)éé_ å!èéhXézéx|
ªþ¯ÿÿéè“\éé|}¦Téé’g¿Aéé˜XéØ%æ#Õÿÿéè“0ééO°Ø+éébœä!éékXééShOé
¯þªÿÿéè“%P_7æé[Ke0ÞéiCi/Xéé³®é¯ªÿÿÿéüÔÊ»«éþȰéöÀ©ÖÅééÍ%é鯪ÿÿÿéû£: éé
¯ªÿÿÿéþ²¼é	¯ªÿÿÿ鯪ÿÿÿ鯪ÿÿÿ鯪ÿÿÿ鯪ÿÿÿé¯	ªÿÿÿé¯
ªÿÿÿ鯪ÿÿÿé¯ªÿÿÿéÿ¯
ªÿÿÿéªÿÿÿéªÿÿÿéÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ?éþmÈéúÍËéé„›éûÎSé­ˆéþÐ<
éú„{ééÈÏéúÈ7éå0âéö´WV¶éÒgw2ÝéÑrééÊiGçéa‘;oÖÚ)&H·”§éÕyAŽâé{”ERÄéÐM9fÈéÈ7één“éõÏ0ÏÐ/áÉšäçéÑE´éé0«Ö<®é+WÜ…Ué„{ééréT‰ÝXˆéPDÕ›OéÙÑé¦QéÈ7éé“iéø‚E{{5žÉéÑ#ÐéÏ/éé–eé+´éäç„{éérååé»@éPžéåéç™^HéÈ7éé¯Jéþze¶ýÔÉ6éÑ:»éÌ1ééšbé+·éäå„{ééräæé¿<éP¯éééžWÎàèÈ7ééš`éøÎ/ÍéÏÙÉ6éÑešéé/²Ù@«é+ZáRé¦YåéréQá]…éP¯ééézzæ„èÈ7ééy†éù­R3JÑÑcéѽMééÄ^=“æé+„2cÒéào>²¤éÑn7„àéw½ééJéÔP1~LèÐdéé5ÕéþIÍéþ+Ôéþ¯féþãäéþIØéþãåé?À¯ÿ«>¯ÿ®ÿ¯?Æþ\ªÆú®¬ÆÆq„Æû¯GÆ“tÆþ±3
ÆúpiÆÆª°Æúª/ÆÃ(ÀÆö™JI›Æ³Xe+¼ÆÑayÆÆ¬Y=†ÄÆR{2^¶º#!=›~ŽÆµf7xÀÆi~:F§Æ±A0WªÆª/ÆÆ]}Æõ°)°±(¿«‚ÂÄÆÑ:™ÆÆ)‘¶3”Æ$J»qHÆpiÆÆaxÆGt¼KtÆD9µ„CƸ²ÆEƪ/ÆÆ}YÆøn:ii-†«ÆÑ±Æ°(ÆÆVÆ$™ÆÁÄpiÆÆaxÃÃÆŸ6ÆD†ÆÃÆÄ‚P=ƪ/ÆÆ•?ÆþhU›ý´«.ÆÑ2ŸÆ­*ÆÆ‚SÆ$›ÆÂÃpiÆÆaxÂÄÆ¢3ÆD”ÆÆƆJ¯¾Ū/ÆÆƒRÆø¯(®Æ°¹«.ÆÑU‚ÆÆ(—¹6‘Æ$M¿xFÆKÃÆaxÆEz¿OqÆD”ÆÆÆhgÄpŪ/ÆÆfrÆù“F+?²²TÆÑ¡AÆÆ§P4}ÄÆ$p+T³Æ¾_5—{‹Æ²]/p¾Æe¡ÆÆ?Æ´D*k@űUÆÆ-µÆþ>®Æþ$´Æþ•WÆþÁÂÆþ>·ÆþÁÃÆ?ÀÆÿÁ>ÆÿÅÿÆ?¯þR–¯úš˜¯¯du¯ûš>¯‚f¯þœ-
¯úc]¯¯–œ¯ú–)¯¬$ª¯ö‡AA‰¯žNY&¦¯ÑVk¯¯˜O6v®¯Im-S¡¤6‰o~¯ [1jª¯]o3>”¯œ:+M–¯–)¯¯Rn¯õœ$œœ#©—s¬®¯Ñ3‡¯¯$€¡-‚¯ A¥d@¯c]¯¯Vj¯?g¦Bf¯<3 u;¯£¯}=¯–)¯¯nO¯øa3]](w—¯Ñœ¯œ#¯¯qL¯ ‡¯«®c]¯¯Vj¬¬¯0¯¯}C¬¯Vj¯=l©Fd¯<ƒ¯¯¯\[­c®–)¯¯[e¯ù‚>&8J¯ÑŽ:¯¯”G.n­¯ c&Jž¯¨T/†l{¯R)c¨¯Yޝ¯8¯Ÿ<%_9®œK¯¯( ¯þ7š¯þ Ÿ¯þ„M¯þª¬¯þ7¢¯þª¬¯?Àéÿä>éÿèÿéÿÿéÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿéþÚ¯
éÿâéÿÿÿéþ¾@
éÿÒéÿÿÿéû¾@ϯçéýÜ´èéýجËéÿÿÿéï¾?f0Ñé‡8j0Äé”1k=éÿÿÿéï¾ãéyˆé%ßé‹té5Ðé”éÿÿÿéï¾1éé¨RÑ)éé½;æééÄéÿÿÿéï¾
äé{…é#àéŽpé2Òé—éÿÿÿéï¾Cn,Ïé:q,Âé2r:éÿÿÿéû߿ɡäéýÕ¦åéýРÙéÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿéÿ¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿéþÚ¯
éÿâ¯ÿÿÿéþ¾@
éÿÒ¯ÿÿÿéû¾@ϯçéýÜ´èéýج˯ÿÿÿéï¾?f0Ñé‡8j0Äé”1k=¯ÿÿÿéï¾ãéyˆé%ßé‹té5Ð锯ÿÿÿéï¾1éé¨RÑ)éé½;æééįÿÿÿéï¾
äé{…é#àéŽpé2Òé—¯ÿÿÿéï¾Cn,Ïé:q,Âé2r:¯ÿÿÿéû߿ɡäéýÕ¦åéýРÙ¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿ¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿéÿÆÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆþº•
ÆÿÀÆÿÿÿÆþ¢6
Æÿ³ÆÿÿÿÆû¢6°”ÄÆý»™ÅÆý·’¬ÆÿÿÿÆï¢6W(²Æs/Z(§Æ~*[4ÆÿÿÿÆï¢ÁÆftƽÆvcÆ-±Æ~ÆÿÿÿÆï¢*ÆÆF²#ÆÆ¡2ÄÆÆ¦ÆÿÿÿÆï¢	ÁÆiqƾÆx`Æ+³Æ€ÆÿÿÿÆï¢9]%¯Æm2`%¥Æy+a2ÆÿÿÿÆû½¢«‰ÂÆýµÃÆý±ˆ¹Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆÿÆÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆþº•
ÆÿÀÆÿÿÿÆþ¢6
Æÿ³ÆÿÿÿÆû¢6°”ÄÆý»™ÅÆý·’¬ÆÿÿÿÆï¢6W(²Æs/Z(§Æ~*[4ÆÿÿÿÆï¢ÁÆftƽÆvcÆ-±Æ~ÆÿÿÿÆï¢*ÆÆF²#ÆÆ¡2ÄÆÆ¦ÆÿÿÿÆï¢	ÁÆiqƾÆx`Æ+³Æ€ÆÿÿÿÆï¢9]%¯Æm2`%¥Æy+a2ÆÿÿÿÆû½¢«‰ÂÆýµÃÆý±ˆ¹Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÆÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆÿ¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ¯þ¤„
¯ÿª¯ÿÿÿ¯þ0
¯ÿž¯ÿÿÿ¯û0œƒ®¯ý¥‡®¯ý¢˜¯ÿÿÿ¯ï/M$¯f*P$”¯o%P.¯ÿÿÿ¯ïª¯[f¯§¯hW¯(œ¯o¯ÿÿÿ¯ï%¯¯~>¯¯Ž-­¯¯“¯ÿÿÿ¯ï«¯]d¯¨¯jT¯&ž¯q¯ÿÿÿ¯ï2R!›¯a,U!‘¯k&V,¯ÿÿÿ¯û§—y¬¯ý }¬¯ýœx£¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ¯ÿéÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯þ¤„
¯ÿªéÿÿÿ¯þ0
¯ÿžéÿÿÿ¯û0œƒ®¯ý¥‡®¯ý¢˜éÿÿÿ¯ï/M$¯f*P$”¯o%P.éÿÿÿ¯ïª¯[f¯§¯hW¯(œ¯oéÿÿÿ¯ï%¯¯~>¯¯Ž-­¯¯“éÿÿÿ¯ï«¯]d¯¨¯jT¯&ž¯qéÿÿÿ¯ï2R!›¯a,U!‘¯k&V,éÿÿÿ¯û§—y¬¯ý }¬¯ýœx£éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿéÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ?éÿ¨éýÀ™»éùÎâééà™ž*éÿ,éóo^»éß9+¥éà™*éë,éºèééÇÚoéé„Sè¸èéé*éë,éK®éæ×oééGééÍéé*éë,é«Oé£Qéoéé4°éé1¹éé*éë,éè&ÖF¯éoééB™ééÇéé*éë,éée(ééoéénoéÕãéé*éï,é騂ééoééÏd#}éÿ*éëºééäÙééo}ÙééÖš¸ééæÌ,éý¶Quéý¥ežéýÙen,éýÁ°æûé?@éÿ¨éýÀ™»éùäçééà™ž*éÿ,éóo^»é¬$–ééà™*éð,éºèééÇÚoééÚßZ–éÿ*éö,éK®éæ×oéþZ–éÿ*éö,é«Oé£QéoéþZ–éÿ*éö,éè&ÖF¯éoéþZ–éÿ*éö,éée(ééoéþZ–éÿ*éï,é騂ééoééÍ^%=aéÿ*éóºééäÙééo}Ùéݯû°éæÌ,éý¶Quéý¥ežéýÙen,éýÁ°æûé?ÆéþÎt=éþ±}éþÎt=éþ±}éþÎt=éþ±xé?ÆÿÆý£‚ŸÆù¯ÀÆÆ¾‚†*Æÿ%Æó_PŸÆ½0$
ƾ‚*Æë%ÆžÅÆÆ©º_yÆÆpGÅœÅÆÆ*Æë%Æ@”ÆÄ¶_yÆÆ=zÆÆ®ÆÆ*Æë%Æ‘DÆŠEÆ_yÆÆ,–ÆÆ*žÆÆ*Æë%ÆÅ!¶<•Æ_yÆÆ8‚ÆÆ©ÆÆ*Æë%ÆÆlU"ÆÆ_yÆÆ]_ƵÁÆÆ*Æï%ÆÆ·nÆÆ_yÆÆ°UjÆÿ*ÆëžÆÆÁ¸ÆÆ_j¸ÆÆ¶ƒœÆÆÄ­,Æý›EcÆýV†Æý¹V],Æý¤–ÄûÆ?@ÆÿÆý£‚ŸÆùÂÄÆÆ¾‚†*Æÿ%Æó_PŸÆ’	ÆÆ¾‚*Æð%ÆžÅÆÆ©º_yÆÆº½LÆÿ*Æö%Æ@”ÆÄ¶_yÆþLÆÿ*Æö%Æ‘DÆŠEÆ_yÆþLÆÿ*Æö%ÆÅ!¶<•Æ_yÆþLÆÿ*Æö%ÆÆlU"ÆÆ_yÆþLÆÿ*Æï%ÆÆ·nÆÆ_yÆÆ®P4RÆÿ*ÆóžÆÆÁ¸ÆÆ_j¸Æ¼”û–ÆÄ­,Æý›EcÆýV†Æý¹V],Æý¤–ÄûÆ?ÆÆþ¯c=Æþ—}Æþ¯c=Æþ—}Æþ¯c=Æþ—xÆ?¯ÿ~¯ýs¯ùšª¯¯¨sw*¯ÿ!¯óTG¯§+ |¯¨s*¯ë!¯Œ®¯¯–¤Tk¯¯c>®Š®¯¯*¯ë!¯8‚¯­¡Tk¯¯6l¯¯
š¯¯*¯ë!¯€<¯z=¯Tk¯¯'„¯¯%‹¯¯*¯ë!¯®¡5„¯Tk¯¯1s¯¯–¯¯*¯ë!¯¯_K¯¯Tk¯¯RT¯ ª¯¯*¯ï!¯¯¢a¯¯Tk¯¯œK^¯ÿ*¯ëŒ¯¯«£¯¯T^£¯¯¡tН¯­™,¯ý‰=X¯ý|Lw¯ý£LR,¯ý‘„­û¯?@¯ÿ~¯ýs¯ù¬®¯¯¨sw*¯ÿ!¯óTG¯q¯¯¨s*¯ð!¯Œ®¯¯–¤Tk¯¯¤§Cq¯ÿ*¯ö!¯8‚¯­¡Tk¯þCq¯ÿ*¯ö!¯€<¯z=¯Tk¯þCq¯ÿ*¯ö!¯®¡5„¯Tk¯þCq¯ÿ*¯ö!¯¯_K¯¯Tk¯þCq¯ÿ*¯ï!¯¯¢a¯¯Tk¯¯šG.I¯ÿ*¯óŒ¯¯«£¯¯T^£¯¦ƒû„¯­™,¯ý‰=X¯ý|Lw¯ý£LR,¯ý‘„­û¯?ƯþšW=¯þ…}¯þšW=¯þ…}¯þšW=¯þ…x¯ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿ ÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿÿþ÷ÞÿþÞ÷ÿÿéÿÿúÿÿš&€ÿú€&šÿÿéÿÿúÈ;
?ú?
;ÈéÿÿúÈ;
?ú?
;Èéÿÿúÿÿš&€ÿú€&šÿÿéÿÿÿÿþ÷ÞÿþÞ÷ÿ ÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿ ÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿÿþ÷ÞÿþÞ÷ÿÿÆÿÿúÿÿš&€ÿú€&šÿÿÆÿÿúÈ;
?ú?
;ÈÆÿÿúÈ;
?ú?
;ÈÆÿÿúÿÿš&€ÿú€&šÿÿÆÿÿÿÿþ÷ÞÿþÞ÷ÿ ÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿ ÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿÿþ÷ÞÿþÞ÷ÿÿ¯ÿÿúÿÿš&€ÿú€&šÿÿ¯ÿÿúÈ;
?ú?
;ȯÿÿúÈ;
?ú?
;ȯÿÿúÿÿš&€ÿú€&šÿÿ¯ÿÿÿÿþ÷ÞÿþÞ÷ÿ ÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ?*éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ"éü­Þé¡éýâ™™éþÞÑéý²™Ìéÿéþ­Þéü8ÇééòÔ™éé%Oæé²F“éÿéþ8ÇéÞÁ©ßééÞ²çéé8Çéééפ¯ãÔ+ééÜÏä8¢éél“éÿéôÁ©ßééÞ²çéé8ÇéÜÑ7\véƒGr:Çé8Çéé¶1€m¹Ô+éé¬+ééqhéél“éÿéòÑ7\véƒGr:Çé8Çéþ€éáæ.åé¡sé8Çéé‚oåééÔ+éé™Léé–Uéél“éÿéþ€é÷æ.åé¡sé8ÇéþD¶éá»HHGjé8ÇééÙb _ØÔ+éé¦4éé|céél“éÿéþD¶é÷»HHGjé8Çéþ}ƒéýä%äéü8ÇééïåhÔ+ééÏæéOéél“éÿéþ}ƒéýä%äéþ8ÇéèÏ0c‚˜é~B‚r…é8Çéé{t†0»Ô+é÷a0Y%Ýéél“éÿéòÏ0c‚˜é~B‚r…é8ÇéÞ¯œÚééÜš«âé½áé´éÕ ›×éÔ%Ìéé讟ÝééÕ^“éÿéô¯œÚééÜš«âé½áéýÝeeéýe¹éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿé?*éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ"éü­Þé¡éøâ™™ééæÉäéý²™Ìéÿéþ­Þéü8ÇééòÔ™é¿#B(qéé²F“éÿéþ8ÇéÞÁ©ßééÞ²çéé8Çéééפ¯ãÔ+ééÚßéäÎéél“éÿéôÁ©ßééÞ²çéé8ÇéèÑ7\véƒGr:Çé8Çéé¶1€m¹Ô+éùç"Äéél“éÿéòÑ7\véƒGr:Çé8Çéþ€éíæ.åé¡sé8Çéé‚oåééÔ+éþrVéþl“éÿéþ€é÷æ.åé¡sé8ÇéþD¶éí»HHGjé8ÇééÙb _ØÔ+éý†Aãéþl“éÿéþD¶é÷»HHGjé8Çéþ}ƒéýä%äéü8ÇééûåhÔ+éýŠ?âéþl“éÿéþ}ƒéýä%äéþ8ÇéÜÏ0c‚˜é~B‚r…é8Çéé{t†0»Ô+éé³U^^¹éél“éÿéòÏ0c‚˜é~B‚r…é8Çé篜ÚééÜš«âé½áé´éÕ ›×éÔ%Ì騝ûÕéÕ^“éÿéô¯œÚééÜš«âé½áéýÝeeéýe¹éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿé?*éÿ>éÿ>éÿ>éÿé?*Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ"Æü“½Æ‰ÆýÀ‚‚Æþ½²Æý—‚­ÆÿÆþ“½Æü/©ÆÆò´‚ÆÆyCÄÆ—<}ÆÿÆþ/©ÆÞ¤½ÆÆ½—ÄÆÆ/©ÆÆÆ¶‹”Á´$ÆÆ»°Á/‰ÆÆ\}ÆÿÆô¤½ÆÆ½—ÄÆÆ/©ÆÜ²/Nd{Æo=a2©Æ/©ÆÆ›*m\´$ÆÆ’$ÆÆ`YÆÆ\}ÆÿÆò²/Nd{Æo=a2©Æ/©ÆmÆáÄ'ÃÆ‰bÆ/©ÆÆn^ÃÆÆ´$ÆÆ‚@ÆÆHÆÆ\}ÆÿÆmÆ÷Ä'ÃÆ‰bÆ/©Æþ9›ÆþŸ=æZÆ/©ÆƸSQ·´$ÆÆ,ÆÆjTÆÆ\}ÆÿÆþ9›ÆþŸ=üZÆ/©ÆþjoÆýÂÂÆü/©ÆÆïÃYm´$ÆÆ°ÄÆDxÆÆ\}ÆÿÆþjoÆýÂÂÆþ/©Æè¯(TnÆk8naqÆ/©ÆÆicr)Ÿ´$Æ÷R)K ¼ÆÆ\}ÆÿÆò¯(TnÆk8naqÆ/©ÆÞ•…ºÆÆ»‚‘ÀÆ¡¿Æ™Æµˆ„¶Æ´ ­ÆÆÅ”‡¼ÆÆµP}ÆÿÆô•…ºÆÆ»‚‘ÀÆ¡¿Æý¼VVÆýyVÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>ÆÿÆ?*Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ"Æü“½Æ‰ÆøÀ‚‚ÆÆÄ«ÁÆý—‚­ÆÿÆþ“½Æü/©ÆÆò´‚Æ¢8"`ÆÆ—<}ÆÿÆþ/©ÆÞ¤½ÆÆ½—ÄÆÆ/©ÆÆÆ¶‹”Á´$ÆÆº½ÆÂ¯ÆÆ\}ÆÿÆô¤½ÆÆ½—ÄÆÆ/©Æè²/Nd{Æo=a2©Æ/©ÆÆ›*m\´$ÆùÄ¦ÆÆ\}ÆÿÆò²/Nd{Æo=a2©Æ/©ÆmÆíÄ'ÃÆ‰bÆ/©ÆÆn^ÃÆÆ´$ÆþaIÆþ\}ÆÿÆmÆ÷Ä'ÃÆ‰bÆ/©Æþ9›ÆþŸ=òZÆ/©ÆƸSQ·´$Æýr7ÁÆþ\}ÆÿÆþ9›ÆþŸ=üZÆ/©ÆþjoÆýÂÂÆü/©ÆÆûÃYm´$Æýu6ÀÆþ\}ÆÿÆþjoÆýÂÂÆþ/©ÆÜ¯(TnÆk8naqÆ/©ÆÆicr)Ÿ´$ÆÆ˜HPPžÆÆ\}ÆÿÆò¯(TnÆk8naqÆ/©Æç•…ºÆÆ»‚‘ÀÆ¡¿Æ™Æµˆ„¶Æ´ ­Æ·”ûµÆµP}ÆÿÆô•…ºÆÆ»‚‘ÀÆ¡¿Æý¼VVÆýyVÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>ÆÿÆ?*Æÿ>Æÿ>Æÿ>ÆÿÆ?*¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ"¯ü‚§¯y¯ýªss¯þ§¯ý†s™¯ÿ¯þ‚§¯ü*–¯¯òŸs¯¯k;­¯†5n¯ÿ¯þ*–¯Þ‘§¯¯§†®¯¯*–¯¯¯¡{ƒªŸ ¯¯¥œ«*y¯¯Qn¯ÿ¯ô‘§¯¯§†®¯¯*–¯Ü)EYl¯b6V,–¯*–¯¯‰%`R‹Ÿ ¯¯ ¯¯UN¯¯Qn¯ÿ¯ò)EYl¯b6V,–¯*–¯þa`¯á­"¬¯yV¯*–¯¯aS¬¯¯Ÿ ¯¯s9¯¯q@¯¯Qn¯ÿ¯þa`¯÷­"¬¯yV¯*–¯þ3‰¯þ6æP¯*–¯¯£IG¢Ÿ ¯¯}'¯¯]J¯¯Qn¯ÿ¯þ3‰¯þ6üP¯*–¯þ^b¯ý¬¬¯ü*–¯¯ï¬NaŸ ¯¯œ­¯¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ¯?*¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ"¯ü‚§¯y¯øªss¯¯­—«¯ý†s™¯ÿ¯þ‚§¯ü*–¯¯òŸs¯1U¯¯†5n¯ÿ¯þ*–¯Þ‘§¯¯§†®¯¯*–¯¯¯¡{ƒªŸ ¯¯¤§¯¬š¯¯Qn¯ÿ¯ô‘§¯¯§†®¯¯*–¯è)EYl¯b6V,–¯*–¯¯‰%`R‹Ÿ ¯ù®“¯¯Qn¯ÿ¯ò)EYl¯b6V,–¯*–¯þa`¯í­"¬¯yV¯*–¯¯aS¬¯¯Ÿ ¯þVA¯þQn¯ÿ¯þa`¯÷­"¬¯yV¯*–¯þ3‰¯þ6òP¯*–¯¯£IG¢Ÿ ¯ýe1ª¯þQn¯ÿ¯þ3‰¯þ6üP¯*–¯þ^b¯ý¬¬¯ü*–¯¯û¬NaŸ ¯ýh/ª¯þQn¯ÿ¯þ^b¯ý¬¬¯þ*–¯Ü›$Jar¯_1aVd¯*–¯¯]We$Ÿ ¯¯‡@GG‹¯¯Qn¯ÿ¯ò›$Jar¯_1aVd¯*–¯ç„u¤¯¯¥s€ª¯Ž©¯‡¯ xu¡¯Ÿ™¯¢ƒû ¯ Gn¯ÿ¯ô„u¤¯¯¥s€ª¯Ž©¯ý¦LL¯ýkL‹¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ¯?*¯ÿ>¯ÿ>¯ÿ>¯ÿ¯ÿ'ÿéÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯ÿ¡éýâ™™éþçäéý²™Ìéÿ
ÿÿÿÿ¯ÿéøÔ™éé?2éý²F“éÿ
ÿÿÿÿ¯÷ééפ¯ãÔ+éýϾ2éþl“éÿ
ÿÿÿÿ¯÷é¶1€m¹Ô+éþ¾2éþl“éÿ
ÿÿÿÿ¯÷é‚oåééÔ+éþ¾2éþl“éÿ
ÿÿÿÿ¯÷éÙb _ØÔ+éþ¾2éþl“éÿ
ÿÿÿÿ¯ÿéûåhÔ+éþ¾2éþl“éÿ
ÿÿÿÿ¯÷é{t†0»Ô+é÷}M^°éél“éÿ
ÿÿÿÿ¯ó´éÕ ›×éÔ%Ìé鼯ûÑéÕ^“éÿ
ÿÿÿÿ¯éýÝeeéýe¹éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯ÿ¡éøâ™™ééäÈÞéý²™Ìéÿ
ÿÿÿÿ¯ÿéòÔ™éÝ$F,Qéé²F“éÿÿþ÷ÞÿþÞ÷ÿÿÿÿ¯óª˜¯¯ééפ¯ãÔ+éú1·éél“éúÿÿš&€ÿú€&šÿÿÿﯯjX¯¯é¶1€m¹Ô+éøÚ·#Óéél“éúÈ;
?ú?
;Èÿï‰(+WWé‚oåééÔ+éø:qæéél“éúÈ;
?ú?
;Èÿï‰(+WWéÙb _ØÔ+éùæ=Ÿéél“éúÿÿš&€ÿú€&šÿÿÿ÷¯¯jX¯¯éûåhÔ+éúY•éél“éÿÿþ÷ÞÿþÞ÷ÿÿÿÿ¯çª˜¯¯é{t†0»Ô+éé°UzO)äéél“éÿ
ÿÿÿÿ¯ñ´éÕ ›×éÔ%Ìéä°“ÀéýÕ^“éÿ
ÿÿÿÿ¯éýÝeeéýe¹éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯'ÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ‰ÆýÀ‚‚ÆþÄÁÆý—‚­Æÿ
ÿÿÿÿÆÿÆø´‚ÆÆ6+Æý—<}Æÿ
ÿÿÿÿÆ÷ÆÆ¶‹”Á´$Æý°¢+Æþ\}Æÿ
ÿÿÿÿÆ÷Æ›*m\´$Æþ¢+Æþ\}Æÿ
ÿÿÿÿÆ÷Æn^ÃÆÆ´$Æþ¢+Æþ\}Æÿ
ÿÿÿÿÆ÷ƸSQ·´$Æþ¢+Æþ\}Æÿ
ÿÿÿÿÆÿÆûÃYm´$Æþ¢+Æþ\}Æÿ
ÿÿÿÿÆ÷Æicr)Ÿ´$Æ÷jAP–ÆÆ\}Æÿ
ÿÿÿÿÆó™Æµˆ„¶Æ´ ­ÆÆ ”û²ÆµP}Æÿ
ÿÿÿÿ
Æý¼VVÆýyVÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ‰ÆøÀ‚‚ÆÆÂª½Æý—‚­Æÿ
ÿÿÿÿÆÿÆò´‚Ƽ<%EÆÆ—<}Æÿÿþ÷ÞÿþÞ÷ÿÿÿÿÆóÀ¬ÆÆÆÆ¶‹”Á´$Æú*›ÆÆ\}Æúÿÿš&€ÿú€&šÿÿÿïÆÆxcÆÆÆ›*m\´$Æøº›³ÆÆ\}ÆúÈ;
?ú?
;Èÿï›.1ccÆn^ÃÆÆ´$Æø1
`ÄÆÆ\}ÆúÈ;
?ú?
;Èÿï›.1ccƸSQ·´$ÆùÄ4‡ÆÆ\}Æúÿÿš&€ÿú€&šÿÿÿ÷ÆÆxcÆÆÆûÃYm´$ÆúKÆÆ\}Æÿÿþ÷ÞÿþÞ÷ÿÿÿÿÆçÀ¬ÆÆÆicr)Ÿ´$ÆÆ–HgC#ÁÆÆ\}Æÿ
ÿÿÿÿÆñ™Æµˆ„¶Æ´ ­ÆÂ–}£ÆýµP}Æÿ
ÿÿÿÿ
Æý¼VVÆýyVÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆ'ÿ¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿéÿy¯ýªss¯þ®«¯ý†s™¯ÿ
ÿÿÿÿéÿ¯øŸs¯¯/&¯ý†5n¯ÿ
ÿÿÿÿé÷¯¯¡{ƒªŸ ¯ýœ&¯þQn¯ÿ
ÿÿÿÿé÷¯‰%`R‹Ÿ ¯þ&¯þQn¯ÿ
ÿÿÿÿé÷¯aS¬¯¯Ÿ ¯þ&¯þQn¯ÿ
ÿÿÿÿé÷¯£IG¢Ÿ ¯þ&¯þQn¯ÿ
ÿÿÿÿéÿ¯û¬NaŸ ¯þ&¯þQn¯ÿ
ÿÿÿÿé÷¯]We$Ÿ ¯÷^:G„¯¯Qn¯ÿ
ÿÿÿÿé󇯠xu¡¯Ÿ™¯¯ƒû¯ Gn¯ÿ
ÿÿÿÿé¯ý¦LL¯ýkL‹¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿéÿy¯øªss¯¯¬–§¯ý†s™¯ÿ
ÿÿÿÿéÿ¯òŸs¯¦5!=¯¯†5n¯ÿÿþ÷ÞÿþÞ÷ÿÿÿÿéóâËé鯯¡{ƒªŸ ¯ú%‰¯¯Qn¯úÿÿš&€ÿú€&šÿÿÿïéé#ué鯉%`R‹Ÿ ¯ø¤‰Ÿ¯¯Qn¯úÈ;
?ú?
;Èÿï·6	:tt¯aS¬¯¯Ÿ ¯ø+U­¯¯Qn¯úÈ;
?ú?
;Èÿï·6	:tt¯£IG¢Ÿ ¯ù­.w¯¯Qn¯úÿÿš&€ÿú€&šÿÿÿ÷éé#uéé¯û¬NaŸ ¯úCp¯¯Qn¯ÿÿþ÷ÞÿþÞ÷ÿÿÿÿéçâËéé¯]We$Ÿ ¯¯„@[;«¯¯Qn¯ÿ
ÿÿÿÿéñ‡¯ xu¡¯Ÿ™¯¬„n¯ý Gn¯ÿ
ÿÿÿÿé¯ý¦LL¯ýkL‹¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé'ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ??¯ÿ­>¯ÿY=¯û£¯k•	¯ú®¬¯¯®¬*¯û<¯<ƒ	¯ú“-¯¯†:)¯Ù®aI¯a¯ø/¡Y¯(œ¯Û”¯<_¯¯§hY¯¯"œ“-¯¯†:¯¯a_¯¯¡‡!¯¯*s¯3¯ù^asW.§
¯Û”¯¯¨«¡#¯¯•/¯¯zE¯¦­‡8¯¯5‹¯˜(¯ù®$®Vw¯Ù®aI¯<:'!u¯¬I+&l¯¯C,b¯:,lªF+&p¯‡8¯¯5‹¯˜(¯ú8=¯…£¯þ<¯þ§«¯þ§®¯þ§®$¯þ£#¯ÿ’sÿ„¯ÿY#¯ÿ’sÿ„¯ÿ­H¯7¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ5¯ÿ̯ÿ4¯þÌé¯ÿ3¯ýÌéé¯ÿ2¯ÿÌé¯ÿ1¯ÿÌé¯ÿ0¯ÿÌé¯ÿ/¯ÿÌé¯ÿ.¯ÿÌé¯ÿ-¯ÿÌé¯þ˜ª¯ÿ,¯ÿÌé¯ýXj¯ÿ+¯ÿÌ	éWù+(‰¯¯þc€¯ÿÌ
éWù+(‰¯¯ñ§Æÿe=Æû¹Æy¨	ÆúÅÂÆÆÅÂ*ÆûDÆD”	Æú¦2ÆÆ—A)ÆÙÅmSÆD…f€ÄÆÆ·n…ÃÆiiZ"i—ƶmˆÄƱ‡†]žµh€ÂÆøsÅÆt«Æ‘ªÆÛ§ÆD(†]=Ʋƒf.Ɖ(›­{2›°¬ †b4Æ™
f‚I‡FnÆø6£¶eÆ.±ÆÛ§ÆDlÆÆ½ueÆÆ'°¦2ÆÆ—AÆÆnlÆÆ!¶™%ÆÆ/‚ÆŸ:Æùjn‚c4½"
ÆÛ§ÆD…ÆÆ/¦X~ÆÆ@”¦2ÆÆ—AÆÆQ…ÆÆ9›™@ÆÆ<Ƭ-Æùž:N¢2„S
ÆÛ§ÆDNƹÆ‘GƾÁ¶(ÆÆ¨6ÆÆŠNƼÄ™@ÆÆ<Ƭ-ÆùÄ)Åb"‡ÆÙÅmSÆDA,%…ÆÂR0+zÆÆL2oÆA2zÀO1+Æ™@ÆÆ<Ƭ-Æú@EÆ—¹ÆþDÆþ½ÁÆþ½ÄÆþ½Å$Æþ¹#Æÿ¥‚ÿ–Æÿe#Æÿ¥‚ÿ–ÆÿÄHÆ7Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ8Æþ¬ÀÆÿ8ÆýcxÆÿ6Æcù1.›ÆÆþpÆcù1.›ÆÆñ½De›¾ÆÁ—ÂÆÆ¯¶œ§Æø©¼Æ­¯Æ»¬ÆýcxÆÿÆð³/Di´*](ÆY)N:RÆø`xÆ6;ÆslÆþ¬ÀÆÿÆðXÆÆ>žÆ˜@ÆYJÆÆ!ºÆø•DÁ/-Ã>¡ÆÿÆðXÆÆÄÆÁÆYrÆÆFŽÆøÁ’]X–ÅÆÿÆð`{ÆÆ;¡Æ›=ÆYLÆÆ"·Æú6:’:B	ÆÿÆñ’(a³‰)c'ŒÆY$T@KÆúkÀ½w	ÆÿÆóº—½Æ½‹½ÆÆY”Æú¸ÆÆœº	Æÿ"ÆþYÆÿóÿ´Æÿ"Æþ¡®Æÿ¯PÿRÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ6Æ??éÿæ>éÿw=éûÙ"éÆ	éúèäééèä*éûOéP¯	éúÄ;éé²M)éÙèbéPœx—æé騂œåé{{©j({±éÖ€ æéПžn¹Õz—äéø‡èéˆÉé«ÈéÛÄéP/žnHéÑ#šy6é¢/¶Ì;¶ÏÊ%s=é´x™VŸR‚éø?ÀÖwé6ÐéÛÄéPéé!ÞŠwéé.ÏÄ;éé²Méé‚éé'Ö´,éé8šé»Eéù}‚št=Þ(
éÛÄéPœéé7Äg”ééL®Ä;éé²Méé_œééD¶´KééF¹éÊ5éùºE[¿:›b
éÛÄéP[éÙ"é«Séàä×/ééÆ?éé£[éÝæ´KééF¹éÊ5éùç0ès(ŸéÙèbéPM4,œéäa93ééZ:ƒéM:â]:3•é´KééF¹éÊ5éúKQé±ÙéþOéþÞäéþÞçéþÞè$éþÙ"#éÿÙÿ°éÿw#éÿÙÿ°éÿæHé7éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ5éÿÌéÿ4éþ̯éÿ3éý̯¯éÿ2éÿ̯éÿ1éÿ̯éÿ0éÿ̯éÿ/éÿ̯éÿ.éÿ̯éÿ-éÿ̯éþËâéÿ,éÿ̯éýu#éÿ+éÿÌ	¯tù:	6·ééþ„ªéÿÌ
¯tù:	6·ééñßPw¶àéä±äééÎ׸Äéÿ̯ò–¦¯™š¯¥˜¯ééu#éÿéîÒ7P{Ô¦1n0¨éi0[E`é̯óUj¯04¯f_¯ééËâéÿéðg˜ééIºé³LéiWéé'¿¯÷„<ª*(¬7ޝéÿéðg˜ééæéäéi†ééH~¯÷«RN„®¯éÿéðq‘ééE½é¶HéiZéÌ¢¯ø03|3:¯¯éÿéñ¬0rÓ¢0u.¤éi+V8C¯ø_ª§i¯¯éÿéóÚ±Þéߤßééi‚‚‹¯ø£‹¯¯Š¥¯¯éÿ"éþ\q¯ÿ¬Ÿ¯éÿ!éýÌŽš¯ÿšGÿI¯éÿ éÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯ÿ/ÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿþh-¯ÿÿý—,¯ÿÿÿ0,¯ÿÿþ
|+¯ÿÿüW{+¯ÿÿþ-¯ÿÿþ-¯ÿÿþ-¯ÿÿþ-¯ÿÿüW{+¯ÿÿþ
|+¯ÿÿÿ0,¯ÿÿý—,¯ÿÿþh-¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿ ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿü¯¯Ìéÿ¯ÿÿý¯Ìéÿ¯ÿÿþÌéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéþô¯þª˜¯þ˜ª¯ÿÿÿéýôÿ¯ýjX¯úXj¯¯ÿÿéöôÿÿ¯‰(+Wú+(‰ÿÿéÿôÿù¯‰(+Wú+(‰ÿÿéÿôÿÿ¯ýjX¯úXj¯¯ÿÿéÿôÿÿ¯þª˜¯þ˜ª¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿô	ÿÿ¯ÿÿÿéÿô
ÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿô
ÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿ
éÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿ
éÿôÿÿ¯ÿÿÿ	éÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿ/ÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿþv-Æÿÿý«,Æÿÿÿ6,ÆÿÿþŒ+Æÿÿüc‹+Æÿÿþ-Æÿÿþ-Æÿÿþ-Æÿÿþ-Æÿÿüc‹+ÆÿÿþŒ+Æÿÿÿ6,Æÿÿý«,Æÿÿþv-Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿ ÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆþÚÆþÀ¬Æþ¬ÀÆÿÿÿÆýÚîÆýxcÆúcxÆÆÿÿÆöÚîîÆ›.1cú1.›ÿÿÆÿÚîùÆ›.1cú1.›ÿÿÆÿÚîÿÆýxcÆúcxÆÆÿÿÆÿÚîÿÆþÀ¬Æþ¬ÀÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚ	îÿÆÿÿÿÆÿÚ
îÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚ
îÿÆÿÿÿÆÿÚîÿÆÿÿÿ
ÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿ
ÆÿÚîÿÆÿÿÿ	ÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿ/ÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿþ‹-éÿÿýÉ,éÿÿÿ@,éÿÿþ
¤+éÿÿüt¤+éÿÿþ$-éÿÿþ$-éÿÿþ$-éÿÿþ$-éÿÿüt¤+éÿÿþ
¤+éÿÿÿ@,éÿÿýÉ,éÿÿþ‹-éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿ éÿÿÿéÿ̯ÿéÿÿÿéÿ̯ÿéÿÿÿéÿ̯ÿéÿÿÿéÿ̯ÿéÿÿüéé̯ÿéÿÿýé̯ÿéÿÿþ̯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯þ¬éþâËéþËâéÿÿÿ¯ý¬ªéý#uéúu#ééÿÿ¯ö¬ªªé·6	:tú:	6·ÿÿ¯ÿ¬ªùé·6	:tú:	6·ÿÿ¯ÿ¬ªÿéý#uéúu#ééÿÿ¯ÿ¬ªÿéþâËéþËâéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬	ªÿéÿÿÿ¯ÿ¬
ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬
ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ
¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ
¯ÿ¬ªÿéÿÿÿ	¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿÿÿéÿïÿÿ¯þÌÿÿéÿïÿÿ¯ýÌéÿÿéÿï	ÿÿ¯üÌééÿÿéÿï
ÿÿ¯ÿÌéÿÿÿéÿïÿÿ¯ÿÌéÿÿÿéÿïÿü¯¯Ìéÿÿÿéÿï
ÿý¯ÌéÿÿÿéÿïÿþÌéÿÿÿéÿïÿÿéÿÿÿéÿïÿÿéÿÿÿéÿïÿÿéÿÿÿéÿïÿÿéÿÿÿéÿïÿÿéÿÿÿéÿïÿÿéÿÿÿéÿïÿÿéÿÿÿ
éÿïÿÿéÿÿÿéÿïÿÿéÿÿÿéÿïÿÿéÿÿÿ
éÿïÿÿéÿÿÿ	éÿïÿÿéÿÿÿéÿïÿÿéÿÿÿéÿïÿÿéÿÿÿéÿïÿÿéÿÿ0ÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿ0>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ
ÿÿÿþÿÿÿÿÿþÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑ	îÿÆÿÿÿÆÿÑ
îÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑ
îÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿ
ÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿ
ÆÿÑîÿÆÿÿÿ	ÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿÿÆÿÑîÿÆÿÿ0ÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿ0>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ
ÿÿÿþÿÿÿÿÿþÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ¯ÿ­ªÿéþÌÿÿ¯ÿ­ªÿéý̯ÿÿ¯ÿ­	ªÿéü̯¯ÿÿ¯ÿ­
ªÿéÿ̯ÿÿÿ¯ÿ­ªÿéÿ̯ÿÿÿ¯ÿ­ªüéé̯ÿÿÿ¯ÿ­
ªýé̯ÿÿÿ¯ÿ­ªþ̯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ
¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ
¯ÿ­ªÿ¯ÿÿÿ	¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿÿ¯ÿ­ªÿ¯ÿÿ0ÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿ0>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ
ÿÿÿþÿÿÿÿÿþÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>&éÿíÿÿ4éÿíÿÿ3éÿí	ÿÿ2éÿí
ÿÿ1éÿíÿÿ0éÿíÿÿ/éÿí
ÿÿ.éÿíÿÿ-éÿíÿÿ,éÿíÿÿ+éÿíÿÿ*éÿíÿÿ)éÿíÿÿ(éÿíÿÿ
éÿíéÿíÿÿéþíÿéÿíÿÿéýíÿÿéÿíÿÿ
éÿíÿéÿíÿÿ	éÿíÿéÿíÿÿéÿíÿéÿíÿÿéÿíÿéÿíÿÿéÿíÿéÿíÿÿéÿíÿéÿíÿÿéÿíÿ?/¯ÿÿ/¯ÿÿ/¯ÿÿ?ÿ<ÿ(ÿÿ)ÿÿ)ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ	ÿÿ	ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿ	ÿÿ&ÆÿÍîÿ4ÆÿÍîÿ3ÆÿÍ	îÿ2ÆÿÍ
îÿ1ÆÿÍîÿ0ÆÿÍîÿ/ÆÿÍ
îÿ.ÆÿÍîÿ-ÆÿÍîÿ,ÆÿÍîÿ+ÆÿÍîÿ*ÆÿÍîÿ)ÆÿÍîÿ(ÆÿÍîÿ
ÆÿÍÆÿÍîÿÆþÍîÆÿÍîÿÆýÍîîÆÿÍîÿ
ÆÿÍîÆÿÍîÿ	ÆÿÍîÆÿÍîÿÆÿÍîÆÿÍîÿÆÿÍîÆÿÍîÿÆÿÍîÆÿÍîÿÆÿÍîÆÿÍîÿÆÿÍî?/Æÿÿ/Æÿÿ/Æÿÿ?ÿ<ÿ(ÿÿ)ÿÿ)ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ	ÿÿ	ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿ	ÿÿ&¯ÿ®ªÿ4¯ÿ®ªÿ3¯ÿ®	ªÿ2¯ÿ®
ªÿ1¯ÿ®ªÿ0¯ÿ®ªÿ/¯ÿ®
ªÿ.¯ÿ®ªÿ-¯ÿ®ªÿ,¯ÿ®ªÿ+¯ÿ®ªÿ*¯ÿ®ªÿ)¯ÿ®ªÿ(¯ÿ®ªÿ
¯ÿ®¯ÿ®ªÿ¯þ®ª¯ÿ®ªÿ¯ý®ªª¯ÿ®ªÿ
¯ÿ®ª¯ÿ®ªÿ	¯ÿ®ª¯ÿ®ªÿ¯ÿ®ª¯ÿ®ªÿ¯ÿ®ª¯ÿ®ªÿ¯ÿ®ª¯ÿ®ªÿ¯ÿ®ª¯ÿ®ªÿ¯ÿ®ª?/éÿÿ/éÿÿ/éÿÿ?ÿ<ÿ(ÿÿ)ÿÿ)ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ	ÿÿ	ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿ	ÿÿÿ
éÿÿÿ¯áE¯e©¯lS¯w#W1¯¦2<+‹¯^1aUe¯_3éÿÿÿ¯ñ‘œ¯¤‹¯¯Ÿ˜¯¯¡w¤ˆ¯ó–¦¬¯¯¥s€ª¯¯œéÿÿÿ¯üB_1R	¯éÿÿÿ¯ý”z¡
¯éþôÿÿ&¯éÿÿÿ&¯éþíÿÿ&¯éýíÿÿ'éüíÿÿÿÿ&¯éÿíÿÿÿÿ&¯éÿíÿÿÿÿ&¯þéíÿÿÿÿ¯þšW¯ÿíÿÿÿÿ¯þ…¯ÿÿÿÿ&¯ÿÿÿÿ¯þšW¯ÿÿÿÿ¯þ…¯ÿÿÿÿ&¯ÿÿÿÿ¯þšW¯ÿÿÿÿ¯þ…¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿ'ÿÿÿÿ	é#ÿÿÿÿé$ÿÿÿÿéÿÿ'
ÿ	ÿ#ÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿ	ÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿýÿÿÿÿÿÿÿÿ
ÿÿýÿÿÿÿÿÿÿÿ	ÿÿÆÿÿÿÆáNÆr¿Æz^Ɔ(c7Ƽ9D1!Æj8n`rÆk9ÆÿÿÿÆñ¤±ÆºÆÆ³¬ÆÆ¶†ºšÆó©¼ÃÆÆ»‚‘ÀÆÆ±ÆÿÿÿÆüKk7\ÆÿÿÿÆý§Š¶ÆþÚÿÿ-Æþîÿÿ,ÆýÍîÿÿ+ÆüÍîîÿ'ÆûÍîîîÿÿ)ÆÿÍîþîÿÿ(ÆÿÍîþîÿÿ'ÆÿÍîþîÿÿÆþ¯cÆÿÍîþîÿÿÆþ—Æîþîÿÿ&ÆîþîÿÿÆþ¯cÆîþîÿÿÆþ—Æîþîÿÿ&ÆîþîÿÿÆþ¯cÆîþîÿÿÆþ—Æîþîÿÿ&Æîþîÿÿ&Æîþîÿÿ&Æîþîÿÿ&Æÿ'ÿÿÿÿ	ÆîÿÿÿÿÆîÿÿÿÿÆîÿ'
ÿ	ÿ#ÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿ	ÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿýÿÿÿÿÿÿÿÿ
ÿÿýÿÿÿÿÿÿÿÿ	ÿÿ¯ÿÿÿéá!\é†áéoéž/tAéÝCO:'¹é}B‚q†é~D¯ÿÿÿéñÁÐéÚ¹ééÓËééÖžÚµéóÇÝ%åééÜš«âééЯÿÿÿéüX~Am	é¯ÿÿÿéýÄ£×
é¯þ¬ÿÿ&é¯þªÿÿ&é¯ý®ªÿÿ&é¯ü®ªªÿ'¯û®ªªªÿÿ&é¯ÿ®ªþªÿÿ&é¯ÿ®ªþªÿÿ&éþ¯®ªþªÿÿéþÎtéÿ®ªþªÿÿéþ±éªþªÿÿ&éªþªÿÿéþÎtéªþªÿÿéþ±éªþªÿÿ&éªþªÿÿéþÎtéªþªÿÿéþ±éªþªÿÿ&éªþªÿÿ&éªþªÿÿ&éªþªÿÿ&éÿ'ÿÿÿÿ	¯ªÿÿÿÿ¯ªÿÿÿÿ¯ªÿ'
ÿ	ÿ#ÿÿÿÿ	ÿÿÿÿÿÿ	ÿÿÿÿÿ	ÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿýÿÿÿÿÿÿÿÿ
ÿÿýÿÿÿÿÿÿÿÿ	ÿÿÿ/ÿ/ÿ.ÿÿó/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿÿ/ÿ'ÿ
ÿY¯ä£¤¯TZd&Êé9eš°Néé‘\^^ÏééH¶éÿÿ„¯í‹¬¬Š¯vŠÚééèÀ°EÐéϯûÞéÐ?¶éÿþ¯¡Ÿÿª¯ÿÌéýÊezéýzeÍéÿþ¯UGÿޝÿÌ-éÿ
¯ÿÌ-éÿ	¯ÿÌ-éÿ¯ÿÌ-éÿ?¯þ±Ú#é¯þ³Ü$é¯þ´Þ%é¯þµà&é¯þ·â'é¯þ¹ã(é¯þ»å)é¯þ½æ*é¯þÀç+é¯þÃè,é¯þÆè-é¯ÿÉ/é
¯ÿÍ0é¯ÿÏ1é¯þËáâöãääååæççèè$é?Àÿ?ÿ9ÿ7ÿ7ÿÿÿ7ÿ<ÿ9ÿ9ÿ7ÿÿdÆä ¸ºÆ`fq%¬Æ0Uƒ–BÆÆ{NPP¯ÆÆ=›Æîÿ•ÆíÃÜƲ††ºÆÆÅ£¥–:±Æ¯”û½Æ±6›Æîþƶ³ÿÁ
Æý¬VgÆýhV®ÆîþÆ`Pÿ¡3Æî9Æî8Æî7Æî?ÀÆ?Àî?ÿ9ÿ7ÿ7ÿÿÿ7ÿ<ÿ9ÿ9ÿ7ÿÿvéä%ÙÚ$épx„&˜¯+Kt„:¯¯mEGG›¯¯6‰¯ªÿ¯éí¹åå¸éÑŠ¤¯¯®‘„3œ¯›ƒû§¯œ/‰¯ªþé×Óÿãéÿ̯ý˜L[¯ý\Lš¯ªþéq^ÿ½éÿÌ-¯ª
éÿÌ-¯ª	éÿÌ-¯ªéÿÌ-¯ª?éþç¾#¯éþå¼$¯éþäº%¯éþã¸&¯éþá¶'¯éþßµ(¯éþݳ)¯éþÛ²*¯éþر+¯éþÕ°,¯éþÒ°-¯éÿÏ/¯
éÿË0¯éÿÉ1¯éþÍ·¶öµ´´³³²±±°°$¯?Àª?ÿ9ÿ7ÿ7ÿÿÿ7ÿ<ÿ9ÿ9ÿ7ÿÿ
ÿÿÿÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿé
ÿþéÿÿéÿÿ,éþÿÿÿ+éÿÿÿÿéý䯷 éÿÿÿÿéýBs®éþžéÿÿÿÿéø½¶Öéä±äéóæ²àéæk[¶Øéé¹Ö
éÿÿÿÿéçŠ{À¥1n0©é¹0m2–ãI>{ÄÊ?lN{éÿÿÿÿéèééG¼é²Méb¢éÌ2é‰uéét›éè/éÿÿÿÿéèééæéãé)Ñééâ‰uéé5?HHDéÿÿÿÿéëééD¿éµIé^¤éÏ0é“oéép”éÿÿÿÿéèéé¡0u.¥éµ.t3éË%oÂÈ7j„Lé	ÿÿÿÿéÿ´éýÞ¤àéýä¤Ùéøà´Öé鳚Íé
ÿÿÿÿ!éÿÿÿÿ éÿÿÿÿé
ÿÿÿÿéÿÿÿÿéÿÿÿÿÿ	¯ÿÌéÿÿÿÿ¯ÿÌéÿÿÿÿ¯ÿÌéÿ	ÿîÿÿîÿÿÿÆîÿÿÿÆîÿÿÿÆîÿÿÿÆîÿÿÿÆ
îþÆÿÿÆÿÿ,Æþîÿÿ+ÆîÿÿÿÆý”› ÆîÿÿÿÆý8b”Æþ†yÆîÿÿÿÆø¡›¶ÆÁ—ÂÆóÄ—¾ÆÄ[N›·ÆÆ¶
ÆîÿÿÿÆçu	i£*](Æ)\+Á>5i§¬6\BiÆîÿÿÿÆèÆÆ= Æ—AÆS‰Æ­+ÆtcÆÆc„ÆÅ(ÆîÿÿÿÆèÆÆÄÆÁÆ#²ÆÆÀtcÆÆ-6==9ÆîÿÿÿÆëÆÆ9¢Æš>ÆPŒÆ¯(Æ}^ÆÆ`~ÆîÿÿÿÆèÆÆ‰)c'Æš'c+{Ƭ_¥ª/Zp@Æ	îÿÿÿÆÿ™Æý½‹¾ÆýÁŒ¹Æø¾™¶ÆÆ˜‚®Æ
îÿÿÿ!Æîÿÿÿ ÆîÿÿÿÆ
îÿÿÿÆîÿÿÿÆÿîÿÿÿÆîÿÿÿÆîÿÿÿÆÿ	ÿªÿÿªÿÿÿ¯ªÿÿÿ¯ªÿÿÿ¯ªÿÿÿ¯ªÿÿÿ¯
ªþ¯ÿÿ¯ÿÿ,¯þªÿÿ+¯ªÿÿÿ¯ý¬ƒ‰ ¯ªÿÿÿ¯ý1V‚¯þwk¯ªÿÿÿ¯øŽ‰¡¯«…¬¯ó­†¨¯­PE‰¢¯¯‹¡
¯ªÿÿÿ¯çh]|%R$¯‹$R&qª7/]”˜/Q:]¯ªÿÿÿ¯è¯¯6¯†:¯Iy¯™&¯gX¯¯Wu¯®#¯ªÿÿÿ¯è¯¯­¯ª¯¯¯ªgX¯¯(/663¯ªÿÿÿ¯ë¯¯3¯ˆ7¯G|¯›$¯nS¯¯To¯ªÿÿÿ¯è¯¯y$X"|¯ˆ"W&l¯˜T‘–)Pc9¯	ªÿÿÿ¯ÿ‡¯ý§{¨¯ý«|£¯ø¨‡¡¯¯‡sš¯
ªÿÿÿ!¯ªÿÿÿ ¯ªÿÿÿ¯
ªÿÿÿ¯ªÿÿÿ¯ÿªÿÿÿ	éÿ̯ªÿÿÿéÿ̯ªÿÿÿéÿ̯ÿ	ÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ
?
éþ¯åéþ¯Ûéû̾é¶ÞéþÓ?
éúp¼éédÊéû“ké˜{	éü×Î̬éþiéëÈÇééç½Ø«äéÙ=‰¶äÄáééÓ»éî¼á®×éé嶨âéé“kéä+àéü”,J…éÑ$ÙééIRQGéà>l-»Ç*]{à=ÁéhD`5ÛéKk)®éškz2²é“kééO¯éýå”0éÝãééÀDééA¾àÊéšféO¯éé=ÁÞ%ééf™é±éŸaéöÉaé“kééo‹éýË”eéÐÜéé‡séés…àééÊ0éO¯éé=Á­Néé™`éâé¼Bé¨?bc3é“kéévƒéþ”jéÑßé¼FééDºàÍédéY©éé=ÁÜ&ééi•éãé¼Bé@¿é’2é“kééV§éþ”jéÑT°ééBYX?éà?s*¸é•9tß=ÁéaHg/Úéãé¼BérKs?2é“kéé.×éþÔÊéþÁOé÷¼»ééàÒžßé÷Ö¯ä¾ßééʯéï¶èéÞÀé賤ڻéÔÊé®béþ…åéþà éþ›Ïéþæ¤ìé?2éÿôÿ1éÿôÿ0éÿô
ÿ?	ÿ?
Æþ”ÃÆþ”ºÆû­¢Æ›½Æþ³6
Æú` ÆÆU¬Æû}[Æi	Æü¶¯­’ÆþY†Æëª©ÆÆÄ¡·‘Áƹ4t›Â¦¿ÆÆ³ŸÆî ¿”¶ÆÆÃ›ÀÆÆ}[ÆÂ$¾Æü~%?qÆÑ¸ÆÆ>FE=ƾ5\&Ÿ©$Oi¾4¤ÆY9R-ºÆ@[#”Æ‚[g+—Æ}[ÆÆC•ÆýÃ~)ÆÝÁÆÆ£9ÆÆ7¢¾¬ÆƒWÆC•ÆÆ4¤½ ÆÆW‚Æ—ƇRÆö«xRÆ}[ÆÆ^vÆý¬~VÆÐ»ÆÆsbÆÆbq¾
ÆÆ¬)ÆC•ÆÆ4¤“BÆÆ‚RÆÀÆ 8Æ6ST+Æ}[ÆÆdoÆþ~ZÆÑ½Æ <ÆÆ9ž¾®Æ†UÆKÆÆ4¤»!ÆÆYÆÁÆ 8Æ6¢Æ|+Æ}[ÆÆIŽÆþ~ZÆÑG–ÆÆ8KK6ƾ6b$œÆ0c½4¤ÆR=X(ºÆÁÆ 8Æa@b6+Æ}[ÆÆ'¶Æþ´¬Æþ¤DÆ÷ ŸÆÆ¾³†½Æ÷¶•¢½ÆÆ¬”Æï›Åƽ£ÆÅ˜ŒºŸÆ´¬Æ”SÆþqÃÆþ¾ Æþ„°ÆþÄ‹ìÆ?2ÆÿÚî1ÆÿÚî0ÆÿÚ
î?	ÿ?
¯þƒ¬¯þƒ¥¯û™¯‰§¯þŸ/
¯úT¯¯K˜¯ûnP¯r]	¯ü¡š™¯þOv¯–¯ï®Ž¢€«¯£.g‰¬“©¯¯Ÿ¯î©‚¡¯¯¬‰~ª¯¯nP¯¬ ¨¯üo!8d¯Ñ£¯¯7>=6¯¨/Q"– F]¨.‘¯N3H(¥¯8P‚¯sP[&†¯nP¯¯;„¯ý¬o$¯Ýª¯¯3¯¯1¨˜¯tM¯;„¯¯.‘§¯¯Ms¯…¯wI¯ö—jI¯nP¯¯Sh¯ý˜oL¯Ð¥¯¯fV¯¯Vd¨¯¯˜$¯;„¯¯.‘‚:¯¯sH¯ª¯1¯~/IJ&¯nP¯¯Yb¯þoP¯Ñ§¯5¯¯3Œ¨š¯vK¯C¯¯.‘¥¯¯Op¯ª¯1¯0¯n&¯nP¯¯A~¯þoP¯Ñ?„¯¯1CB/¯¨/V Нp+W§.‘¯I6N#¤¯ª¯1¯V8V/&¯nP¯¯"¡¯þŸ˜¯þ‘<¯¯û¨žw§¯÷¡„¬§¯¯˜ƒ¯ï‰®¯§¯®‡|¤¯Ÿ˜¯‚I¯þd¬¯þ¨ ¯þuœ¯þ­{ì¯?2¯ÿ¬ª1¯ÿ¬ª0¯ÿ¬
ª?	ÿÿ
ÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿÿÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿ	ÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÿîÿÿÿÆîÿÿÿÆîÿÿÿÆÿ	ÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿÿªÿÿÿéªÿÿÿéªÿÿÿéÿ	ÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ
?#ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜ÿ?À¯?	ÿ?#ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜ÿ?ÀÆ?	ÿ?#ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'=ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜ÿ?Àé?	ÿÿ
ÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿ ÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿÿ	ÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿ ÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÿ	ÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿ ÿéÿÿÿÿéÿÿÿÿéÿÿÿÿÿ	ÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ
*éÿ>éÿ>éÿ>éÿ"éü­Þé¡éýâ™™éùåäéé²™Ìéÿ"éü8ÇééýÔ™éøä,Aéé²F“éÿéêÁ©ßééÞ²çéé8Çéééפ¯ãÔ+éýtfAéþl“éÿéèÑ7\véƒGr:Çé8Çéé¶1€m¹Ô+éüÀQ¯Aéþl“éÿéþ€éææ.åé¡sé8Çéé‚oåééÔ+ééæ>Ó¯Aéþl“éÿéþD¶éá»HHGjé8ÇééÙb _ØÔ+éé‰W§~/¶éél“éÿéþ}ƒéýä%äéü8ÇééïåhÔ+éé¨mmRˆéél“éÿéèÏ0c‚˜é~B‚r…é8Çéé{t†0»Ô+éþ¯Aéþl“éÿé÷¤=éÏééÑ
é鯜ÚééÜš«âé½áé´éÕ ›×éÔ%ÌéùÛ¿ééÕ^“éÿé÷Ù¾éã´éé³äéýÝeeéýe¹éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿé?Àÿ?	ÿ*Æÿ>Æÿ>Æÿ>Æÿ"Æü“½Æ‰ÆýÀ‚‚ÆùÃÂÆÆ—‚­Æÿ"Æü/©ÆÆý´‚ÆøÂ%7ÆÆ—<}ÆÿÆê¤½ÆÆ½—ÄÆÆ/©ÆÆÆ¶‹”Á´$ÆýcW7Æþ\}ÆÿÆè²/Nd{Æo=a2©Æ/©ÆÆ›*m\´$Æü£E•7Æþ\}ÆÿÆmÆæÄ'ÃÆ‰bÆ/©ÆÆn^ÃÆÆ´$ÆÆÄ5³•7Æþ\}ÆÿÆþ9›ÆþŸ=æZÆ/©ÆƸSQ·´$ÆÆtJŽk(›ÆÆ\}ÆÿÆþjoÆýÂÂÆü/©ÆÆïÃYm´$ÆÆ\\FtÆÆ\}ÆÿÆè¯(TnÆk8naqÆ/©ÆÆicr)Ÿ´$Æþ•7Æþ\}ÆÿÆ÷Œ4ưÆÆ²
Æé•…ºÆÆ»‚‘ÀÆ¡¿Æ™Æµˆ„¶Æ´ ­Æùº¢ÆÆµP}ÆÿÆ÷¸¢ÆÁ™ÆÆ˜ÁÆý¼VVÆýyVÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>ÆÿÆ?Àÿ?	ÿ*¯ÿ>¯ÿ>¯ÿ>¯ÿ"¯ü‚§¯y¯ýªss¯¬¯ý†s™¯ÿ"¯ü*–¯¯ýŸs¯ø¬!1¯¯†5n¯ÿ¯ê‘§¯¯§†®¯¯*–¯¯¯¡{ƒªŸ ¯ýWM1¯þQn¯ÿ¯è)EYl¯b6V,–¯*–¯¯‰%`R‹Ÿ ¯ü=„1¯þQn¯ÿ¯þa`¯æ­"¬¯yV¯*–¯¯aS¬¯¯Ÿ ¯¯­/Ÿ„1¯þQn¯ÿ¯þ3‰¯þ6æP¯*–¯¯£IG¢Ÿ ¯¯gA~_#‰¯¯Qn¯ÿ¯þ^b¯ý¬¬¯ü*–¯¯ï¬NaŸ ¯¯~RR>f¯¯Qn¯ÿ¯è›$Jar¯_1aVd¯*–¯¯]We$Ÿ ¯þ„1¯þQn¯ÿ¯÷|.¯œ¯¯
¯é„u¤¯¯¥s€ª¯Ž©¯‡¯ xu¡¯Ÿ™¯ù¥¯¯ Gn¯ÿ¯÷£¯ª‡¯¯‡«¯ý¦LL¯ýkL‹¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ¯?Àÿ?	ÿÿ
éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯'ÿ&ÿÿ>ÿÿ>ÿÿÿ'	ÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆ'ÿ&ÿÿ>ÿÿ>ÿÿÿ'	ÿ¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé'ÿ&ÿÿ>ÿÿ>ÿÿÿ'	ÿ'ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿÿ
¯ÿ¯ÿÌé¯ÿ¯ÿÌ é¯ÿ¯ÿÌ!é¯ÿ¯ÿÌ"é¯ÿ¯ÿÌ#é¯ÿ¯ÿÌ$é¯ÿ¯ÿÌ%é¯7~¯ÿ­>¯ÿY=¯þ£=¯þ<<¯ý®aI>¯ÿ”>¯ÿ”>¯ÿ”>¯ÿ”<¯ý®aI=¯þ<=¯þ£>¯ÿY>¯ÿ­?ÿÿ=ÿ¯=ÿÿ¯lÿÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æ7~ÆÿÄ>Æÿe=Æþ¹=ÆþD<ÆýÅmS>Æÿ§>Æÿ§>Æÿ§>Æÿ§<ÆýÅmS=ÆþD=Æþ¹>Æÿe>ÆÿÄ?ÿÿ=ÿÆ=ÿÿÆlÿéÿéÿ̯éÿéÿÌ ¯éÿéÿÌ!¯éÿéÿÌ"¯éÿéÿÌ#¯éÿéÿÌ$¯éÿéÿÌ%¯é7~éÿæ>éÿw=éþÙ"=éþO<éýèb>éÿÄ>éÿÄ>éÿÄ>éÿÄ<éýèb=éþO=éþÙ">éÿw>éÿæ?ÿÿ=ÿé=ÿÿélÿÿÿ=þ=ÿléÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿüééôÿÿ¯ÿÿýéôÿÿ¯ÿÿ ¯ÿÿÿ.¯ÿÿþh-¯ÿÿý—,¯ÿÿÿ0,¯ÿÿþ
|+¯ÿÿüW{+¯ÿÿþ-¯ÿÿþ-¯ÿÿþ-¯ÿÿþ-¯ÿÿüW{+¯ÿÿþ
|+¯ÿÿÿ0,¯ÿÿý—,¯ÿÿþh-¯ÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿüÆÆÚîÿÆÿÿýÆÚîÿÆÿÿ Æÿÿÿ.Æÿÿþv-Æÿÿý«,Æÿÿÿ6,ÆÿÿþŒ+Æÿÿüc‹+Æÿÿþ-Æÿÿþ-Æÿÿþ-Æÿÿþ-Æÿÿüc‹+ÆÿÿþŒ+Æÿÿÿ6,Æÿÿý«,Æÿÿþv-Æÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿü¯¯¬ªÿéÿÿý¯¬ªÿéÿÿ éÿÿÿ.éÿÿþ‹-éÿÿýÉ,éÿÿÿ@,éÿÿþ
¤+éÿÿüt¤+éÿÿþ$-éÿÿþ$-éÿÿþ$-éÿÿþ$-éÿÿüt¤+éÿÿþ
¤+éÿÿÿ@,éÿÿýÉ,éÿÿþ‹-éÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿþÿÿÿÿÿÿþÿÿÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ0ÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿþÿÿÿÿÿÿþÿÿÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ0ÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿþÿÿÿÿÿÿþÿÿÿÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿ0ÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿ
ÿ
ÿÿÿÿÿ
ÿÿÿÙÿ¯ÿ?/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿ
ÿ
ÿÿÿÿÿ
ÿÿÿÙÿÆÿ?/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿ
ÿ
ÿÿÿÿÿ
ÿÿÿÙÿéÿ?/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿÿÿÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿ	ÿÿÿÿÿÿÿÿ
ÿÿ+ÿÿÿ6ÿ7ÿ:ÿÛÿ¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ'¯ÿÿ'ÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿ	ÿÿÿÿÿÿÿÿ
ÿÿ+ÿÿÿ6ÿ7ÿ:ÿÛÿÆÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ'Æÿÿ'ÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿÿÿÿ	ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ	ÿ	ÿÿÿÿÿÿÿÿ
ÿÿ+ÿÿÿ6ÿ7ÿ:ÿÛÿéÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ'éÿÿ'ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿÿ6ÿÿÿ7ÿ7ÿ8ÿúÿ¯ÿ?5éÿÌÿ4é
ÿ3éÿ2éÿ1é
ÿ0éÿ/éÿ.éÿ-éÿ,éÿ+éÿÿ6ÿÿÿ7ÿ7ÿ8ÿúÿÆÿ?6Æî4Æ
î3Æî2Æî1Æ
î0Æî/Æî.Æî-Æî,Æî+Æîÿ6ÿÿÿ7ÿ7ÿ8ÿúÿéÿ?5¯ÿ̪4¯
ª3¯ª2¯ª1¯
ª0¯ª/¯ª.¯ª-¯ª,¯ª+¯ªÿÿÿ¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯/ÿ¯/ÿ¯/ÿ¯0ÿÿÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéþ´léýç:éÿÿÿÿéþªUéýæéÿÿÿÿéçªOe<–ééÓxCä鯂f¯þœ-
¯úc]¯¯–œ¯ú–)¯¬$ª¯ö‡AA‰¯žNY&¦¯ÑVk¯¯˜O6v®¯Im-S¡¤6‰o~¯ [1jª¯]o3>”¯œ:+M–¯–)¯¯Rn¯ÿ¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ¯ÿ®'¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ/¯ÿ¯ÿÿéÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿéþÚ¯
éÿâéÿÿÿéþ¾@
éÿÒéÿÿÿéû¾@ϯçéýÜ´èéýجËÿÆÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/ÆÿÆÿÅ'Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/ÆÿÆÿÿÆÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆþº•
ÆÿÀÆÿÿÿÆþ¢6
Æÿ³ÆÿÿÿÆû¢6°”ÄÆý»™ÅÆý·’¬ÿéÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿ/éÿéÿç'éÿ/éÿ/éÿ/éÿ/éÿ/éÿéÿÿ¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ¯þ¤„
¯ÿª¯ÿÿÿ¯þ0
¯ÿž¯ÿÿÿ¯û0œƒ®¯ý¥‡®¯ý¢˜ÿ¯ÿ?éÿ¨éýÀ™»éùÎâééà™ž*éÿ,éóo^»éß9+¥éà™*éë,éºèééÇÚoéé„Sè¸èéé*éÿÆÿ?ÆÿÆý£‚ŸÆù¯ÀÆÆ¾‚†*Æÿ%Æó_PŸÆ½0$
ƾ‚*Æë%ÆžÅÆÆ©º_yÆÆpGÅœÅÆÆ*Æÿéÿ?¯ÿ~¯ýs¯ùšª¯¯¨sw*¯ÿ!¯óTG¯§+ |¯¨s*¯ë!¯Œ®¯¯–¤Tk¯¯c>®Š®¯¯*¯ÿ¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿé¯ÿéÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿÆÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/Æÿ/ÆÿÆÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿéÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯éÿ¯ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ7éÿ6éÿ5é	ÿ4é
ÿ3éÿ2éÿ1é
ÿ0éÿ/éÿ.éÿ-éÿ,éÿ+éÿ*éÿ)éÿ(éÿ'éÿ&éÿ%éÿ$éÿ#éÿ"éÿ!éÿ éÿ?*éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ"éü­Þé¡éýâ™™éþÞÑéý²™Ìéÿéþ­Þéü8ÇééòÔ™éé%Oæé²F“éÿéý8Çéÿ7Æî6Æî5Æ	î4Æ
î3Æî2Æî1Æ
î0Æî/Æî.Æî-Æî,Æî+Æî*Æî)Æî(Æî'Æî&Æî%Æî$Æî#Æî"Æî!Æî Æîÿ?*Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ"Æü“½Æ‰ÆýÀ‚‚Æþ½²Æý—‚­ÆÿÆþ“½Æü/©ÆÆò´‚ÆÆyCÄÆ—<}ÆÿÆý/©Æÿ7¯ª6¯ª5¯	ª4¯
ª3¯ª2¯ª1¯
ª0¯ª/¯ª.¯ª-¯ª,¯ª+¯ª*¯ª)¯ª(¯ª'¯ª&¯ª%¯ª$¯ª#¯ª"¯ª!¯ª ¯ªÿ?*¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ"¯ü‚§¯y¯ýªss¯þ§¯ý†s™¯ÿ¯þ‚§¯ü*–¯¯òŸs¯¯k;­¯†5n¯ÿ¯ý*–¯8ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯7ÿ¯ÿ'ÿéÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯ÿ¡éýâ™™éþçäéý²™Ìéÿ
ÿÿÿÿ¯ÿéøÔ™éé?2éý²F“éÿ
ÿÿÿÿ¯ÿ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆ'îÿÆÿ'ÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ‰ÆýÀ‚‚ÆþÄÁÆý—‚­Æÿ
ÿÿÿÿÆÿÆø´‚ÆÆ6+Æý—<}Æÿ
ÿÿÿÿÆÿ'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿé'ªÿéÿ'ÿ¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿéÿy¯ýªss¯þ®«¯ý†s™¯ÿ
ÿÿÿÿéÿ¯øŸs¯¯/&¯ý†5n¯ÿ
ÿÿÿÿéÿ/¯é.¯é-¯é,¯é+¯é*¯é)¯é(¯é'¯é&¯é%¯é$¯é#¯é"¯é!¯é ¯é¯é¯ é¯!é¯"é¯#é¯$é¯%é¯&éÿ??¯ÿ­¯ÿ%¯ÿY¯ÿ#¯û£¯k•	¯ú®¬¯¯®¬¯ÿ#¯û<¯<ƒ	¯ú“-¯¯†:¯ÿ"¯Ù®aI¯a¯ø/¡Y¯(œ¯”ÿÆÿ??ÆÿÄÆî%ÆÿeÆî#Æû¹Æy¨	ÆúÅÂÆÆÅÂÆî#ÆûDÆD”	Æú¦2ÆÆ—AÆî"ÆÙÅmSÆD…f€ÄÆÆ·n…ÃÆiiZ"i—ƶîîÄÆ±‡†]žµh€ÂÆøsÅÆt«Æ‘ªÆÛ§ÆD(†]=Ʋƒf.Ɖ(›­{2›°¬ †b4Æ™
f‚I‡FnÆø6£¶eÆ.±Ƨÿ/é¯.é¯-é¯,é¯+é¯*é¯)é¯(é¯'é¯&é¯%é¯$é¯#é¯"é¯!é¯ é¯é¯é ¯é!¯é"¯é#¯é$¯é%¯é&¯ÿ??éÿæéª%éÿwéª#éûÙ"éÆ	éúèäééèäéª#éûOéP¯	éúÄ;éé²Méª"éÙèbéPœx—æé騂œåé{{©j({±éÖªªæéПžn¹Õz—äéø‡èéˆÉé«ÈéÛÄéP/žnHéÑ#šy6é¢/¶Ì;¶ÏÊ%s=é´x™VŸR‚éø?ÀÖwé6ÐéÄ—þ=ý<ý<ý
<þf>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿéÿíÿÿéÿíÿÿéÿí ÿÿéÿí!ÿÿéÿí"ÿÿéÿí#ÿÿéÿí$ÿÿéÿí%ÿÿéÿí&ÿÿéÿí'ÿÿéÿí(ÿÿéÿí)ÿÿéÿí*ÿÿéÿí+ÿÿéÿí,ÿÿéÿí-ÿÿéÿí.ÿÿ
éÿí/ÿÿéÿí0ÿÿéÿí1ÿÿ
éÿí2ÿÿ	éÿí3ÿÿéÿí4ÿÿéÿí5ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ0ÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿþh-¯ÿÿý—,¯ÿÿÿ0,¯ÿÿþ
|+¯ÿÿüW{+¯ÿÿþ-¯ÿÿþ-¯ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿÆÿÍîÿÿ
ÆÿÍ îÿÿÆÿÍ!îÿÿÆÿÍ"îÿÿ
ÆÿÍ#îÿÿ	ÆÿÍ$îÿÿÆÿÍ%îÿÿÆÿÍ&îÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ0ÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿþv-Æÿÿý«,Æÿÿÿ6,ÆÿÿþŒ+Æÿÿüc‹+Æÿÿþ-Æÿÿþ-Æÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ¯ÿ®ªÿÿ
¯ÿ® ªÿÿ¯ÿ®!ªÿÿ¯ÿ®"ªÿÿ
¯ÿ®#ªÿÿ	¯ÿ®$ªÿÿ¯ÿ®%ªÿÿ¯ÿ®&ªÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ0ÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿþ‹-éÿÿýÉ,éÿÿÿ@,éÿÿþ
¤+éÿÿüt¤+éÿÿþ$-éÿÿþ$-éÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿÿÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿ&éÿÿÿÿôÿÿ%éÿÿ
ÿÿôÿÿ$éÿÿ	ÿÿôÿÿ#é	ÿÿÿÿôÿÿ"é
ÿÿÿÿôÿÿ!éÿÿÿÿôÿÿ éÿÿÿÿôÿÿé
ÿÿÿÿô	ÿÿéÿÿÿÿô
ÿÿéÿÿÿÿôÿÿéÿüÿÿôÿÿéÿýÿô
ÿÿéÿþôÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&Æÿÿÿÿ&ÆÿÿÿÿÚÿÿ%Æÿîÿÿ
ÿþÚîÿÿ$Æîÿÿ	ÿýÚîîÿÿ#ÆîÿÿÿÿÚîÿÿ"ÆîÿÿÿÿÚîÿÿ!ÆîÿÿÿÿÚîÿÿ ÆîÿÿÿÿÚîÿÿÆîÿÿÿÿÚîÿÿÆîÿÿÿÿÚîÿÿÆîÿÿÿÿÚîÿÿÆ	îÿüÿÿÚ	îÿÿÆ
îÿýÿÚ
îÿÿÆîÿþÚîÿÿÆîÿÿîÿÿÆ
îÿÿîÿÿÆîÿÿîÿÿÆîÿÿîÿÿÆîÿÿîÿÿÆîÿÿîÿÿÆîÿÿîÿÿÆîÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ&¯ÿÿÿÿ¬ÿÿ%¯ÿªÿÿ
ÿþ¬ªÿÿ$¯ªÿÿ	ÿý¬ªªÿÿ#¯ªÿÿÿÿ¬ªÿÿ"¯ªÿÿÿÿ¬ªÿÿ!¯ªÿÿÿÿ¬ªÿÿ ¯ªÿÿÿÿ¬ªÿÿ¯ªÿÿÿÿ¬ªÿÿ¯ªÿÿÿÿ¬ªÿÿ¯ªÿÿÿÿ¬ªÿÿ¯	ªÿüÿÿ¬	ªÿÿ¯
ªÿýÿ¬
ªÿÿ¯ªÿþ¬ªÿÿ¯ªÿÿªÿÿ¯
ªÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿ*éÿ)éÿ(éÿ'éÿ&éÿ%éÿ$éÿ#éÿ"éÿ!éÿ éÿéÿé ÿé!ÿé"ÿé#ÿé$ÿé%ÿé&ÿé'ÿé(ÿé)ÿé*ÿé+ÿé,ÿé-ÿé.ÿé/ÿé0ÿ
é1ÿé2ÿé3ÿ
é4ÿ	é5ÿé6ÿé7ÿé8ÿé9ÿé:ÿé;ÿé<ÿé=ÿÿéÿ*Æî)Æî(Æî'Æî&Æî%Æî$Æî#Æî"Æî!Æî ÆîÆîÆ îÆ!îÆ"îÆ#îÆ$îÆ%îÆ&îÆ'îÆ(îÆ)îÆ*îÆ+îÆ,îÆ-îÆ.îÆ/îÆ0î
Æ1îÆ2îÆ3î
Æ4î	Æ5îÆ6îÆ7îÆ8îÆ9îÆ:îÆ;îÆ<îÆ=îÿÆî*¯ª)¯ª(¯ª'¯ª&¯ª%¯ª$¯ª#¯ª"¯ª!¯ª ¯ª¯ª¯ ª¯!ª¯"ª¯#ª¯$ª¯%ª¯&ª¯'ª¯(ª¯)ª¯*ª¯+ª¯,ª¯-ª¯.ª¯/ª¯0ª
¯1ª¯2ª¯3ª
¯4ª	¯5ª¯6ª¯7ª¯8ª¯9ª¯:ª¯;ª¯<ª¯=ªÿ¯ªÿÿÿÿÿéçª
¶Õ$Úé>©ãf—éÄæÝE¾éivÞeéÿÿÿÿéçªEééZ¤Õ{{cHéÉ}M,zé)ÙéÆéÿÿÿÿéøªUééažÏ$¶òÁé6®Þ|ké&ÛéÉéÿÿÿÿéçªUééažé;¥çãÆåÚÑ-kéf{âjéÿÿÿÿéç¹zé郱éÎr4:žé—5A‡‹éØl0ŠMéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯þ™
¯þŸŠ¯ÿÿÿÿ¯þnQ
¯þ~B¯ÿÿÿÿ¯ünQ”Нþޝú˜‡|B«¯ÿÿÿÿÿ¯èn 8B/®¯J5F)¨¯T.I&B®Ÿ¯œÿÿÿÿ¯èn"¯¯:‡¤¯¯Gx¬®¯NB¯_\¯Z]ÿÿÿÿ¯ènE¯¯]^}?¯¯nN1¯¯rB¯¢­ ÿÿÿÿ¯èn$¯¯<„¢ ¯¯Iu«¯¯PB¯¯>N<¯ÿÿÿÿ¯ènçécF]7àép=b3Xè&ÔéÐ#ªÿÿÿéè“-ééM´Ú)éé_ å!èéhXézéx|ªÿÿÿéè“\éé|}¦Téé’g¿Aéé˜XéØ%æ#Õªÿÿÿéè“0ééO°Ø+éébœä!éékXééShOéªÿÿÿéè“%P_7æé[Ke0ÞéiCi/Xéé³®éªÿÿÿéüÔÊ»«éþȰéöÀ©ÖÅééÍ%ééªÿÿÿéû£: ééªÿÿÿéþ²¼éªÿÿÿéªÿÿÿéªÿÿÿéªÿÿÿéªÿÿÿéªÿÿÿéªÿÿÿéªÿÿÿéªÿÿÿéªÿÿÿéªÿÿÿéªÿÿÿéªÿÿªÿÿÿ¯ªÿÿÿ¯ªÿÿÿ¯ªÿÿÿ¯ªÿÿÿ¯ªÿÿÿ¯ªÿÿÿ¯ªÿ
ªÿÿ¯ªÿ
ªÿÿ¯ý¬ƒ‰¯ªÿ
ªÿÿ¯ý1V‚¯þwk¯ªÿ
ªÿÿ¯øŽ‰¡¯«…¬¯ò­†¨¯­PE‰¢¯¯‹¡¯ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/õÏ0ÏÐ/áÉšäçéÑE´éé0«Ö<®é+WÜ…Ué„{ééréT‰ÝXˆéPDÕ›OéÙÑé¦QéÈ7éé“iéø‚E{{5žÉéÑ#ÐéÏ/éé–eé+´éäç„{éérååé»@éPžéåéç™^HéÈ7éé¯Jéþze¶ýÔÉ6éÑ:»éÌ1ééšbé+·éäå„{ééräæé¿<éP¯éééžWÎàèÈ7ééš`éøÎ/ÍéÏÙÉ6éÑešéé/²Ù@«é+ZáRé¦YåéréQá]…éP¯ééézzæ„èÈ7ééy†éù­R3JÑÑcéѽMééÄ^=“æé+„2cÒéào>²¤éÑn7„àéw½ééJéÔP1~LèÐdéé5ÕéþIÍéþ+Ôéþ¯féþãäéþIØéþãåé?À¯ÿ«>¯ÿ®ÿ¯?
éþ¯åéþ¯Ûéû̾é¶ÞéþÓ?
éúp¼éédÊéû“ké˜{	éü×Î̬éþiéëÈÇééç½Ø«äéÙ=‰¶äÄáééÓ»éî¼á®×éé嶨âéé“kéä+àéõ°)°±(¿«‚ÂÄÆÑ:™ÆÆ)‘¶3”Æ$J»qHÆpiÆÆaxÆGt¼KtÆD9µ„CƸ²ÆEƪ/ÆÆ}YÆøn:ii-†«ÆÑ±Æ°(ÆÆVÆ$™ÆÁÄpiÆÆaxÃÃÆŸ6ÆD†ÆÃÆÄ‚P=ƪ/ÆÆ•?ÆþhU›ý´«.ÆÑ2ŸÆ­*ÆÆ‚SÆ$›ÆÂÃpiÆÆaxÂÄÆ¢3ÆD”ÆÆƆJ¯¾Ū/ÆÆƒRÆø¯(®Æ°¹«.ÆÑU‚ÆÆ(—¹6‘Æ$M¿xFÆKÃÆaxÆEz¿OqÆD”ÆÆÆhgÄpŪ/ÆÆfrÆù“F+?²²TÆÑ¡AÆÆ§P4}ÄÆ$p+T³Æ¾_5—{‹Æ²]/p¾Æe¡ÆÆ?Æ´D*k@űUÆÆ-µÆþ>®Æþ$´Æþ•WÆþÁÂÆþ>·ÆþÁÃÆ?ÀÆÿÁ>ÆÿÅÿÆ?
Æþ”ÃÆþ”ºÆû­¢Æ›½Æþ³6
Æú` ÆÆU¬Æû}[Æi	Æü¶¯­’ÆþY†Æëª©ÆÆÄ¡·‘Áƹ4t›Â¦¿ÆÆ³ŸÆî ¿”¶ÆÆÃ›ÀÆÆ}[ÆÂ$¾Æõœ$œœ#©—s¬®¯Ñ3‡¯¯$€¡-‚¯ A¥d@¯c]¯¯Vj¯?g¦Bf¯<3 u;¯£¯}=¯–)¯¯nO¯øa3]](w—¯Ñœ¯œ#¯¯qL¯ ‡¯«®c]¯¯Vj¬¬¯0¯¯}C¬¯Vj¯=l©Fd¯<ƒ¯¯¯\[­c®–)¯¯[e¯ù‚>&8J¯ÑŽ:¯¯”G.n­¯ c&Jž¯¨T/†l{¯R)c¨¯Yޝ¯8¯Ÿ<%_9®œK¯¯( ¯þ7š¯þ Ÿ¯þ„M¯þª¬¯þ7¢¯þª¬¯?Àéÿä>éÿèÿé?
¯þƒ¬¯þƒ¥¯û™¯‰§¯þŸ/
¯úT¯¯K˜¯ûnP¯r]	¯ü¡š™¯þOv¯–¯ï®Ž¢€«¯£.g‰¬“©¯¯Ÿ¯î©‚¡¯¯¬‰~ª¯¯nP¯¬ ¨¯éÿÿÿéï¾?f0Ñé‡8j0Äé”1k=éÿÿÿéï¾ãéyˆé%ßé‹té5Ðé”éÿÿÿéï¾1éé¨RÑ)éé½;æééÄéÿÿÿéï¾
äé{…é#àéŽpé2Òé—éÿÿÿéï¾Cn,Ïé:q,Âé2r:éÿÿÿéû߿ɡäéýÕ¦åéýРÙéÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿ-éÿÿÿéÿ¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿéþÚ¯
éÿâ¯ÿÿÿéþ¾@
éÿÒ¯ÿÿÿéû¾@ϯçéýÜ´èéýج˯ÿÿÿéï¾?f0Ñé‡8j0Äé”1k=¯ÿÿÿéï¾ãéyˆé%ßé‹té5Ð锯ÿÿÿéï¾1éé¨RÑ)éé½;æééįÿÿÿéï¾
äé{…é#àéŽpé2Òé—¯ÿÿÿéï¾Cn,Ïé:q,Âé2r:¯ÿÿÿéû߿ɡäéýÕ¦åéýРÙ¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿ¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿé¯ÿÿÿéÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿÆÿÿÿÆï¢6W(²Æs/Z(§Æ~*[4ÆÿÿÿÆï¢ÁÆftƽÆvcÆ-±Æ~ÆÿÿÿÆï¢*ÆÆF²#ÆÆ¡2ÄÆÆ¦ÆÿÿÿÆï¢	ÁÆiqƾÆx`Æ+³Æ€ÆÿÿÿÆï¢9]%¯Æm2`%¥Æy+a2ÆÿÿÿÆû½¢«‰ÂÆýµÃÆý±ˆ¹Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆÿÆÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆþº•
ÆÿÀÆÿÿÿÆþ¢6
Æÿ³ÆÿÿÿÆû¢6°”ÄÆý»™ÅÆý·’¬ÆÿÿÿÆï¢6W(²Æs/Z(§Æ~*[4ÆÿÿÿÆï¢ÁÆftƽÆvcÆ-±Æ~ÆÿÿÿÆï¢*ÆÆF²#ÆÆ¡2ÄÆÆ¦ÆÿÿÿÆï¢	ÁÆiqƾÆx`Æ+³Æ€ÆÿÿÿÆï¢9]%¯Æm2`%¥Æy+a2ÆÿÿÿÆû½¢«‰ÂÆýµÃÆý±ˆ¹Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÆÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-Æÿÿÿ-ÆÿÿÿÆÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿ¯ÿÿÿ¯ï/M$¯f*P$”¯o%P.¯ÿÿÿ¯ïª¯[f¯§¯hW¯(œ¯o¯ÿÿÿ¯ï%¯¯~>¯¯Ž-­¯¯“¯ÿÿÿ¯ï«¯]d¯¨¯jT¯&ž¯q¯ÿÿÿ¯ï2R!›¯a,U!‘¯k&V,¯ÿÿÿ¯û§—y¬¯ý }¬¯ýœx£¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ-¯ÿÿÿ¯ÿéÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯þ¤„
¯ÿªéÿÿÿ¯þ0
¯ÿžéÿÿÿ¯û0œƒ®¯ý¥‡®¯ý¢˜éÿÿÿ¯ï/M$¯f*P$”¯o%P.éÿÿÿ¯ïª¯[f¯§¯hW¯(œ¯oéÿÿÿ¯ï%¯¯~>¯¯Ž-­¯¯“éÿÿÿ¯ï«¯]d¯¨¯jT¯&ž¯qéÿÿÿ¯ï2R!›¯a,U!‘¯k&V,éÿÿÿ¯û§—y¬¯ý }¬¯ýœx£éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿéÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯éÿÿÿ¯ÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿë,éK®éæ×oééGééÍéé*éë,é«Oé£Qéoéé4°éé1¹éé*éë,éè&ÖF¯éoééB™ééÇéé*éë,éée(ééoéénoéÕãéé*éï,é騂ééoééÏd#}éÿ*éëºééäÙééo}ÙééÖš¸ééæÌ,éý¶Quéý¥ežéýÙen,éýÁ°æûé?@éÿ¨éýÀ™»éùäçééà™ž*éÿ,éóo^»é¬$–ééà™*éð,éºèééÇÚoééÚßZ–éÿ*éö,éK®éæ×oéþZ–éÿ*éö,é«Oé£QéoéþZ–éÿ*éö,éè&ÖF¯éoéþZ–éÿ*éö,éée(ééoéþZ–éÿ*éï,é騂ééoééÍ^%=aéÿ*éóºééäÙééo}Ùéݯû°éæÌ,éý¶Quéý¥ežéýÙen,éýÁ°æûé?ÆéþÎt=éþ±}éþÎt=éþ±}éþÎt=éþ±xé?#ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'ÿë%Æ@”ÆÄ¶_yÆÆ=zÆÆ®ÆÆ*Æë%Æ‘DÆŠEÆ_yÆÆ,–ÆÆ*žÆÆ*Æë%ÆÅ!¶<•Æ_yÆÆ8‚ÆÆ©ÆÆ*Æë%ÆÆlU"ÆÆ_yÆÆ]_ƵÁÆÆ*Æï%ÆÆ·nÆÆ_yÆÆ°UjÆÿ*ÆëžÆÆÁ¸ÆÆ_j¸ÆÆ¶ƒœÆÆÄ­,Æý›EcÆýV†Æý¹V],Æý¤–ÄûÆ?@ÆÿÆý£‚ŸÆùÂÄÆÆ¾‚†*Æÿ%Æó_PŸÆ’	ÆÆ¾‚*Æð%ÆžÅÆÆ©º_yÆÆº½LÆÿ*Æö%Æ@”ÆÄ¶_yÆþLÆÿ*Æö%Æ‘DÆŠEÆ_yÆþLÆÿ*Æö%ÆÅ!¶<•Æ_yÆþLÆÿ*Æö%ÆÆlU"ÆÆ_yÆþLÆÿ*Æï%ÆÆ·nÆÆ_yÆÆ®P4RÆÿ*ÆóžÆÆÁ¸ÆÆ_j¸Æ¼”û–ÆÄ­,Æý›EcÆýV†Æý¹V],Æý¤–ÄûÆ?ÆÆþ¯c=Æþ—}Æþ¯c=Æþ—}Æþ¯c=Æþ—xÆ?#ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'ÿë!¯8‚¯­¡Tk¯¯6l¯¯
š¯¯*¯ë!¯€<¯z=¯Tk¯¯'„¯¯%‹¯¯*¯ë!¯®¡5„¯Tk¯¯1s¯¯–¯¯*¯ë!¯¯_K¯¯Tk¯¯RT¯ ª¯¯*¯ï!¯¯¢a¯¯Tk¯¯œK^¯ÿ*¯ëŒ¯¯«£¯¯T^£¯¯¡tН¯­™,¯ý‰=X¯ý|Lw¯ý£LR,¯ý‘„­û¯?@¯ÿ~¯ýs¯ù¬®¯¯¨sw*¯ÿ!¯óTG¯q¯¯¨s*¯ð!¯Œ®¯¯–¤Tk¯¯¤§Cq¯ÿ*¯ö!¯8‚¯­¡Tk¯þCq¯ÿ*¯ö!¯€<¯z=¯Tk¯þCq¯ÿ*¯ö!¯®¡5„¯Tk¯þCq¯ÿ*¯ö!¯¯_K¯¯Tk¯þCq¯ÿ*¯ï!¯¯¢a¯¯Tk¯¯šG.I¯ÿ*¯óŒ¯¯«£¯¯T^£¯¦ƒû„¯­™,¯ý‰=X¯ý|Lw¯ý£LR,¯ý‘„­û¯?ƯþšW=¯þ…}¯þšW=¯þ…}¯þšW=¯þ…x¯?#ÿþü˜=ÿýƒÜ;ÿüî%F;ÿûW´9ÿúþk³;ÿþ×'=ÿþ×'ÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿ ÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿÿþ÷ÞÿþÞ÷ÿÿéÿÿúÿÿš&€ÿú€&šÿÿéÿÿúÈ;
?ú?
;ÈéÿÿúÈ;
?ú?
;Èéÿÿúÿÿš&€ÿú€&šÿÿéÿÿÿÿþ÷ÞÿþÞ÷ÿ ÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿÿéÿÿÿ
ÿ ÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿ ÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿÿþ÷ÞÿþÞ÷ÿÿÆÿÿúÿÿš&€ÿú€&šÿÿÆÿÿúÈ;
?ú?
;ÈÆÿÿúÈ;
?ú?
;ÈÆÿÿúÿÿš&€ÿú€&šÿÿÆÿÿÿÿþ÷ÞÿþÞ÷ÿ ÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿÿÆÿÿÿ
ÿ ÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿ ÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿÿþ÷ÞÿþÞ÷ÿÿ¯ÿÿúÿÿš&€ÿú€&šÿÿ¯ÿÿúÈ;
?ú?
;ȯÿÿúÈ;
?ú?
;ȯÿÿúÿÿš&€ÿú€&šÿÿ¯ÿÿÿÿþ÷ÞÿþÞ÷ÿ ÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿÿ¯ÿÿÿ
ÿ ÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿéÞÁ©ßééÞ²çéé8Çéééפ¯ãÔ+ééÜÏä8¢éél“éÿéôÁ©ßééÞ²çéé8ÇéÜÑ7\véƒGr:Çé8Çéé¶1€m¹Ô+éé¬+ééqhéél“éÿéòÑ7\véƒGr:Çé8Çéþ€éáæ.åé¡sé8Çéé‚oåééÔ+éé™Léé–Uéél“éÿéþ€é÷æ.åé¡sé8ÇéþD¶éá»HHGjé8ÇééÙb _ØÔ+éé¦4éé|céél“éÿéþD¶é÷»HHGjé8Çéþ}ƒéýä%äéü8ÇééïåhÔ+ééÏæéOéél“éÿéþ}ƒéýä%äéþ8ÇéèÏ0c‚˜é~B‚r…é8Çéé{t†0»Ô+é÷a0Y%Ýéél“éÿéòÏ0c‚˜é~B‚r…é8ÇéÞ¯œÚééÜš«âé½áé´éÕ ›×éÔ%Ìéé讟ÝééÕ^“éÿéô¯œÚééÜš«âé½áéýÝeeéýe¹éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿé?*éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ"éü­Þé¡éøâ™™ééæÉäéý²™Ìéÿéþ­Þéü8ÇééòÔ™é¿#B(qéé²F“éÿéþ8ÇéÞÁ©ßééÞ²çéé8Çéééפ¯ãÔ+ééÚßéäÎéél“éÿéôÁ©ßééÞ²çéé8ÇéèÑ7\véƒGr:Çé8Çéé¶1€m¹Ô+éùç"Äéél“éÿéòÑ7\véƒGr:Çé8Çéþ€éíæ.åé¡sé8Çéé‚oåééÔ+éþrVéþl“éÿéþ€é÷æ.åé¡sé8ÇéþD¶éí»HHGjé8ÇééÙb _ØÔ+éý†Aãéþl“éÿéþD¶é÷»HHGjé8Çéþ}ƒéýä%äéü8ÇééûåhÔ+éýŠ?âéþl“éÿéþ}ƒéýä%äéþ8ÇéÜÏ0c‚˜é~B‚r…é8Çéé{t†0»Ô+éé³U^^¹éél“éÿéòÏ0c‚˜é~B‚r…é8Çé篜ÚééÜš«âé½áé´éÕ ›×éÔ%Ì騝ûÕéÕ^“éÿéô¯œÚééÜš«âé½áéýÝeeéýe¹éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿé?*éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ"éü­Þé¡éýâ™™éùåäéé²™Ìéÿ"éü8ÇééýÔ™éøä,Aéé²F“éÿéêÁ©ßééÞ²çéé8Çéééפ¯ãÔ+éýtfAéþl“éÿéèÑ7\véƒGr:Çé8Çéé¶1€m¹Ô+éüÀQ¯Aéþl“éÿéþ€éææ.åé¡sé8Çéé‚oåééÔ+ééæ>Ó¯Aéþl“éÿéþD¶éá»HHGjé8ÇééÙb _ØÔ+éé‰W§~/¶éél“éÿéþ}ƒéýä%äéü8ÇééïåhÔ+éé¨mmRˆéél“éÿéèÏ0c‚˜é~B‚r…é8Çéé{t†0»Ô+éþ¯Aéþl“éÿé÷¤=éÏééÑéÆÞ¤½ÆÆ½—ÄÆÆ/©ÆÆÆ¶‹”Á´$ÆÆ»°Á/‰ÆÆ\}ÆÿÆô¤½ÆÆ½—ÄÆÆ/©ÆÜ²/Nd{Æo=a2©Æ/©ÆÆ›*m\´$ÆÆ’$ÆÆ`YÆÆ\}ÆÿÆò²/Nd{Æo=a2©Æ/©ÆmÆáÄ'ÃÆ‰bÆ/©ÆÆn^ÃÆÆ´$ÆÆ‚@ÆÆHÆÆ\}ÆÿÆmÆ÷Ä'ÃÆ‰bÆ/©Æþ9›ÆþŸ=æZÆ/©ÆƸSQ·´$ÆÆ,ÆÆjTÆÆ\}ÆÿÆþ9›ÆþŸ=üZÆ/©ÆþjoÆýÂÂÆü/©ÆÆïÃYm´$ÆÆ°ÄÆDxÆÆ\}ÆÿÆþjoÆýÂÂÆþ/©Æè¯(TnÆk8naqÆ/©ÆÆicr)Ÿ´$Æ÷R)K ¼ÆÆ\}ÆÿÆò¯(TnÆk8naqÆ/©ÆÞ•…ºÆÆ»‚‘ÀÆ¡¿Æ™Æµˆ„¶Æ´ ­ÆÆÅ”‡¼ÆÆµP}ÆÿÆô•…ºÆÆ»‚‘ÀÆ¡¿Æý¼VVÆýyVÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>ÆÿÆ?*Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ"Æü“½Æ‰ÆøÀ‚‚ÆÆÄ«ÁÆý—‚­ÆÿÆþ“½Æü/©ÆÆò´‚Æ¢8"`ÆÆ—<}ÆÿÆþ/©ÆÞ¤½ÆÆ½—ÄÆÆ/©ÆÆÆ¶‹”Á´$ÆÆº½ÆÂ¯ÆÆ\}ÆÿÆô¤½ÆÆ½—ÄÆÆ/©Æè²/Nd{Æo=a2©Æ/©ÆÆ›*m\´$ÆùÄ¦ÆÆ\}ÆÿÆò²/Nd{Æo=a2©Æ/©ÆmÆíÄ'ÃÆ‰bÆ/©ÆÆn^ÃÆÆ´$ÆþaIÆþ\}ÆÿÆmÆ÷Ä'ÃÆ‰bÆ/©Æþ9›ÆþŸ=òZÆ/©ÆƸSQ·´$Æýr7ÁÆþ\}ÆÿÆþ9›ÆþŸ=üZÆ/©ÆþjoÆýÂÂÆü/©ÆÆûÃYm´$Æýu6ÀÆþ\}ÆÿÆþjoÆýÂÂÆþ/©ÆÜ¯(TnÆk8naqÆ/©ÆÆicr)Ÿ´$ÆÆ˜HPPžÆÆ\}ÆÿÆò¯(TnÆk8naqÆ/©Æç•…ºÆÆ»‚‘ÀÆ¡¿Æ™Æµˆ„¶Æ´ ­Æ·”ûµÆµP}ÆÿÆô•…ºÆÆ»‚‘ÀÆ¡¿Æý¼VVÆýyVÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>ÆÿÆ?*Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ"Æü“½Æ‰ÆýÀ‚‚ÆùÃÂÆÆ—‚­Æÿ"Æü/©ÆÆý´‚ÆøÂ%7ÆÆ—<}ÆÿÆê¤½ÆÆ½—ÄÆÆ/©ÆÆÆ¶‹”Á´$ÆýcW7Æþ\}ÆÿÆè²/Nd{Æo=a2©Æ/©ÆÆ›*m\´$Æü£E•7Æþ\}ÆÿÆmÆæÄ'ÃÆ‰bÆ/©ÆÆn^ÃÆÆ´$ÆÆÄ5³•7Æþ\}ÆÿÆþ9›ÆþŸ=æZÆ/©ÆƸSQ·´$ÆÆtJŽk(›ÆÆ\}ÆÿÆþjoÆýÂÂÆü/©ÆÆïÃYm´$ÆÆ\\FtÆÆ\}ÆÿÆè¯(TnÆk8naqÆ/©ÆÆicr)Ÿ´$Æþ•7Æþ\}ÆÿÆ÷Œ4ưÆÆ²Æ¯Þ‘§¯¯§†®¯¯*–¯¯¯¡{ƒªŸ ¯¯¥œ«*y¯¯Qn¯ÿ¯ô‘§¯¯§†®¯¯*–¯Ü)EYl¯b6V,–¯*–¯¯‰%`R‹Ÿ ¯¯ ¯¯UN¯¯Qn¯ÿ¯ò)EYl¯b6V,–¯*–¯þa`¯á­"¬¯yV¯*–¯¯aS¬¯¯Ÿ ¯¯s9¯¯q@¯¯Qn¯ÿ¯þa`¯÷­"¬¯yV¯*–¯þ3‰¯þ6æP¯*–¯¯£IG¢Ÿ ¯¯}'¯¯]J¯¯Qn¯ÿ¯þ3‰¯þ6üP¯*–¯þ^b¯ý¬¬¯ü*–¯¯ï¬NaŸ ¯¯œ­¯¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ¯?*¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ"¯ü‚§¯y¯øªss¯¯­—«¯ý†s™¯ÿ¯þ‚§¯ü*–¯¯òŸs¯1U¯¯†5n¯ÿ¯þ*–¯Þ‘§¯¯§†®¯¯*–¯¯¯¡{ƒªŸ ¯¯¤§¯¬š¯¯Qn¯ÿ¯ô‘§¯¯§†®¯¯*–¯è)EYl¯b6V,–¯*–¯¯‰%`R‹Ÿ ¯ù®“¯¯Qn¯ÿ¯ò)EYl¯b6V,–¯*–¯þa`¯í­"¬¯yV¯*–¯¯aS¬¯¯Ÿ ¯þVA¯þQn¯ÿ¯þa`¯÷­"¬¯yV¯*–¯þ3‰¯þ6òP¯*–¯¯£IG¢Ÿ ¯ýe1ª¯þQn¯ÿ¯þ3‰¯þ6üP¯*–¯þ^b¯ý¬¬¯ü*–¯¯û¬NaŸ ¯ýh/ª¯þQn¯ÿ¯þ^b¯ý¬¬¯þ*–¯Ü›$Jar¯_1aVd¯*–¯¯]We$Ÿ ¯¯‡@GG‹¯¯Qn¯ÿ¯ò›$Jar¯_1aVd¯*–¯ç„u¤¯¯¥s€ª¯Ž©¯‡¯ xu¡¯Ÿ™¯¢ƒû ¯ Gn¯ÿ¯ô„u¤¯¯¥s€ª¯Ž©¯ý¦LL¯ýkL‹¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ¯?*¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ"¯ü‚§¯y¯ýªss¯¬¯ý†s™¯ÿ"¯ü*–¯¯ýŸs¯ø¬!1¯¯†5n¯ÿ¯ê‘§¯¯§†®¯¯*–¯¯¯¡{ƒªŸ ¯ýWM1¯þQn¯ÿ¯è)EYl¯b6V,–¯*–¯¯‰%`R‹Ÿ ¯ü=„1¯þQn¯ÿ¯þa`¯æ­"¬¯yV¯*–¯¯aS¬¯¯Ÿ ¯¯­/Ÿ„1¯þQn¯ÿ¯þ3‰¯þ6æP¯*–¯¯£IG¢Ÿ ¯¯gA~_#‰¯¯Qn¯ÿ¯þ^b¯ý¬¬¯ü*–¯¯ï¬NaŸ ¯¯~RR>f¯¯Qn¯ÿ¯è›$Jar¯_1aVd¯*–¯¯]We$Ÿ ¯þ„1¯þQn¯ÿ¯÷|.¯œ¯¯¯÷ééפ¯ãÔ+éýϾ2éþl“éÿ
ÿÿÿÿ¯÷é¶1€m¹Ô+éþ¾2éþl“éÿ
ÿÿÿÿ¯÷é‚oåééÔ+éþ¾2éþl“éÿ
ÿÿÿÿ¯÷éÙb _ØÔ+éþ¾2éþl“éÿ
ÿÿÿÿ¯ÿéûåhÔ+éþ¾2éþl“éÿ
ÿÿÿÿ¯÷é{t†0»Ô+é÷}M^°éél“éÿ
ÿÿÿÿ¯ó´éÕ ›×éÔ%Ìé鼯ûÑéÕ^“éÿ
ÿÿÿÿ¯éýÝeeéýe¹éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯ÿ¡éøâ™™ééäÈÞéý²™Ìéÿ
ÿÿÿÿ¯ÿéòÔ™éÝ$F,Qéé²F“éÿÿþ÷ÞÿþÞ÷ÿÿÿÿ¯óª˜¯¯ééפ¯ãÔ+éú1·éél“éúÿÿš&€ÿú€&šÿÿÿﯯjX¯¯é¶1€m¹Ô+éøÚ·#Óéél“éúÈ;
?ú?
;Èÿï‰(+WWé‚oåééÔ+éø:qæéél“éúÈ;
?ú?
;Èÿï‰(+WWéÙb _ØÔ+éùæ=Ÿéél“éúÿÿš&€ÿú€&šÿÿÿ÷¯¯jX¯¯éûåhÔ+éúY•éél“éÿÿþ÷ÞÿþÞ÷ÿÿÿÿ¯çª˜¯¯é{t†0»Ô+éé°UzO)äéél“éÿ
ÿÿÿÿ¯ñ´éÕ ›×éÔ%Ìéä°“ÀéýÕ^“éÿ
ÿÿÿÿ¯éýÝeeéýe¹éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯÷ÆÆ¶‹”Á´$Æý°¢+Æþ\}Æÿ
ÿÿÿÿÆ÷Æ›*m\´$Æþ¢+Æþ\}Æÿ
ÿÿÿÿÆ÷Æn^ÃÆÆ´$Æþ¢+Æþ\}Æÿ
ÿÿÿÿÆ÷ƸSQ·´$Æþ¢+Æþ\}Æÿ
ÿÿÿÿÆÿÆûÃYm´$Æþ¢+Æþ\}Æÿ
ÿÿÿÿÆ÷Æicr)Ÿ´$Æ÷jAP–ÆÆ\}Æÿ
ÿÿÿÿÆó™Æµˆ„¶Æ´ ­ÆÆ ”û²ÆµP}Æÿ
ÿÿÿÿ
Æý¼VVÆýyVÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ‰ÆøÀ‚‚ÆÆÂª½Æý—‚­Æÿ
ÿÿÿÿÆÿÆò´‚Ƽ<%EÆÆ—<}Æÿÿþ÷ÞÿþÞ÷ÿÿÿÿÆóÀ¬ÆÆÆÆ¶‹”Á´$Æú*›ÆÆ\}Æúÿÿš&€ÿú€&šÿÿÿïÆÆxcÆÆÆ›*m\´$Æøº›³ÆÆ\}ÆúÈ;
?ú?
;Èÿï›.1ccÆn^ÃÆÆ´$Æø1
`ÄÆÆ\}ÆúÈ;
?ú?
;Èÿï›.1ccƸSQ·´$ÆùÄ4‡ÆÆ\}Æúÿÿš&€ÿú€&šÿÿÿ÷ÆÆxcÆÆÆûÃYm´$ÆúKÆÆ\}Æÿÿþ÷ÞÿþÞ÷ÿÿÿÿÆçÀ¬ÆÆÆicr)Ÿ´$ÆÆ–HgC#ÁÆÆ\}Æÿ
ÿÿÿÿÆñ™Æµˆ„¶Æ´ ­ÆÂ–}£ÆýµP}Æÿ
ÿÿÿÿ
Æý¼VVÆýyVÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆ÷¯¯¡{ƒªŸ ¯ýœ&¯þQn¯ÿ
ÿÿÿÿé÷¯‰%`R‹Ÿ ¯þ&¯þQn¯ÿ
ÿÿÿÿé÷¯aS¬¯¯Ÿ ¯þ&¯þQn¯ÿ
ÿÿÿÿé÷¯£IG¢Ÿ ¯þ&¯þQn¯ÿ
ÿÿÿÿéÿ¯û¬NaŸ ¯þ&¯þQn¯ÿ
ÿÿÿÿé÷¯]We$Ÿ ¯÷^:G„¯¯Qn¯ÿ
ÿÿÿÿé󇯠xu¡¯Ÿ™¯¯ƒû¯ Gn¯ÿ
ÿÿÿÿé¯ý¦LL¯ýkL‹¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿéÿy¯øªss¯¯¬–§¯ý†s™¯ÿ
ÿÿÿÿéÿ¯òŸs¯¦5!=¯¯†5n¯ÿÿþ÷ÞÿþÞ÷ÿÿÿÿéóâËé鯯¡{ƒªŸ ¯ú%‰¯¯Qn¯úÿÿš&€ÿú€&šÿÿÿïéé#ué鯉%`R‹Ÿ ¯ø¤‰Ÿ¯¯Qn¯úÈ;
?ú?
;Èÿï·6	:tt¯aS¬¯¯Ÿ ¯ø+U­¯¯Qn¯úÈ;
?ú?
;Èÿï·6	:tt¯£IG¢Ÿ ¯ù­.w¯¯Qn¯úÿÿš&€ÿú€&šÿÿÿ÷éé#uéé¯û¬NaŸ ¯úCp¯¯Qn¯ÿÿþ÷ÞÿþÞ÷ÿÿÿÿéçâËéé¯]We$Ÿ ¯¯„@[;«¯¯Qn¯ÿ
ÿÿÿÿéñ‡¯ xu¡¯Ÿ™¯¬„n¯ý Gn¯ÿ
ÿÿÿÿé¯ý¦LL¯ýkL‹¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿéܯ<_¯¯§hY¯¯"œ“-¯¯†:¯¯a_¯¯¡‡!¯¯*s¯3¯ù^asW.§
¯Û”¯¯¨«¡#¯¯•/¯¯zE¯¦­‡8¯¯5‹¯˜(¯ù®$®Vw¯Ö®aI¯<:'!u¯¬I+&l¯¯C,b¯:,lªF+&p¯‡8¯¯5‹¯˜(¯¯Ê¯ú8=¯…£¯þ<¯þ§«¯þ§®¯þ§®¯ÿÿ¯þ£#¯ú’sÿss„¯ÿY¯ÿÿ¯ú’sÿss„¯ÿ­¯ÿÿ¯ÿÿ9¯ÿÿ¯ÿÿ9¯ÿÿ¯ÿ=¯ÿ;¯ÿþ¯ÿ0¯
ÿþÿ(¯ÿ	¯ÿþ¯ÿ0¯ÿ	¯ÿ/¯ÿ	¯.¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ5¯ÿ̯ÿ4¯þÌé¯ÿ3¯ýÌéé¯ÿ2¯ÿÌé¯ÿ1¯ÿÌé¯ÿ0¯ÿÌé¯ÿ/¯ÿÌé¯ÿ.¯ÿÌé¯ÿ-¯ÿÌé¯þ˜ª¯ÿ,¯ÿÌé¯ýXj¯ÿ+¯ÿÌ	éWù+(‰¯¯þc€¯ÿÌ
éWù+(‰¯¯ñ§¯ÿY=¯þ£ÜÆDlÆÆ½ueÆÆ'°¦2ÆÆ—AÆÆnlÆÆ!¶™%ÆÆ/‚ÆŸ:Æùjn‚c4½"
ÆÛ§ÆD…ÆÆ/¦X~ÆÆ@”¦2ÆÆ—AÆÆQ…ÆÆ9›™@ÆÆ<Ƭ-Æùž:N¢2„S
ÆÛ§ÆDNƹÆ‘GƾÁ¶(ÆÆ¨6ÆÆŠNƼÄ™@ÆÆ<Ƭ-ÆùÄ)Åb"‡ÆÖÅmSÆDA,%…ÆÂR0+zÆÆL2oÆA2zÀO1+Æ™@ÆÆ<Ƭ-ÆÆ½Æú@EÆ—¹ÆþDÆþ½ÁÆþ½ÄÆþ½ÅÆÿîÆþ¹#Æú¥‚î‚‚–ÆÿeÆÿîÆú¥‚î‚‚–ÆÿÄÆÿîÆÿî9ÆÿîÆÿî9ÆÿîÆî=Æî;ÆîþÆî0Æ
îþî(Æÿ	ÆîþÆî0Æÿ	Æî/Æÿ	Æ.Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ8Æþ¬ÀÆÿ8ÆýcxÆÿ6Æcù1.›ÆÆþpÆcù1.›ÆÆñ½De›¾ÆÁ—ÂÆÆ¯¶œ§Æø©¼Æ­¯Æ»¬ÆýcxÆÿÆð³/Di´*](ÆY)N:RÆø`xÆ6;ÆslÆþ¬ÀÆÿÆðXÆÆ>žÆ˜@ÆYJÆÆ!ºÆø•DÁ/-Ã>¡ÆÿÆðXÆÆÄÆÁÆYrÆÆFŽÆøÁ’]X–ÅÆÿÆð`{ÆÆ;¡Æ›=ÆYLÆÆ"·Æú6:’:B	ÆÿÆñ’(a³‰)c'ŒÆY$T@KÆúkÀ½w	ÆÿÆóº—½Æ½‹½ÆÆY”Æú¸ÆÆœº	Æÿ"ÆþYÆÿóÿ´Æÿ"Æþ¡®Æÿ¯PÿRÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æ7~ÆÿÄ>Æÿe=Æþ¹ÜéPéé!ÞŠwéé.ÏÄ;éé²Méé‚éé'Ö´,éé8šé»Eéù}‚št=Þ(
éÛÄéPœéé7Äg”ééL®Ä;éé²Méé_œééD¶´KééF¹éÊ5éùºE[¿:›b
éÛÄéP[éÙ"é«Séàä×/ééÆ?éé£[éÝæ´KééF¹éÊ5éùç0ès(ŸéÖèbéPM4,œéäa93ééZ:ƒéM:â]:3•é´KééF¹éÊ5éé‡éúKQé±ÙéþOéþÞäéþÞçéþÞèéÿªéþÙ"#éúÙª™™°éÿwéÿªéúÙª™™°éÿæéÿªéÿª9éÿªéÿª9éÿªéª=éª;éªþéª0é
ªþª(éÿ	éªþéª0éÿ	éª/éÿ	é.éÿ>éÿ>éÿ>éÿ>éÿ5éÿÌéÿ4éþ̯éÿ3éý̯¯éÿ2éÿ̯éÿ1éÿ̯éÿ0éÿ̯éÿ/éÿ̯éÿ.éÿ̯éÿ-éÿ̯éþËâéÿ,éÿ̯éýu#éÿ+éÿÌ	¯tù:	6·ééþ„ªéÿÌ
¯tù:	6·ééñßPw¶àéä±äééÎ׸Äéÿ̯ò–¦¯™š¯¥˜¯ééu#éÿéîÒ7P{Ô¦1n0¨éi0[E`é̯óUj¯04¯f_¯ééËâéÿéðg˜ééIºé³LéiWéé'¿¯÷„<ª*(¬7ޝéÿéðg˜ééæéäéi†ééH~¯÷«RN„®¯éÿéðq‘ééE½é¶HéiZéÌ¢¯ø03|3:¯¯éÿéñ¬0rÓ¢0u.¤éi+V8C¯ø_ª§i¯¯éÿéóÚ±Þéߤßééi‚‚‹¯ø£‹¯¯Š¥¯¯éÿ"éþ\q¯ÿ¬Ÿ¯éÿ!éýÌŽš¯ÿšGÿI¯éÿ éÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿ̯éÿéÿÌ ¯éÿéÿÌ!¯éÿéÿÌ"¯éÿéÿÌ#¯éÿéÿÌ$¯éÿéÿÌ%¯é7~éÿæ>éÿw=éþÙ"æÿ>ÿ>ÿ)ÿÿ)ÿÿ9ÿÿ9ÿþ=þ;þ;þ;ü;þ:ú'ÿ-¯ÿÿþ-¯ÿÿüW{+¯ÿÿþ
|+¯ÿÿÿ0,¯ÿÿý—,¯ÿÿþh-¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿÿ.¯ÿÿ ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿÿ¯ÿÌéÿ¯ÿÿü¯¯Ìéÿ¯ÿÿý¯Ìéÿ¯ÿÿþÌéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéÿ¯ÿÿÿéþô¯þª˜¯þ˜ª¯ÿÿÿéýôÿ¯ýjX¯úXj¯¯ÿÿéöôÿÿ¯‰(+Wú+(‰ÿÿéÿôÿù¯‰(+Wú+(‰ÿÿéÿôÿÿ¯ýjX¯úXj¯¯ÿÿéÿôÿÿ¯þª˜¯þ˜ª¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿô	ÿÿ¯ÿÿÿéÿô
ÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿô
ÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿ
éÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿ
éÿôÿÿ¯ÿÿÿ	éÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿÿéÿôÿÿ¯ÿÿüééôÿÿ¯ÿÿýéôÿÿ¯ÿÿ ¯ÿÿÿ.¯ÿÿþh-¯ÿÿý—,¯ÿÿÿ0,¯ÿÿÿ-Æÿÿþ-Æÿÿüc‹+ÆÿÿþŒ+Æÿÿÿ6,Æÿÿý«,Æÿÿþv-Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿÿ.Æÿÿ ÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆÿÆÿÿÿÆþÚÆþÀ¬Æþ¬ÀÆÿÿÿÆýÚîÆýxcÆúcxÆÆÿÿÆöÚîîÆ›.1cú1.›ÿÿÆÿÚîùÆ›.1cú1.›ÿÿÆÿÚîÿÆýxcÆúcxÆÆÿÿÆÿÚîÿÆþÀ¬Æþ¬ÀÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚ	îÿÆÿÿÿÆÿÚ
îÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚ
îÿÆÿÿÿÆÿÚîÿÆÿÿÿ
ÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿ
ÆÿÚîÿÆÿÿÿ	ÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿÿÆÿÚîÿÆÿÿüÆÆÚîÿÆÿÿýÆÚîÿÆÿÿ Æÿÿÿ.Æÿÿþv-Æÿÿý«,Æÿÿÿ6,Æÿÿÿ$-éÿÿþ$-éÿÿüt¤+éÿÿþ
¤+éÿÿÿ@,éÿÿýÉ,éÿÿþ‹-éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿÿ.éÿÿ éÿÿÿéÿ̯ÿéÿÿÿéÿ̯ÿéÿÿÿéÿ̯ÿéÿÿÿéÿ̯ÿéÿÿüéé̯ÿéÿÿýé̯ÿéÿÿþ̯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯ÿéÿÿÿ¯þ¬éþâËéþËâéÿÿÿ¯ý¬ªéý#uéúu#ééÿÿ¯ö¬ªªé·6	:tú:	6·ÿÿ¯ÿ¬ªùé·6	:tú:	6·ÿÿ¯ÿ¬ªÿéý#uéúu#ééÿÿ¯ÿ¬ªÿéþâËéþËâéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬	ªÿéÿÿÿ¯ÿ¬
ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬
ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ
¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ
¯ÿ¬ªÿéÿÿÿ	¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿÿ¯ÿ¬ªÿéÿÿü¯¯¬ªÿéÿÿý¯¬ªÿéÿÿ éÿÿÿ.éÿÿþ‹-éÿÿýÉ,éÿÿÿ@,éÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿ0ÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿÿÿÿ¯ÿÿ0>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ?ÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿ0ÿÿÿÿÆÿÿÿÿÿÆÿÿÿÿÿÆÿÿ0>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ?ÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿ0ÿÿÿÿéÿÿÿÿÿéÿÿÿÿÿéÿÿ0>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ?ÿÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>?ÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ/¯ÿÿ?/¯ÿÿ/¯ÿÿ/¯ÿÿ?Àÿ?/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ/Æÿÿ?/Æÿÿ/Æÿÿ/Æÿÿ?Àÿ?/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ/éÿÿ?/éÿÿ/éÿÿ/éÿÿ?Àÿ?ÿÀ?ÿÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿ
éÿÿÿÿé ÿÿÿÿé!ÿÿÿÿ
é"ÿÿÿÿ	é#ÿÿÿÿé$ÿÿÿÿéÿÿ'ÿÿÿÿ	é#ÿÿÿÿé$ÿÿÿÿéÿÿ'Àÿ?ÿÿîÿÿÆîÿÿîÿÿÆîÿÿîÿÿÆîÿÿîÿÿÆîÿÿîÿÿ
ÆîÿÿîÿÿÆîÿÿîÿÿÆîÿÿîÿÿ
Æîÿÿîÿÿ	ÆîÿÿîÿÿÆîÿÿîÿÿÆîÿ'ÿÿÿÿ	ÆîÿÿÿÿÆîÿÿÿÿÆîÿ'Àÿ?ÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿÿªÿÿ
¯ªÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿÿªÿÿ
¯ªÿÿªÿÿ	¯ªÿÿªÿÿ¯ªÿÿªÿÿ¯ªÿ'ÿÿÿÿ	¯ªÿÿÿÿ¯ªÿÿÿÿ¯ªÿ'Àÿ?ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ'ÿÀ?ÿÀÿ?Àÿ?Àÿ?Àî?Àî?9ÿî8ÿî7ÿî6ÿîÀÿ?Àª?Àª?9ÿª8ÿª7ÿª6ÿªÀÿ?ÿÀ?ÿÿÿÿÿéçŠ{À¥1n0©é¹0m2–ãI>{ÄÊ?lN{ÿÿÿÿéèééG¼é²Méb¢éÌ2é‰uéét›éè/ÿÿÿÿéèééæéãé)Ñééâ‰uéé5?HHDÿÿÿÿéëééD¿éµIé^¤éÏ0é“oéép”éÿÿÿÿéèéé¡0u.¥éµ.t3éË%oÂÈ7j„Lÿÿÿÿéÿ´éýÞ¤àéýä¤Ùéøà´Öé鳚Íÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿ>ÿÿ>ÿÿ/ÿðÿ?îÿ
îÿÿÆçu	i£*](Æ)\+Á>5i§¬6\Biîÿ
îÿÿÆèÆÆ= Æ—AÆS‰Æ­+ÆtcÆÆc„ÆÅ(îÿ
îÿÿÆèÆÆÄÆÁÆ#²ÆÆÀtcÆÆ-6==9îÿ
îÿÿÆëÆÆ9¢Æš>ÆPŒÆ¯(Æ}^ÆÆ`~Æîÿ
îÿÿÆèÆÆ‰)c'Æš'c+{Ƭ_¥ª/Zp@îÿ
îÿÿÆÿ™Æý½‹¾ÆýÁŒ¹Æø¾™¶ÆÆ˜‚®îÿ
îÿÿÆîÿ
îÿÿÆîÿ
îÿÿÆîÿ
îÿÿÆîÿ
îÿÿÆ
îÿîÿ
î!ÿîÿ
î!ÿîÿ
î!ÿ
î!ÿî!ÿî!ÿî!ÿîâÿ?ªÿ
ªÿÿ¯çh]|%R$¯‹$R&qª7/]”˜/Q:]ªÿ
ªÿÿ¯è¯¯6¯†:¯Iy¯™&¯gX¯¯Wu¯®#ªÿ
ªÿÿ¯è¯¯­¯ª¯¯¯ªgX¯¯(/663ªÿ
ªÿÿ¯ë¯¯3¯ˆ7¯G|¯›$¯nS¯¯To¯ªÿ
ªÿÿ¯è¯¯y$X"|¯ˆ"W&l¯˜T‘–)Pc9ªÿ
ªÿÿ¯ÿ‡¯ý§{¨¯ý«|£¯ø¨‡¡¯¯‡sšªÿ
ªÿÿ¯ªÿ
ªÿÿ¯ªÿ
ªÿÿ¯ªÿ
ªÿÿ¯ªÿ
ªÿÿ¯
ªÿªÿ
ª!ÿªÿ
ª!ÿªÿ
ª!ÿ
ª!ÿª!ÿª!ÿª!ÿªâÿ?ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿð?ÿûé”,J…éÑ$ÙééIRQGéà>l-»Ç*]{à=ÁéhD`5ÛéKk)®éškz2²é“kééO¯éýå”0éÝãééÀDééA¾àÊéšféO¯éé=ÁÞ%ééf™é±éŸaéöÉaé“kééo‹éýË”eéÐÜéé‡séés…àééÊ0éO¯éé=Á­Néé™`éâé¼Bé¨?bc3é“kéévƒéþ”jéÑßé¼FééDºàÍédéY©éé=ÁÜ&ééi•éãé¼Bé@¿é’2é“kééV§éþ”jéÑT°ééBYX?éà?s*¸é•9tß=ÁéaHg/Úéãé¼BérKs?2é“kéé.×éþÔÊéþÁOé÷¼»ééàÒžßé÷Ö¯ä¾ßééʯéï¶èéÞÀé賤ڻéÔÊé®béþ…åéþà éþ›Ïéþæ¤ìé?Àÿ?ûÆ~%?qÆÑ¸ÆÆ>FE=ƾ5\&Ÿ©$Oi¾4¤ÆY9R-ºÆ@[#”Æ‚[g+—Æ}[ÆÆC•ÆýÃ~)ÆÝÁÆÆ£9ÆÆ7¢¾¬ÆƒWÆC•ÆÆ4¤½ ÆÆW‚Æ—ƇRÆö«xRÆ}[ÆÆ^vÆý¬~VÆÐ»ÆÆsbÆÆbq¾
ÆÆ¬)ÆC•ÆÆ4¤“BÆÆ‚RÆÀÆ 8Æ6ST+Æ}[ÆÆdoÆþ~ZÆÑ½Æ <ÆÆ9ž¾®Æ†UÆKÆÆ4¤»!ÆÆYÆÁÆ 8Æ6¢Æ|+Æ}[ÆÆIŽÆþ~ZÆÑG–ÆÆ8KK6ƾ6b$œÆ0c½4¤ÆR=X(ºÆÁÆ 8Æa@b6+Æ}[ÆÆ'¶Æþ´¬Æþ¤DÆ÷ ŸÆÆ¾³†½Æ÷¶•¢½ÆÆ¬”Æï›Åƽ£ÆÅ˜ŒºŸÆ´¬Æ”SÆþqÃÆþ¾ Æþ„°ÆþÄ‹ìÆ?Àÿ?û¯o!8d¯Ñ£¯¯7>=6¯¨/Q"– F]¨.‘¯N3H(¥¯8P‚¯sP[&†¯nP¯¯;„¯ý¬o$¯Ýª¯¯3¯¯1¨˜¯tM¯;„¯¯.‘§¯¯Ms¯…¯wI¯ö—jI¯nP¯¯Sh¯ý˜oL¯Ð¥¯¯fV¯¯Vd¨¯¯˜$¯;„¯¯.‘‚:¯¯sH¯ª¯1¯~/IJ&¯nP¯¯Yb¯þoP¯Ñ§¯5¯¯3Œ¨š¯vK¯C¯¯.‘¥¯¯Op¯ª¯1¯0¯n&¯nP¯¯A~¯þoP¯Ñ?„¯¯1CB/¯¨/V Нp+W§.‘¯I6N#¤¯ª¯1¯V8V/&¯nP¯¯"¡¯þŸ˜¯þ‘<¯¯û¨žw§¯÷¡„¬§¯¯˜ƒ¯ï‰®¯§¯®‡|¤¯Ÿ˜¯‚I¯þd¬¯þ¨ ¯þuœ¯þ­{ì¯?Àÿ?À?ÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿéÿÿÿÿÿÀÿ?ÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÆÿÿÿÿÿÀÿ?¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿ¯ÿÿÿÿÿÀÿ?À?ÿ"ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜ÿ?Àÿ?"ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜ÿ?Àÿ?"ÿþ×'=ÿþ×';ÿúþk³:ÿûW´:ÿüî%F<ÿýƒÜ<ÿþü˜ÿ?Àÿ?À?ÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿ ÿÀÿ?ÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿ ÿÀÿ?ÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿ ÿÀÿ?À?ÿé鯜ÚééÜš«âé½áé´éÕ ›×éÔ%ÌéùÛ¿ééÕ^“éÿé÷Ù¾éã´éé³äéýÝeeéýe¹éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿ>éÿé?Àÿ?Æé•…ºÆÆ»‚‘ÀÆ¡¿Æ™Æµˆ„¶Æ´ ­Æùº¢ÆÆµP}ÆÿÆ÷¸¢ÆÁ™ÆÆ˜ÁÆý¼VVÆýyVÆÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>Æÿ>ÆÿÆ?Àÿ?¯é„u¤¯¯¥s€ª¯Ž©¯‡¯ xu¡¯Ÿ™¯ù¥¯¯ Gn¯ÿ¯÷£¯ª‡¯¯‡«¯ý¦LL¯ýkL‹¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ>¯ÿ¯?Àÿ?À?ÿéÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯éÿ
ÿÿÿÿ¯'ÿÀÿ?Æÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆÿ
ÿÿÿÿÆ'ÿÀÿ?¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé¯ÿ
ÿÿÿÿé'ÿÀÿ?À?ÿ=¯þ<<¯ý®aI>¯ÿ”>¯ÿ”>¯ÿ”>¯ÿ”<¯ý®aI=¯þ<=¯þ£>¯ÿY>¯ÿ­?Àÿ?=ÆþD<ÆýÅmS>Æÿ§>Æÿ§>Æÿ§>Æÿ§<ÆýÅmS=ÆþD=Æþ¹>Æÿe>ÆÿÄ?Àÿ?=éþO<éýèb>éÿÄ>éÿÄ>éÿÄ>éÿÄ<éýèb=éþO=éþÙ">éÿw>éÿæ?Àÿ?À?ÿý
|+¯ÿÿüW{+¯ÿÿþ-¯ÿÿþ-¯ÿÿþ-¯ÿÿþ-¯ÿÿüW{+¯ÿÿþ
|+¯ÿÿÿ0,¯ÿÿý—,¯ÿÿþh-¯ÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ@ýŒ+Æÿÿüc‹+Æÿÿþ-Æÿÿþ-Æÿÿþ-Æÿÿþ-Æÿÿüc‹+ÆÿÿþŒ+Æÿÿÿ6,Æÿÿý«,Æÿÿþv-Æÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ@ý
¤+éÿÿüt¤+éÿÿþ$-éÿÿþ$-éÿÿþ$-éÿÿþ$-éÿÿüt¤+éÿÿþ
¤+éÿÿÿ@,éÿÿýÉ,éÿÿþ‹-éÿÿ0ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿ@>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>ÿÿ>@ÿ ЇhC4!urwid-2.6.16/docs/manual/images/widget_layout.png000066400000000000000000000456671470350774000220310ustar00rootroot00000000000000‰PNG


IHDRXwyt5¢sBIT|dˆtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwXTgÚð{½÷Ž"ÅÒ[4–$šl6Å$¦›M¯›Þ{¢1½~›ÞËnš±ÅØ+¨(½÷Þ‡:íûcäÀ83€q„ûw]^áœ÷œ÷<3„áá­"‘ÉH@©hí8ˆˆˆˆÆ©ÌâÑ‚ˆˆˆh¼a‚EDDDdbL°ˆˆˆˆLŒ	‘‰1Á""""21&XDDDD&Æ‹ˆˆˆÈĘ`™,""""c‚EDDDdbL°ˆˆˆˆLLúwoÔh4hiiA[[;:::¡P( P(´•J¥Éd°³³…½½ìììL0ÑXwÚ	V[[
‹QS[+$TC±´´„·7`ffvÚAKD4JEÛj4dg碠°P¯L"‘ÀÖÆR©Ý==Ëåë033ì™3àáá~¦qIR™Ý𬌌()-Õ9'‰
?H$á|kk+REWW—^=b±ñq1pvv>ã@DDD4ÖHevÃäÞÜܬ—\Ú±V~~¾:ÉØÛÛcZx˜ÁºÔj523sþF¸DDDDç†a%XÕÕµÏ+
äå,suu1Z_Kk‹ÁÖ-"""¢ñ`X	Voo¯Ñ²êšƒç¥R©^ËÖ@­­CwK‹†•`98Ø-“IeÆËdÆË†;‘ˆˆˆè\3¬eüü|QVV–Öób±aa!ëÁ*•êoÝ×ç¹ç^†ƒe,_Ѝ¨È3ªÿlikkC]]=‚‚
–§¤AVV6®¿~µpî§Ÿ~FGG‡Þ9Wøx{áÊU×áè‘ýë[·îMtwwãÉ'9£¸;;;QVVà3ª‡ˆˆh"V–X,FbbÂÃBáêâggNžŒ…æÁÅE6`ww7jjj¡T*M°!¯¿þ

ŠFäYgjÏÞý¸éæÛŒ–÷ö*pÿB­Vçžxò9<úØÓ:çâY455ÃÁÁW^q™ÑúZZ[ÐØÔtÆqgdœÀ¥ÿ\uÆõMÃ^hT"‘ 0p2'땵··£±±	MMÍhjnFgg§Iƒ4ä©§¾þÏ>ÃUW]ŽeKÏ”—WàóÏ¿‚F£Á¢Eáëëhllı´Ìœ1[·nƒ¹¹9.ºh9,,,ÇŽ¥ÃÆÆ8pà""f">>¥¥eظq||½±â¢tâ8pàÒÒ2àáé/X*,¤ªT*±ióVÃÑÑsç·‡;’“£±±	¿þöÄb‘^}11³ÑÛÛ‹´´DFÎBEE%d2)BC§êœ+**Ƽ¹s`nn†ði¡:uìÛw™™YHHˆÓ{ßZZZ°aÃ&H¥R,]ºÉ)G+¬¶ßØØˆ¿þÚ‰¶¶v,\8“¡V«±gÏ~Èåøõ·?^°RéßÞ€ˆˆh\û[{j4Ô×7 =ý8¶l݆»ö ãø	TTVŽHr5˜””#˜5GSÓ–~³£æàС@vvV¯¾	W_}Š‹Kñå—ßbÞü% þwßû7¯¹¯®}å¸àÂK±víX³æÔÔÖâᇟijϽ$<ë™g_ÄêkoF]}=>üà?˜7	zzz«¯½¯½ö&äò?‘…¯¾ú½½½ÈÍÉC{»ÉÉ)HI9¢¿™™âc±{÷^ÀîÝ{‘””ˆ¤¤Dsáá¡pqqFyy%®»îáþ^x7Ý|êêêñøãÏ`ûö]BY{{;ââ`ãÆ-ÈÏ/ÄêÕ7cÕªëQ\¬]‚ãèÑcˆŽ™‹CɇQYU……ç-Ç–­Û´Kkde£««ÉÉ)HNN9ã.^""¢ñì´š ”J%ŠŠŠQ\R*$I$¸¹ºÂÅÅŽŽHN9bðº³éÉ'ŸÃ}÷Þ‰Gý7ÀËÓ=ü8öìÞhhhÄÛo¯GHH04
¢c’ðí·?ã›ñÝ·ŸÌÍÍñæ[à,--±`Á<ÜvÛ=xú©ÇPUUuëÞDFz²ÐÊ=Ÿ~ú%n»m
¶lÙ†Û7!"b¦N|×^w5jëêðòKÏ}
óæ%aÏžý¸ï¾»°{Ï>,˜?®n®xïÝ„sóæ%éÝ×ÐЈW^]ô´CBL³"ú[±>þøsNÀ÷ß	@ÛbÓ_Ïý<Œ'Ÿx7Üp- &&
>ú–¦Äm·ÞŒ#GŽ7i
;Á*++GVvŽÁ%,--2žž:K3ˆD"ÓDyIÅK/=+Ÿþyxñ¥µÂ±‡‡»0P[$aáÂù8––ëO–0Éáa¡°´´ŽëëiéðóóºLÅb1-Zˆcié€k®¹K–®ÄÅ_ˆå˗⢗
ºlÅ@óæ%áõ7ÞZ­ÆîÝûðÔ“ÂÑÑ×\s£pnàkì“••ww7˜æÎ#”§¥Gbb¼p1SxmJ¥ÉÉGàîæ†={ºººÆðd"""2lX	V~~²sr
–ÙÛÙ#!!vÐ%F’X,Ö®Ñht½e}åbqOéÀqEb±Xï¸ïþSŸsj]ï¼½kn¾ü±>ú¾ûöüðÃWÃz
11³ÑÓÓƒM›·B­VÃÇÇ8›6oEaaæ
HœŒ½ö¾˜ú˜››¡WÑŸ «Õj‰"‘W\y|¼½„s÷Ý{ç°b&""¢~CŽÁjnn6š\ÀŒáF“«Ñ§ƒ-[¶	Çü±1ѳ…ãÚÚ:?ž)Ä÷çŸ!úo,é1k&*+«››@Û´eËŸˆ‰Ž M^f̘†Ç{~ð6vîÒŽŸ²´´D]]àu÷ÃzþùW””(œOJJÀóÏ¿"Œ¿:Uxx›„˜T*vîÜ#”ÏMJĆ
›„VÈß7lÖ#“J¥HLˆCaAbc£…Ñѳ!‰`ii‰úúÆÓ~Ÿˆˆˆ&¢![°ú@bffGGGƒe½½½£²˜è‹/>ƒeË/FqI)D"6oÞŠMÊÝÜ\ñÀ rö,$'†““#.¿üÒÓ~Ž»»ž}æq,Yº—^z1RRŽÀÃÝ×^{z{{.X
klذ	×\s%`vä,tvvbáyËàíí¯¾üØ`ýóæ%áég^Àm·Þ,œKJJÄk¯½…Ûo¿Åà=ŽŽŽBLW^ùO¤Mƒ³³“P¾jÕåøßÿ~Ãô1
œG8883ßzë5¬¼ør=šŠÉ(..EgG'~ÿý'L:..Θ“´¾¾>øô“„îE"""Ò% Q*Œo[³cçnÈårƒe8ñyË*+«p4õ˜Ñz§…‡aòä€Ó
Ö˜ƒ“1uê89i“‰††FìÝws“…Öž}û⦛oÃá”=ؽg$	·@H0rsó`ii	??_ÚÖ®ÚÚ:̘1
ÐÓӃÇS1gNÿ8¦ÌÌl¤§k—i˜?/Iè",,,Bjjºººª3¶«««™™ÙèììÂܹý-TÕ×7 3+‘3…%ºººœrÁS‚àåå	@»è±cHLìÌžšš†¼¼|ÄÆFжdõ-lªÑhŸ_©T
''G¸ºù£¹©666B}ûöDuU5¼¼½0'1^H¤zzz••ƒÖ¶6$ÍIö˜2""¢‰D*³:ÁúkûÎA—^Xºd± ´wï4·4½oJPBC§žVÀgª/ÁÊÍIÑçŽ%ï½÷"#gA£Ñ`ýú· •J‡=6Œˆˆˆ†&•Ù
ÝEhan1h‚•‹™3§ÇÇgš\@Ee%ÐÐЈú†ÌIŒ?ëåíííuVŸ1ÖuwwcíÚ7 ±±1¸ûnã«ÊÑß3dVqq	ŽŸÈ´GG¸¸8C­V£¶®NèR´¶¶†X,F{{ûàAˆDXºdñ˜™‰HDDDôw
«kÒ$TVV£©Ùø~vÍ-Íz-V®..ˆŠŠDkk<4è3&MbrEDDDãÆ-X€v	‚¬¬”•—ë­³t*L	
öÿ´{¦g7xoÀ¤I˜6-lT%%"""2µa
r¨··55µhkkGoo/Ôj5¤R)¤R)¬­­àìì$Ìx;UOOÊË+ —w@¥RÁÆÆÞÞ^Âì5"""¢ñà´,""""œTf7ôJîDDDDtz˜`™,""""c‚EDDDdbL°ˆˆˆˆLŒ	‘‰1Á""""21&XDDDD&Æ‹ˆˆˆÈĘ`™,""""c‚EDDDdbÒyˆÌn$CDDD4$¥¢í¬?cD,èlÏ©Gde<"Ïa!‘‰1Á""""21&XDDDD&6bc°ú´Ë;ÐÚÒ…R	…B	¥B¡ý¯R…Rûµö¼îî.˜93l¤C$""":##ž`UUÕ¢  xX×j4š³
‘鱋ˆˆˆÈÄF¼kJÐ$Lò…R©Dcc32ŽgtDDDDgÕˆ·`‰Åb˜™É`ee	__/ØÚÚŒtDDDDgÕ¨wZX˜vDDDD&5ê	–X<ê!™Ô¨g7"ˆF;""""“õ‹ˆˆˆh¼a‚EDDDdbL°ˆˆˆˆLlÄ×Á:Síòtvt¢§§b±æ°³³…™™l´C#"""p%Xee•((,EWW—^™H$‚³³#‚'ÁÙÙq¢#"""ê7æ,…B‰ÔcÇÑÐÐdôFƒ††&444ÁËÓ3f„B"‘Œ`”DDDDýÆô,¥J…”Ãiƒ&W§ªª®ErJ”JÕYŒŒˆˆˆÈ¸1Ý‚U[[/|íëãoXXX ««ÅÅ娪®5x_ssŽÏFDÄ´‘
•ˆˆˆH0¦[°úbÆŒPØÛÛÁÜÜöˆˆ˜oO£÷TU×¢ººîŒžûÒËæÌê0…Ï>ÿ©ÇNÇ
…/¾ôŽÁsÍxýÿॗß5Zßü…—áDfîÇUQYƒºúÆ!¯{쉵økû>ƒe˜6]]ÝgOQQZ[Û…ãÖÖvL
›…B1¬û;::±go2Þzû¼ûþ:e*•
+/¹	Íg'c>Á²³³EP ¿Á² A·ÚÉ/(>£g¿øò;¨©©ú³ìhêq|ñåOÂqjê	¬[ÿ¡Þ¹·ÞùvˆˆGTÔ£õUVÖ §§÷Œãzmý‡øüó½&;»lü$,W«Õ(/¯‚F£9ãxn»ã1ìÞsèo×ýÃpÿÏaÃÆíxï”K"‘༅‰X»îƒ3Ž“ˆˆÆ¿1ÝE“ü} ÞNÇÜÜn®.¨©5ÜÊÔÞ.Gss+íÏ8ެì|€T"Áî=ÉðÅ¢óæå[¶îBB|:в²*,:/~B¹Z­ÆöûQXTŠðÐ`$%ÅèÜ7É)ÇPQQo¸BçÙs“bðÊÚ÷…ã½ûRpíêK±w_ŠÎ¹„øÙH$ðpwƒJÝ?­³³7m‡R©Âù‹ç꽶̬<ìßAAþ˜6-ÙÙù˜77N(?‘™‹C‡RáèhåËÂÒÒee•()©@kk;~ß°
®ˆ‰ž¥W÷ÇŸ|‡+.»H'nhh¦Í;aggƒøøÙz÷TTÖ`×®Ðh4X¼x.<Ü]hg’ÖÔÖÃÝÍ;v€‡‡+–.™‘H„̬<466ãð‘tÀ” xxhïëîîŦÍ;ÑÜÜŠåËÂÝÍEï™pã
WàÆ®ÀÖ?wãÞûŸÕ+¿òÊ•ˆ˜½O=y/¬­­ÖADDœ-X..Nƒ–;:
ž<ÕŸÆùÁ|õõϸýŽÇðØ¯¢²ªwÝó”NkÆMkÄu7܇­[wãø‰lÄ%®Dqq §§‹—^ÿûÏ7hkmÇO­Ã}~ß´æA\½ú.ü¾a**«õž”‹œœB¡;nÏÞd¬ºb%ººztÎÍMŠ|ûý¯øâmëVWW7œw~úïF•áºîCGgÿR[ÿÜ¥ËW£¼¢
¿þ¶W]sþýàBù+¯¾‡k¯¿õõMؼeç^‚öv9êêQ[[ŠÊ¤NC~¾áÖÂ
ÿ‚ùñÂqyyâVâPr*Ž¥ebÍ-é\¿qÓ,\tòò‹‘•]€9Iÿº3ÿÚ¾ÿºíQÜvÇ㨩©Ç˯¼‡Õ×Ý(-­D[»%H9œ†òŠ*¡Î[o‡¤c×îCˆ‹_öv¹áoòÜ\áë㥓Ø2¦[°ÌÍÍaii1è56C´$´´´š,…B‰Ÿ~ø"‘s£qÿ¿ŸÃCÞ&”/Y2·Þr
 ¹¹
?ÿ²ÜþïãoagkƒŸ~øpÇí×!$|î¸ý:M,]ºwÞ~Áçzz¸!(höíKÁŠ‹#-=³gOÇœÄháÜÁC©xò‰{ôîýêëŸack¿×&ƒÇÒ2‘˜t‰Pþ̳¯ãµµOàŠË/<üèËØ±c? °°o¿ûާmÖ»öúûðɧßãÞ{nFll¼<ÝuÞƒ›QQQààɹ7ÞüK–ÌÃ{ïh“¸ï¾ÿMŸ¥R©p÷=OáËÏß@bb4ÀÛÛ/½ü¾ýZ;¦¬¦¦»wü;;Üuçõ	_€ä”4,_¶o½ý	V]¹+.Z|ò{ ýÞßpýX¼(	˜t	þÚ¾—\¼Ä`ÌC	žŒŒŒl,]2ÿoÝODDØN°¬¬,‡¼Æbˆ¬»»ÇTá >n¶Ð]0ÉWo}€€þòääTÔÕ5`Í¿ÊE" ;§@H°¢}öܤXìÙ›ooO„†L™™æ$FçD""
ÌšLKÏļ¹±ÂqĬpØÚZÐ&4'2ó©óú¬#G3 •JðÈc¯å……%°°04Ö>ryÀÎÎF8w,=·ßººÿu
ˆ­¬¬Õ5uøüËÿâó/ÿ¨«k@iY¥pͬ™áB}66Öˆ™…´´LÄÆèwO|M}|Q]cxöépØÙÙ ©ÙtI;Oc:Á²0úùPŠ*JS…©¬ÿí‰Dzƒ§eR™Ñò˜èYXµj¥p¼ææUœÜ?x¨×:7)¯®ý¾>^˜3GÛº3'1o¿ó)|}¼„ñW§‹ÅP«uãì;‹ÅɤèííŸe×Û«;øÝÝÍkn^¥sÎÙix«åÛÚj¡¶6¹ÐÕ{j<šSb“H$¸ñúË!‘ö¿33³þë5ú¯e°‰ “
üß\ÿûv:ÚÚäð÷óþÛ÷ÑÄ0¦Ç`‰Å†·ë\cd|cäGRÒœXK;¨Ù3=1ѳ5NNï#)Ù9øù—͘s²û,0Ð--møù—ÍÂø«SÍŽœŽ;IErJ:::hߛĄ(üüËfáú_~Ý"|Ò²J¸º8	qÇDÏB@€/ÀÒÒõƒ,Óàää??oääç¢"§cûÉ2:_OšäOO7ÔÖ5èx7¯y›6ï„‹‹òó‹àíí7_×¾>_O\yõˆ‰À–-»°rÅùB×èòeñüoá¯íûpÉÅK°x‘þŒÉÁdeçãÞûŸAsS+jjêpþ²«1%(@/V[×€ªêZ!Á%""2F@£T´Õ‡HevèlÏ3Xväh†ÎŠíy{y`Ö¬ðAënk—cïÞd£åžü{+ºïÝ›‚ˆˆpØØX£¸¸
0y²vé…îî=š!Æ>xð(fÎÆ•””C¥R#pÀ^‡¤#?¿VV–ˆ€§‡›Á{ÉÌÊCGG§Îr¥¥(-«DbB”ÐExê³{zz…–¢¹I1ÈÌÊGXhÐ…×ÕÕܼ"Lò÷Á?nÀŽûñÃwýËB”•UâÈÑ(J„„bæŒþœúú&–ÀÖÎáaÁz1çåáŸWÜŠ´£[„„¹¥¥
;v€““=âb#‘r8
s£…ò¦¦J>†ææLò÷All¤R)>ýìlÚ¼ï¿÷öí;G½–»ŠŠj”–VÀË˾¾^8pð(’æÄ-™ÙÙ°³··—‡^¬mmr¤¥gꜳ±±¸·ßù5µ
xé…‡ôî%"¢sƒ•m0F"ïõ+åpšÑn77DGÍ´îúúF¤N3Z>mZÇÌ¢  ‡’S1mZJJÊñàC/àõõO㢙ì?¹æ'è¬öwô%Xÿýñ#E6|*•
ÿøç-øì“õ§ÕµKDDcËH%X£ÞE8Ø€ãÎë5#?9žÈ˜¡Öњ謬-‘r8
?þô\]ñö[ÏaÙÒ&}Ƌϛ¦ÅÇÃÃ
ááSMR×é’H$øí—OFåÙDDtîõëÔnuttB¡Pž2LWs“ñ)ó®®Î°ÆR™—§;Þ~ó¹ÑcX–/[€åËL›ü
£>‹P­V-Óh4FÇgÚn›ÁfO	8£ØˆˆˆˆþŽ1`@aQ©ÑkJJ+ T^ç*((g¾!Ñéõkà"—€¶[Ïýäæ¾€v5ðŒŒl½$«¡¡	yyEëôôpgëšQƒ5påp+++DÌš‰DŒcÇ2QS«Ýj¦²ªM-ðôp…D*Ekk›Ñ®Á  L°÷ÑHÕ«»»Gh™’J¤ˆŠš!hŒœ†¢â2äçC¥R¡»»Å%åFërvvDhHìííF$v""""cF9Áê†ùÉ}æ¦O­µP&‰8Ù¾>^¨¬ªAScºº»ÑÓÓFss3XX˜ÃÙÙ®®Î:÷¦Q_h”ˆˆˆh¤ŒÔB££>Ȉˆˆh¼a‚EDDDdbL°ˆˆˆˆLŒ	‘‰1Á""""21&XDDDD&Æ‹ˆˆˆÈĘ`™,""""õÍžMÅÊ6x´C ³Fb'…ñò3È÷Šht—_ÆM‚õÕ¿vDcŽ«çŠ{Ö¹þ3È÷ŠhtäÏàÙÆ.B""""c‚EDDDdbL°ˆˆˆˆLl\Á"¢ñA¥RC¡Pjÿ)UP)UP(•P)µç•*”
”*
‚§øÀÖÖj´Ã&"0Á"¢1§¸¤•þ> Àó,FCDtúØEHDDDdblÁ"¢1ÇÓÃv¶VP©ÕPô*QZV‹ŽŽîÑ‹ˆhؘ`јcmmkkãÃGrG1""¢ÓÃ.B"ó¬¬,†¾ˆˆha‚EDcžX,툈N,"óDb~Tѹ…ŸZD4æ‰À,":·LøAî
…
MMmhll…¼£½½J(
@$‚L*…••9ìì¬ááîËÑ—ˆˆˆÎ6ÁêììFII
**ë¡Vk^Óäò.ÔÕµ   ÎNvžê{;뎖ˆ¨_ww/êë[ÐÐØŠîî^ôö*¡V« “É`n.ƒ££-œíàè`;Ú¡MX.ÁÒh4(,ªBaa%4ò*''[ØÛÛ@$š›ÛÑÜ,ʛڜœ…™3‚àîî8‚Qo==
¨Tj˜™É “IF;¤1©µ­ùùhhh5XÞÓ£„\Þ…ÆÆ6TÂÖÖA>ü¼"*ÁR©ÔHMÍCcS›^™¹¹3gN“£î_|騮nŽÕj
Ò3
νψ΀R©BUUªkÑÒ"×ùƒÇÜBwWGøû{謇5Qi4äT ¸¸ZïC™LwwGXX˜C.ïBmm“pM{{Ž¥åÃ×Ç¡¡“8“hM˜K£Ñ -½À`r%³fMÑkNW©ÔhkëÔ»^­Ö ¸¤3¦žµx‰Æ³êšFää–¡§[a°¼§[²ò:TTÖcJ||ÜF8±C£Ñ =£55MzeööÖ˜33™p®©¹GŽäè}(¯¨‡B©Â¬™A#3M Y„yù¨¯o1Xæííbp¬B~A…Ñí9
7ÑÑàòòË‘ž^h4¹H­Ö 7¯#ÙØ”_Pa0¹’HĈŒ˜¢“\€“£-üüÜõ®¯©iBIIÍY‹“ˆtMˆ,¹¼%%ÕFË}}õ?Œ ²²Þè=½½Ê3Ž‹h¢ÉË/GQ‘áŸE‘
ò†¯d2)ZZåÈÉ)CkkJËjG8Ò±¡¡¡Õèûåãã
ss3ƒežžÎ“©‚Â
x{»rŒÑ˜-XÅ%ñ!Í
 IDATúãú˜[ÈŒÎ
l¼‚¥¥¹)Búõߣ¦Vÿ¯Ó‘öÕ7"=½@8V(”XûÚwÏ55µá÷þ‡uë¿7ZßÒDVvÉÇUUÕ€úÃ-=óÜgعë˜Á²ŽÎnDDß„®®ž3ާ¸¤mmÂq[["¢o‚B1¼„» °ï¼÷?Ü}ï[xêÙO‘™U,”©T*\qÕ3h2Ð=46O`Ú´Éœì
33D"l
{û‰;k77¯Ìh™»»“Ñ2[+ˆ||)•j”•³‹h$ŒûK¥Rl^ïcce|m+wg£e~FZ½N׺õß¡¶¶Ù$u‰´´||ýݶþãô¼õÎOzçÞÿðWØÛ[cæŒ DFL1Z_Uuz{†îÊ›ïü„¯¿ùsÐkrs˰ek2æÍi°\£V£¢¢Þh’}:î½ÿìÛ\8V«5'ë^å<ö*+
3™K–ÿÉ)Y‰D‚ysgá·~<ó@ÇFƒÌ¬£åNN¶ðörÑ;/‘ˆ1}Úä³ÙØU[ÛŒöö.£åvƒL²‹EÉwPöyHD¦3î»[[åP©ÔFË-­7±@p°/z
Y„b±“&y`Ò$“Æ	9¹¥©D‚}ûÃßßæGåÛþ:‚¸Ø0$§d£¢¢óçG`’jµ»v§¡¸¤!Sý‘˜0MçÞØ˜P>’ƒÊª\{Íg'$LÃëoöÿb?pðV]¹žÐ9
‰Dw7G¨Ôýïkgg¶ü™•R…óÎÖ{mÙ9¥8t(“'{!,lrsË0'q†Pž•]‚Çs`ï`%‹c`iiŽòò:”•Õ¢­­›6‚»»#fGNÕ«ûó/·à—̃xÀv*mغ-v¶Vˆ‰	Õ»§ªª{ö¦C
.˜
w7í4öòò:ÔÕ7ÃÕÕ»÷¤ÃÝ݋ϋ‚H$BvN)ššÚp458ÙKhEèéQàÏm‡ÑÜ"Ç’ó£áæjxZü÷ß<
©´¿{¦²ªÿûe7bc—ýs>’nÇ#_ëq´ÁqMm:;· úúÄncc	gg;46ŽÏ–=cjëŒ'BffRÿ1–ô··w¡W¡„™‘ŒˆLcÜ·`ÉåÆÿ©Ôø‡ŒD"ÆÌA˜›4‘S5óçG xН©Ã|÷ÃvÜ{ÿ»xú¹ÏPUÝ€?ô¾NkÆíw½Ž[n[‡¿¶Á‰¬b,XtJJµÍý==
¬¸äQ|úù&´µuà¹>ÇÃ~¨sïk^Ŧ͇PUÕ ÷ìÄ„éÈË+ºãö8ŽË.î®^s‰	Ó?þw'¾ùVÛºÕÕÕƒå=ˆ_~Ý‹â’ÜrÛkèììŸð×ö£¸øÒÇPQY
àÆ›_ÁcOüG(_ÿÆXsë:Ô7´`Û_G°héýË;ÑÐЊººTU5àÈÑ\T|ß6o=„¹Iý­Wõ˜¿èn>œôŒBÜy÷›:×oý3ËW<„‚ÂJää–aÑ’û„îÌ»á®{ß½÷¿ƒÚÚ&¼¶þ{¬ù×:@Yy-Úå(,ªÂ‘£¹¨0FïîûÞÂÑÔ<ìÛ—çݹ\ö)½_Šmm°µéo‰puq€·+8qê­ç´ŠrããE"ÀÅÙaÐûûà‰ÄØZW}ÊËëŒþËÊ*B¡2zïPŸ‹DtæÆýŸ0=½ƒwSI$Cç˜VV°¡Ö¥R‰¯¿x"‘ñqÓðèãá¾{.Ê…›n¸ÐÚ"Çïöáî;ÿ‰Ï¾Ø[[+|ýÅ€[Ö¬@dôM¸eÍ
Nö,^…­Yað¹îNœì…Oà‚eñÈ8^ˆˆYS.œKIÉÆ#]­wï÷?l‡%¾úüq@zF!-¹O(镯ðÒó·àÒÌ<õÌ'ص;
PT\…>ú)>‚““6ö[×á‹/·âŽÛ/ATÔTxz8ë¼55µ¡²²S‚|„sï¾ÿ3Ÿ…×_»ðÓw
ã³T*xè}üçÃðòtÁºõßá³ íšÙºñ5ØÚZáÖ­DdôÍ8r4KÇàý~Ååÿ\€åËâ--Ú…hW_½Dj¿GKïÇÎ]i¸èƒ1÷Ù¹ë’S²ðÚ«·ëœŸäƒ™EX¼(jÐûÏjµÍ-íFË­¬,†tmï`cê°Æ4…B5èDšÞ^å ]®CÖÏI:DgݸO°D†Fz Tÿ+o4ÄD‡
1ûû»ë—èëJÒ–{å‡ä ¾¾wÞÓßZ#‰—W&$Xq±áƒ>;1a:ö8o/L
öƒ™™ñqáÂ9ˆD˜9Cí¯Œã…:Ý}3g
û6ªT*de— &º¿›.6&LH°ŽˇT*ÁSÏ~*”Wu*y‡ö/q[Ûþ±tÇqóM
Çc+¯¨Gmm¾ùn¾99¾¬¾¾eåý³ÔfL‘µ±¶Dôìdd!jvˆÑ8bc|_üÜQSÛ8hÜÇŽåãÖ;^Ãÿ}ð <=uÇúÙÚZ¡¹EnäÎsO[{‡Ñí¨€áM±²?Ý¥ÃÑÓÓ{VëW(™`mã>Á2?e˜SõÑÂ5Ò¤ÆEˆD"½q²]LÚòþ²Ù‘Sqùe„ã®[†€IžÂ±…ùàïEBÂ4¼ñæOðñvEB¼6‹ÇþoWaüÕ©Äb1ÔjÝqnš“¿PÅb1d2)z̲;µUÑÍÕ7\·L眣ãðöPëë^ko³ìä3EÂóèÅ&‘ˆ±úêóuºë>õ=WkÔC®€­ó}hÐõé…XµúY¬{åv¡Õk ööÎAÇ$k†ZïÊÌlè!©tÜfÐ1Ô¤	KKsDÍÖ8\§®ED¦7î?µ,,o	1¶¨1µµÍc®å+1~:Ò3´Ýz³#§bväTDF;Q´-X¹yeø}Ã~ÄÇiÈOðBK«¿oØ/Œ¿:Õ¬YS°{OºðKáÈÑtœƒ%‰†ß7ì®ßðÇ~áëè蔕×ÁÙÙ^ˆ{väTað¾¥¥ù cQmáëã¦3=bÖ¡…víéÿÚßÏN¨¯oÑyÞ´ðáš´ô47k»´ÚÚ:r8³´3&­¬ÌÑ0Œe#Œ9‘YŒ+¯z/¿ø/¬¸(Ñà5¹yåãjæÜPÄHÄC‰D"ƒËŒWæCü1¤Ö¨ammñ·ÿq,¢³oÜ·`9:ÚB,í¢Ë» Tª†œ‘Ó§¾¾ÇÒò!‹ÆÔ¦Ï×®^‚í;Žâüe`Nâ´·wbï¾lÜð*\]@ܧoVfV‰NwX|l~ý}Ö¾r›Áû®¸l¾ùv.½üI̘ˆ™Åp0fæùgoÂeW>]»ÓÐÑÙЩþ¼Ÿ¯;{ä\¸ò\tA$1Ò3
pÅåqÍUçã¼³qó¿Ö¢¤¤ññá¸óöè=ù²8ìÝ—Ž„xmRxÇíÿÀ=Œ5ÿZWWE"Þ~ãÜ~רúça8;Û¡ °^^ÎXû²öõùx»âú›^BÔìlûë.\™'·Y²8¯¬ý;vÃE&à¼ú3&sÇ]o@©Ráó/6áó/6fG†à©'®ÔÕ7£º¦QHpÇ…!#õ0–¸Ðh4&Yfã\af&ƒ™™Ôè8¬ÞÔj
÷$ÃD4JÅÙþ,•Ù¡³=ï¬>ÃÊ6õÕ¿,;ššgt›˜1c2¼<õ×à1äPr&ZZ: óæÎ²…l(ûœÀÌ™°±¶DIi
4Э×ÝÝ‹ciùÂ`ìä”,LŸ++형Ҳ¨TjLðêK=–‡‚‚JXY™#**'—8õ^c²sJÑÙÙ­³BYy-ÊËë&tžúìžvíÑ$OL˜ŽœœR„LõƒÍÉ.¼®®äTÀßÏÿûyví9†/?{\xFyyRÓò T¨ì«Ó‚ÓÐЊ¢â*ØÚZ!4Ä_/æ‚‚
\sÝ8°÷}a©†ÖV9vïI‡££
¢£Bq45ñqáByss;RŽd£¥Y??wDG…@*•à˯·bëŸ)xóõ»pð`&œíôZî*+ëQV^OOgøx»"9%	ñÓ„±s¹¹e°³³Ö[Õ÷ý9uÁSGG[„…N¼ÿᯨ«kÆ3OÝ0è÷ét¸z®8ë?€ñŸÁÚÚfKË7zŸ»»£ÐBhL¯B‰;R–ÇÄ„êmÔþwŒö{5ЉÌ"TTèÏøí“?
vvÃßp¾««YÙ¥ðõq…««ÃãS‰FÃHüZÙc$òžqß‚^ƒ&XEEÕðôpò§¼¢--ÚU¼=<œÎ8¹ ³VÕÀ5­m÷f_rèp?ýµ¸"#‚¬wþÔ{1”Àøùºë-¬zê³ÍÍeX²8F8ØVXT…dz6	{÷eàÍw~«/ýKç~__7£ãŽ\\ìáâbo4æ  ,[‡Ý{Ò…uÃìímtºàNM’muâ=•«‹ƒÑ.ª16«×Tœíè…ÂÂ*ƒå--Øà8B¦úÁÍÍQgÐ{OO/ŠŠªQV^+|†…„øñC¢2a,0“I5EÅU(.®†R©¿G¡F£í2”CẇX,Bh¨?|}˜\.GÛA÷¬®n„·«°š…B…'Ї¬àÖLãÍ” ¨TjÙ°uw÷"-½R©ÖÖ–J$èîéÕëZžâÃÏ/¢4¡,@Ûì8Ù~¾¨¨¨Cm]Z[;ŒN—JÅpww” “j'š¨fLŸŒýN\z@£ÑÎö
	ñƒ§‡3Äb1ZZÚ‘•]ŠŽŽnH¥bØØXmÉÊ/¨@ggÄ'·¾òóuÖ
ñ犩~pu±Ç‰Ì½Y¨}”J5Z[õß33)ÂB'ÁÃÃél‡IDL¸«L&A@€'<¡P¨ÐÞÞÞ^%
% dR),,Í`ogÍéÌD&`nn†ÈÈ`¤¦æL²T*523K™Y¢s¾o͹šÚ&£	–R©FiYÿvG®®ã*Á´Ý…s“f ®®åäŒævƒ­ð}¬­-àíå??÷a¯óGD¦3a¬d2‰^×™žƒ½
âãÂqâD±Ñ±™›K1sFœœìPSk|×D!‰àîîwwGh4Èå]èêêB©‚J©‚X"†¹™¶¶Vlq'eL°ˆhDYZš#::uuͨªnDCC‹^KŒµµ¼¼œáçë!Ìsws„­$R1$1¤	¤R	Ä'¿–HÄH%J$b…s‘H[[+acr"[˜`Ѩpss„››¶%F¡T¡·G‘Hss™Á.-7·±±-Ñp0Á"¢Q%‰`&“ÂLÆ#"?†ÞÆžˆˆˆˆN,""""c‚EDDDdbL°ˆˆˆˆLŒ	‘‰1Á""""21&XDDDD&Æ‹ˆˆˆÈĸ²Ñ8äê¹b´C q¨¾ú÷ÑáœÁ‹ˆhœ*Í[?Ú!Ð8âüÀh‡pNa!‘‰1Á""""21&XDDDD&Æ‹ˆˆˆÈÄ8ȈˆÕÕÕ‹¦æôö*¡P¨ •Šamm'GkÈd’ÑhLb‚EDD54¶£°°mí]ËÅb1¼½8Ù‰Ñ)˜`‘µZ™•¨­kÕ+37—¡·W	FµZòŠF45w`vä$˜›ñW
QŽÁ"""Z­FjZ©^r%“I€9	S`mm®SÖÑѼ¼ê‘“hÌc‚EDD‚ü‚Z47wèõ‚££5
Šê —wë•×ÖµA©TDˆDç&XDDhmëBYy£Þy33)\]lUU-ïÕh4èéQžÕøˆÎ%L°ˆˆ&W`cc‘HP©Ô¯‰D0ã,",""‚J¥68¨$âþ_n®¶¯qw·çLB¢˜`ZZ;¡Qk–I$"áë©^pu±Ó)wu±CèTϳѹ†í¹DD„––N£e"q‚%“I0k¦äònt÷(`meKK³‘‘èœÂ‹ˆˆÐÛk|€º"½s66°±±8›!ÓØEHDDP(¸Ä‘)±‹ˆhª®iAgg¯pÜn`m«e…Euzç]œm`oouVâ#:×1Á""š€ªª[ÐÔ$Öµíí]h7°¡L*a‚Ed»‰ˆˆˆLŒ-XDDPÐd7ôø8	ÇÅÅõh3ÐJŽŽÖðóuÖ;o{¹ñõ>ÄÅbjðè.û°åÏã°²2ÃÜ9S…s¶ÁS<ôÎ-œŠìœjìØõ¯\i°¾»îÿ+.˜…ÅçM;£¸ššäèêVÀÛËñŒê¡³-XDD½½Ü\í„ææ2£×ZZ˜é\Û÷ïl,ÏðÅ×û‘›WcòzOWYy#>úx§pÜÔ$ÇÚõ›ôÎ=ÿòï°´4Ã$ÌI˜b´¾††vtóöwýº!ëßÚrÆõÐÙÇ,""“**›P_ß{ìÚ“{{+,Y4
‰¶m 9¥“&¹¢¬¬Ù¹ÕˆŠ@X¨—N‡#+»>ÞNX0/b±î½¥¥
ÈÌ®ÄÕWÆëlõˆõomB¡‚L&Á¡ÃEX¾töìËÓ9ççëO¨Õøû¹÷«Tjlß™…¦æÌ¬÷ÚÊ+š°w_.\]mˆ”ÃEX´0\(/+kÄÁ”H¥,œ
GGk47w '·UUÍØºí8¬­Í1'A¿nÆU‚åê¹b´C ""Ù³/Ÿ}¹^ž˜>Í_}síÈÄ믮¼¼îXY™ÃÃÃÎN6X»~þïý¸çoPQÕŒsCðÕ·ûñùWûðå'k ‰ðòº?`n.ƒ››<ÜíõöXõ†L*AZF¢gàPrâ§ ¡Q®s.>6°ÿ`>6nNǯÜ|Û§h—w#)1<ü=*«š…ºsr«qåµ`å…()kÀç_íGÊáBäg®üòûQ¼öÆfüceÚå]X÷úfüøÍ퀊Êf46uàXZ)œm˜`aã&ÁêlÏ툈ÈÄššäøý¿÷ÀÒÒWü3󿌵/^©T»ïað<óÄÅs~ùí(â‚°cW6NdU`ë†C*•@£Ñ`ÙÊõع;ç‡
÷>ÿô?>W"#&j2%h“©”BÜtý\ÔֶꜻuÍB½{÷íÏCVvöíx2™׬’#~îóBùÛïoÃuW'â¾»—¾þîR:;{ðôs¿à¿ß݉à);[K¼ÿÑv¼òÂåX´0'²*ñȃšè¦³eÜ$XDD4þ„‡yc½|}œ R©ÑÔÜ7Wí~ˆÑ³„k}}q¢p$µJ¥=þ£PÞÑÑ‹¼ü!ÁЉê¿×¸Ø@ìÞ›‹kVu µµþ~.ˆ‹	Ä›ïnÃ5«:›W#´`
t"«Q³„ͯl„d	²²«pÕåqÂñÀ×_P‹®®^|8`¬WUU3z{¹칆	Y}-U}D"‘ΦÔRiÿ\-±HõÉ2‘H„ @w¬^• ”¯^•Oáxà˜+CúÆaí;‡è“ÉØŒ~8‘U}ò„ñW§‹Ezgk4ýÇffôX9à×"‘ææ2\³*Agƒ"î÷xîa‚EDDãN\Ìdü÷çÞâkksá¼Z­ä.]}ã°>úd®¼,`n&ÅÔ)øè“][¯`æt_üß'»ÐÝ£€…¹µumÈÍ[Ò…–´›Ó…²à`H¤bôô(¤·…¹Ã[ –F,""w’§bÉâiX~ñëX´0½½J>Z„ž¹Q‘ƒw
ö釵}W^_»J8ˆ·ß߆¯›kð¾Ø˜@ÄÆâÒ+ÞAÒœ©H=Vïþ5Çî¼m®¾þ#\té›°°!Àß²“­iæ2¬}ñrÜuß×X´0¶¶–È˯Ax˜7º9âã‚ðêë›pÍ
aj°'ž|”“»Æ*RÑvV"•Ùq:Ñ8geŒúêßG;Œ3âê¹bD>«Fâ½rõ\Ò¼õþ>-½õ
†xy:"<ÌÛT¡
*=£ÞÞŽpq¶Em]ZZ:t=”RˆÈYþ03“âDV¼<àäd¨ohGCC;BCú—jÈ˯AVN¤1¦…û`’¿v)…Sï5¦¬¬U5-ˆž‘HÛi×ÐØŽü‚ZL÷ÍÉÅVëêÛÐÔÔ©ÚX5
öÈG›¼qÑ“QSÛ77[¸8ÛÐn®]PXWW[dçTáÙÃ_›žÛÐØŽÔc¥h—wc’¿"fú	KL´µw¡°°"‘³fúÑû}:üƒ‘ÿoÏöÏ •m0F"ïa3gô'învpw³Ó)‹éï–›æ£SæêbW[sÁSZŒÚÚV$&LAôìÉB}66øè½ëq(¹±1HJºç%…`ûæ‡PRÚ té}üÁ
ðõqî¿âŸ±FZ}ùùËp4µuõm°³µÄËÏ_w7;¡|מDÌò‡Ÿ¯³Éß'û˜`јagk‰»n[„›ÓqÕÚV,s-®™1IçKK3Ì3Õh¶6X|Þ4ƒe.ζpq¶Ž£"u×÷˜lªwà:X§Ú¸9Ý¿Üh9oL°ˆˆhLY}U¢Iê±³µÄ¬~&©ëïxcÝU£öl}äNDDãRX¨“5L°ˆˆˆˆLŒ	‘‰1Á""""21&XDDDD&Æ‹ˆˆˆÈĘ`™,""""c‚EDDDdbL°ˆˆˆˆLŒ	‘‰1Á""""21&XDDDD&Æ‹ˆˆˆÈĘ`™,""""c‚EDDDdbL°ˆˆˆˆLŒ	‘‰1Á""""21&XDDDD&Æ‹ˆˆˆÈĘ`™,""""c‚EDDDdbL°ˆˆˆˆLL:ÚÑÙáüÀh‡@4a1Á""‡ê«íˆ&4v™,""""c‚EDDDdbL°ˆˆˆˆLŒ	‘‰1Á""""21&XDDDD&Æ‹ˆˆˆÈĘ`Ñÿ·w·ËM\w‡ÿ+i%Û²cƒÁ`“)yù—ét:½‰ÜL®£7“kh“v¦Ihš„„°%ÛØ–_õÖ”¤Æ’›–ÂÞçùfíj}ðŒG?ÎYŸ%1˜ÀHL`$&°X‰	,€Ä@b 1˜ÀHL`$&°X‰	,€Ä@b•Q€ô.\þxÔCàj<útÔC81À)õÃí?Žzœ"×n|2ê!œ(–X‰Y" ‰~¿Ë+›±¶ÖŠýýNŒWcáÊtLNŽzhðÊ	,^X«µ7¿ú)¶·÷½¾´´¾¿³³S#Œ†Ààˆ~¿ÛÛû±³{v7º½~”˥ˣ>Q‹Zí—ÕµV|yóAt»½׉¸w¿!°(@D<ªFs+–>‰õõíÁôÌd½ssgâ왉¸ùÕÓ¸Êór”Ë¥ØÛk:·Û~8­±þd;nÝz­ç–øžÉ²§³QÏ´¶÷£u¯ñó×µZ%~÷Û7cu­ßÜztè½ç¦ë/eÌð:X÷ÓÒZÜúöÑ¡€zfqa&®-ÎÄØX­Ö^|}ëallì9/˲èõû1y:ÍV4›[q~º¿yëâËþ'ÀkG`X³¹udÆé™+óçâí—~þzrr,>úàZüùóï¢Ýî:wo¯ûûýøÃïߊ>XŒÝƒˆˆ˜¯¾¼ÁÃkÌ>XÕëõãŸß<züÚâì‘×ò¼ssgžpЉo¾}kãUqE¡	,€‚ZilÆÁAgà±J¥õzmà±³gƇ^³ÑØ<2»E$°
êÉÆÎÐcÕêð;HjÇë÷#ZÛ{/4.8
@Aíïž½ŠxºeÃ0•JùØëvÚ¶ePPyeøGÀÁÁðe¾N÷ø%Àãf¿ (@AMM
¿—ªÛí
½?kgç`èûò¼SSž=  .ÍryøÇÀ³½¬ž×hl}ÏÕ+ÓQ*e/<68é@Aåy9Þù}®ž÷ýÍè<÷¸œå•ÍX]Ûx~½^‹7Þ¸tŒpRY((°ùùéèt{qû»ÇGvrßÙ9ˆ¿üõNÌÏOG5/ÇÖÖ^,=\x©É±øðƒÅ¨3#E"°
nqa&ÎO×ãν•h6·…Öîn;îÞ]úÞYP$  ®Î$¿f»Ý/¾ü1vw‡ÏŽA,€‚Z\8‹é#«ÓéÅÝ{Ã÷΂"p@AeYo߸ssgâÁƒµX^Ù8²›ûÿ«ÑÜŠ~¿èy(3X¶¹¹®ÇÆæN²¸Šx:‹µ¿ßIwA8aÌ`Ô½ûÁ¹ze:ææÎ}_·Ó‹n¯»»íøii-ööÚÏóèŠL`Ðêjk`\]¾t6Þ}gþW_çâÅ©øìó;G^ÏsûcQlþ{P@Ë+_Ÿ>WÿŸ®SŸ¨
©K—†Ï€A,~¶ºþë6}¦ÙÜŠv»{èµjµo,Φœ8 €.Ìžøúòòf|qóÇX²½Þà»Þ{½~llìÆ··Ç—ÿxpèXž—ãÃ÷cl,O>f8I܃P@.LÅõ7/ĽïGŽ5[ÑhlE©”Ež—£šW"²ˆn·N/ÚíÎÀ¿8œŠ÷Þ™ZÍGø-(¨·®_Œ¹‹gâÇ«±Ò8ºÔ×ëõc¿sìv“õZÌÌLÆ•ùé¨×k/{Èpb,€›œ‹÷޽ア³{[[{±¿ßŽƒƒNtº½èu{Ñëõ£TÊ¢\.G¥RбZãÕ˜¬×¢Zõ1ƒøÍ ""&Æ«11^õ0àTp“;@b 1˜ÀHL`$&°X‰	,€Ä@bvr8¥®ÝødÔC€ÂX§Pãѧ£š%B€Ä@b 1˜ÀHL`$&°X‰	,€Ä@b 1˜ÀHL`$&°X‰	,€Ä@b 1˜ÀHL`$&°«ŒzÀéqáòÇ£‰ág§›À’ØÙº=ê!œ~VpúY"HL`$&°X‰	,€Ä@b 1˜ÀHL`$&°X‰	,€Ä@b 1˜ÀHL`$&°X‰	,€Ä@b ±Ê«úFS7^Õ·©WXöæ«ø6¯K„‰	,€Ä@b 1˜ÀHL`$&°X‰	,€Ä@b 1˜ÀHL`$&°X‰	,€Ä@b 1˜ÀHL`$–EDÔƒ8MþsäÙêÞTçIEND®B`‚urwid-2.6.16/docs/manual/index.rst000066400000000000000000000003461470350774000170200ustar00rootroot00000000000000.. _urwid-manual:

################
  Urwid Manual
################

.. toctree::
   :maxdepth: 2

   overview
   mainloop
   displaymodules
   widgets
   userinput
   textlayout
   encodings
   displayattributes
   canvascache


urwid-2.6.16/docs/manual/mainloop.rst000066400000000000000000000145361470350774000175350ustar00rootroot00000000000000.. _main-loop:

*************
  Main Loop
*************

.. currentmodule:: urwid

The :class:`MainLoop` class ties together a :ref:`display
module `, a set of widgets and an :ref:`event loop
`. It handles passing input from the display module to the
widgets, rendering the widgets and passing the rendered canvas to the display
module to be drawn.

You may filter the user's input before it is passed to the widgets with your
own code by using :meth:`MainLoop.input_filter`, or have
special code to handle input not handled by the widgets by using
:func:`MainLoop.unhandled_input`.

You may set alarms to create timed events using
:meth:`MainLoop.set_alarm_at` or
:meth:`MainLoop.set_alarm_in`. These methods automatically add
a call to :func:`MainLoop.draw_screen` after calling your
callback. :meth:`MainLoop.remove_alarm` may be used to remove
alarms.

When the main loop is running, any code that raises an
:exc:`ExitMainLoop` exception will cause the loop to
exit cleanly. If any other exception reaches the main loop code, it will shut
down the screen to avoid leaving the terminal in an unusual state then re-raise
the exception for normal handling.

Using :class:`MainLoop` is highly recommended, but if it does
not fit the needs of your application you may choose to use your own code
instead. There are no dependencies on :class:`MainLoop` in
other parts of Urwid.

Widgets Displayed
=================

The topmost widget displayed by :class:`MainLoop` must be
passed as the first parameter to the constructor. If you want to change the
topmost widget while running, you can assign a new widget to the
:class:`MainLoop` object's
:attr:`MainLoop.widget` attribute. This is useful for
applications that have a number of different modes or views.

The displayed widgets will be handling user input, so it is better to extend
the widgets that are displayed with your application-specific input handling so
that the application's behaviour changes when the widgets change. If all your
custom input handling is done from :meth:`MainLoop.unhandled_input`,
it will be difficult to extend as your application gets more complicated.


.. _event-loops:

Event Loops
===========

Urwid's event loop classes handle waiting for things for the
:class:`MainLoop`. The different event loops allow you to
integrate with Asyncio_, Twisted_, Glib_, Tornado_, ZMQ_ libraries,
or use a simple ``select``-based loop.
Event loop classes abstract the particulars of waiting for input and
calling functions as a result of timeouts.

You will typically only have a single event loop in your application, even if
you have more than one :class:`MainLoop` running.

You can add your own files to watch to your event loop, with the
:meth:`watch_file() ` method.
Using this interface gives you the special handling
of :exc:`ExitMainLoop` and other exceptions when using Glib_, Twisted_ or
Tornado_ callbacks.

.. _Asyncio: https://docs.python.org/3/library/asyncio.html
.. _Twisted: http://twistedmatrix.com/trac/
.. _Glib: http://developer.gnome.org/glib/stable/
.. _Tornado: http://www.tornadoweb.org/
.. _ZMQ: https://pyzmq.readthedocs.io/en/latest/

``SelectEventLoop``
-------------------

This event loop is based on ``select.select()``. This is the default event loop
created if none is passed to :class:`~urwid.main_loop.MainLoop`.

::

    # same as urwid.MainLoop(widget, event_loop=urwid.SelectEventLoop())
    loop = urwid.MainLoop(widget)

.. seealso::

  :class:`SelectEventLoop reference `

``AsyncioEventLoop``
--------------------

This event loop integrates with the asyncio module in Python.

::

    import asyncio
    evl = urwid.AsyncioEventLoop(loop=asyncio.get_event_loop())
    loop = urwid.MainLoop(widget, event_loop=evl)

.. note::

    In case of multithreading or multiprocessing usage required, do not use executor directly!
    Use instead method :meth:`run_in_executor` of :class:`AsyncioEventLoop`,
    which forward API of the same method from asyncio Event Loop run_in_executor_.

.. warning::

    For input handling, selectors based logic is used.
    Under Windows OS only `SelectorEventLoop`_ is supported.

.. note:: `uvloop` event loop is not supported officially.

.. _run_in_executor: https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor
.. _SelectorEventLoop: https://docs.python.org/3/library/asyncio-eventloop.html?highlight=selectoreventloop#asyncio.SelectorEventLoop

.. seealso::

  :class:`AsyncioEventLoop reference `

``TwistedEventLoop``
--------------------

This event loop uses Twisted's reactor. It has been set up to emulate
:class:`SelectEventLoop`'s behaviour and will start the
reactor and stop it on an error. This is not the standard way of using
Twisted's reactor, so you may need to modify this behaviour for your
application.

::

    loop = urwid.MainLoop(widget, event_loop=urwid.TwistedEventLoop())

.. note::

    In case of threading usage required, please use twisted deferToThread_ method. It supports callbacks if needed.
    Executors from `concurrent.futures` are not supported.

.. _deferToThread: https://docs.twisted.org/en/stable/core/howto/threading.html#getting-results

.. seealso::

  :class:`TwistedEventLoop reference `

``GLibEventLoop``
-----------------

This event loop uses GLib's event loop. This is useful if you are building an
application that depends on DBus events, but don't want to base your
application on Twisted.

::

    loop = urwid.MainLoop(widget, event_loop=urwid.GLibEventLoop())

.. seealso::

  :class:`GLibEventLoop reference `

``TornadoEventLoop``
--------------------

This event loop integrates with Tornado.

::

    from tornado.ioloop import IOLoop
    evl = urwid.TornadoEventLoop(IOLoop())
    loop = urwid.MainLoop(widget, event_loop=evl)

.. note::

    In case of multithreading or multiprocessing usage required, do not use executor directly!
    Use instead method :meth:`run_in_executor` of :class:`TornadoEventLoop`,
    which forward API of the same method from `tornado.ioloop.IOLoop` (and internally use asyncio run_in_executor_).

.. seealso::

  :class:`TornadoEventLoop reference `

``ZMQEventLoop``
----------------

This event loop integrates with 0MQ.

::

    evl = urwid.ZMQEventLoop()
    loop = urwid.MainLoop(widget, event_loop=evl)

.. seealso::

  :class:`ZMQEventLoop reference `urwid-2.6.16/docs/manual/overview.rst000066400000000000000000000060161470350774000175570ustar00rootroot00000000000000Library Overview
================

.. currentmodule:: urwid

Urwid is a console user interface library for `Python`_. Urwid offers an
alternative to using Python's curses module directly and handles many of the
difficult and tedious tasks for you.

.. _Python: http://www.python.org/

.. image:: images/introduction.png

Each Urwid component is loosely coupled and designed to be extended by the
user.

:ref:`Display modules ` are responsible for accepting
:ref:`user input ` and converting escape sequences to lists of
keystrokes and mouse events. They also draw the screen contents and convert
attributes used in the canvases rendered to the actual colors that appear on
screen.

The included widgets are simple building blocks and examples that try not to
impose a particular style of interface.
It may be helpful to think of Urwid as a console widget construction set rather
than a finished UI library like GTK or Qt. The :class:`Widget base class
` describes the widget interface and :ref:`widget layout
` describes how widgets are nested and arranged on the screen.

Text is the bulk of what will be displayed in any console user interface.
Urwid supports a number of :ref:`text encodings ` and Urwid
comes with a configurable :ref:`text layout ` that handles the
most of the common alignment and wrapping modes. If you need more flexibility
you can also write your own text layout classes.

Urwid supports a range of common :ref:`display attributes `,
including 24-bit and 256-color foreground and background settings, bold,
underline and standout settings for displaying text. Not all of these are
supported by all terminals, so Urwid helps you write applications that support
different color modes depending on what the user's terminal supports and what
they choose to enable.

:class:`ListBox` is one of Urwid's most powerful widgets,
and you may control of the :ref:`listbox contents ` by using
a built-in list walker class or by writing one yourself. This is very useful
for scrolling through lists of any significant length, or with nesting, folding
and other similar features.

When a widget renders a canvas to be drawn on screen, a weak reference to it is
stored in the :ref:`canvas cache `. This cache is used any time a
widget needs to be rendered again, reducing the amount of work required to
update the screen. Since only weak references are used, Urwid's display modules
will hold on to a reference to the canvas that they are currently displaying as
a way to keep the cache alive and populated with current data.

Urwid's :ref:`main loop ` simplifies handling of input and updating
the screen.  It also lets you use one of a number of :ref:`the event loops
`, allowing integration with Twisted_'s reactor or Glib_'s event
loop if desired.

.. _Glib: http://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html
.. _Twisted: http://twistedmatrix.com/documents/current/core/howto/reactor-basics.html
urwid-2.6.16/docs/manual/safe_combinations.py000077500000000000000000000030331470350774000212130ustar00rootroot00000000000000#!/usr/bin/env python

from __future__ import annotations

import sys

import urwid

BLACK_FGS = (
    "light gray",
    "dark cyan",
    "dark red",
    "dark green",
    "dark magenta",
    "white",
    "light blue",
    "light cyan",
    "light red",
    "light green",
    "yellow",
    "light magenta",
)
GRAY_FGS = (
    "black",
    "dark blue",
    "dark cyan",
    "dark red",
    "dark green",
    "dark magenta",
    "white",
    "light red",
    "yellow",
    "light magenta",
)
BLUE_FGS = (
    "light gray",
    "dark cyan",
    "white",
    "light cyan",
    "light red",
    "light green",
    "yellow",
    "light magenta",
)
CYAN_FGS = (
    "black",
    "light gray",
    "dark blue",
    "white",
    "light cyan",
    "light green",
    "yellow",
)

BG_FGS = [
    ("black", BLACK_FGS),
    ("light gray", GRAY_FGS),
    ("dark blue", BLUE_FGS),
    ("dark cyan", CYAN_FGS),
]

body = urwid.SimpleFocusListWalker([])

for bg, fgs in BG_FGS:
    spec = urwid.AttrSpec(fgs[0], bg)

    body.extend(
        (
            urwid.AttrMap(urwid.Divider(), spec),
            urwid.AttrMap(
                urwid.GridFlow(
                    (urwid.AttrMap(urwid.Text(f"'{fg}' on '{bg}'"), urwid.AttrSpec(fg, bg)) for fg in fgs),
                    35,
                    0,
                    0,
                    urwid.LEFT,
                ),
                spec,
            ),
            urwid.AttrMap(urwid.Divider(), spec),
        )
    )

try:
    urwid.MainLoop(urwid.ListBox(body)).run()
except KeyboardInterrupt:
    sys.exit(0)
urwid-2.6.16/docs/manual/safe_combinations.py.xdotool000066400000000000000000000000521470350774000226750ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 70 27
urwid-2.6.16/docs/manual/safe_combinations1.png000066400000000000000000000221111470350774000214230ustar00rootroot00000000000000‰PNG


IHDRv•B¡{*PLTEåååÍÍÍÍÍÍÿÿÿÿÿÿÿÿÿÿÿÿÍäYIbKGDaf¸}	pHYsÒÝ~ü#¸IDATxÚí;oWûÀLJK(vÖ	%ï,aùØ’?.Ü¿I1]¤tn#ªm"A9RD9Ò?iG‘ ŠÝ""„ù.ÿçvn3;»s[³Àyvw|æ¹s~û\&ï[8¼xñâÅ‹/^¼xñâÅ‹/^¼xñâå›”Ql…ðskÄ÷[«¶j÷¡‘«Ô×.²m]IV:Wä@¢¢ƒ ¼ÏAvŽ­UÇ]Á.ØTvg·I쎊w4Ú‚K†¡XPƒ¶-Ðl9H³e—舢Fl‚=Fh‰¢í`;Š
TÁ¸¡Å¶]UÍh(M°¡µÏ ìøœ[Ø÷,FtŠƒËýÙYIhgßn;
¾ÿ6qŽ ¬¾álÚÛ¥Ü,i";û¶{ŸaÙv£zvN	Jì0ƒØm3ÄqYEgØvÍ.0û¬Ÿ•asvâôÏ:a·-uf²E³CScvâ–u†v/—õ³‘!ÕŒÝhè¼;à¼äÝA›¼;XgÞšÝHáPì¶F[ú!p1»Ð°Û¶ßAz	;·ß‘v{%»È°Û¸ßé±(ÃtK—•‰kv F#7ª”wfttÎ(vÖœ
P‹&—”Œ`›¦¯´Ï°sÖ-ºešQ°Yr°1ÿ’ì¶6ŒÝö&±Û
Ã¥š0ܘª”ó†•/W Ϭ+ʵEN×jôH±ÇÏ–ìÙY®[W’“U'uûÐ&×Ï–mþ¥Ø_;‡”g÷%Ù!™ãgÏ‚c¨ºg׎
¦kX‡×Ž¡¯ÙÎàsÍ.Ñg胤Äù™:„œ]‡Z;ƒCž ¦sýìä„4h09Q%Íuˆ`Rgu½78:öµgëâãla A
×pé$xX4íì#ôÏì€þ©wÆ	…§=	ÎT~‘á:Ñ$
gvÒ-МØÙÇQ×O`;<¼B‡H»k%vÍ31’Ýu†…‡?9,v'6»“
»ªÆf™ØŸüø˜êUgÓmsvר90ëäÀ×ϸ^ƒ³Àe‡xvæ³æìÄyˆ¬³p;+ïT>6d÷lmywÂy˜¼£îu"ÃA³»ëçÝ5nj×T×+³;^Æîذ»¶¦~w‚Ir"]Oò.hÉîİ;®ßÉœ•¼Ó\ˆÛâ9ëæ¯f^?gO;=ge˜9ë< T44gÅùúpsök”“Ïz^<»¡åºó$ìÅËw$Ï­+ÊNãÈÚ
i·‹çK~KC‰ùÇÿ3×’©$àÃnñÿʾnø—a÷¼!»`ì€ì~ðìlvîjØ!„‹çσ¨±ç;HdG´;ÏárÕw±C‹p„Ò<·ÃÅdH¡’ÚQÎÏ;€ãCãû‡ÿa	Â燘±ÅîÐÄ?Ä6»XôtĈ½×Ã=§<ÚA||ƒ·Ï	"^ùðÈR1ØqMJ.ìì£/ã9ê:	±Ó‡çM±­1Î1»Æ?X“È9wˆÌŽ [Ä.¨gWÑØì0»þã0鸻Ø.XN´ŠFW.éã8X+;¬E<¦BÇì è˜Ý/„nvtκ…ìâ¸;ð»v;’w›w‚U%¦°ÛišwÏ×”wAvʬ·fwð„’wN¿“ËŽ—°»0ìvz÷;›´0·ß9ìT¿C?éwÁÚOæ¬äž³ÄQà5‚wdòZáåLTãÕLðns6дà¢gll?øÒœuóN
d‚Îsv}‰·m›ÿØðâÙ
%Γ°/^¾¨Üø>—²@¹,[Aõ«­»¬Ûgóe`íÜA¦øùM 7[Pã0•nN+¾Ó~ì‚
;uÃ?o´awchv7+ì¦Ùëa·ð¸Ã.ØhvXk——¿þz	|ãR*ïÆ¯¸`.ð¾¼´ØÝÓåK›8cσÎ첬+;ˆü‚ì ‚™/„Ý£Þ}¥(ö¡è
8ӏLS+
&0óvõ’ó¾SÏûRà ‘© ðP4hã_±ŒéBvNö0‡¶ì)Bš šk¤gWÈ4£-q€’áDÁ³ÍB9s!àˆÝ~97gû³j®)ç™ÍˆÑ‹Cv[%v˜‰a‰ÝV=»­*»pPvrr<欰-cÖ³+ìf°ÃÊ}vX³Á`ìö®W•uevº»²£JïÅn«mÞ…ª÷‘iŽl
™w3λ°>ïÂFì°Õ¹ì¨/ÎLTç~‡ª¦ßÕæf7RΚ]àîÓ£ßͰKͤë-ìwÍØÍt˜Ù“šwÞŸíë‡Àæy§Æë(ØÚª›³Mi(›9«kxCÍÙÙLgŸ«´ ù8›-˜³»}ã¬Lû…eâšÅ}fmÙu—QåÁ®¬éÏnH™Uв¬Ùv[_»ý«dÎW4£ŠùËʾ=h*æu²ûŽÅ³ëÅnB‹‰V—ùOj1$BS´Ômå>UÙµ®$G‹¼ŽjôxŒ¦ãeᥭ®žcš¬Úè
Ù9¦£ÒvžÝc‡§Œ&“0‚ª›Œ#<²œš4xC‹(²âªÄ1ŽÈ4&Ó8Š&ÊyL;›
[Éññn¸{|G†òÓ˜ŽvÑt¦£#Û|ŽK8ЗöAÓîññ‘ÙYïs´”i-;8Ï„eŒøø&dˆrÔ1¶2'7K^3÷	›ÆÆ™~;´f·+Ýê(D|GªuíÂiwCÒìÙλèQÎ5Òs°aDšÝ#qèÌN†Ç:U‰ÂnRa7©g7©²{°“ÒÃÓ[va‰ÝQ=»£*»pvQDõª²Îf‡¦þìÔ>]ØírU™Iíñì°fû°KÞ…Õ¼·É»0ŠB—iÆ¥&Ð’Ý‘ÜÉ;=
±C—ivMTç~‡é%'tûi[åf76ß°‹¸¶îwº/™~gØí6Í;ÍÎß»æWwa'svl²CÓq4–Aéâ‰YÖŒ£È6™9«kvÌv˜³GŠpÙUSuÁœuرϱm2sV×,þŠNy·TZu\‰(kÚ²Hv+veÍÐì&C³›l*»£ÁÙ£IËcT‰p4í7Jô#ñbÍnÅÜ¿f½xvë`wJ‹S­Ú[Sã;ì¡ù´ß>ŽÜµ®$çv^»áš.šnx~5ìÓ^iß«aw·;Çt^úžÝ±ÃSœž†gg§á)TÝž>ÕÙÙiÈ´w&p´Î^ÑPàÙ)oH¦³³³PÿнPí³”iY..î†w/.à€Plçê0çh:Óù9-Îч5N8›\w/dg¥×ù]Ùù|)SawÊMjœî©Žµ'†S¢	ºS¸Ùs­¤áõ)Å…l:ÕÎps&­ØÝ•Þ€`}®Õù]‚x—Nx¡9í¹.&7•HÏÛF¤¹{níÜ€TjÏjö||Ã.¬°ëÙ…v§ÙI¡áYÎu¯°;¯gw^Ïî¼Ê.lÍIÏIsØ=4Ágv´Yvw¹†ÌxdvPpçl¾0L	veê±Û“¼uÞ…Ø«ö,"acvØê\vÔOMTKvçrL7ï빞¼:o˜wáÅEè²#Í]Õ°ßévd÷»®ìŒó©=ÇOõïÚkÙïtrú˜B‡Ý]vn–wV”ž·wÍ/½XÍNæìž"¥ç¬4¨33÷Js¶¢)9ïY¦Sý»N[ÏÙsÅN(ÐB`‹øV“׳;=‹(c2¿´AÞ-–½f`‹ä´R”eÍi8”œ·ù…S¨^³ìö¾vçÝÙQõu“=õH\£©˜{ˆû$ÜJìy¼@s÷â¼3;/¡g·.v÷~âUl]E”©$èÿdœkÜVîÓDü¼’ÈýWK<“…¹ŸX±I“_Ú‘]܀ݽ
»xmìîWØ%mÙ…võ_Bgvavá²û³sÐÅŽÞñ=8=~â˜?Yg
8üTbÇñ=2Qb
mô“Ú¼7;,ºûIrÿþ+¨à$‘Ó%÷á?P×;Ô$÷“2;òK^½B쥲5¸O"_@cvÏGŒuÝÆvÞÀËÑ蚨1›bMï1€öW:<žø~:B~Ýu?±Ãë•“YòÀ(¿â•Ú>§(¡F>|Ëû´íw:)öéhØ÷,FtŠƒËýÙÙhfœÅ3ħ:(û(vûÍ“NgGÄÇ>," ‡sy8?,åfE3?¤­æˆoŠ‚}»ÃVIWۯgW¸ì¤ ãÌ>=ÙÍùüxÔÃÈ*.«h˜š·^vT¡ÍØ…\Üzn—Ø…aØÝ<âz-¢¢ÌMØE\å*ëÊì¢(êÅŽþâ¼ÃŽzÕ¾!µ’ÝLò®¨Ï»¢=»CÉŒywØ0ïæ’wE}ÞõìwåCî«ö²™I¦vè#í±¾ßµfqÃ:”®gá í¼;ò‘¼«íw]ØÍøœëa:3ϲdÚ×3t¿®ÉDf§j¶œwÅ ì>R[ÈnÚ0ïT¦fËyW´d‡d³ã^æö»_J¦òNÍ
ÕïPƒ¦¡ó®Ð즺ß዆€KJz—«A•wjÕïhë{iÈNhqòŒýÇ©7š³N2"ë8Vy§ŸN8/4g™nìþ}]ÿ&°ÄJRçʧ$I901!x÷ºêÜPRü¼•ȧ·M£ªÂ±ÖzIþŸÒªsvE…Ý"HÙýÛݧ
»tìŠ윳m»b£Ùae%Éë×	Tð¿‰ÔÙ¿¯“ÕX×6;Ѐ鵅#yMa„¿E¢Â_+èðN’®ì° >¥é§Oo¡‚ÓTe!kà.%;˜¨ò£)ô>Ÿ0þ-E}z+„t½1ª;}x9œÖwøB¯­„Wbc{ù‘H<û(v´O{Q5‹§‚C¦†
j
AД¾µ½P#®#±b—®Ìë:v¸N,vüIÜ‚]¬¨œ–V™«wÒ2éªìðŒttÍŽoÐJ¦Ô.½@å´TÙmÞÊg v ØßRvI².v PÀi#v踌îÓžö»ÅyW4a'…Y¦fËyWô`÷‰Z”ÍîS*dRñ
v©Î;]³¥¼k&•¼+;9½X’ýÎa‡ŽÚ¥ì‘¸ýŽÂiŸ>yWhvÖduJYèR½ËÕ¼å.IQjV¨~‡IÉÙܘÖV\pWRÕ•°ý5ÏY“fIù‘ç,–¶°SO'©Â“¤Ã¬ø”ÊhýDó‘ðIQ±‰NŸ¾¥,tI}b³£A*£†`‡uô9Ï?þœçrº9Ø@M&¸€é3Ý@_1Ÿ1|`Cˆø „Ø'—7F-eÇ ðèxDÅ.£—>¤ùÛÆ ÷üŠVí“é]zŠªY<œ-7HàüxW2À)|æ#çöW &[ó|ðnÀZòQìòå	nØ™,S™˜i®­¨œ–ªZÍ[ùÅNÈ8ì
fÇ&q+•ž19Êi©BÌ[ù4`—eØý‡QKØyÝì ‚™/„Ýg]ØõìÈg	»<ϲ³gEcv™Î;]³¥¼ë/Š5¶
;EÁA\yƒ¼ËuÞéš-å]±²fMçrûe¢ÓïÊðoœ¤f…êw˜”8-Ì»B³Ëmv9=75+ÐíwE©ª.YX³Bõ;ü:Lõ/c§&&â‚X`	þçL3yñü­Ø©§œ³\ÿD¶÷¬ ±™ó0•šU5&5KyÇWR†fdnïñ¡Äî3E}Pìô—AÑx!²«gEQƒך%¯°ËW>ó­AT¿ËŠÍ—dW0¯@>×½ÏË'â:Ùyñì<;ÏγóâÙyvžgçųóì<;ÏγóâÙ­‘]䥫xvžgçÙyv^<;Ïγóì¼xvžgçÙyv^<;Ïn£ÙmÓ–Û–~Ûº–åÀº.q‹Ên]dŸpŒQŒWEŒk÷QוûLZ³‹6•sÎñªÃyv_	;<|0‚\P½ÛÒvo‚r>ˆlgÖÀ
-‚íaœs‚5†“h†pÊÑIƒ6ÔŒC“&ÍCÁ‹œk0“pÜøŸT³ï“eÛž"¤	lš‰n–ó6‡oó>Û}“ÎΑIÄ'#‹Ÿy,;òíüšHÓÄ=&¼“l>žˆßx2nšt&ïJì0£»í;vÃ3ÎÛlÚ6ßÁàìÆUvQ-»q‰tt‡îæÂ.êÎjÙÑjvRÜÆY³“}®ŠÖc؈݄‹ÛÌmÃM}Øm·É»mλhAÞm¯#ï¢kÐb„š‰!µšÝXø/È»q·¼»ƒº~·]éwèÌìÜ~GÚƒµäfgÍ͉êeã(\Á.4í±ÔïH;é”wj¼ÂhÜ®›³%Í;oG‘;g#Ô¢i0vXf8e˜âÛÌY]³X•9ë<‹°ÏX±³æl„Z4u`7°lGk‘êÁÊšI÷ÍÇíC†gwðÅØ;³›l»(XÛÿ¾V\M›çÚÊ>]yéœ$AvsZ̵êÐYÐßR62_¸Ë¡Xç•ÝVKÌ?ýl®%SI*žnÐ"çµ±+õ‹³3ÇõìÚ°sW›ÀŽ
s>ð/+Ï¡D5þêôÆ¿®lW/9—4‡äs¡Iác„ó†QKvø~ôsDŽϣ˜±ÅîhâG±…ãøƒÏσB"M¤M?ÃŽ6§Œ8D||ƒy^ÀÑáç¥ÜF%MÁ[!¬yT蜊ߜ7Œ:à4±©Yƒƒíð*kðž%M5&v~Vö“@ŸÙá	WØL…U¼†]Ô‰œžóE±‹í‚%}U#P9-uk“ZÄ®àòRYWfWÅJv‡×kTDevhŒ]¯d÷}³CÓ°ì%ï¢ú¼‹V±›+ÇjÞ͇ͻh»XçªÙØ6
ÉîpN
ëPu½…ýn»‚ûæ\ºžõ­ö°+;&ª×Q/sû]TÒ GŠ@â(¦áØÉœ•¼sæì!ÿ1õÃEsÖyaŸ¹bgÍÙµhêÆNhQ­ÉŒcëÜ4gvˆæ2Š2ù~Ö¦áØ­]æÑÆHodWÊîpsعÕþ5ä]QlLÖ9ÕþuÔì7)ž]v_DÑã),q2]³ØÃÕ&î²Æ¹¤øy#'ošFU…c­–mÖà1»¨ÂnÙᮚݓ
»tÓØ9h6Ž]´Ñ즰˜N_¼˜B?žNåt°Bë”TÜŠÝcðaÂñx
W}üœ¿Ð™ŒšH›^t`÷$ÅOúäɨà4UYȸKɦ'©ÅÎh"½ÏŒCQOÞ(B¤‰´éMCv:•äx/ÔA•N‹?hñÂ>4ê*¼ç'T§´åcf=Ú‰ªÙOžòJ‘Åû7Oè¡)}cz¡F\)Æt‚”Ø=aÖi£W•dÙ«¸¦Êaj,ùW5•Ó²´y$;³SÙaØñ
ZÉ”Ú[£¨œ–vv³I-d7…‚kÂj»ŽšÖÄ
8mÄkØ¡©#;lH5ìjHÕhtš:ì¦ýòî	µ(›Ý“TȤâìRwªfSÛÔ=ï"ÅNNÈK.)îweÍœ*²Ÿl!Ù̦^yiv©™§¢N)RÜïÊš7Ü%)J éf™ò0iÃËŒFëTMUX™9KÇÆrsI=VIjióÅN™pgejÏ’‹Gë=
U±±I%e¡Kê	›
æ›ÔL8›Úæ]©ÙW¥íl\ mÓ­"iåÁ-ŽÄ	Þ\±›ö?·IÂMegµÑNì¨f|Ú?íôÃvÓ¥é›Uš>_MÛ½üÿŽÒ]<»>ìžþeÝ>Íà’Ùš([ÇQV¬³ÍBço‘zêÙyvWÁŽßYf±{
…›Eò–rÍÓÌÂñ4£¨¿žþ¡OÿR„HiÓ_ß0;GÇ#*v½ì¤#Í_6¹ç@ISÍdCb}“Ü\v&ËT&fš‡ëck*§¥]õlR‹o]–u`÷£³CÓ÷®”5ÍØe:ï”of›¾}v¦s¹ýŽ2Ñéw倞*v‰; ˜¾vjb".¸–àSg2˜Ék4X˜)vÊÄálúØ­á1âÛEæ²393¸o÷Ѥ”wY6p–¾á&׬Ïγóì<;/žgçÙyvžÏγóì<;/žÝºäÿgô–ï•pVôIEND®B`‚urwid-2.6.16/docs/manual/textlayout.rst000066400000000000000000000115161470350774000201340ustar00rootroot00000000000000.. _text-layout:

***************
  Text Layout
***************

.. currentmodule:: urwid

Mapping a text string to screen coordinates within a widget is called text
layout. The :class:`Text` widget's default layout class supports
aligning text to the left, center or right, and can wrap text on space
characters, at any location, or clip text that is off the edge, optionally
inserting an ellipsis character.

::

    Text("Showing some different alignment modes", align=...)

    align='left' (default)
    +----------------+   +------------------------+
    |Showing some    |   |Showing some different  |
    |different       |   |alignment modes         |
    |alignment modes |   +------------------------+
    +----------------+

    align='center'
    +----------------+   +------------------------+
    |  Showing some  |   | Showing some different |
    |   different    |   |    alignment modes     |
    |alignment modes |   +------------------------+
    +----------------+

    align='right'
    +----------------+   +------------------------+
    |    Showing some|   |  Showing some different|
    |       different|   |         alignment modes|
    | alignment modes|   +------------------------+
    +----------------+

::

    Text("Showing some different wrapping modes\nnewline", wrap=...)

    wrap='space' (default)
    +----------------+   +------------------------+
    |Showing some    |   |Showing some different  |
    |different       |   |wrapping modes          |
    |wrapping modes  |   |newline                 |
    |newline         |   +------------------------+
    +----------------+

    wrap='any'
    +----------------+   +------------------------+
    |Showing some dif|   |Showing some different w|
    |ferent wrapping |   |rapping modes           |
    |modes           |   |newline                 |
    |newline         |   +------------------------+
    +----------------+

    wrap='clip'
    +----------------+   +------------------------+
    |Showing some dif|   |Showing some different w|
    |newline         |   |newline                 |
    +----------------+   +------------------------+

    wrap='ellipsis'
    +----------------+   +------------------------+
    |Showing some di…|   |Showing some different …|
    |newline         |   |newline                 |
    +----------------+   +------------------------+

If this is good enough for your application feel free to skip the rest of this
section.

.. seealso::
   :class:`Text widget reference `


Custom Text Layouts
===================

The :class:`StandardTextLayout` is set as the class variable
:attr:`Text.layout`. Individual :class:`Text`
widgets may use a different layout class, or you can change the default by
setting the :attr:`Text.layout` class variable itself.

A custom text layout class should extend the
:class:`TextLayout` base class and return text layout
structures from its ``layout()`` method.

.. seealso::
   :class:`TextLayout reference `


Text Layout Structures
======================

::

    "This is how a string of text might be displayed"
    0----5---10---15---20---25---30---35---40---45--

    0----5---10---15---+   right_aligned_text_layout = [
    |     This is how a|     [(5, 0), (13, 0, 13)],
    |    string of text|     [(4, 13), (14, 14, 28)],
    |might be displayed|     [(18, 29, 47)]
    +------------------+   ]

The mapping from a text string to where that text will be displayed in the
widget is expressed as a text layout structure.

Text layout structures are used both for rendering :class:`Text`
widgets and for mapping ``(x, y)`` positions within a widget back to the
corresponding offsets in the text. The latter is used when moving the cursor in
:class:`Edit` widgets up and down or by clicking with the mouse.

A text layout structure is a list of one or more line layouts. Each line layout
corresponds to a row of text in the widget, starting from its top.

A line layout is a list zero or more of the following tuples, each expressing
text to be displayed from left to right:

A. (*column width*, *starting text offset*, *ending text offset*)
B. (*column width of space characters to insert*, *text offset* or ``None``)
C. (*column width*, *text offset*, *new text to insert*)``

Tuple A displays a segment of text from the :class:`Text` widget.
Column width is explicitly specified because some characters within the text
may be zero width or double width.

Tuple B inserts any number of space characters, and if those characters
correspond to an offset within the text, that may be specified.

Tuple C allows insertion of arbitrary text. This could be used for hyphenating
split words or any other effect not covered by A or B. The
:class:`StandardTextLayout` does not currently use this
tuple in its line layouts.

.. seealso::
   :class:`TextLayout reference `,
   :class:`StandardTextLayout reference `

urwid-2.6.16/docs/manual/userinput.rst000066400000000000000000000077561470350774000177630ustar00rootroot00000000000000.. vim: set fileencoding=utf-8:

.. _user-input:

**************
  User Input
**************

.. currentmodule:: urwid

All input from the user is parsed by a display module, and returned from either
the :meth:`get_input() ` or
:meth:`get_input_nonblocking() ` methods as a list.
Window resize events are also included in the user input.

The :class:`MainLoop` class will take this input and pass each
item to the widget methods :meth:`keypress() ` or
:meth:`mouse_event() `. You may
filter input (possibly removing or altering it) before it is passed to the
widgets, or can catch unhandled input by passing functions into the
:class:`MainLoop` constructor. If the window was resized
:class:`MainLoop` will query the new display size and update
the screen.

There may be more than one keystroke or mouse event processed at a time, and
each is sent as a separate item in the list.

.. _keyboard-input:

Keyboard Input
==============

Not all keystrokes are sent by a user's terminal to the program, and which keys
are sent varies from terminal to terminal, but Urwid will report any keys that
are sent.

============= =================
Key pressed   Input returned
============= =================
H             ``'h'``
SHIFT+H       ``'H'``
SPACE         ``' '``
ENTER         ``'enter'``
UP            ``'up'``
PAGE DOWN     ``'page down'``
F5            ``'f5'``
SHIFT+F5      ``'shift f5'``
CTRL+SHIFT+F5 ``'shift ctrl f5'``
ALT+J         ``'meta j'``
============= =================

With Unicode :ref:`text encoding ` you will also receive
Unicode strings for any non-ASCII characters:

=========== ==============
Key pressed Input returned
=========== ==============
é           ``u'é'``
Ж           ``u'Ж'``
ã‚«          ``u'ã‚«'``
=========== ==============

With non-Unicode :ref:`text encoding ` characters will be sent
as-is in the original encoding.

=========== =========================================
Key pressed Input returned (each in its own encoding)
=========== =========================================
é           ``'é'``
Ж           ``'Ж'``
ã‚«          ``'ã‚«'`` (two bytes)
=========== =========================================

Urwid does not try to convert this text to Unicode to avoid losing any
information. If you want the input converted to Unicode in all cases you may
create an input filter to do so.


.. _mouse-input:

Mouse Input
===========

Mouse input is sent as a (*event*, *button*, *x*, *y*) tuple. *event* is a string
describing the event. If the *SHIFT*, *ALT* or *CTRL* keys are held when a mouse
event is sent then *event* may be prefixed by ``'shift '``, ``'meta '`` or
``'ctrl'``. *button* is a number from 1 to 5. *x* and *y* are character
coordinates starting from ``(0, 0)`` at the top-left of the screen.

Support for the right-mouse button and use of modifier keys is poor in many
terminals and some users don't have a middle mouse button, so these shouldn't
be relied on.

``'mouse press'`` Events
------------------------

A mouse button was pressed.

=============== ======================
`button` number Mouse button
=============== ======================
1               Left button
2               Middle button
3               Right button
4               Scroll wheel up [#first]_
5               Scroll wheel down [#first]_
=============== ======================

.. [#first] typically no corresponding release event is sent

``'mouse release'`` Events
--------------------------

Mouse release events will often not have information about which button was
released. In this case *button* will be set to 0.

``'mouse drag'`` Events
-----------------------

In the rare event that your user is using a terminal that can send these events
you can use them to track their mouse dragging from one character cell to the
next across the screen. Be aware that you might see *x* and/or *y* coordinates
one position off the screen if the user drags their mouse to the edge.
urwid-2.6.16/docs/manual/wanat.py000066400000000000000000000013621470350774000166420ustar00rootroot00000000000000from __future__ import annotations

import urwid


class Pudding(urwid.Widget):
    _sizing = frozenset((urwid.FLOW,))

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        return 1

    def render(self, size: tuple[int], focus: bool = False) -> urwid.TextCanvas:
        (maxcol,) = size
        num_pudding = maxcol // len("Pudding")
        return urwid.TextCanvas([b"Pudding" * num_pudding], maxcol=maxcol)


class BoxPudding(urwid.Widget):
    _sizing = frozenset((urwid.BOX,))

    def render(self, size: tuple[int, int], focus: bool = False) -> urwid.TextCanvas:
        (maxcol, maxrow) = size
        num_pudding = maxcol // len("Pudding")
        return urwid.TextCanvas([b"Pudding" * num_pudding] * maxrow, maxcol=maxcol)
urwid-2.6.16/docs/manual/wanat_multi.py000066400000000000000000000010601470350774000200470ustar00rootroot00000000000000from __future__ import annotations

import urwid


class MultiPudding(urwid.Widget):
    _sizing = frozenset((urwid.FLOW, urwid.BOX))

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        return 1

    def render(self, size: tuple[int], focus: bool = False) -> urwid.TextCanvas:
        if len(size) == 1:
            (maxcol,) = size
            maxrow = 1
        else:
            (maxcol, maxrow) = size
        num_pudding = maxcol // len("Pudding")
        return urwid.TextCanvas([b"Pudding" * num_pudding] * maxrow, maxcol=maxcol)
urwid-2.6.16/docs/manual/wcur1.py000066400000000000000000000022221470350774000165650ustar00rootroot00000000000000from __future__ import annotations

import urwid


class CursorPudding(urwid.Widget):
    _sizing = frozenset(["flow"])
    _selectable = True

    def __init__(self):
        super().__init__()
        self.cursor_col = 0

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        return 1

    def render(self, size: tuple[int], focus: bool = False) -> urwid.TextCanvas:
        (maxcol,) = size
        num_pudding = maxcol // len("Pudding")
        cursor = None
        if focus:
            cursor = self.get_cursor_coords(size)
        return urwid.TextCanvas([b"Pudding" * num_pudding], [], cursor=cursor, maxcol=maxcol)

    def get_cursor_coords(self, size: tuple[int]) -> tuple[int, int]:
        (maxcol,) = size
        col = min(self.cursor_col, maxcol - 1)
        return col, 0

    def keypress(self, size: tuple[int], key: str) -> str | None:
        (maxcol,) = size
        if key == "left":
            col = self.cursor_col - 1
        elif key == "right":
            col = self.cursor_col + 1
        else:
            return key
        self.cursor_x = max(0, min(maxcol - 1, col))
        self._invalidate()
        return None
urwid-2.6.16/docs/manual/wcur2.py000066400000000000000000000005711470350774000165730ustar00rootroot00000000000000from __future__ import annotations

import urwid


class Widget(urwid.ListBox):
    def get_pref_col(self, size: tuple[int, int]) -> int:
        return self.cursor_x

    def move_cursor_to_coords(self, size: tuple[int, int], col: int, row: int) -> bool:
        assert row == 0  # noqa: S101  # in examples we can use `assert`
        self.cursor_x = col
        return True
urwid-2.6.16/docs/manual/widgets.rst000066400000000000000000001020751470350774000173610ustar00rootroot00000000000000***********
  Widgets
***********

.. currentmodule:: urwid

.. _widget-layout:

Widget Layout
=============

Urwid uses widgets to divide up the available screen space. This makes it easy
to create a fluid interface that moves and changes with the user's terminal and
font size.

.. image:: images/widget_layout.png

The result of rendering a widget is a canvas suitable for displaying on the
screen. When we render the topmost widget:

1. The topmost widget *(a)* is rendered the full size of the screen
2. *(a)* renders *(b)* any size up to the full size of the screen
3. *(b)* renders *(c)*, *(d)* and *(e)* dividing its available screen columns
   between them
4. *(e)* renders *(f)* and *(g)* dividing its available screen rows between
   them
5. *(e)* combines the canvases from *(f)* and *(g)* and returns them
6. *(b)* combines the canvases from *(c)*, *(d)* and *(e)* and returns them
7. *(a)* possibly modifies the canvas from *(b)* and returns it

Widgets *(a)*, *(b)* and *(e)* are called container widgets because they
contain other widgets. Container widgets choose the size and position of their
contained widgets.

Container widgets must also keep track of which one of their contained widgets
is in focus. The focus is used when handling keyboard input. If in the above
example *(b)* 's focus widget is *(e)* and *(e)* 's focus widget is
*(f)* then keyboard input will be handled this way:

1. The keypress is passed to the topmost widget *(a)*
2. *(a)* passes the keypress to *(b)*
3. *(b)* passes the keypress to *(e)*, its focus widget
4. *(e)* passes the keypress to *(f)*, its focus widget
5. *(f)* either handles the keypress or returns it
6. *(e)* has an opportunity to handle the keypress if it was returned from
   *(f)*
7. *(b)* has an opportunity to handle the keypress if it was returned from
   *(e)*
8. *(a)* has an opportunity to handle the keypress if it was returned from
   *(b)*

Box, Flow and Fixed Widgets
===========================

The size of a widget is measured in screen columns and rows. Widgets that are
given an exact number of screen columns and rows are called box widgets. The
topmost widget is always a box widget.

Much of the information displayed in a console user interface is text and the
best way to display text is to have it flow from one screen row to the next.
Widgets like this that require a variable number of screen rows are called flow
widgets. Flow widgets are given a number of screen columns and can calculate
how many screen rows they need.

Occasionally it is also useful to have a widget that knows how many screen
columns and rows it requires, regardless of the space available. This is
called a fixed widget.

.. list-table:: How a Widget's Size is Determined
   :widths: 20 40 40
   :header-rows: 1

   * - sizing mode
     - width
     - height
   * - ``'box'``
     - container decides
     - container decides
   * - ``'flow'``
     - container decides
     - widget's :meth:`rows() ` method
   * - ``'fixed'``
     - widget's :meth:`pack() ` method
     - widget's :meth:`pack() ` method

It is an Urwid convention to use the variables :attr:`maxcol` and
:attr:`maxrow` to store a widget's size. Box widgets require both of ``(maxcol,
maxrow)`` to be specified.

Flow widgets expect a single-element tuple ``(maxcol,)`` instead because they
calculate their :attr:`maxrow` based on the :attr:`maxcol` value.

Fixed widgets expect the value ``()`` to be passed in to functions that take
a size because they know their :attr:`maxcol` and :attr:`maxrow` values.

.. _basic-grafic-widgets:

Included Widgets
================

:ref:`Widget class reference `

.. image:: images/urwid_widgets_1.png

Basic and graphic widgets are the content with which users interact. They may
also be used as part of custom widgets you create.

.. image:: images/urwid_widgets_2.png

.. _decoration-widgets:

Decoration Widgets
==================

Decoration widgets alter the appearance or position of a single other widget.
The widget they wrap is available as the
:attr:`original_widget ` property.
If you might be using more than one decoration widget you may use the
:attr:`base_widget ` property to access the
"most" original_widget.
:attr:`Widget.base_widget` points to ``self`` on all non-decoration widgets, so
it is safe to use in any situation.

.. _container-widgets:

Container Widgets
=================

Container widgets divide their available space between their child widgets.
This is how widget layouts are defined. When handling selectable widgets
container widgets also keep track of which of their child widgets is in focus.
Container widgets may be nested, so the actual widget in focus may be many
levels below the topmost widget.

Urwid's container widgets have a common API you can use, regardless of the
container type.  Backwards compatibility is still maintained for the old
container-specific ways of accessing and modifying contents, but this API
is now the preferred way of modifying and traversing containers.

::

  container.focus

is a read-only property that returns the widget in focus for this container.
Empty containers and non-container widgets (that inherit from Widget)
return ``None``.

::

  container.focus_position

is a read/write property that provides access to the position of the
container's widget in focus.  This will often be an integer value but may be
any object.
:class:`Columns`, :class:`Pile`, :class:`GridFlow`, :class:`Overlay` and
:class:`ListBox` with a :class:`SimpleListWalker` or :class:`SimpleFocusListWalker`
as its body use integer positions.  :class:`Frame` uses ``'body'``, ``'header'``
and ``'footer'``;  :class:`ListBox` with a custom list walker will use the
positions the list walker returns.

Reading this value on an empty container or on any non-container widgets
(that inherit from Widget) raises an IndexError.  Writing to this property with
an invalid position will also raise an IndexError.  Writing a new value
automatically marks this widget to be redrawn and will be reflected in
``container.focus``.

::

  container.contents

is a read-only property (read/write in some cases) that provides access to a
mapping- or list-like object that contains the child widgets and the options
used for displaying those widgets in this container.  The mapping- or list-like
object always allows reading from positions with the usual ``__getitem__()``
method and may support assignment and deletion with ``__setitem__()`` and
``__delitem__()`` methods.  The values are ``(child widget, option)`` tuples.
When this object or its contents are modified the widget is automatically
flagged to be redrawn.

:class:`Columns`, :class:`Pile` and :class:`GridFlow` allow assigning an
iterable to ``container.contents`` to overwrite the values in
with the ones provided.

:class:`Columns`, :class:`Pile`, :class:`GridFlow`, :class:`Overlay` and
:class:`Frame` support ``container.contents`` item assignment and deletion.

::

  container.options(...)

is a method that returns options objects for use in items added to
``container.contents``.  The arguments are specific to the container type,
and generally match the ``__init__()`` arguments for the container.
The objects returned are currently tuples of strings and integers or ``None``
for containers without child widget options.  This method exists to allow
future versions of Urwid to add new options to existing containers.  Code
that expects the option tuples to remain the same size will fail when new
options are added, so defensive programming with options tuples is strongly
encouraged.

::

  container.__getitem__(x)
  # a.k.a.
  container[x]

is a short-cut method behaving identically to:
``container.contents[x][0].base_widget``.
Which means roughly "give me the child widget at position *x* and skip all
the decoration widgets wrapping it".  Decoration widgets include
:class:`Padding`, :class:`Filler`, :class:`AttrMap` etc.

::

  container.get_focus_path()

is a method that returns the focus position for this container *and* all child
containers along the path defined by their focus settings.  This list of
positions is the closest thing we have to the singular widget-in-focus in
other UI frameworks, because the ultimate widget in focus in Urwid depends
on the focus setting of all its parent container widgets.

::

  container.set_focus_path(p)

is a method that assigns to the focus_position property of each container
along the path given by the list of positions *p*.  It may be used to restore
focus to a widget as returned by a previous call to ``container.get_focus_path()``.

::

  container.get_focus_widgets()

is a method that returns the ``.focus`` values starting from this container
and proceeding along each child widget until reaching a leaf
(non-container) widget.

Note that the list does not contain the topmost container widget
(i.e., on which this method is called), but does include the
lowest leaf widget.

::

  container.__iter__()
  # typically
  for x in container: ...

  container.__reversed__()
  # a.k.a
  reversed(container)

are methods that allow iteration over the *positions* of this container.
Normally the order of the positions generated by __reversed__() will be the
opposite of __iter__().  The exception is the case of :class:`ListBox` with
certain custom list walkers, and the reason goes back to the original way list
walker interface was defined.  Note that a custom list walker might also generate
an unbounded number of positions, so care should be used with this interface and
:class:`ListBox`.


Pile Widgets
------------

:class:`Pile` widgets are used to combine multiple widgets by stacking them vertically.
A Pile can manage selectable widgets by keeping track
of which widget is in focus and it can handle moving the focus between widgets
when the user presses the *UP* and *DOWN* keys.
A Pile will also work well when used within a :class:`ListBox`.

A Pile is selectable only if its focus widget is selectable. If you create a
Pile containing one Text widget and one Edit widget the Pile will choose the
Edit widget as its default focus widget.


Columns Widgets
---------------

:class:`Columns` widgets may be used to arrange either flow widgets,
box widgets or fixed widgets horizontally into columns.
Columns widgets will manage selectable widgets by keeping track of which column is in focus and it can
handle moving the focus between columns when the user presses the *LEFT* and *RIGHT* keys.
Columns widgets also work well when used within a :class:`ListBox`.

Columns widgets are selectable only if the column in focus is selectable.
If a focus column is not specified the first selectable widget will be chosen as the
focus column.


GridFlow Widgets
----------------

The :class:`GridFlow` widget is a fixed/flow widget designed for use
with :class:`Button`, :class:`CheckBox` and :class:`RadioButton` widgets.
It renders all the widgets it contains the same width
and it arranges them from left to right and top to bottom.

The GridFlow widget uses Pile, Columns, Padding and Divider widgets to build a
display widget that will handle the keyboard input and rendering. When the
GridFlow widget is resized it regenerates the display widget to accommodate the
new space.


Overlay Widgets
---------------

The :class:`Overlay` widget is a box/flow/fixed widget that contains two other widgets.
The bottom widget is BOX rendered the full size of the Overlay widget
and the top widget is placed on top, obscuring an area of the bottom widget.
Widget sizing depends on the top widget sizing and sizing options.
This widget can be used to create effects such as overlapping "windows" or pop-up menus.

The Overlay widget always treats the top widget as the one in focus.
All keyboard input will be passed to the top widget.

If you want to use a flow widget for the bottom widget,
first wrap the flow widget with a :class:`Filler` widget.


Scrollable Widgets
==================
Scrollable widgets can scroll long content which normally not fit into the screen resolution.
Scrolling is normally supported using keyboard positioning keys and mouse wheel.

For scrolling is expected to be used :class:`ScrollBar` which accept any BOX widget supporting scrollable api.

ScrollBar Widget
----------------
The :class:`ScrollBar` widget draw optional scrollbar on the right or left side of widget if scrolling is required
(widget not fit in the desired amount of rows).
The widget is always rendered BOX and wrapped widget is also should be BOX.
In case of other widget types, they should be wrapped to support BOX sizing.

* :class:`Scrollable` make any FLOW or FIXED widget compatible with scrollable api.
* :class:`ListBox` can be used with :class:`ScrollBar` directly,
  all input is always handled by :class:`ListBox` and it's widgets.

Keyboard positioning keys and mouse wheel is used for scrolling if not handled by the wrapped widget.
Scrolling is done on per-line basis.

Scrollable Widget
-----------------
The :class:`Scrollable` widget is used to make  any FLOW or FIXED widget compatible with scrollable api by changing
target widget render and scrolling along full-size / columns defined sized widget.

Keyboard positioning keys and mouse wheel is used for scrolling if not handled by the wrapped widget.

.. note::
    :class:`Scrollable` do not recognize if selectable wrapped widget part visible or not
    if wrapped widget do not draw cursor.
    This means, keyboard input maybe forwarded to the wrapped selectable widget even if it not visible.
    **Mouse input is not affected by this limitation** due to known column and raw of mouse event.


Comparing to the :class:`ListBox`,
:class:`Scrollable` handles fixed and flow widgets directly instead of using list of small widgets.
:class:`ListBox` should be used to scroll between widgets, which can be multiline by itself.

.. _scrollable-api:

Scrollable API
--------------
Widget pretending to be scrolled via :class:`ScrollBar` should subclass :class:`Widget`
and implement positioning API:

.. currentmodule:: Scrollable

.. py:method:: get_scrollpos(size=None, focus=False)

    Get scrolling position

    :param size: widget render size. If `size` is not given, the currently rendered number of rows is returned.
    :type size: tuple[int, int] | None
    :param focus: widget is focused
    :type focus: bool
    :return: the index of the first visible row.
    :rtype: int

.. py:method:: set_scrollpos(position: SupportsInt)

    **Optional** method for setting scrolling position.

    Method is called on mouse wheel scroll if it implemented and mouse event was not handled by widget.

    If `position` is positive it is interpreted as lines from the top.
    If `position` is negative it is interpreted as lines from the bottom.

.. py:method:: rows_max(size=None, focus=False)

    Get the total number of rows `widget` can render.

    :param size: widget render size. If `size` is not given, the currently rendered number of rows is returned.
    :type size: tuple[int, int] | None
    :param focus: widget is focused
    :type focus: bool
    :return: the number of rows for `size`
    :rtype: int

.. currentmodule:: urwid


.. _listbox-contents:

ListBox Contents
================

:class:`ListBox` is a box widget that contains flow widgets.
Its contents are displayed stacked vertically, and the
:class:`ListBox` allows the user to scroll through its content.
One of the flow widgets displayed in the :class:`ListBox` is its
focus widget.

ListBox Focus and Scrolling
---------------------------

The :class:`ListBox` is a box widget that contains flow widgets.
Its contents are displayed stacked vertically, and the
:class:`ListBox` allows the user to scroll through its content.
One of the flow widgets displayed in the :class:`ListBox` is the
focus widget. The :class:`ListBox` passes key presses to the
focus widget to allow the user to interact with it. If the focus widget does
not handle a keypress then the :class:`ListBox` may handle the
keypress by scrolling and/or selecting another widget to become the focus
widget.

The :class:`ListBox` tries to do the most sensible thing when
scrolling and changing focus. When the widgets displayed are all
:class:`Text` widgets or other unselectable widgets then the
:class:`ListBox` will behave like a web browser does when the
user presses *UP*, *DOWN*, *PAGE UP* and *PAGE DOWN*: new text is immediately
scrolled in from the top or bottom. The :class:`ListBox` chooses
one of the visible widgets as its focus widget when scrolling. When scrolling
up the :class:`ListBox` chooses the topmost widget as the focus,
and when scrolling down the :class:`ListBox` chooses the
bottommost widget as the focus.

The :class:`ListBox` remembers the location of the widget in
focus as either an "offset" or an "inset". An offset is the number of rows
between the top of the :class:`ListBox` and the beginning of the
focus widget. An offset of zero corresponds to a widget with its top aligned
with the top of the :class:`ListBox`. An inset is the fraction
of rows of the focus widget that are "above" the top of the
:class:`ListBox` and not visible. The
:class:`ListBox` uses this method of remembering the focus
widget location so that when the :class:`ListBox` is resized the
text displayed will stay roughly aligned with the top of the
:class:`ListBox`.

When there are selectable widgets in the :class:`ListBox` the
focus will move between the selectable widgets, skipping the unselectable
widgets. The :class:`ListBox` will try to scroll all the rows of
a selectable widget into view so that the user can see the new focus widget in
its entirety. This behavior can be used to bring more than a single widget into
view by using composite widgets to combine a selectable widget with other
widgets that should be displayed at the same time.


Dynamic ListBox with ListWalker
-------------------------------

While the :class:`ListBox` stores the location of its focus
widget, it does not directly store the actual focus widget or other contents of
the :class:`ListBox`. The storage of a
:class:`ListBox`'s content is delegated to a "List Walker"
object. If a list of widgets is passed to the :class:`ListBox`
constructor then it creates a :class:`SimpleListWalker` object
to manage the list.

When the :class:`ListBox` is `rendering a canvas`_ or `handling
input`_ it will:

.. _rendering a canvas: :meth:`ListBox.render`
.. _handling input: :meth:`ListBox.keypress`

1. Call the :meth:`get_focus` method of its list walker object. This method
   will return the focus widget and a position object.
2. Optionally call the :meth:`get_prev` method of its List Walker object one or
   more times, initially passing the focus position and then passing the new
   position returned on each successive call. This method will return the
   widget and position object "above" the position passed.
3. Optionally call the :meth:`get_next` method of its List Walker object one or
   more times, similarly, to collect widgets and position objects "below" the
   focus position.
4. Optionally call the :meth:`set_focus` method passing one of the position
   objects returned in the previous steps.

This is the only way the :class:`ListBox` accesses its contents,
and it will not store copies of any of the widgets or position objects beyond
the current rendering or input handling operation.

The :class:`SimpleListWalker` stores a list of widgets, and uses
integer indexes into this list as its position objects. It stores the focus
position as an integer, so if you insert a widget into the list above the focus
position then you need to remember to increment the focus position in the
:class:`SimpleListWalker` object or the contents of the
:class:`ListBox` will shift.

A custom List Walker object may be passed to the
:class:`ListBox` constructor instead of a plain list of widgets.
List Walker objects must implement the :ref:`list-walker-interface`.

The fib.py_ example program demonstrates a custom list walker that doesn't
store any widgets. It uses a tuple of two successive Fibonacci numbers as its
position objects and it generates Text widgets to display the numbers on the
fly. The result is a :class:`ListBox` that can scroll through an
unending list of widgets.

The edit.py_ example program demonstrates a custom list walker that loads lines
from a text file only as the user scrolls them into view. This allows even
huge files to be opened almost instantly.

The browse.py_ example program demonstrates a custom list walker that uses a
tuple of strings as position objects, one for the parent directory and one for
the file selected. The widgets are cached in a separate class that is accessed
using a dictionary indexed by parent directory names. This allows the
directories to be read only as required. The custom list walker also allows
directories to be hidden from view when they are "collapsed".

.. _fib.py: https://github.com/urwid/urwid/blob/master/examples/fib.py
.. _edit.py: https://github.com/urwid/urwid/blob/master/examples/edit.py
.. _browse.py: https://github.com/urwid/urwid/blob/master/examples/browse.py


Setting the Focus
-----------------

The easiest way to change the current :class:`ListBox` focus is
to call the :meth:`ListBox.set_focus` method. This method doesn't
require that you know the :class:`ListBox`'s current dimensions
``(maxcol, maxrow)``. It will wait until the next call to either keypress or
render to complete setting the offset and inset values using the dimensions
passed to that method.

The position object passed to :meth:`set_focus` must be compatible with the
List Walker object that the :class:`ListBox` is using. For
:class:`SimpleListWalker` the position is the integer index of
the widget within the list.

The *coming_from* parameter should be set if you know that the old position is
"above" or "below" the previous position. When the
:class:`ListBox` completes setting the offset and inset values
it tries to find the old widget among the visible widgets. If the old widget is
still visible, if will try to avoid causing the :class:`ListBox`
contents to scroll up or down from its previous position. If the widget is not
visible, then the :class:`ListBox` will:

* Display the new focus at the bottom of the :class:`ListBox` if
  *coming_from* is "above".
* Display the new focus at the top of the :class:`ListBox` if
  *coming_from* is "below".
* Display the new focus in the middle of the :class:`ListBox` if
  coming_from is ``None``.

If you know exactly where you want to display the new focus widget within the
:class:`ListBox` you may call
:meth:`ListBox.set_focus_valign`.  This method lets you specify
the *top*, *bottom*, *middle*, a relative position or the exact number of rows
from the top or bottom of the :class:`ListBox`.

List Walkers
------------

:class:`ListBox` does not manage the widgets it displays
directly, instead it passes that task to a class called a "list walker". List
walkers keep track of the widget in focus and provide an opaque position object
that the :class:`ListBox` may use to iterate through widgets
above and below the focus widget.

A :class:`SimpleFocusListWalker`
is a list walker that behaves like a normal Python list. It may be used any
time you will be displaying a moderate number of widgets.

If you need to display a large number of widgets you should implement your own
list walker that manages creating widgets as they are requested and destroying
them later to avoid excessive memory use.

List walkers may also be used to display tree or other structures within a
:class:`ListBox`. A number of the `example programs
`_ demonstrate the use of custom list
walker classes.

.. seealso:: :class:`ListWalker base class reference `


.. _list-walker-interface:

List Walker Interface
---------------------

List Walker API Version 1
~~~~~~~~~~~~~~~~~~~~~~~~~

This API will remain available and is still the least restrictive option fo the programmer.
Your class should subclass :class:`ListWalker`.
Whenever the focus or content changes you are responsible for calling :meth:`ListWalker._modified`.

.. currentmodule:: MyV1ListWalker

.. method:: get_focus()

   return a ``(widget, position)`` tuple or ``(None, None)`` if empty

.. method:: set_focus(position)

   set the focus and call ``self._modified()`` or raise an :exc:`IndexError`.

.. method:: get_next(position)

   return the ``(widget, position)`` tuple below *position* passed
   or ``(None, None)`` if there is none.

.. method:: get_prev(position)

   return the ``(widget, position)`` tuple above *position* passed
   or ``(None, None)`` if there is none.

List Walker API Version 2
~~~~~~~~~~~~~~~~~~~~~~~~~

This API is an attempt to remove some of the duplicate code that V1 requires for
many users.  List walker API V1 will be implemented automatically by
subclassing :class:`ListWalker` and implementing the V2 methods.
Whenever the focus or content changes you are responsible for
calling :meth:`ListWalker._modified`.

.. currentmodule:: MyV2ListWalker

.. method:: __getitem__(position)

   return widget at *position* or raise an :exc:`IndexError` or :exc:`KeyError`

.. method:: next_position(position)

   return the position below passed *position* or raise an :exc:`IndexError` or :exc:`KeyError`

.. method:: prev_position(position)

   return the position above passed *position* or raise an :exc:`IndexError` or :exc:`KeyError`

.. method:: set_focus(position)

   set the focus and call ``self._modified()`` or raise an :exc:`IndexError`.

.. attribute:: focus

   attribute or property containing the focus position, or define
   :meth:`MyV1ListWalker.get_focus` as above

List Walker Iteration
~~~~~~~~~~~~~~~~~~~~~

There is an optional iteration helper method that may be defined in any list walker.
When this is defined it will be used by :meth:`ListBox.__iter__` and
:meth:`ListBox.__reversed__`:

.. method:: positions(reverse=False)

   return a forward or reverse iterable of positions

.. currentmodule:: urwid

Custom Widgets
==============

Widgets in Urwid are easiest to create by extending other widgets. If you are
making a new type of widget that can use other widgets to display its content,
like a new type of button or control, then you should start by extending
:class:`WidgetWrap` and passing the display widget to its constructor.

The :class:`Widget` interface is described in detail in the
:class:`Widget base class reference ` and is useful if you're looking to modify
the behavior of an existing widget,
build a new widget class from scratch or just want a better understanding of
the library.

One Urwid design choice that stands out is that widgets typically have no
size. Widgets don't store their size on screen, and instead are
passed that information when they need it.

This choice has some advantages:

* widgets may be reused in different locations
* reused widgets only need to be rendered once per size displayed
* widgets don't need to know their parents
* less data to store and update
* no worrying about widgets that haven't received their size yet
* same widgets could be displayed at different sizes to different users
  simultaneously

It also has disadvantages:

* difficult to determine a widget's size on screen
* more parameters to parse
* duplicated size calculations across methods

For determining a widget's size on screen it is possible to look up the size(s)
it was rendered at in the :class:`CanvasCache`. There are plans
to address some of the duplicated size handling code in the container widgets
in a future Urwid release.

The same holds true for a widget's focus state, so that too is passed in to
functions that need it.


Modifying Existing Widgets
--------------------------

The easiest way to create a custom widget is to modify an existing widget.
This can be done by either subclassing the original widget or by wrapping it.
Subclassing is appropriate when you need to interact at a very low level with
the original widget, such as if you are creating a custom edit widget with
different behavior than the usual Edit widgets. If you are creating a custom
widget that doesn't need tight coupling with the original widget then
wrapping is more appropriate.

The :class:`WidgetWrap` class simplifies wrapping existing
widgets. You can create a custom widget simply by creating a subclass of
WidgetWrap and passing a widget into WidgetWrap's constructor.


This is an example of a custom widget that uses WidgetWrap:

.. literalinclude:: wmod.py
   :linenos:

The above code creates a group of RadioButtons and provides a method to
query the state of the buttons.


Widgets from Scratch
--------------------

Widgets must inherit from :class:`Widget`.
Box widgets must implement :meth:`Widget.selectable` and :meth:`Widget.render`
methods, and flow widgets must implement :meth:`Widget.selectable`,
:meth:`Widget.render` and :meth:`Widget.rows` methods.

The default :meth:`Widget.sizing` method returns a set of sizing modes supported
from ``self._sizing``, so we define ``_sizing`` attributes for our flow and
box widgets below.

.. literalinclude:: wanat.py
   :linenos:

The above code implements two widget classes. Pudding is a flow widget and
BoxPudding is a box widget. Pudding will render as much "Pudding" as will fit
in a single row, and BoxPudding will render as much "Pudding" as will fit into
the entire area given.

Note that the rows and render methods' focus parameter must have a default
value of False. Also note that for flow widgets the number of rows returned by
the rows method must match the number of rows rendered by the render method.

To improve the efficiency of your Urwid application you should be careful of
how long your ``rows()`` methods take to execute. The ``rows()`` methods may be called many
times as part of input handling and rendering operations. If you are using a
display widget that is time consuming to create you should consider caching it
to reduce its impact on performance.

It is possible to create a widget that will behave as either a flow widget or
box widget depending on what is required:

.. literalinclude:: wanat_multi.py
   :linenos:

MultiPudding will work in place of either Pudding or BoxPudding above. The
number of elements in the size tuple determines whether the containing widget
is expecting a flow widget or a box widget.


Selectable Widgets
------------------

Selectable widgets such as Edit and Button widgets allow the user to interact
with the application. A widget is selectable if its selectable method returns
True. Selectable widgets must implement the :meth:`Widget.keypress` method to
handle keyboard input.

.. literalinclude:: wsel.py

The SelectablePudding widget will display its contents in uppercase when it is
in focus, and it allows the user to "eat" the pudding by pressing each of the
letters *P*, *U*, *D*, *D*, *I*, *N* and *G* on the keyboard. When the user has
"eaten" all the pudding the widget will reset to its initial state.

Note that keys that are unhandled in the keypress method are returned so that
another widget may be able to handle them. This is a good convention to follow
unless you have a very good reason not to. In this case the *UP* and *DOWN*
keys are returned so that if this widget is in a
:class:`ListBox` the :class:`ListBox` will behave
as the user expects and change the focus or scroll the
:class:`ListBox`.


Widget Displaying the Cursor
----------------------------

Widgets that display the cursor must implement the
:meth:`Widget.get_cursor_coords` method.
Similar to the rows method for flow widgets, this method lets other widgets
make layout decisions without rendering the entire widget. The
:class:`ListBox` widget in particular uses get_cursor_coords to
make sure that the cursor is visible within its focus widget.

.. literalinclude:: wcur1.py
   :linenos:

CursorPudding will let the user move the cursor through the widget by pressing
*LEFT* and *RIGHT*. The cursor must only be added to the canvas when the widget
is in focus. The get_cursor_coords method must always return the same cursor
coordinates that render does.

A widget displaying a cursor may choose to implement :meth:`Widget.get_pref_col`.
This method
returns the preferred column for the cursor, and is called when the focus is
moving up or down off this widget.

Another optional method is :meth:`Widget.move_cursor_to_coords`. This method allows other
widgets to try to position the cursor within this widget. The
:class:`ListBox` widget uses :meth:`Widget.move_cursor_to_coords` when
changing focus and when the user pressed *PAGE UP* or *PAGE DOWN*. This method
must return ``True`` on success and ``False`` on failure. If the cursor may be
placed at any position within the row specified (not only at the exact column
specified) then this method must move the cursor to that position and return
``True``.

.. literalinclude:: wcur2.py
   :linenos:
   :lines: 7-


Widget Metaclass
================

The :class:`Widget` base class has a metaclass defined that
creates a ``__super`` attribute for calling your superclass:
``self.__super`` is the same as the usual ``super(MyClassName, self)``.
This shortcut is of little use with Python 3's new ``super()`` syntax, but
will likely be retained for backwards compatibility in future versions.

This metaclass also uses :class:`MetaSignal`
to allow signals to be defined as a list of signal names
in a ``signals`` class attribute.  This is equivalent to calling
:func:`register_signal` with the class name and list of signals and all those
defined in superclasses after the class definition.

.. seealso::

    :class:`Widget metaclass WidgetMeta `


urwid-2.6.16/docs/manual/wmod.py000066400000000000000000000011011470350774000164650ustar00rootroot00000000000000from __future__ import annotations

import urwid


class QuestionnaireItem(urwid.WidgetWrap[urwid.GridFlow]):
    def __init__(self) -> None:
        self.options: list[urwid.RadioButton] = []
        unsure = urwid.RadioButton(self.options, "Unsure")
        yes = urwid.RadioButton(self.options, "Yes")
        no = urwid.RadioButton(self.options, "No")
        display_widget = urwid.GridFlow([unsure, yes, no], 15, 3, 1, "left")
        super().__init__(display_widget)

    def get_state(self) -> str:
        return next(o.label for o in self.options if o.state is True)
urwid-2.6.16/docs/manual/wsel.py000066400000000000000000000021431470350774000165000ustar00rootroot00000000000000from __future__ import annotations

import urwid


class SelectablePudding(urwid.Widget):
    _sizing = frozenset((urwid.FLOW,))
    _selectable = True

    def __init__(self) -> None:
        super().__init__()
        self.pudding = "pudding"

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        return 1

    def render(self, size: tuple[int], focus: bool = False) -> urwid.TextCanvas:
        (maxcol,) = size
        num_pudding = maxcol // len(self.pudding)
        pudding = self.pudding
        if focus:
            pudding = pudding.upper()
        return urwid.TextCanvas([pudding.encode("utf-8") * num_pudding], maxcol=maxcol)

    def keypress(self, size: tuple[int], key: str) -> str | None:
        if len(key) > 1:
            return key

        if key.lower() in self.pudding:
            # remove letter from pudding
            n = self.pudding.index(key.lower())
            self.pudding = self.pudding[:n] + self.pudding[n + 1 :]
            if not self.pudding:
                self.pudding = "pudding"
            self._invalidate()
            return None

        return key
urwid-2.6.16/docs/reference/000077500000000000000000000000001470350774000156355ustar00rootroot00000000000000urwid-2.6.16/docs/reference/attrspec.rst000066400000000000000000000001411470350774000202100ustar00rootroot00000000000000Raw Display Attributes
======================

.. currentmodule:: urwid

.. autoclass:: AttrSpec
urwid-2.6.16/docs/reference/canvas.rst000066400000000000000000000007051470350774000176440ustar00rootroot00000000000000Canvas Classes and Functions
============================

.. currentmodule:: urwid

Canvas Classes
--------------

.. autoclass:: Canvas

.. autoclass:: TextCanvas

.. autoclass:: BlankCanvas

.. autoclass:: SolidCanvas

.. autoclass:: CompositeCanvas

CompositeCanvas Builders
------------------------

.. autofunction:: CanvasCombine

.. autofunction:: CanvasJoin

.. autofunction:: CanvasOverlay

CanvasCache
-----------

.. autoclass:: CanvasCache
urwid-2.6.16/docs/reference/command_map.rst000066400000000000000000000001151470350774000206370ustar00rootroot00000000000000Command Map
-----------

.. currentmodule:: urwid

.. autoclass:: CommandMap
urwid-2.6.16/docs/reference/constants.rst000066400000000000000000000164061470350774000204120ustar00rootroot00000000000000Constants
=========

.. currentmodule:: urwid

.. note::

   These constants may be used, but using the string values themselves in
   your program is equally supported.  These constants are used internally
   by urwid just to avoid possible misspelling, but the example programs
   and tutorials tend to use the string values.

Widget Sizing Methods
---------------------

One or more of these values returned by :meth:`Widget.sizing` to indicate
supported sizing methods.

.. data:: FLOW
   :annotation: = 'flow'

   Widget that is given a number of columns by its parent widget and
   calculates the number of rows it requires for rendering
   e.g. :class:`Text`

.. data:: BOX
   :annotation: = 'box'

   Widget that is given a number of columns and rows by its parent
   widget and must render that size
   e.g. :class:`ListBox`

.. data:: FIXED
   :annotation: = 'fixed'

   Widget that knows the number of columns and rows it requires and will
   only render at that exact size
   e.g. :class:`BigText`


Horizontal Alignment
--------------------

Used to horizontally align text in :class:`Text` widgets and child widgets
of :class:`Padding` and :class:`Overlay`.

.. data:: LEFT
   :annotation: = 'left'

.. data:: CENTER
   :annotation: = 'center'

.. data:: RIGHT
   :annotation: = 'right'

Veritcal Alignment
------------------

Used to vertically align child widgets of :class:`Filler` and
:class:`Overlay`.

.. data:: TOP
   :annotation: = 'top'

.. data:: MIDDLE
   :annotation: = 'middle'

.. data:: BOTTOM
   :annotation: = 'bottom'

Width and Height Settings
-------------------------

Used to distribute or set widths and heights of child widgets of
:class:`Padding`, :class:`Filler`, :class:`Columns`,
:class:`Pile` and :class:`Overlay`.

.. data:: PACK
   :annotation: = 'pack'

   Ask the child widget to calculate the number of columns or rows it needs

.. data:: GIVEN
   :annotation: = 'given'

   A set number of columns or rows, e.g. ('given', 10) will have exactly
   10 columns or rows given to the child widget

.. data:: RELATIVE
   :annotation: = 'relative'

   A percentage of the total space, e.g. ('relative', 50) will give half
   of the total columns or rows to the child widget

.. data:: RELATIVE_100
   :annotation: = ('relative', 100)

.. data:: WEIGHT
   :annotation: = 'weight'

   A weight value for distributing columns or rows, e.g. ('weight', 3)
   will give 3 times as many columns or rows as another widget in the same
   container with ('weight', 1).


Text Wrapping Modes
-------------------

.. data:: SPACE
   :annotation: = 'space'

   wrap text on space characters or at the boundaries of wide characters

.. data:: ANY
   :annotation: = 'any'

   wrap before any wide or narrow character that would exceed the available
   screen columns

.. data:: CLIP
   :annotation: = 'clip'

   clip before any wide or narrow character that would exceed the available
   screen columns and don't display the remaining text on the line

.. data:: ELLIPSIS
   :annotation: = 'ellipsis'

   clip if text would exceed the available screen columns, add an ellipsis
   character at the end


Foreground and Background Colors
--------------------------------

Standard background and foreground colors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. data:: BLACK
   :annotation: = 'black'

.. data:: DARK_RED
   :annotation: = 'dark red'

.. data:: DARK_GREEN
   :annotation: = 'dark green'

.. data:: BROWN
   :annotation: = 'brown'

.. data:: DARK_BLUE
   :annotation: = 'dark blue'

.. data:: DARK_MAGENTA
   :annotation: = 'dark magenta'

.. data:: DARK_CYAN
   :annotation: = 'dark cyan'

.. data:: LIGHT_GRAY
   :annotation: = 'light gray'

Standard foreground colors (not safe to use as background)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. data:: DARK_GRAY
   :annotation: = 'dark gray'

.. data:: LIGHT_RED
   :annotation: = 'light red'

.. data:: LIGHT_GREEN
   :annotation: = 'light green'

.. data:: YELLOW
   :annotation: = 'yellow'

.. data:: LIGHT_BLUE
   :annotation: = 'light blue'

.. data:: LIGHT_MAGENTA
   :annotation: = 'light magenta'

.. data:: LIGHT_CYAN
   :annotation: = 'light cyan'

.. data:: WHITE
   :annotation: = 'white'

User's terminal configuration default foreground or background
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. note::

   There is no way to tell if the user's terminal has a light
   or dark color as their default foreground or background, so
   it is highly recommended to use this setting for both foreground
   and background when you do use it.

.. data:: DEFAULT
   :annotation: = 'default'


256 and 88 Color Foregrounds and Backgrounds
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Constants are not defined for these colors.

.. seealso::

   :ref:`high-colors`
 


Signal Names
------------

.. data:: UPDATE_PALETTE_ENTRY
   :annotation: = 'update palette entry'

   sent by :class:`BaseScreen` (and subclasses like 
   :class:`raw_display.Screen`) when a palette entry is changed.
   :class:`MainLoop` handles this signal by redrawing the whole
   screen.

.. data:: INPUT_DESCRIPTORS_CHANGED
   :annotation: = 'input descriptors changed'

   sent by :class:`BaseScreen` (and subclasses like 
   :class:`raw_display.Screen`) when the list of input file descriptors
   has changed.  :class:`MainLoop` handles this signal by updating
   the file descriptors being watched by its event loop.


Command Names
-------------

Command names are used as values in :class:`CommandMap` instances.
Widgets look up the command associated with keypresses in their
:meth:`Widget.keypress` methods.

You may define any new command names as you wish and look for them in
your own widget code.  These are the standard ones expected by code
included in Urwid.

.. data:: REDRAW_SCREEN
   :annotation: = 'redraw screen'

   Default associated keypress: 'ctrl l'

   :meth:`MainLoop.process_input` looks for this command to force
   a screen refresh. This is useful in case the screen becomes
   corrupted.

.. data:: ACTIVATE
   :annotation: = 'activate'

   Default associated keypresses: ' ' (space), 'enter'

   Activate a widget such as a :class:`Button`, :class:`CheckBox`
   or :class:`RadioButton`.

.. data:: CURSOR_UP
   :annotation: = 'cursor up'

   Default associated keypress: 'up'

   Move the cursor or selection up one row.

.. data:: CURSOR_DOWN
   :annotation: = 'cursor down'

   Default associated keypress: 'down'

   Move the cursor or selection down one row.

.. data:: CURSOR_LEFT
   :annotation: = 'cursor left'

   Default associated keypress: 'left'

   Move the cursor or selection left one column.

.. data:: CURSOR_RIGHT
   :annotation: = 'cursor right'

   Default associated keypress: 'right'

   Move the cursor or selection right one column.

.. data:: CURSOR_PAGE_UP
   :annotation: = 'cursor page up'

   Default associated keypress: 'page up'

   Move the cursor or selection up one page.

.. data:: CURSOR_PAGE_DOWN
   :annotation: = 'cursor page down'

   Default associated keypress: 'page down'

   Move the cursor or selection down one page.

.. data:: CURSOR_MAX_LEFT
   :annotation: = 'cursor max left'

   Default associated keypress: 'home'

   Move the cursor or selection to the leftmost column.

.. data:: CURSOR_MAX_RIGHT
   :annotation: = 'cursor max right'

   Default associated keypress: 'end'

   Move the cursor or selection to the rightmost column.

urwid-2.6.16/docs/reference/deprecated.rst000066400000000000000000000002521470350774000204660ustar00rootroot00000000000000Deprecated Classes
------------------

.. currentmodule:: urwid

.. autoclass:: FlowWidget

.. autoclass:: BoxWidget

.. autoclass:: FixedWidget

.. autoclass:: AttrWrap
urwid-2.6.16/docs/reference/display_modules.rst000066400000000000000000000012451470350774000215660ustar00rootroot00000000000000Display Modules
===============

.. currentmodule:: urwid

.. autoclass:: BaseScreen

RAW display
-----------

.. module:: urwid.display.raw

.. autoclass:: Screen

Curses display
--------------

.. module:: urwid.display.curses

.. autoclass:: Screen

WEB display
-----------

.. module:: urwid.display.web

.. autoclass:: Screen

HTML fragment
-------------

.. module:: urwid.display.html_fragment

.. autoclass:: HtmlGenerator

.. autofunction:: screenshot_init

.. autofunction:: screenshot_collect

LCD display
-----------

.. module:: urwid.display.lcd

.. autoclass:: LCDScreen

.. autoclass:: CFLCDScreen

.. autoclass:: CF635Screen

.. autoclass:: KeyRepeatSimulator
urwid-2.6.16/docs/reference/exceptions.rst000066400000000000000000000006241470350774000205520ustar00rootroot00000000000000Exceptions
==========

.. currentmodule:: urwid

.. autoexception:: ExitMainLoop

.. autoexception:: WidgetError

.. autoexception:: ListBoxError

.. autoexception:: ColumnsError

.. autoexception:: PileError

.. autoexception:: GridFlowError

.. autoexception:: BoxAdapterError

.. autoexception:: OverlayError

.. autoexception:: TextError

.. autoexception:: EditError

.. autoexception:: CanvasError
urwid-2.6.16/docs/reference/global_settings.rst000066400000000000000000000002441470350774000215470ustar00rootroot00000000000000Global Settings
===============

.. currentmodule:: urwid

.. autofunction:: set_encoding

.. autofunction:: get_encoding_mode

.. autofunction:: supports_unicode

urwid-2.6.16/docs/reference/index.rst000066400000000000000000000004221470350774000174740ustar00rootroot00000000000000.. _urwid-reference:

###############
Urwid Reference
###############

.. toctree::

   main_loop
   widget
   display_modules
   list_walkers
   signals
   global_settings
   attrspec
   canvas
   text_layout
   command_map
   constants
   exceptions
   meta
   deprecated
urwid-2.6.16/docs/reference/list_walkers.rst000066400000000000000000000005441470350774000210750ustar00rootroot00000000000000List Walker Classes
===================

.. currentmodule:: urwid

ListWalker
----------

.. autoclass:: ListWalker

List-like List Walkers
----------------------

.. autoclass:: SimpleFocusListWalker

.. autoclass:: SimpleListWalker

TreeWalker and Nodes
--------------------

.. autoclass:: TreeWalker

.. autoclass:: TreeNode

.. autoclass:: ParentNode
urwid-2.6.16/docs/reference/main_loop.rst000066400000000000000000000010661470350774000203470ustar00rootroot00000000000000MainLoop and Event Loops
========================

.. currentmodule:: urwid

MainLoop
--------

.. autoclass:: MainLoop

SelectEventLoop
---------------

.. autoclass:: SelectEventLoop

AsyncioEventLoop
----------------

.. autoclass:: AsyncioEventLoop

TrioEventLoop
----------------

.. autoclass:: TrioEventLoop

GLibEventLoop
-------------

.. autoclass:: GLibEventLoop

TwistedEventLoop
----------------

.. autoclass:: TwistedEventLoop

TornadoEventLoop
----------------

.. autoclass:: TornadoEventLoop

ZMQEventLoop
------------

.. autoclass:: ZMQEventLoop
urwid-2.6.16/docs/reference/meta.rst000066400000000000000000000002031470350774000173100ustar00rootroot00000000000000Metaclasses
===========

.. currentmodule:: urwid

.. autoclass:: WidgetMeta

.. autoclass:: MetaSuper

.. autoclass:: MetaSignals
urwid-2.6.16/docs/reference/signals.rst000066400000000000000000000014011470350774000200230ustar00rootroot00000000000000Signal Functions
================

.. currentmodule:: urwid

The :func:`urwid.\*_signal` functions use a shared Signals object instance
for tracking registered and connected signals.  There is no reason to
instantiate your own Signals object.

.. function:: connect_signal(obj, name, callback, user_arg=None, weak_args=None, user_args=None)

.. automethod:: Signals.connect

.. function:: disconnect_signal_by_key(obj, name, key)

.. automethod:: Signals.disconnect_by_key

.. function:: disconnect_signal(obj, name, callback, user_arg=None, weak_args=None, user_args=None)

.. automethod:: Signals.disconnect

.. function:: register_signal(sig_cls, signals)

.. automethod:: Signals.register

.. function:: emit_signal(obj, name, \*args)

.. automethod:: Signals.emit
urwid-2.6.16/docs/reference/text_layout.rst000066400000000000000000000002001470350774000207400ustar00rootroot00000000000000Text Layout Classes
===================

.. currentmodule:: urwid

.. autoclass:: TextLayout

.. autoclass:: StandardTextLayout
urwid-2.6.16/docs/reference/widget.rst000066400000000000000000000046111470350774000176540ustar00rootroot00000000000000.. _widget-classes:

Widget Classes
==============

.. currentmodule:: urwid

Widget Base Classes
-------------------

Widget
~~~~~~

.. autoclass:: Widget
   :private-members: _invalidate, _emit

WidgetWrap
~~~~~~~~~~

.. autoclass:: WidgetWrap

WidgetDecoration
~~~~~~~~~~~~~~~~

.. autoclass:: WidgetDecoration

WidgetContainerMixin
~~~~~~~~~~~~~~~~~~~~

.. autoclass:: WidgetContainerMixin


Basic Widget Classes
--------------------

Text
~~~~

.. autoclass:: Text

Edit
~~~~

.. autoclass:: Edit

IntEdit
~~~~~~~

.. autoclass:: IntEdit

Button
~~~~~~

.. autoclass:: Button

CheckBox
~~~~~~~~

.. autoclass:: CheckBox

RadioButton
~~~~~~~~~~~

.. autoclass:: RadioButton

TreeWidget
~~~~~~~~~~

.. autoclass:: TreeWidget


.. currentmodule:: urwid.numedit

Extended Numerical Editing Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

IntegerEdit
~~~~~~~~~~~

.. autoclass:: IntegerEdit

FloatEdit
~~~~~~~~~~~

.. autoclass:: FloatEdit


.. currentmodule:: urwid

SelectableIcon
~~~~~~~~~~~~~~

.. autoclass:: SelectableIcon


Decoration Widget Classes
-------------------------

AttrMap
~~~~~~~

.. autoclass:: AttrMap

Padding
~~~~~~~

.. autoclass:: Padding

Filler
~~~~~~

.. autoclass:: Filler

Divider
~~~~~~~

.. autoclass:: Divider

LineBox
~~~~~~~

.. autoclass:: LineBox

SolidFill
~~~~~~~~~

.. autoclass:: SolidFill

PopUpLauncher
~~~~~~~~~~~~~

.. autoclass:: PopUpLauncher

PopUpTarget
~~~~~~~~~~~

.. autoclass:: PopUpTarget

WidgetPlaceholder
~~~~~~~~~~~~~~~~~

.. autoclass:: WidgetPlaceholder

WidgetDisable
~~~~~~~~~~~~~

.. autoclass:: WidgetDisable

Container Widget Classes
------------------------

Frame
~~~~~

.. autoclass:: Frame

ListBox
~~~~~~~

.. autoclass:: ListBox

TreeListBox
~~~~~~~~~~~

.. autoclass:: TreeListBox

Columns
~~~~~~~

.. autoclass:: Columns

Pile
~~~~

.. autoclass:: Pile

GridFlow
~~~~~~~~

.. autoclass:: GridFlow

BoxAdapter
~~~~~~~~~~

.. autoclass:: BoxAdapter

Overlay
~~~~~~~

.. autoclass:: Overlay

Graphic Widget Classes
----------------------

BarGraph
~~~~~~~~

.. autoclass:: BarGraph

GraphVScale
~~~~~~~~~~~

.. autoclass:: GraphVScale

ProgressBar
~~~~~~~~~~~

.. autoclass:: ProgressBar

BigText
~~~~~~~

.. autoclass:: BigText

.. autofunction:: get_all_fonts

Terminal
~~~~~~~~

.. autoclass:: Terminal


Scroll Widget Classes
---------------------

Scrollable
~~~~~~~~~~

.. autoclass:: Scrollable

ScrollBar
~~~~~~~~~

.. autoclass:: ScrollBar

.. currentmodule:: urwidurwid-2.6.16/docs/tools/000077500000000000000000000000001470350774000150375ustar00rootroot00000000000000urwid-2.6.16/docs/tools/compile_pngs.sh000077500000000000000000000006331470350774000200570ustar00rootroot00000000000000#!/bin/bash -e

# args: scripts to capture

DISPLAYNUM=5
SCREENSHOTS=`dirname $0`/screenshots.sh

XVFB=$(which Xvfb)
if [ -n $XVFB ]; then
	Xvfb :$DISPLAYNUM -screen 0 1024x768x24 &
	XVFBPID=$!
	DISPLAY=:$DISPLAYNUM # this still doesn't work
	trap "kill $XVFBPID" EXIT
fi

for script in $@; do
	echo "doing $script"
	if [ -f "${script}.xdotool" ]; then
		"$SCREENSHOTS" "$script" < "${script}.xdotool"
	fi
done
urwid-2.6.16/docs/tools/screenshots.sh000077500000000000000000000012611470350774000177360ustar00rootroot00000000000000#!/bin/bash

# $1: python script to run
# urxvt, xdotool and import are required to run this script

CLASSNAME=$(head -c 6 /dev/urandom | base64 | tr -cd [:alnum:])
PYTHON=python

urxvt -bg gray90 -b 0 +sb -fn '-misc-fixed-medium-*-*-*-*-140-*-*-*-*-*-*' \
	-fb '-misc-fixed-bold-*-*-*-*-140-*-*-*-*-*-*' \
	-name "$CLASSNAME" -e "$PYTHON" "$1" &
RXVTPID=$!
until RXVTWINDOWID=$(xdotool search --classname "$CLASSNAME"); do
	sleep 0.1
done
export RXVTWINDOWID
image=${1%.py}

c=1
while read -r line; do
	# the echo trick is needed to expand RXVTWINDOWID variable
	echo $line | xdotool -
	echo "sending $line"
	import -window "$RXVTWINDOWID" "${image}$c.png"
	(( c++ ))
done

kill $RXVTPID
urwid-2.6.16/docs/tools/static/000077500000000000000000000000001470350774000163265ustar00rootroot00000000000000urwid-2.6.16/docs/tools/static/.placeholder000066400000000000000000000000001470350774000205770ustar00rootroot00000000000000urwid-2.6.16/docs/tools/templates/000077500000000000000000000000001470350774000170355ustar00rootroot00000000000000urwid-2.6.16/docs/tools/templates/indexcontent.html000066400000000000000000000061651470350774000224350ustar00rootroot00000000000000{% extends "layout.html" %}
{% block body %}

Urwid{% if 'dev' in release %} development version{% endif %}

Console user interface library for Python

git clone https://github.com/urwid/urwid.git

Wiki:

Requirements

  • Python 3.7+ or PyPy
  • Linux, OSX, Cygwin or other unix-like OS
  • python-gi for GlibEventLoop (optional)
  • Twisted for TwistedEventLoop (optional)
  • Tornado for TornadoEventLoop (optional)
  • Trio for TrioEventLoop (optional)
  • ZMQ for ZMQEventLoop (optional)
  • Apache for web_display module (optional)
  • ncurses for curses_display module (optional)
 
{% endblock %} urwid-2.6.16/docs/tools/templates/indexsidebar.html000066400000000000000000000007061470350774000223670ustar00rootroot00000000000000

Documentation

urwid-2.6.16/docs/tools/templates/localtoc.html000066400000000000000000000007221470350774000215240ustar00rootroot00000000000000{%- if pagename.startswith('manual/') -%}

Table of contents

  • Manual
  • {%- if display_toc -%} {{ toc }} {%- endif %}
{%- elif pagename.startswith('reference/') -%}

Table of contents

  • Reference
  • {%- if display_toc -%} {{ toc }} {%- endif %}
{%- elif display_toc -%}

Table of contents

{{ toc }} {% endif %} urwid-2.6.16/docs/tutorial/000077500000000000000000000000001470350774000155425ustar00rootroot00000000000000urwid-2.6.16/docs/tutorial/adventure.py000066400000000000000000000061661470350774000201220ustar00rootroot00000000000000from __future__ import annotations import typing import urwid if typing.TYPE_CHECKING: from collections.abc import Callable, Hashable, MutableSequence class ActionButton(urwid.Button): def __init__( self, caption: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]], callback: Callable[[ActionButton], typing.Any], ) -> None: super().__init__("", on_press=callback) self._w = urwid.AttrMap(urwid.SelectableIcon(caption, 1), None, focus_map="reversed") class Place(urwid.WidgetWrap[ActionButton]): def __init__(self, name: str, choices: MutableSequence[urwid.Widget]) -> None: super().__init__(ActionButton([" > go to ", name], self.enter_place)) self.heading = urwid.Text(["\nLocation: ", name, "\n"]) self.choices = choices # create links back to ourself for child in choices: getattr(child, "choices", []).insert(0, self) def enter_place(self, button: ActionButton) -> None: game.update_place(self) class Thing(urwid.WidgetWrap[ActionButton]): def __init__(self, name: str) -> None: super().__init__(ActionButton([" * take ", name], self.take_thing)) self.name = name def take_thing(self, button: ActionButton) -> None: self._w = urwid.Text(f" - {self.name} (taken)") game.take_thing(self) def exit_program(button: ActionButton) -> typing.NoReturn: raise urwid.ExitMainLoop() map_top = Place( "porch", [ Place( "kitchen", [ Place("refrigerator", []), Place("cupboard", [Thing("jug")]), ], ), Place( "garden", [ Place( "tree", [ Thing("lemon"), Thing("bird"), ], ), ], ), Place( "street", [ Place("store", [Thing("sugar")]), Place( "lake", [Place("beach", [])], ), ], ), ], ) class AdventureGame: def __init__(self) -> None: self.log = urwid.SimpleFocusListWalker([]) self.top = urwid.ListBox(self.log) self.inventory = set() self.update_place(map_top) def update_place(self, place: Place) -> None: if self.log: # disable interaction with previous place self.log[-1] = urwid.WidgetDisable(self.log[-1]) self.log.append(urwid.Pile([place.heading, *place.choices])) self.top.focus_position = len(self.log) - 1 self.place = place def take_thing(self, thing: Thing) -> None: self.inventory.add(thing.name) if self.inventory >= {"sugar", "lemon", "jug"}: response = urwid.Text("You can make lemonade!\n") done = ActionButton(" - Joy", exit_program) self.log[:] = [response, done] else: self.update_place(self.place) game = AdventureGame() urwid.MainLoop(game.top, palette=[("reversed", "standout", "")]).run() urwid-2.6.16/docs/tutorial/adventure.py.xdotool000066400000000000000000000002371470350774000216020ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 23 16 key --window $RXVTWINDOWID Return Down Down key --window $RXVTWINDOWID Return Down key --window $RXVTWINDOWID Return urwid-2.6.16/docs/tutorial/adventure1.png000066400000000000000000000014461470350774000203330ustar00rootroot00000000000000‰PNG  IHDRÏð‚ÆoûbKGDÿ‡Ì¿ pHYs  ÒÝ~üÊIDATxÚíÛkvƒ †áoÿûœ}ôô4*ÃEË`Ð×­‘‹>'‰ì^›ðàÁƒ:ò¼qÿÜ«=¶éÜÉz_"žvê©»+ÇíÌæñÜÁ;_è•çìUéfž~ó÷«í,Öó?Ê.¶PæùrÛmφö·sž¤æËû·iBÒ_ƃsó…JËë×xÐs¾àÚöÈ3É|a˳Wah|«$N5lx”ÇæûÆžùkónóùdà%öÈòWêoNêïQš‹Í­GL†•—<×£=ýbãì#í!DGzŠrèÑYɹ>s<ËÊz}dÏsàþÙ¹W#š|}â<Õj?^ZÞ®yŠØvï_ÃodD~}p-^pÊ[›¿ ©"ã…êÚîy/¬ûle˜Z¿9y¿pWïðàÁƒU³IœMC8Á…ô=y@ûÚFv೺Ã:ëàBMòÞ>ÙÕ6Ú©®ÓžGxô˜'f¼sU&ÝÂŽÇL¦d{CÉþ½˜2?Rïä½z¸¼âzÖ/œò8# ÍdJαyÂÆ;›äN@×íÛéL¦äô†ºT³ü€ùžîë1Þ9zþ–Ç,z£#èpÞßs*—'xÚl~k@©F¬[ÖÆŠšTºdö»òùĤ*v®Z?%¼8!‹Õò5´L¾¬­+uªÏNŸT·ó¸ÉíJáñ7E–·­J§ÄzÚ\ÖVa–GëëëÑÞÿ{ýóý³Þ-•'äþ±ylTbUu¤ýT¿'½]¿U×'¶~s€šðEÍYÞæ‘ &¸Ž>å÷,xðàÁƒ¦Þ›I:*äÇÔô±¦ºúv É # yWÁëxÎ×ÃÕŽä¹ÏƒÿŠúÇ_ÊÊ~«e×rvÚéÎ<Ø>Q>9”ÏUåÃášÀJ;=–GnòØÝF©ƒúLžC?sä‘Q<ˆiŸ1<Û—OûàŠçÆûsñ®zð Ðb~ÛïÕP}'<Ò ¬u©?O\1–"ÏïòyÈCò‡<ä!yÈCò'…î"Í™\dð<» žÀžG—Y§ùs›C½ €v²®”ñï,£²¶ôà²Ð€ËþgÍú¡¾ûã WWn§NBx€Z\…gÍ-»ó(·üœ­<›gë‹!åà,ÐL¸©ºìå,ÃmG‚yüì…CC5<Èâq²w&œ©¾P} D—ÈA©Ÿâé´FZ´Öáy[¬½àU§÷®ð2?{¡ˆzÀ‰{0Ö°ÊQ6ÌùÑàöñã)¾ÚöBjëã9ôÀÚ½ÿc&h‘ñú Ï^Pú[-)úÀÓ^(ÚöŠg{áSçL`¨~Sr}aV®/‡<ä!yÈCò‡<ä!yÒxâò#dˆÌ'4ÂñÿÍ<þù‘¶Ï¼@†ÐüH’È›)?!4?Ò€@†ØüHù ¡ù‘$?!6?ÒA¿%2püFò‡<ä!yÈCò‡<ä!yžð`Ìeã#í–VÊ4S~¤ümÌåáï?`Û…PiyŠ_$Ðs8*Þ9y›‚›ú 3?Ò,P~?l˜Å±ám<^ƒù78ž *ä!yÈCò‡<ä!yÈó;¸‹LÛvAì¿0b›C0^Æãï,ÙÛ.Ü{pýþñÙÛ.Äòäo»p¯¿}“¿ê.϶ 7õAgüBþ¶ 'ýKûûqüBö¶ *‚[~¤ƒ~KÚvá„A½³eÊOñ¬¸¿3ËBE¾¬0U{Ëöïäþ—êŸÿ/Þã=<¨õC9è‘§2ªóeßF?“¶l$XS_š/“¾?òÏ)kðX÷wìyRþ0;(Fûà”óý‡-Ÿ×æ¯M\¾?¸nŸ9UÞn§ÙúMQkK¨DÓ“¶Ò/ò(oÿ€þöDÉŠ„d ÛIEND®B`‚urwid-2.6.16/docs/tutorial/adventure4.png000066400000000000000000000024401470350774000203310ustar00rootroot00000000000000‰PNG  IHDRÏð‚ÆoûbKGDÿ‡Ì¿ pHYs  ÒÝ~üÄIDATxÚí[¶¤ EÏüç™yôêÕ]%DPëðu/jpó*B„¼+<ä!yÈCò‡<ä!y~†§“è}üñ`HöqyÎóào2ÿ@™Sß A‘g¼+Ч %‹Ÿs‘ç[<êšC]hn..<_Ôº=ôã¦ÀH9Éãw€&)’ÇSôèqâó|ÊJäÁôö‘<žÏ[ûàˆçäøßÍ"xPÍbj~û¾«3õíó|ç·b^´·ÆüF<9‘‡<ä!yÈCò‡<ä!yÈCž\¬)6O0¦½Òž®bžÀ£þýì¿¡Ýó×–‚B†±GjDè¨(Ýÿ¼=¾ÖRP þÏ)÷ %Òˆ0™G4O•#W7AGû[,ÌçQ­UYKÇãv>gǚȣÅû“˜h‹ƒ?¿bD蘌‹ˆ™~ŠÇìfOæy^¢¾0XÔÒ0¿}òô\«. Þ‰—ñé ú§Æ%§C¹€¯€‘¬¯µvP,xL¿ŒÖAo(ïžl‘VÛ©ÚéˆGNðÈ9ž}¡h µ€SJ[¨×vbxCáx,&è þË[Yž—Õ]{ybô….貫{+o¨Nž }Ak–BçúvÖ÷xÞPC3{š–’WÄOñŒé /[õ…çð`ji9õ<×¾0™'ݾ0›'D_¨7Ù*k‚µ5gª aþ½áûñ›¶­°=Ri¸ùø^ȇƒÙßfð(5!’'оp’ggÏþ:O}¡gül£ÅP.ò„é møÃÑü&J-͉_ˆZüÞűámR+!ßÐz>ROŒ¡!÷|¤ž CCöùH³ ™ç#­04Ì]ïÜÉÃ<ïçyC‘‡<ä!yÈCò‡<ä!Ïýx.rÚglÁ1ñ +>SàÆËx¢âeögÎUܸüìÏ.äòÌÿì¹þvåüª³<1Ÿ]89 Æ/ÌÿìÂNÿ²þïŽ_˜ýÙ!ì|¤b~›ôÙ…óÍ“~ŠgF¼óTžç%¬‚E<'úÛ˜R€5<2ðÈOÐ‘ÐÆ¡È²¡—J‡?šÅ„¹„§9¾ÇZÔÛíuQ«dóÇÿDñ`Ï9æ1íóUÔAI·ã±”â–NûÈ]ÛÇ“Ñúá9‡…¥. ×£³ÐnµÞGz~» Ùa:y¤c¬ã¹Q:ÖtÎ×äÔÈCò‡<ä!yÈCòç©<Œw&ÏÆ;3Þ¹}ŠñÎxï<>~ï<ãweyiä!Ï»jŒ<ä!yÈCò‡<äù!¦Ç$ ã¶¿ÜHz.Èž®bžÀÓ–óoD¶¼])ü1µŒOäƒ}sãÌ]åäÉ÷Å Xiß¾¼ R‹koþl97äÙêÙä©ÀÔ‰ýmG´<ŸçÓSxÔ°•²äµrxÜYÈ㑞ñ#~7ËáÑÂüIl›½öç·ÒNUÍfí=9ãÇ|Ïïm urwid.AttrMap: button = urwid.Button(caption, on_press=callback) return urwid.AttrMap(button, None, focus_map="reversed") def sub_menu( caption: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]], choices: Iterable[urwid.Widget], ) -> urwid.Widget: contents = menu(caption, choices) def open_menu(button: urwid.Button) -> None: return top.open_box(contents) return menu_button([caption, "..."], open_menu) def menu( title: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]], choices: Iterable[urwid.Widget], ) -> urwid.ListBox: body = [urwid.Text(title), urwid.Divider(), *choices] return urwid.ListBox(urwid.SimpleFocusListWalker(body)) def item_chosen(button: urwid.Button) -> None: response = urwid.Text(["You chose ", button.label, "\n"]) done = menu_button("Ok", exit_program) top.open_box(urwid.Filler(urwid.Pile([response, done]))) def exit_program(button: urwid.Button) -> typing.NoReturn: raise urwid.ExitMainLoop() menu_top = menu( "Main Menu", [ sub_menu( "Applications", [ sub_menu( "Accessories", [ menu_button("Text Editor", item_chosen), menu_button("Terminal", item_chosen), ], ), ], ), sub_menu( "System", [ sub_menu( "Preferences", [menu_button("Appearance", item_chosen)], ), menu_button("Lock Screen", item_chosen), ], ), ], ) class CascadingBoxes(urwid.WidgetPlaceholder): max_box_levels = 4 def __init__(self, box: urwid.Widget) -> None: super().__init__(urwid.SolidFill("/")) self.box_level = 0 self.open_box(box) def open_box(self, box: urwid.Widget) -> None: self.original_widget = urwid.Overlay( urwid.LineBox(box), self.original_widget, align=urwid.CENTER, width=(urwid.RELATIVE, 80), valign=urwid.MIDDLE, height=(urwid.RELATIVE, 80), min_width=24, min_height=8, left=self.box_level * 3, right=(self.max_box_levels - self.box_level - 1) * 3, top=self.box_level * 2, bottom=(self.max_box_levels - self.box_level - 1) * 2, ) self.box_level += 1 def keypress(self, size, key: str) -> str | None: if key == "esc" and self.box_level > 1: self.original_widget = self.original_widget[0] self.box_level -= 1 return None return super().keypress(size, key) top = CascadingBoxes(menu_top) urwid.MainLoop(top, palette=[("reversed", "standout", "")]).run() urwid-2.6.16/docs/tutorial/cmenu.py.xdotool000066400000000000000000000002251470350774000207110ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 33 14 key --window $RXVTWINDOWID Return key --window $RXVTWINDOWID Return Down key --window $RXVTWINDOWID Return urwid-2.6.16/docs/tutorial/cmenu1.png000066400000000000000000000022161470350774000174410ustar00rootroot00000000000000‰PNG  IHDR)Ò3Ði0bKGDÿ‡Ì¿ pHYs  ÒÝ~ü2IDATxÚíÛ[’ã DÑÚÿ>ÙG´m‘tSBàËDLTx4¶tŒ¤ÁMkRéñý»Ø\jRH†Ú[j$Ôë¯Íš£Ôž]i¬ØÆRƒ»RH¾Zí|rxW¤B ©5¤¬ð1f—{óLb+ÕcRë?øE¤Fg vôS¥z6ÞNêõç5bþõøü“¥,ùÆŸm¦_ú̪,•_fùùÿÚø³Ùg›çHye –F{®.a–nŸ_"¥¯Ô¥Ö/ºûTèéS¯üç+º[–PØå¿]§^{œÞ;ƒBŸ=¡õô¶ì)ÁW*¥ºº÷…ëâØñòm±töýK*{òôK]¤ç¢ÇyfûBê/Rû4¤ž%u^`d]ñqÅsË\+|ºÝùÍ ºc‡…u¥ŠGá%e—ŸmñùÝiŽW&/βzï<×%?L³Óè0ÿ‰þ´’ä Tþ38ŠG™.D³Ên}™Tå0­tö»õ¡M—²ÚYè0×¥vE/Ý!­xÑ›¾$øg ªDÊ ×)«HÅ!à©ð‡ºi®KåÉ3 \Žó.~%_cz«”¾à¡b£™eÆ<3Ç}k§Ž×ÿÍ–Zª!…”§+ŒD)ÖÍ 5XŠF¢ëf,Å #QŠu3H –b…‘ܧ<Þ)¤B ©iR¤.¢YRƒ¥H]D)²¤K‘ºˆRd H –"u‘û”Ç»"…RH!5MŠÔE”"K@j°©‹(å%lÖü¤6m© Pj–€ÔWK¹¤.´yRH!õp)ŸçÚ ¡'K€C–B#t¦.AœÁAq-…†Rb”.‚V0šQRH!…Ô*RÜÝZY©‹í‚,A-ê‘BC)ÈÔ)] ­`4£6¤B ©U¤¸»µ ²R)4ÚY‚Z Õ#…†R%¨RºZÁhFmH!…R«Hqwkd ¤.>Rh´ ²µ@ªG ¥ KP ¤t)´‚ÑŒÚB )¤V‘âîÖ*ÈH]|¤Ðhd jTJA– HéR h£µ!…RH­"ÅÝ­U%ºøH¡Ñ.ÈÔ©)4”‚,A-Ò¥@Ð F3jC ©Ñí·ô ;yÁIEND®B`‚urwid-2.6.16/docs/tutorial/cmenu2.png000066400000000000000000000023061470350774000174420ustar00rootroot00000000000000‰PNG  IHDR)Ò3Ði0bKGDÿ‡Ì¿ pHYs  ÒÝ~üjIDATxÚíÝkÒ›0 †QíŸÚG|iø&l °yèLGÓ gl/N*Êb[¤öÇ7\,.å…RÞPkKyB}þZl ”Z³)ùŠ-,åÜ´BÊ{´Zyœ Ø+RH!…ÔR’y‘ê§y&±äjŸÔEÎü$RÞY‚üÚX¥Î¬¼œÔçÏçŽùÏãû’ìYÒ•¿ëÜ>ôIT^*é_"iÿÿ¬ü]í»Îs¤¢²ÙG{¢µ!Löë'ÅK¤6)O[êÿÊÏ“ K]ÒÖQ—ÒB|ŠT\–p8T¤ÚR'±ýy­(•Yç»ù­R¤.öq*`¯H!…Ô]þH!eꤧS ªRH9C½VÊÓ`ÌRk6¥€¹.Lî°Îà@êÕR!© 7ÏŠRH!5©””v]|ß…¯ešÂÍS¦Á¤¤´ëÚÃ9÷ "5–%Hi×Õ/޼PJJ»Þ<õ<ÎlÙhʯÙ’LžIgȨ§ÓÄJ ¥.¢m)9Zô‘Ž„Å^Òöœ²‹”f,+¥F©}¡sHUz_~BKKjÓëšR×ö¾áÔE\Û”œiSzeïsÈÄsœÚÌ™-L3*‘=^ªpåY;÷I½øõßì:·ô¾+R—˺Ið8Õ»)RHÝ'5û‚Ô Rò© Ôe=© Ô)ã}ß‚RA©ËzRQ© R¯í}a©ËjRq© R/½òd®‹¹MuoŠRWK­3m©gIÁÔë‰òRÎß0ÚÕERù®÷ûá—“…~çÀèþ‡,¶…3ÕþHÝ¿aôÿ9°”¤ú Ùðka‡ÞT]>‡Á|\J’¢þ’¨Ô¥dV©´÷}^Îqè·e:]:Äii«ÀÞõ £Lwh6Éý{Mª°UȈö½™ÌgoFÒ§ŠR»Î¸™¹u(†®b¥öTƒç¾C_-û|¤’+ϸÔå)—Eƒ]û>¤&”ZgA ©H©ˆÔeI©,©7KŤ. Jñk%H9Kñ».F)~­)g)溘ÛT÷¦H!…RH=QŠÔÅ(E–€”³©‹Qª7Kyì³¢”­c¯$Õ›º¼Nª;K@ ©üáô§.¯kS›"…RH=Eª÷zkb©žÔeà*b^©®,)³TÿõÖ´Rƒ©K¥XLjtRu©š©‹ÿGc­þ&ÇRYRF©úØDêBêâ½)RH!…ÔS¤H]”Ô¥ Eê+Eꢤ.©€Ôe±eLjù–äÒ¶(c”] (uI]X^Ó~B ©e¤.^_RN© Å^ õK]^_ uF K!  ¤ìR Ø îf¬ RH!…Ô,RœÝZY©KŒí‚,ÁZ uF KA–`-²K`+¸›±.H!…R³Hqvkd ¤.1Rh´ ²kÔ)4,Y‚µ@Ê.‚­ànƺ …RHÍ"ÅÙ­U%ºÄH¡Ñ.ȬRg¤Ð°d Ö)»¶‚»ë‚RÞË?,ël%s .èIEND®B`‚urwid-2.6.16/docs/tutorial/cmenu3.png000066400000000000000000000027461470350774000174530ustar00rootroot00000000000000‰PNG  IHDR)Ò3Ði0bKGDÿ‡Ì¿ pHYs  ÒÝ~üŠIDATxÚíÝa’£ àwÿ{ö=öÇfhPá¹[[ÔÆÉèW€æÙÛPêºÃÍ¥ü (E)o¨½¥<¡>ÿl¶L”Ú³+ùŠm,åܵ(E)ïÙjçyj»RŠR”¢Ô;¤ù5@qkžIŒ\Û'uAûοDÊ;KÀ¯ÿÀ*Õ²òvRŸ?ŸOÌÿ=¾/áÊ’®ü]çö©3 òRÉøÒñÿYù»ÚwçHÍÊpö ¥) ×õ“Æ!RAÊS—ú[ùyRÓR—´w”¥DéƒO‘š—%D»J©ºÔÿƒØõ¸¦JeÖùþø­RL]ìóÔ„w¥¥(u—?¥(e¤Í© ¡Š JQÊêX)ÿ –Á˜¥öìJj]XÜa­à ÔÑRSR~xJQŠR”z©´·VÇïÄ×R¦ps)Ã@L$í­K[ж…‘Ë ½uñÆ‘¥ ½upÕ3®l 4ñkaIñLZ!#ˆËiæJ ¥.ºâ†‹>Ò™P}Ã%}Ï)K°HIfDzRb”º6äR…Ñ—/h©I£®*µvô §.píShéS²rô9d 𜧂šˆRf¤‘=^J9ó,ûPnüÆov[FߊÔeÙ0™5cw)E)JQj©S¦.RL]¤˜º0u隺l¶¤NîIö¾B!¥ìR„2Aí¿ëþ†\(E)J=\jðÍ ÁØ T›5ê ÁØ T‹5, ÁØ ”]ж?ÍXJQŠR”z‹nµ³¦.s¤¨Qo0K°6(Õ"E KƒY‚µA)»l ~š±.”¢”÷òôa²Èx¡IEND®B`‚urwid-2.6.16/docs/tutorial/cmenu4.png000066400000000000000000000026121470350774000174440ustar00rootroot00000000000000‰PNG  IHDR)Ò3Ði0bKGDÿ‡Ì¿ pHYs  ÒÝ~ü.IDATxÚíÝ ’Û €aÝÿžÜ£3ÝlÖ6/„màwg:Lã8ÉWAlY lºM @êüùÌÅ¥ü BÊjm)O¨Ï_‹m¥Ö %_±…¥œC )¤¼G«•Ç©GE )¤šCJ/#R|7ï$–TÛ'ë"í~)ï\‚üÅh¥Zv^NêóçsÅüãñ}HÎ,ñÎß}údTZ*ê_"qÿÿìüÝí»Ï{¤FåäœÚ“PÂä¼ÔØDêå©Kýîü>©aY—8:ÊR!ƒo‘—K¸|T¤êR?_bçﵬTbŸïÓ•"뢧)¤zÊ)¤T´9ëT±RÎPÛJùWpP£–Z3”ÔºPÜ¡­à@jk©!Y.žRH!…Ô¤R’;tö5þN|5e —2Ê`.R’;téf‹´½Ã—HÙr ’;tqâȆR’;ôá®çµ²å ) ‘cC¢â™¸B&ȵœf¬”)ë"¡.%×F(}Ä#aö€·ÄžS.A#,)”RçF˜CªÐûÒ-5©C¯«JÝÛûÌYq)i‰©pgïsÈ%ˆç8u¨™‘)3Ê‘½^*sæYúî“rã¯ÿ&÷y¤÷Ý‘u¹­› §zŸŠRÏIm¼!Õ+%H[Öe{(mÖ)åuPÚ\P&©ÍÊ` ½o³2ȾYq‡á,ao©–3ÏÍÊ` W3›Ý^F )¤ZHª½ ¦ñ­¾» F/ÕU3gLuÍ›1–Ál(U/ƒ©ÝøËÞïË•ÁÜs°Ô{šfYŠ;4 õÛË7KÙæÍ¨¥Bv­‰ôC¹°î_<Úk†‘¶ æôr*5»tºxˆ“ܳn•2Ï0R–Áž/6ž–Ê´·H9Ì›•”Ô—|ÑJɬRµ2˜¾ï¾K_M7n”"뢧Š#…ROI"…RH!…RF©Ë`I­NH!…RH!…RH=,µø¼$?©Õç%¹I-?/ÉKjýyINRÌK:HUr Hå].zŸÔN#ºQj£³«Ô>gžf)b )¤Bê=RÅÚâ*G­TûnÔo]45 ¢X¸Yª\1[ûlµ²bw){Ø>#å2ùh©JmqùŤôÐaÞBHÒ²Daâ?ã:Séÿ"†§)KÉ{;¥4K†RàT­ðc­…euËSâjøïÎ×åF%û–æ“ º)E©• nR%cÖÅ"uY ;-%ç9Ke©è±¥Šd̺˜¤¢àÊÅTÐIu|#;K². ãT­á$eˆ©òØä3í=µ¾n8?¤š©”%¯iìDç=ýKÏʽ\Í …RH-)e˺l$e˺ …T$eʺì$eÊ% õiXÊ`–“jФcª )¤rR½P\Í´=)¤z¡Ô>“µ‘JJõþœdU*Ä¿*ýë;‰ÒRÝ?’X“ ‰WSHI¸U*ȼR‡ÊDGjþ9ɲT¢ÔÛ½B$Õ¥ R’š`Ìošâ»ñ*Õécê}Ó TG©Þ@²èsô½û¤Jg o·ŠÎºÇ&û™ç«©2gž\ÍdÁBjB©u6¤º¤d]Ö”‘K@jk©!Y—¥Æäú6*' JyEÒò1å…ÔæR ¸ši;4RH!…RH/hžúyÂû¥|¡BÊŠ\B{©M¥ü}È%8w µÔˆ±‰«™¦C#…RH!õˆY¥¹¤œ¥Èº(¥È%<'µØöûáˆ$e`…”³Pê˜bC ©G¶PÞ—ö39…IEND®B`‚urwid-2.6.16/docs/tutorial/highcolors.py000066400000000000000000000017551470350774000202650ustar00rootroot00000000000000from __future__ import annotations import urwid def exit_on_q(key): if key in {"q", "Q"}: raise urwid.ExitMainLoop() palette = [ ("banner", "", "", "", "#ffa", "#60d"), ("streak", "", "", "", "g50", "#60a"), ("inside", "", "", "", "g38", "#808"), ("outside", "", "", "", "g27", "#a06"), ("bg", "", "", "", "g7", "#d06"), ] placeholder = urwid.SolidFill() loop = urwid.MainLoop(placeholder, palette, unhandled_input=exit_on_q) loop.screen.set_terminal_properties(colors=256) loop.widget = urwid.AttrMap(placeholder, "bg") loop.widget.original_widget = urwid.Filler(urwid.Pile([])) div = urwid.Divider() outside = urwid.AttrMap(div, "outside") inside = urwid.AttrMap(div, "inside") txt = urwid.Text(("banner", " Hello World "), align="center") streak = urwid.AttrMap(txt, "streak") pile = loop.widget.base_widget # .base_widget skips the decorations for item in (outside, inside, streak, inside, outside): pile.contents.append((item, pile.options())) loop.run() urwid-2.6.16/docs/tutorial/highcolors.py.xdotool000066400000000000000000000000511470350774000217400ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 26 9 urwid-2.6.16/docs/tutorial/highcolors1.png000066400000000000000000000004761470350774000205010ustar00rootroot00000000000000‰PNG  IHDRê‡êÐØùPLTE×_¯_‡‡_¯_×ÿÿ¯àÃlN pHYs  ÒÝ~üÒIDAThÞíØë  @á®à ® #À÷î¿JyU°&˜˜k‘zNú£BÂ×bŒËBDDDDDDÓv*****êaêuD¨¨¨¨¨¨‡©·~w÷qUXõ銩„[m–®Sé“nƒ¦ª÷ÄìÔr?½êUUã^·S>ï~š2R5,+®\åý¶×ŸÇµŽ«Ý»¾S¥Žî°¦Mí©î ª´§i´zQÕø Ç]~Ü”Ç(#’Ýþ§ïa{•ߨ¨¨¨¨çVùõÜ*MÛ ¤X1·ŸIEND®B`‚urwid-2.6.16/docs/tutorial/hmenu.py000066400000000000000000000067711470350774000172430ustar00rootroot00000000000000from __future__ import annotations import typing import urwid if typing.TYPE_CHECKING: from collections.abc import Callable, Hashable, Iterable class MenuButton(urwid.Button): def __init__( self, caption: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]], callback: Callable[[MenuButton], typing.Any], ) -> None: super().__init__("", on_press=callback) self._w = urwid.AttrMap( urwid.SelectableIcon([" \N{BULLET} ", caption], 2), None, "selected", ) class SubMenu(urwid.WidgetWrap[MenuButton]): def __init__( self, caption: str | tuple[Hashable, str], choices: Iterable[urwid.Widget], ) -> None: super().__init__(MenuButton([caption, "\N{HORIZONTAL ELLIPSIS}"], self.open_menu)) line = urwid.Divider("\N{LOWER ONE QUARTER BLOCK}") listbox = urwid.ListBox( urwid.SimpleFocusListWalker( [ urwid.AttrMap(urwid.Text(["\n ", caption]), "heading"), urwid.AttrMap(line, "line"), urwid.Divider(), *choices, urwid.Divider(), ] ) ) self.menu = urwid.AttrMap(listbox, "options") def open_menu(self, button: MenuButton) -> None: top.open_box(self.menu) class Choice(urwid.WidgetWrap[MenuButton]): def __init__( self, caption: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]], ) -> None: super().__init__(MenuButton(caption, self.item_chosen)) self.caption = caption def item_chosen(self, button: MenuButton) -> None: response = urwid.Text([" You chose ", self.caption, "\n"]) done = MenuButton("Ok", exit_program) response_box = urwid.Filler(urwid.Pile([response, done])) top.open_box(urwid.AttrMap(response_box, "options")) def exit_program(key): raise urwid.ExitMainLoop() menu_top = SubMenu( "Main Menu", [ SubMenu( "Applications", [ SubMenu( "Accessories", [ Choice("Text Editor"), Choice("Terminal"), ], ) ], ), SubMenu( "System", [ SubMenu("Preferences", [Choice("Appearance")]), Choice("Lock Screen"), ], ), ], ) palette = [ (None, "light gray", "black"), ("heading", "black", "light gray"), ("line", "black", "light gray"), ("options", "dark gray", "black"), ("focus heading", "white", "dark red"), ("focus line", "black", "dark red"), ("focus options", "black", "light gray"), ("selected", "white", "dark blue"), ] focus_map = {"heading": "focus heading", "options": "focus options", "line": "focus line"} class HorizontalBoxes(urwid.Columns): def __init__(self) -> None: super().__init__([], dividechars=1) def open_box(self, box: urwid.Widget) -> None: if self.contents: del self.contents[self.focus_position + 1 :] self.contents.append( ( urwid.AttrMap(box, "options", focus_map), self.options(urwid.GIVEN, 24), ) ) self.focus_position = len(self.contents) - 1 top = HorizontalBoxes() top.open_box(menu_top.menu) urwid.MainLoop(urwid.Filler(top, "middle", 10), palette).run() urwid-2.6.16/docs/tutorial/hmenu.py.xdotool000066400000000000000000000002251470350774000207160ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 80 12 key --window $RXVTWINDOWID Return key --window $RXVTWINDOWID Return Down key --window $RXVTWINDOWID Return urwid-2.6.16/docs/tutorial/hmenu1.png000066400000000000000000000015261470350774000174510ustar00rootroot00000000000000‰PNG  IHDRдOƒÊgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEÍÿÿÿåååÍÞþ³wbKGDf |d¹IDATxÚíØëmÛ0€QÊñ¾Ð¼àýg*I=]¹€SSBÒž/nCÑúuB\+iI’$I’$Iú¿û<1РAƒ 4hРAƒ 4hРAƒ ôYÐQ^¹ËuµIëí%è Ð×Ë3èHo€® ÚÛ\Ò.èºÐ—kd×|¶Ó¡žxÓ"ð´[vÖñú=蘆rž+tùÊ; kAÏŸ~Ëg`Žòîç¸ý.t~•‰ü}S kBoóz³] úÙ‰ÎÛÓâºÒèÈ–q}„þ\vâ ú]h¿‚ƒ 4hРAƒ 4hРAƒ-IÒ‹ÝN 4hРAƒ 4hРAƒ 4hР¿a÷¹|ñq?4йéèÓ ‡nƒ3Äï;û{@ÿtÄVg].@¿=tŠQå0ƒÞC'ÙºèÒÿ1A§‹4CòÑ¥ïݼ3Ý _‡Nd+t¤oQŽö<:b<걎Žùž´~픃ÞCg»×#ëL¹B/÷¼Ü ¡o};Cß@ YçÑQ ó} ˜ÑôftÜ@Ý,3º] ûi ºt™}3Ž~zêho +Cožòüfx4t úèþˆ¿w€>)РAƒ 4hРAƒ 4hРAƒ 4hРAƒ 4hРAƒ 4hРAƒ 4hРAƒ 4hРAƒ 4hРAƒ 4hРAƒ 4hРAK’$I’$I’¾G¿¸?!©É×5IEND®B`‚urwid-2.6.16/docs/tutorial/hmenu2.png000066400000000000000000000021751470350774000174530ustar00rootroot00000000000000‰PNG  IHDRдOƒÊgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEåååÍÿÿÿÍMMMeHíÃbKGD LòÝIDATxÚíÙan£0†a³Zþ“¬Ê pP)€?¾ÿUÖ3`c©’ÓÔy¿%±QWz2š‚c !„B!„B!„¼wNFþ¿?ÇåÐ@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@¿-t£‡Žª§Ò# ´MQlw=³¾¦4èEòA·jÏVжTèê´ÕG̲Àw„¶ Ŷ۵˜_ÚçÔÈ„‘*+•ªt¦y®¼Ð^¶ílkåg7B·bkýYký§à_af¼¦µ¶+:ö HZ»ŠÌìݵ]«¥Z‡—s} 3Ó5öÑ*Õ]%б5Wã|£3ûC{;DhWt¼ÆÒ:ı1f Z®ÛÚ÷†[в´†.¥u„²½‚>%•~PE§+èRZ‡B7ÇBoöhyu±)kŸJ)Ð ÷)ôJj?òn7ï Ùëh ¿„Πh h h &„B^0Õ| h h h h è×Ëß~ʧœõYó ´(L¯7†®‡ôÄ]Ϭ¯yÚÞf „vnq¶¢v†¾Øó[B×ÃBÑíVÌ· í·?´—uõàÿÓMÐþÄ÷YpƒÂÌtsî èËYëËúb½øø²rrÖb·ôóÒ/‡®]íü›ÓÒ­Ã¥îæÖ®ñãû«<¶çZ¨ý?èÉålÇRñ^Ç}\* ZìÜ8Yå ¯y¨—Ü€¶ã@AmÄÏ`šK¥@ûÜ€®¥I¬ ¿Û:´ï ´ô†Ð:® ÃRQ=zÚmVôC oôè´¢û8^Ut\*z»G;)ûØ”õšð©ÔÆ߇Nzôä;÷è—.½?J»ëÐf!7µ´„ãÌ`bëx:½ë8/ï:BGI–Ê€6¡¸¿™Gp K…–³·‡Îö£>¼mèfÐÙãݹ€Îm€>z|Ø›7Eý‰>n½Kož{ô¼!]ë^¨Û¥¸ŽÐ&ÙT ÛúPt&èqCc§@ghÓó¾è¼ôÞÊáK*¾¿’o°úÞ-: ïk!ºZÏwú,й¼‡,¿èkeg€>¢QçÚïú  4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4ЄB!„B!„B~>ÿ1ÿzMöNIEND®B`‚urwid-2.6.16/docs/tutorial/hmenu3.png000066400000000000000000000027051470350774000174530ustar00rootroot00000000000000‰PNG  IHDRдOƒÊgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEåååÍÿÿÿMMMÍK™rIbKGD Lò%IDATxÚíÜ]’¢0†á¤Š¹[èÞ”÷\dVï+“œüÎtay¿¶[ùÐÇã1â¨R„B!„B!„òçv`üþôû3~‡h h h h h h Omä$—J -§&ЦZæÌïvµ#ôçýïTßYg7è* ¡µžìh´wE÷ý9 õbù¨ºÀw„65esèÏû6ÅÝ ]üU b–h—›÷ñs̶ò®¡ý^Œ‰[–I£d%-WÄ qެóÓ_C÷þôé˺ïïî‚ÿuú0ÇÝrÁÝyQCèÜ+T"õÐÒUüœý¡uÜrذ;˜giNܻ٠݇ÂîãÏGï&ü?ï.—?ò¢f=Zй5ko¡n¶\Bß ãÊ·ÐÒ<úЋ] §¦ïƒ4?.j -×9´_owh?ñ:´)ôÖÖá2B»‰{n5t^ÔpÔ¡ç}+*ý¨ŠÖ‹}ÛÞ:ÊŠN3CýV5„6ÇB/÷h¹flÙf¼WöëÑÁ·èÑix—µ„vƒ€8Æ(¡Ç9J™}¡M9¢0ã?Æ.çìÑ:&£Ž¾u¤ŽR,ºÐKðôXiŽu ôE¡Ûh h h h OM!„ü³ýl4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð'ƒî†rÂNçÌ×Ù»Ó:ß„6Ëü*¼Eo6|Òÿ§·ÛNoaM?ì ÝÙáPèÉg t}nª³–ÐÝP)Ú¦Åö`U»œÚÝjÛ ®Ê¬´ ù|ÝùvpçCš×±v‹”»ãÜÉo5î"l° sdCè`v;^@›ðæ–WÑÊÈ'ýMþìÿíÿçç»àÚW™•ÒN­Ã†R·cëHë©•<ŒÐ²å°‹¸Aw äºøS·j_eo. å¤Ë¯#‰‹V¡šMZMý ¸W@盚 ÓÍ¡ó:k{I Ÿ òmq¨tMVìëOÐÆLZÇøõ Z·ƒvyÝÉcz ½¶u$h÷Ï h¿±Ô:&ÐiÑÐ*}½Àß¡ÛöèEh»XÑŸ gŸqí¬¢W<¿èÑâ÷½Šn ½Ü£¥aæFZhâPî´ºèÑåÝlÓzôš}ý Z—–ã :@›c*ºQtiHÐÉc¼¤Ö±ºu õ¨#u”bÑÐyÔ‘;Š4“0ê/Šùo­&â7Ϲu ôµ /”¯?ñë«i€öùOf\Ú]ØDèù('úc mÕ»wG_¨èŠuaÎqÐꃮs tx6u/ÂËäË@?ûÇWïþ<Ÿçê&Üù÷•GsètP)ÍÉG6ÂñIûžÅ=B»Þ;B÷îÇÍèïÓ+Ë„?ïƒVÅA¥t˜C^”_ ZNRÙúZÊ?€G†tàôZÐnä±ý”ÉA=:C«Ô:B×~{è²GKñΡû#*:+§7©¬Êo.ÅÖ±þÝÙ ¥/A?ƒŽÇ'»qÄ!ïÉ^ Ú5‰±¢Ÿ¡ø Îzþz¦éKð·@¿†>ѱyÃè*ú:Ç;*…óA_'h h h h h h h h h h h h h h h h h h h h hB!„B!„B!³üu Œ±YIEND®B`‚urwid-2.6.16/docs/tutorial/hmenu4.png000066400000000000000000000023661470350774000174570ustar00rootroot00000000000000‰PNG  IHDRдOƒÊgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEåååMMMÍÿÿÿ$‘m¹bKGDhÙQYIDATxÚíÚ z¢0†áº°žl¡ÝAÝÿš†ÜA[!06|ÿØV.cä5F)B!„B!„B¹vÆcÛ“ÛÓÿãh h h h h úM ¥zŽö×BEËÞçQ<€þÍéZW¾T&Ï£x9ºw¿)´Ô]úphý«.Ý´¶7™Whm5,‚ûkw±>n߸Æí£_=ŠZìMÅÇYÜÑ£»3›:‚že®Ã:ôh‰7»(Å·¼ÚË—ÐþÕ]½3ªð4òšžzt<Ôz\;û¡Ó ©ãЊÜAwÓ£íÂh·ßzgé° Ú6‘J‡¤O ]nê¾Gëõ=6)cîÈé~ ]lê¼Fk7ž•\²e[Ý|^£ï¹‚î®FW#Šb$ ý9Lì/ò|Ô+Jw£Žñþ]|Ìóh > úøç4Ð@ 4Ð@ 4Ð@ 4Ð…î>@ 4Ð@ 4Ð'Cëõ/äáÈð~•TÛnýãì ôÃÒ!«x@7‡ÖzŒ“R$ˆ›ÊRÏ*IÐ:NjUfÒ¦~]L³’\LÔb.kµ³›Ô*qZ–*çÈýôÝäØh)¡èýÐÚOZUygf¶ÞA»}€Þ =ƹ–åÎãZÓ£7ÕhYLjýšý#´N“cÓ¨c1©µÞYÆbâk¸óh&-М‚ 4Ð@ýZ†©Í>¿mÎ3­¯ö íkë¡Ýž=7¸¶ZÐÛÛ{_èajª¸zß û¾ÐÆÞÛ­í[zp?ó/ã×̇ìîØ^7µ€vìÜЃ™ÿ¤fRëz0´ñÛ„Ê8¿n oç¸i³ï”¡Sk¹G»W×)‡Ò‘[ïÚLú,2±ƒ…× •N¿©txMu鈭¨ðÂÆÖ;ž“¡í •Ž:mjí+“ºtY£Ëʨë¿U^~”m­ÑþA¯Ñ£«Ã.j´÷-jtdˆ›Ô4¨i'tú> Ð6áç²Ðk—IëËLScèÛmþ¹b6Ï× ¡¿¾¿¾^ëò¡]¾,t¼:¹sPw.j”?o:úvèåeÒù— §õþz¨ÙÿUV†þº•Ðsù¸"tºÈ‘¡‹ë´G@Q;þt¸ {í$@7¬Ñ&Õê:_&m_£/T:JèøEÖÚLG ï®óaXB+ÿ a€Îßæ¤Ë¤ ¡Ã Ë]úÝ¡Í ôaè¡íÐÇ…ëÑ@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@ 4Ð@M!„B!„B!d‘Mr‚¦^IEND®B`‚urwid-2.6.16/docs/tutorial/index.rst000066400000000000000000000375171470350774000174200ustar00rootroot00000000000000.. _urwid-tutorial: ****************** Urwid Tutorial ****************** .. currentmodule:: urwid Minimal Application ------------------- .. image:: minimal1.png This program displays the string ``Hello World`` in the top left corner of the screen and will run until interrupted with *CTRL+C* (*^C*). .. literalinclude:: minimal.py :linenos: * The *txt* :class:`Text` widget handles formatting blocks of text, wrapping to the next line when necessary. Widgets like this are called "flow widgets" because their sizing can have a number of columns given, in this case the full screen width, then they will flow to fill as many rows as necessary. * The *fill* :class:`Filler` widget fills in blank lines above or below flow widgets so that they can be displayed in a fixed number of rows. This Filler will align our Text to the top of the screen, filling all the rows below with blank lines. Widgets which are given both the number of columns and number of rows they must be displayed in are called "box widgets". * The :class:`MainLoop` class handles displaying our widgets as well as accepting input from the user. The widget passed to :class:`MainLoop` is called the "topmost" widget. The topmost widget is used to render the whole screen and so it must be a box widget. In this case our widgets can't handle any user input so we need to interrupt the program to exit with *^C*. Global Input ------------ .. image:: input1.png .. image:: input2.png .. image:: input3.png .. image:: input4.png .. image:: input5.png This program initially displays the string ``Hello World``, then it displays each key pressed, exiting when the user presses *Q*. .. literalinclude:: input.py :linenos: * The :class:`MainLoop` class has an optional function parameter *unhandled_input*. This function will be called once for each keypress that is not handled by the widgets being displayed. Since none of the widgets being displayed here handle input, every key the user presses will be passed to the *show_or_exit* function. * The :exc:`ExitMainLoop` exception is used to exit cleanly from the :meth:`MainLoop.run` function when the user presses *Q*. All other input is displayed by replacing the current Text widget's content. Display Attributes ------------------ .. image:: urwid_attr1.png .. image:: urwid_attr2.png .. image:: urwid_attr3.png .. image:: urwid_attr4.png This program displays the string ``Hello World`` in the center of the screen. It uses different attributes for the text, the space on either side of the text and the space above and below the text. It waits for a keypress before exiting. The screenshots above show how these widgets react to being resized. .. literalinclude:: urwid_attr.py :linenos: * Display attributes are defined as part of a palette. Valid foreground, background and setting values are documented in :ref:`foreground-background` A palette is a list of tuples containing: 1. Name of the display attribute, typically a string 2. Foreground color and settings for 16-color (normal) mode 3. Background color for normal mode 4. Settings for monochrome mode (optional) 5. Foreground color and settings for 88 and 256-color modes (optional, see next example) 6. Background color for 88 and 256-color modes (optional) * A :class:`Text` widget is created containing the string ``" Hello World "`` with display attribute ``'banner'``. The attributes of text in a Text widget is set by using a (*attribute*, *text*) tuple instead of a simple text string. Display attributes will flow with the text, and multiple display attributes may be specified by combining tuples into a list. This format is called :ref:`text-markup`. * An :class:`AttrMap` widget is created to wrap the text widget with display attribute ``'streak'``. :class:`AttrMap` widgets allow you to map any display attribute to any other display attribute, but by default they will set the display attribute of everything that does not already have a display attribute. In this case the text has an attribute, so only the areas around the text used for alignment will have the new attribute. * A second :class:`AttrMap` widget is created to wrap the :class:`Filler` widget with attribute ``'bg'``. When this program is run you can now clearly see the separation of the text, the alignment around the text, and the filler above and below the text. .. seealso:: :ref:`using-display-attributes` High Color Modes ---------------- .. image:: highcolors1.png This program displays the string ``Hello World`` in the center of the screen. It uses a number of 256-color-mode colors to decorate the text, and will work in any terminal that supports 256-color mode. It will exit when *Q* is pressed. .. literalinclude:: highcolors.py :linenos: This palette only defines values for the high color foreground and backgrounds, because only the high colors will be used. A real application should define values for all the modes in their palette. Valid foreground, background and setting values are documented in :ref:`foreground-background`. * Behind the scenes our :class:`MainLoop` class has created a :class:`raw_display.Screen` object for drawing the screen. The program is put into 256-color mode by using the screen object's :meth:`set_terminal_properties() ` method. This example also demonstrates how you can build the widgets to display in a top-down order instead of the usual bottom-up order. In some places we need to use a *placeholder* widget because we must provide a widget before the correct one has been created. * We change the topmost widget used by the :class:`MainLoop` by assigning to its :attr:`MainLoop.widget` property. * :ref:`decoration-widgets` like :class:`AttrMap` have an ``original_widget`` property that we can assign to change the widget they wrap. * :class:`Divider` widgets are used to create blank lines, colored with :class:`AttrMap`. * :ref:`container-widgets` like :class:`Pile` have a ``contents`` property that we can treat like a list of (*widget*, *options*) tuples. :attr:`Pile.contents` supports normal list operations including ``append()`` to add child widgets. :meth:`Pile.options` is used to generate the default options for the new child widgets. Question and Answer ------------------- .. image:: qa1.png .. image:: qa2.png .. image:: qa3.png This program asks for your name then responds ``Nice to meet you, (your name).`` .. literalinclude:: qa.py :linenos: The :class:`Edit` widget is based on the :class:`Text` widget but it accepts keyboard input for entering text, making corrections and moving the cursor around with the *HOME*, *END* and arrow keys. Here we are customizing the :class:`Filler` decoration widget that is holding our :class:`Edit` widget by subclassing it and defining a new ``keypress()`` method. Customizing decoration or container widgets to handle input this way is a common pattern in Urwid applications. This pattern is easier to maintain and extend than handling all special input in an *unhandled_input* function. * In *QuestionBox.keypress()* all keypresses except *ENTER* are passed along to the default :meth:`Filler.keypress` which sends them to the child :meth:`Edit.keypress` method. * Note that names containing *Q* can be entered into the :class:`Edit` widget without causing the program to exit because :meth:`Edit.keypress` indicates that it has handled the key by returning ``None``. See :meth:`Widget.keypress` for more information. * When *ENTER* is pressed the child widget ``original_widget`` is changed to a :class:`Text` widget. * :class:`Text` widgets don't handle any keyboard input so all input ends up in the *unhandled_input* function *exit_on_q*, allowing the user to exit the program. Signal Handlers --------------- .. image:: sig1.png .. image:: sig2.png .. image:: sig3.png .. image:: sig4.png This program asks for your name and responds ``Nice to meet you, (your name)`` *while* you type your name. Press *DOWN* then *SPACE* or *ENTER* to exit. .. literalinclude:: sig.py :linenos: * An :class:`Edit` widget and a :class:`Text` reply widget are created, like in the previous example. * The :func:`connect_signal` function is used to attach our *on_ask_change()* function to our :class:`Edit` widget's ``'change'`` signal. Now any time the content of the :class:`Edit` widget changes *on_ask_change()* will be called and passed the new content. * Finally we attach our *on_exit_clicked()* function to our exit :class:`Button`'s ``'click'`` signal. * *on_ask_change()* updates the reply text as the user enters their name and *on_exit_click()* exits. Multiple Questions ------------------ .. image:: multiple1.png .. image:: multiple2.png .. image:: multiple3.png .. image:: multiple4.png This program asks for your name and responds ``Nice to meet you, (your name).`` It then asks again, and again. Old values may be changed and the responses will be updated when you press *ENTER*. *ENTER* on a blank line exits. .. literalinclude:: multiple.py :linenos: :class:`ListBox` widgets let you scroll through a number of flow widgets vertically. It handles *UP*, *DOWN*, *PAGE UP* and *PAGE DOWN* keystrokes and changing the focus for you. :ref:`listbox-contents` are managed by a "list walker", one of the list walkers that is easiest to use is :class:`SimpleFocusListWalker`. :class:`SimpleFocusListWalker` is like a normal python list of widgets, but any time you insert or remove widgets the focus position is updated automatically. Here we are customizing our :class:`ListBox`'s keypress handling by overriding it in a subclass. * The *question()* function is used to build widgets to communicate with the user. Here we return a :class:`Pile` widget with a single :class:`Edit` widget to start. * We retrieve the name entered with :attr:`ListBox.focus` to get the :class:`Pile` in focus, the standard :ref:`container widget ` method ``[0]`` to get the first child of the pile and :attr:`Edit.edit_text` to get the user-entered text. * For the response we use the fact that we can treat :attr:`Pile.contents` like a list of (*widget*, *options*) tuples to create or replace any existing response by assigning a one-tuple list to *contents[1:]*. We create the default options using :meth:`Pile.options`. * To add another question after the current one we treat our :class:`SimpleFocusListWalker` stored as :attr:`ListBox.body` like a normal list of widgets by calling *insert()*, then update the focus position to the widget we just created. Simple Menu ----------- .. image:: smenu1.png .. image:: smenu2.png .. image:: smenu3.png We can create a very simple menu using a list of :class:`Button` widgets. This program lets you choose an option then repeats what you chose. .. literalinclude:: smenu.py :linenos: * *menu()* builds a :class:`ListBox` with a *title* and a sequence of :class:`Button` widgets. Each button has its ``'click'`` signal attached to *item_chosen*, with item name is passed as data. The buttons are decorated with an :class:`AttrMap` that applies a display attribute when a button is in focus. * *item_chosen()* replaces the menu displayed with text indicating the users' choice. * *exit_program()* causes the program to exit on any keystroke. * The menu is created and decorated with an :class:`Overlay` using a :class:`SolidFill` as the background. The :class:`Overlay` is given a miniumum width and height but is allowed to expand to 60% of the available space if the user's terminal window is large enough. Cascading Menu -------------- .. image:: cmenu1.png .. image:: cmenu2.png .. image:: cmenu3.png .. image:: cmenu4.png A nested menu effect can be created by having some buttons open new menus. This program lets you choose an option from a nested menu that cascades across the screen. You may return to previous menus by pressing *ESC*. .. literalinclude:: cmenu.py :linenos: * *menu_button()* returns an :class:`AttrMap`-decorated :class:`Button` and attaches a *callback* to its ``'click'`` signal. This function is used for both sub-menus and final selection buttons. * *sub_menu()* creates a menu button and a closure that will open the menu when that button is clicked. Notice that :ref:`text markup ` is used to add ``'...'`` to the end of the *caption* passed to *menu_button()*. * *menu()* builds a :class:`ListBox` with a *title* and a sequence of widgets. * *item_chosen()* displays the users' choice similar to the previous example. * *menu_top* is the top level menu with all of its child menus and options built using the functions above. This example introduces :class:`WidgetPlaceholder`. :class:`WidgetPlaceholder` is a :ref:`decoration widget ` that does nothing to the widget it decorates. It is useful if you need a simple way to replace a widget that doesn't involve knowing its position in a :ref:`container `, or in this case as a base class for a widget that will be replacing its own contents regularly. * *CascadingBoxes* is a new widget that extends :class:`WidgetPlaceholder`. It provides an *open_box()* method that displays a box widget *box* "on top of" all the previous content with an :class:`Overlay` and a :class:`LineBox`. The position of each successive box is shifted right and down from the previous one. * *CascadingBoxes.keypress()* intercepts *ESC* keys to cause the current box to be removed and the previous one to be shown. This allows the user to return to a previous menu level. Horizontal Menu --------------- .. image:: hmenu1.png .. image:: hmenu2.png .. image:: hmenu3.png .. image:: hmenu4.png This example is like the previous but new menus appear on the right and push old menus off the left side of the screen. The look of buttons and other menu elements are heavily customized and new widget classes are used instead of factory functions. .. literalinclude:: hmenu.py :linenos: * *MenuButton* is a customized :class:`Button` widget. :class:`Button` uses :class:`WidgetWrap` to create its appearance and this class replaces the display widget created by :class:`Button` by the wrapped widget in *self._w*. * *SubMenu* is implemented with a *MenuButton* but uses :class:`WidgetWrap` to hide the implementation instead of inheriting from *MenuButton*. The constructor builds a widget for the menu that this button will open and stores it in *self.menu*. * *Choice* is like *SubMenu* but displays the item chosen instead of another menu. The *palette* used in this example includes an entry with the special name ``None``. The foreground and background specified in this entry are used as a default when no other display attribute is specified. * *HorizontalBoxes* arranges the menus displayed similar to the previous example. There is no special handling required for going to previous menus here because :class:`Columns` already handles switching focus when *LEFT* or *RIGHT* is pressed. :class:`AttrMap` with the *focus_map* dict is used to change the appearance of a number of the display attributes when a menu is in focus. Adventure Game -------------- .. image:: adventure1.png .. image:: adventure2.png .. image:: adventure3.png .. image:: adventure4.png We can use the same sort of code to build a simple adventure game. Instead of menus we have "places" and instead of submenus and parent menus we just have "exits". This example scrolls previous places off the top of the screen, allowing you to scroll back to view but not interact with previous places. .. literalinclude:: adventure.py :linenos: This example starts to show some separation between the application logic and the widgets that have been created. The *AdventureGame* class is responsible for all the changes that happen through the game and manages the topmost widget, but isn't a widget itself. This is a good pattern to follow as your application grows larger. urwid-2.6.16/docs/tutorial/input.py000066400000000000000000000004671470350774000172620ustar00rootroot00000000000000from __future__ import annotations import urwid def show_or_exit(key: str) -> None: if key in {"q", "Q"}: raise urwid.ExitMainLoop() txt.set_text(repr(key)) txt = urwid.Text("Hello World") fill = urwid.Filler(txt, "top") loop = urwid.MainLoop(fill, unhandled_input=show_or_exit) loop.run() urwid-2.6.16/docs/tutorial/input.py.xdotool000066400000000000000000000002421470350774000207400ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 12 3 key --window $RXVTWINDOWID Return key --window $RXVTWINDOWID N key --window $RXVTWINDOWID O key --window $RXVTWINDOWID P urwid-2.6.16/docs/tutorial/input1.png000066400000000000000000000003511470350774000174670ustar00rootroot00000000000000‰PNG  IHDRl-n ÑØbKGDÿ‡Ì¿ pHYs  ÒÝ~üIDATXÃíÔK€ EÑ»ÿ}²~(µƒ óÐÚâ¤LlÌÅ8£€ë$“Œ£í(:˜yÁÚ÷_À ÖÏ¥šÇö¶üu”ÃØÒ‰¦L²Ì}¨Q>ñÊcq~‚Q‹5Œ•»X¾A^ÇÖ«‹ÑÛæ{.Çê:ê<‡þ… &L˜0a„ &LXÒ¶Iu!vëIEND®B`‚urwid-2.6.16/docs/tutorial/input2.png000066400000000000000000000003221470350774000174660ustar00rootroot00000000000000‰PNG  IHDRl-n ÑØbKGDÿ‡Ì¿ pHYs  ÒÝ~üvIDATXÃíÔA €@…áwÿ{¾{D‘aSÔ"3‚ßÅ ºøFaFn µcjºç ¦Ÿc‘ϱÔ"‰Jjy­<\¼â”Æ™·–ƒ¬Ã|¹‹­`J{,yg:Œ8LæLo`Þ¯JQË­:¬ç»û›ú´¢é"äeIEND®B`‚urwid-2.6.16/docs/tutorial/input3.png000066400000000000000000000002511470350774000174700ustar00rootroot00000000000000‰PNG  IHDRl-n ÑØbKGDÿ‡Ì¿ pHYs  ÒÝ~üMIDATXÃíÑ1 0CÑÜÿžÞ£K:º‰¡”DÜU0ŠcªûþSÛ§†K<0Oów!ÌÅ…°kbó{[#à¢Ç>ñ¶IEND®B`‚urwid-2.6.16/docs/tutorial/input4.png000066400000000000000000000002441470350774000174730ustar00rootroot00000000000000‰PNG  IHDRl-n ÑØbKGDÿ‡Ì¿ pHYs  ÒÝ~üHIDATXÃíÔ1 @QïOïACÐÔ ¼?¸>l,Ú±%Ƭr‡ ;çÿX–30ØåÙŽº+ƒÁ`0 ƒÁ`o`x¬ß½”¾IEND®B`‚urwid-2.6.16/docs/tutorial/input5.png000066400000000000000000000002441470350774000174740ustar00rootroot00000000000000‰PNG  IHDRl-n ÑØbKGDÿ‡Ì¿ pHYs  ÒÝ~üHIDATXÃíÑ1 ÀþÿŸý‡ ‹ Ž-ˆ—!ëµ$²1ÑŽ-1f*oر£ßDzœ9±m³ìù »` ƒÁ`0 ƒÁ`°O°Þõâlä-XvIEND®B`‚urwid-2.6.16/docs/tutorial/menu25.png000066400000000000000000000005561470350774000173710ustar00rootroot00000000000000‰PNG  IHDRÏxp%ùNbKGDÿ‡Ì¿ pHYs  ÒÝ~üIDATxÚíÕQ‚0Àwÿ{ö&*–UDBN¿Ú- »,)×áááááááááááááááááááááááááááááááááááááéxòë+~ãã®7ñ§òüžþ-ž{PÊu<5GÏêÈü¨º4TPú“ùN~N’òŠ e~Î6OSrYòÔ¥Œ÷,O‡W—׿IHÚ»æOé3ÊZOézÊ9<µ––ºY&.Íg›NS‹ðxÏ,Q½ü”už–Ÿ¬˜ìäÙ#?m§š€šzÉÖþ6öL˵¹ÒÞâ¸ÄéÏ{nêÛÅ­–h÷@IEND®B`‚urwid-2.6.16/docs/tutorial/minimal.py000066400000000000000000000002311470350774000175360ustar00rootroot00000000000000from __future__ import annotations import urwid txt = urwid.Text("Hello World") fill = urwid.Filler(txt, "top") loop = urwid.MainLoop(fill) loop.run() urwid-2.6.16/docs/tutorial/minimal.py.xdotool000066400000000000000000000000511470350774000212250ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 21 7 urwid-2.6.16/docs/tutorial/minimal1.png000066400000000000000000000004531470350774000177610ustar00rootroot00000000000000‰PNG  IHDR½iJ}ÕbKGDÿ‡Ì¿ pHYs  ÒÝ~üÏIDATxÚíÔAƒ Ðÿ{z.Ú R¬44] Ã„Çd4ËÌ#“ëózŠ$ÍdçÔýè6+£õÕãýö=ý'ú¤´L³wjõÏPêŽ+YWëóØ?½¢f§Ò©îõ½õTû¯õýõ£ô)ýqZ¿üL¿ÿÕþŸþ~ê3üŸóf²6kÖPuqígôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôóëoœÅ©¤HÔšIEND®B`‚urwid-2.6.16/docs/tutorial/multiple.py000066400000000000000000000020031470350774000177420ustar00rootroot00000000000000from __future__ import annotations import urwid def question(): return urwid.Pile([urwid.Edit(("I say", "What is your name?\n"))]) def answer(name): return urwid.Text(("I say", f"Nice to meet you, {name}\n")) class ConversationListBox(urwid.ListBox): def __init__(self) -> None: body = urwid.SimpleFocusListWalker([question()]) super().__init__(body) def keypress(self, size: tuple[int, int], key: str) -> str | None: key = super().keypress(size, key) if key != "enter": return key name = self.focus[0].edit_text if not name: raise urwid.ExitMainLoop() # replace or add response self.focus.contents[1:] = [(answer(name), self.focus.options())] pos = self.focus_position # add a new question self.body.insert(pos + 1, question()) self.focus_position = pos + 1 return None palette = [("I say", "default,bold", "default")] urwid.MainLoop(ConversationListBox(), palette).run() urwid-2.6.16/docs/tutorial/multiple.py.xdotool000066400000000000000000000002631470350774000214370ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 23 13 key --window $RXVTWINDOWID A b e Return B o b key --window $RXVTWINDOWID Return C a r l Return key --window $RXVTWINDOWID D a v e Return urwid-2.6.16/docs/tutorial/multiple1.png000066400000000000000000000007671470350774000201760ustar00rootroot00000000000000‰PNG  IHDRÏÃ(øbKGDÿ‡Ì¿ pHYs  ÒÝ~ü›IDATxÚíÖ Ž‚0йÿ=¹Ç&» ÓbA!KÑHjaú:ÓBL÷:ânžˆ¿Ÿ)âÍ ÎÛp´×žNÛyžßH£á†=g–ÎQÏ3½DG'áE—hè1¤¡Ìw®:×±–)-úîñ$Ì2µÙIã.þ^Zò÷yuç:Öü™Š¾»ÖO“³‘íVRµ·'mï6VãõÞóÄ\LëûÁz½yž±r™Ì·Þ±|#ÒëqlíoÝzò”klà±ñÊ3g)ÅXòSX[?åBIWo{¢Y?Ó‘üt¦³¿×ä2ëì}‘ÆEËF½Uɉtý¸ç ÏŒ+ø¨ärÎó³çmë žÈäÿ?–gÏž›½_߃ÁÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃsøøßo?+9`Ý·IEND®B`‚urwid-2.6.16/docs/tutorial/multiple2.png000066400000000000000000000015001470350774000201610ustar00rootroot00000000000000‰PNG  IHDRÏÃ(øbKGDÿ‡Ì¿ pHYs  ÒÝ~üäIDATxÚíÛn„ …áwÿ{r&mf@ÑV…ü¦M›Õ>‡Á]¥µ6­æ‘~þ$éàî¬Ûpi}OðÚ}žï’F‹öÜÙt®z>áý ´‚€‡ÈÑë`ª’Ï\\—µ]ÒâØ3ƒÙ*Òº:¦ÞÅîíûûùOõÁuYù'ÇžÊg'Ú^R½îÿñGû²âjŒ·÷ȣܘÚýA»½y>eÙf’O}"}%sƺ{ý[ØÞ†ÁåŒûÛÌ‚¾O¦~*^ÙioUpdÞ?îù‡{Æ“›þTò8çãsæÓÖ›=ïØ–ôhÚw[Õü¥íw6×ö¹é ]ÕeLÀf WŸ5<Ê_s æmpíø¬–?ó{ÖèßÖÚðàÁsÁÓ¯yW-V¯k>væsõ¹rÑ5àÑ@OxܨöAO¾‰ÊÞXÝÑJá®`‚ 9V×™Œè4¶jV¢™?á(xã¢ld?VJRÃÓj)}OàoÎSŒVÑÍ4¿ÑÈfNŸ0³wãSîý—øtî?jçOÐÞÂÔ &Êy¾£“¦_êæO§½Ù©%׿)¼]ŸŸ h÷†Éˆž§žu <é¹­Ùl.Ü®ôœ¤]ëé<ýoÀW>Mð}ΉâX¿ÀúÖ/°~¡èY¿Àú…N{cý Ö/Üî‘æ{’TöÍå :ª¹=®Ê xŠ:¯Ÿ|¯LöóióÇ=ˆ=y|܃ò“çžYÚÛÜù£ú!Ÿ¹û·ý}x^êYìóõŒÁónà,O¾ˆŠ¾°6¹%™‡þŸ£*•ûµ=Oþ;H>ôú±k/ÆjHRõ˜r W‡Žó¨þñÚy|[V3âiÏ|‚'g´êÐÈÅ¡|ݺs¼.o2÷ìs"³$ußVñxžÒçù-¥Š_äÉ'< åÝbŠ9¦}Ì‘=´Oyô+öé\Ä?†¿™C«?Uq+÷1TIz^êŽŸŽ¿å³ÕÏ †Uý©/÷œQ¼9èÌå7žb~³çƒô\rÝæÂåJž#ñ[ާÿ|ån‚çxàxàxàž ñÝ}<§c —xît«<Õê–ØËoGiÐÍHj3×uá‰ô!‚©V]3×{“2hBLm]Fxâ#‘6.з¶·ÐÜ[’nr·uÙ͘÷w‹Ç èš<›ãQ±ƒ*<ñÙð+1¶÷÷,O9Æ&.=žr-\ÇÝÑÁ€Gšñ“®ØÇèN{®ÑnfÌ}~È`ào•qtTbžç ׌'“ü)Éó2þ®klšãyauc¢ŠÍÓ l²v;ûäke ­_ÈwoãH`ùŒ²Ï.ú…žÅý-öø‘z“Oìùm¯<ð\àY_¿àn2ö2Ïð<°ß¿]™ð,®_ðÖʇ<+ëŠ3«ð„5~–×/ˆÔÂë æèú…©Ìaõ gy–Ô/Tcº;~Òêú…vÂôç·ô\B¿°*úxàxàxàx*ô ÷á|Púô èÐ/s"úô C¿pú…ÅyÖHòt%Þ¶KìæÊ³‹?jžc¯ÚšÝ>ÇMüF±y$kš¢óT»+ žx/bàþ xàÉ<Þ¿0ñDáý Ý¥ ƒ'„~!Íó,ýþ…ªµÞ]˜÷/T óŠ„yÿBñ¯ £_P òDÐ/$'`1æYM¿ cxmÀÂäYZ¿ jš€EVKÏ%ô «ò _€xàxàxà§âA¿pÎÕ¡_@¿€~ýB1'¢_@¿Ðñ7ô 7ð _øt^n0×4xàxàxày‚g¯ûëÞþ†èÒpIEND®B`‚urwid-2.6.16/docs/tutorial/multiple4.png000066400000000000000000000020431470350774000201660ustar00rootroot00000000000000‰PNG  IHDRÏÃ(øbKGDÿ‡Ì¿ pHYs  ÒÝ~üÇIDATxÚí rà Euÿ{rN§‰ÑÊb'6¢ß3í4ž%D‚ø5•½Úއ~mé¯÷´`ØÅ>oÏûóÁôãç0Q^+1ûTžÄ^Gx÷·Üãçf{Ä·½ð€<x^Á™ý^°‹*Ü6úùÑ£<ç/Ý(ÖÁ1šhð)q‰AžÃªõ#·1´˜|å@$*ªÖa|ßaŽ7Û"Þ¿ˆ&|"ÑyÇrÙúÀë:ìþDý¬ge3‘®º”ÃsôÚàÈÚÔ¥„¯phŸ±ÂTÄð„¡¥ÉóªÅª_ç/LaÔö*ŒÚG¾û(2°µ;´äøQÕ½Òu°"Tx\Òñ`püÔ«±°&ÿ0–úŽ;çT·k|¼°Îíôc w3'öÅ)ƒŽ¿)ãðtÂ<ÏæŒ'ú(Éó2>×ZbÓ¾/,ÏÓp”|Û.©³›7+Ï.þÈyêþ^¶5)»}ê‡øxÊ<ÄnƒñCÒ>¹yèÐ4eçQ»+ÏŸÉ&y¶:Àð\àY_¿0õ&~¡½4áð¤Ð/”yž•õ º·Ñ] ý‚q< «dÑ/È—q‚(~u¨Ë“A¿P‚„EŸg5ýÏáÙ„…˳´~-Ô˜„…M«•çèVå~<àxÀð€<àxÀ£x _¸çDsÐ/@¿ýô "&B¿ýBÃß _¸ú…ÅyÖ8þÃó’óle&JýßÕÛ¡Sx†g‰ç/ôà³=agiý_š³ÕÅøI¡_àWµÕ[ None: if key in {"q", "Q"}: raise urwid.ExitMainLoop() class QuestionBox(urwid.Filler): def keypress(self, size, key: str) -> str | None: if key != "enter": return super().keypress(size, key) self.original_widget = urwid.Text( f"Nice to meet you,\n{edit.edit_text}.\n\nPress Q to exit.", ) return None edit = urwid.Edit("What is your name?\n") fill = QuestionBox(edit) loop = urwid.MainLoop(fill, unhandled_input=exit_on_q) loop.run() urwid-2.6.16/docs/tutorial/qa.py.xdotool000066400000000000000000000002051470350774000202010ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 21 7 type --window $RXVTWINDOWID 'Arthur, King of the Britons' key --window $RXVTWINDOWID Return urwid-2.6.16/docs/tutorial/qa1.png000066400000000000000000000006141470350774000167330ustar00rootroot00000000000000‰PNG  IHDR½iJ}ÕbKGDÿ‡Ì¿ pHYs  ÒÝ~ü0IDATxÚíÖ …áwÿ{öÆ(¬ƒâ:uÌO·±~”Â&[¹ =zôèÑ£G=zôèÑ£G=zôèÑ£ÿS½Ê(rѼñêÞ>Ñÿrjt,ø#}™©2eêç®þï.iû)Ý˹~5lËög,©ÚÝ­A_ ã­n2U‹ºÕãcùÃ]JÔ}Ô-¥·¬>jB/;¯ß*f¨Q9#}œ‚Ë•Àöè£eé•Ƚ«œ¡^ƒÜ[Z¯:µÑ¤ê^ïé5Ò'¶%µ9{ìÚï1í$ÞŽÔÝ% ŠÕÔR6÷ìÑ“_ _{Ò²z;ü*¹@_›-Ø”{©¡G=zôèÑ£Ÿ¬_úsE4zôèÑ£G=zôèÑ£G=zôèÑ£G=zôèÑ£GýôvÍ*øÛIEND®B`‚urwid-2.6.16/docs/tutorial/qa2.png000066400000000000000000000012101470350774000167250ustar00rootroot00000000000000‰PNG  IHDR½iJ}ÕbKGDÿ‡Ì¿ pHYs  ÒÝ~ü,IDATxÚíÙ r„ àwÿ{rÎN ’H@]Ñ>fg»?"!n‘ž\@=õÔSO=õÔSO=õÔSO=õÔSO=õÔSO=õ/Õ#÷"Ö|¯¿ø”#ú;§}Á‹ôy¦ò”AÏ]y¯¾Âö”«çOPÕ+Íê¶’ü$õˆ£áÔ…30µµšL”‹êê©ÛªÕW¸·ª…ô)ª7»Ð#ë·ˆqõ;‘ãéí!ÈmU!°ºwÙYzÆ>EŽ«‡3ö)¬GéCÝj£ Å=æôðôe í˜ý®kL»ƒØËÔ1ÖXm5±û Öè/o §é±úÔÍJ–ÖßP^ Ç]«w‰±ŠÎስ u fõS©WTŒèÝi\?‡Çôb¯–‰qÉcwrÝvªPi°Ôø¹ŒnݿɂØäš(ÙÂN®k†/¼êÖE⼬îÜzNÀ_Ï®/8©þ]zs·Ãcô{ãz¯õgèeö[6õëÊZ׉ûf‡«þV S~!fn…¸Gêëå‹•ân8©ö*‘3§_3r´~ä, ×¹jý+.ô¿¢VŠœsêSƒþÑ9æ½…zê©§žzê©§žzê©§žzê©§žzê©§žú+Ë:–« ¸›RIEND®B`‚urwid-2.6.16/docs/tutorial/qa3.png000066400000000000000000000013371470350774000167400ustar00rootroot00000000000000‰PNG  IHDR½iJ}ÕbKGDÿ‡Ì¿ pHYs  ÒÝ~üƒIDATxÚíš në „çþ÷ä•¢˜û¬í`ÒAz­UƒùXÆ0ÞÒΤ'=éIOzÒ“žô¤'=鈟Q`·±Tô÷Mn£ÇÖô¯G¿þÊÞ—ù"Wwo¥æÖqY¹Pë»Nd/v¤J?ù§&*$Q3õ¡7‡6}…èõ£&}<ý±Ú´ôrÊGèVEó{齨§Pìë»_§‡M=Ú§˜ôÃûNI/×)œ¼2éˈñœb=+W(Ñ×ñqúu§©°|[ú¤H2E‡¼˜W -ì´- a¬Oè›I„>0•H®鵕ÍÃôž£/w»ÆÐ"µöîD7;Sn^îDÅš£ÉÞÕM³¥OÂgg‡…ZÒèÉVsí%±_77l êùƒ˜;ˆ;n¸½êE´Þ-zTŸ7i€¾£œz/öÕ1û3ôùnëX¯„Qú‘·¶¡G^‚û9zœ O¨Í÷gÑèÐWYñÌkŽÑ;Ì=&X&ÍÔ ¦äßÒO4…kˆ×Ä~}Ù>‡¬~yoCïMÁÃGÑÉ€ïA_»ß¼‘Èlpßµ®Ò}³Ã¿ Ãt\•§f”ãÑCK7ÞEo¼Ví¦bŸ¾{`çÔq ŸÞÐ}4­ÑHž9¢ÜKA‡sÑ_9§p[9E°ü¿~è1IOzÒ“žô¤'=éIOúß¡ÿÇKMÝúÀŽIEND®B`‚urwid-2.6.16/docs/tutorial/sig.py000066400000000000000000000013311470350774000166740ustar00rootroot00000000000000from __future__ import annotations import typing import urwid palette = [("I say", "default,bold", "default", "bold")] ask = urwid.Edit(("I say", "What is your name?\n")) reply = urwid.Text("") button_inst = urwid.Button("Exit") div = urwid.Divider() pile = urwid.Pile([ask, div, reply, div, button_inst]) top = urwid.Filler(pile, valign="top") def on_ask_change(_edit: urwid.Edit, new_edit_text: str) -> None: reply.set_text(("I say", f"Nice to meet you, {new_edit_text}")) def on_exit_clicked(_button: urwid.Button) -> typing.NoReturn: raise urwid.ExitMainLoop() urwid.connect_signal(ask, "change", on_ask_change) urwid.connect_signal(button_inst, "click", on_exit_clicked) urwid.MainLoop(top, palette).run() urwid-2.6.16/docs/tutorial/sig.py.xdotool000066400000000000000000000002301470350774000203600ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 21 7 type --window $RXVTWINDOWID 'Tim t' type --window $RXVTWINDOWID 'he Enchanter' key --window $RXVTWINDOWID Down urwid-2.6.16/docs/tutorial/sig1.png000066400000000000000000000007631470350774000171210ustar00rootroot00000000000000‰PNG  IHDR½iJ}ÕbKGDÿ‡Ì¿ pHYs  ÒÝ~ü—IDATxÚíÛá®Â †áïþï“û8‰:(Pbœ%ç]4.“­Pºý™ÒÉ›ÎÖKÏŸ$-žp§mí½Þ9vŸþé}¸mýi±§¿&ê5er¦®j¢®£¯ˆ&p¾rÓ¸U°j;×z ;ê¹QV—#ö{í©mÜÆÊŸTµ]È{UŸÌ[ïnŽ÷;}ë>–Ϙe®§WN‹ñªgΞþŠeS _zºì$s~uVsÜÌÙÒ×kc£p}žsÅ2öÍåFy_'¸9{®W—÷i}ì¡ò׿M§ÉhT™dN3ð2çÏô_¨Ñ7Þn¼·Ö/<•|A/[¢NÛÊ=zôèÑ£G}$ýÑϘ'¢Ñ£G=zôèÑ£G=zôèÑ£G=zôèÑ£G=zôèÑÿ?½ó‚ƒ÷ÂFL¶Öç#H/Œ[é8}k,¾ ÷[¾œÑ4/Ýô+#Nþk ·»­:š~œ9}Òk¾j㎽šÊãèy •Û¿[U5ç*5!jŽ¢-Â:=zôèÑ£G=zô?Þþïç5Ĭ‰ÅÅIEND®B`‚urwid-2.6.16/docs/tutorial/sig2.png000066400000000000000000000013301470350774000171110ustar00rootroot00000000000000‰PNG  IHDR½iJ}ÕbKGDÿ‡Ì¿ pHYs  ÒÝ~ü|IDATxÚíœ nà …ßýïÉ=&­ ÿI›ÄÙêÔ(!øÃƒÆÛPž\ðlzàõU€Å®d›XÓ;÷®£ÿµ46·›þʰØG¿ Ô{Èà ]W¦£o‹ÂpmYUÖ¶š»ºszÞÌF=”ÝãvGþlWЕµ­ú)]Ý…¸G×ñɸYnuß^ØÚÖ–1‹\5,âYGÎ>úÍ– ÚôtÚâ}mu–sÜÈÙEßω»Ñ×-6߫梸ï\¼=§‡‰û²î{ÇUþü—ãä#tw&‘£ñþŒþ„}á’páÚ’˜~aWr7=d>KPp¨vú]«C:ú5þTz½ÈèÙšÝ÷ÕÃЄOˆÒ“þ zdÏ9¯½–G˜7çì®Múè½ÇÌVHOzÒ“žô¤'=éIOzÒÿ!úð-bô[n}*?oêÀ§ðª’„S9¾Fï K‘g´EA…žÎ#G&žŽ$Cú «íê#xÐíØÃf-“è¥>½•d,DN§ Qô¶ªmÇÕˆ¨¦Š‘,`/}úhÌÇôUøª5¾J_0ð½ÛÎÔ÷ýÓ éUÐZß»S‘Iô*¥UI†È‹ô¥rÙ ³@8éÈÊ$â 5d èÑ2äûsë"úéò€û¸cÆäôcIÆúÊÌ2éÓçÜÕÿúTçPŒÒ“~™þ¡=è6©¤n!=éIOzÒ“žô¤'=éIOzÒ“ÞvîŸþæÅÆúx$é…àFy}GØ:þ ‹{ñáxS³Ú™‘'þÐËKM>ŽE‰è1Ÿµy}•yú îô ãöW«.ç(uUxîsH°üà±—üKIEND®B`‚urwid-2.6.16/docs/tutorial/sig3.png000066400000000000000000000016371470350774000171240ustar00rootroot00000000000000‰PNG  IHDR½iJ}ÕbKGDÿ‡Ì¿ pHYs  ÒÝ~üCIDATxÚí› ’¬ Eïþ÷™}LÕ{-æ8bãTºz¦-Äpá£\AoþàÝôÀÿ/x’­SZNï¤=Gÿ¯¤¼¸iú'ÃbŽþh¨O“Ái:‘¦¢ŸYÁͲʬË:(òöéúYlTsF)NŸ)üï8‚άËj_yâ¢âv³Ü*ÝØÜ¶,£¹=ZXĽ6Žœ9ú£,Ít·Ûìz]joÌq#gŠ^ö‰û¤o-À,ž¾W梸—ήîÓÃÄ=ûÞq•ßÿyÀ8ã DJ'r”ãÁ®ïÑ/£œœ[6¦X•ìL;;ßG?@š™2S‰§°Ž~Ì:úôEObõKÑ$Eg”é‰L#Ï“¤ð°=N}&èißãø}U«à™AYV½‡ç)…¥@MGõÐSLïÑdÎå–žÖÓÛ%ò=Ú­”¶gè\ëµ³¾§Izô|Oëèq‰Þ8¾Ç ô²éÜÛqv\‚—G¦èn£±Yú-?EÿEúW¯1gڧ苾苾苾苾苾èÿ}¸‹¶bô”[ïÊ÷M]øm|CM’°”ã6z¯YˆïÑ’x 3× yz\RP|wQ!íãÅ]^µ—ÓóµH2·´_£USìrÚ ’oô=¢yMrV?§ØåôÜÐ|äø &H‰ÂU´»0ND±+è‘qNoÁjyò}Øýúä¦ä4¸ˆÞŠžsßsÝ<E1yw—‹|OÐ-TPØ<®¢˜ÕS©¶п÷SôE_ôE_ôE_ôE_ôE_ôE_ôEo+÷Õß}±1Þ›Ô¾vó&zñÆ(ÞvaAµ ¾÷ò{rMS¯à<ŽïÒóCM½}96Š6¢G¿×îë{¨‘Ç¡â~ƒîÑwîÈÓ@| ¾Ö9EñóSþ%1Ne®OIEND®B`‚urwid-2.6.16/docs/tutorial/sig4.png000066400000000000000000000016441470350774000171230ustar00rootroot00000000000000‰PNG  IHDR½iJ}ÕbKGDÿ‡Ì¿ pHYs  ÒÝ~üHIDATxÚí› ’ã Dûþ÷Ô=¶j6ýOpð®R™I cxrh@O~áÙôÀß&o¸“mP[Nï¤ÝGÿSS^Ý2ýn±Fÿî¨W—Áé:‘¦¡¯YÅ­d•Y×Õ (òŽéz¯6j9£—{ ÿ{ƒÎ¬ëjoy'ü¢áƒ~³Ü*Ý~±¹m]>ÆÈs=z4·ˆGmì9kôﺸ ´¢‡Ã`÷ëZGsŽë9Kôrl,Lܾõ+±Û^ù½tpv÷˜ÆïiÞöŽ©üñÏÆ™Àh Rž£ vÿˆ~Ã}ã’pãÚr0ýDTr2ý7^O§_Y²Æ¡ï`ý\éÓ_ì`=‰è—¢EŠº—é…L:#Ï“¤p·}_¢éÉ ªâ×h›´ö Ênjôð<0µ°¨ªé݆ ôÓ{…h2çvKOûémˆêóœ¾ BµÆGé ‰íÝr†¶—Wo¤WNkmï G&!UJ³’ oã'§')äB¸9EOGV&ÏP‰$#¡GŸ!_ï¯.¢¿]ð=î˜ñpú\’1¿2W„|=.)(¾TcJûxÑG·7íáô<IÖ–öi´ÊaŠ §­ ùƒ¶G´®³EÎê‡ãN¯= ­{Ž0AJDFÑn`œˆbwÐ"ãœÞ(‚Uxr}8üú䡤¸‰ÞŠžsÛsÝ<E1yO—›lOÐ-TPØ<®¢˜µS©¶6Ð?÷UôE_ôE_ôE_ôE_ôE_ôE,=ü:Ÿþ9ÝâœC²Óy$¶ Š“Š=vó$zqâÆ§§]˜Wƒï>bgw`GÆ9þ€ž7ÇÎDx™3`Ñc urwid.ListBox: body = [urwid.Text(title), urwid.Divider()] for c in choices_: button = urwid.Button(c) urwid.connect_signal(button, "click", item_chosen, c) body.append(urwid.AttrMap(button, None, focus_map="reversed")) return urwid.ListBox(urwid.SimpleFocusListWalker(body)) def item_chosen(button: urwid.Button, choice: str) -> None: response = urwid.Text(["You chose ", choice, "\n"]) done = urwid.Button("Ok") urwid.connect_signal(done, "click", exit_program) main.original_widget = urwid.Filler( urwid.Pile( [ response, urwid.AttrMap(done, None, focus_map="reversed"), ] ) ) def exit_program(button: urwid.Button) -> None: raise urwid.ExitMainLoop() main = urwid.Padding(menu("Pythons", choices), left=2, right=2) top = urwid.Overlay( main, urwid.SolidFill("\N{MEDIUM SHADE}"), align=urwid.CENTER, width=(urwid.RELATIVE, 60), valign=urwid.MIDDLE, height=(urwid.RELATIVE, 60), min_width=20, min_height=9, ) urwid.MainLoop(top, palette=[("reversed", "standout", "")]).run() urwid-2.6.16/docs/tutorial/smenu.py.xdotool000066400000000000000000000001661470350774000207350ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 24 11 key --window $RXVTWINDOWID Down Down Down key --window $RXVTWINDOWID Return urwid-2.6.16/docs/tutorial/smenu1.png000066400000000000000000000017451470350774000174670ustar00rootroot00000000000000‰PNG  IHDRØ¥÷l.bKGDÿ‡Ì¿ pHYs  ÒÝ~ü‰IDATxÚíav¤ „ûþ÷¬{ì{³ÊŠÐ Ò݈[“atâLå+°KˆÁöõÕ>Öø,(#1#1#1#1#1µQc·ÞãšWVÖ–XþØï¡qebRÑ oö˜ØÛ… “ýY2ço’ä×ê–ú˜üDŽšbËÛ‰Õ…¡–7ècw…Z1–XäèbALH.Xq>¶Õ`©Y3Þ +>%¦É|Íc¸V”— 'g®QbŒ-k(öoר08œÇš'èÝ Ûõ]cÂôu#•ÇÍw÷<™Í©gûŸˆ1A¯¡ìI‚–Ê) …QßXm%U຀‹Êîýiì cb‚®°£9µ†»²»Ä]aQ+YĔ؀0D{ÞÇúVÌ‚ç_çeß¼(Ö£bwðý•†£‹˜ŠÝá¾æ°p±§ëQ塟 ¯7œtq¶e]eœm¡¶¢2*B²¥´Ç¬Mpl)_â†=6¶ ‹Œ-‡Sï¹q4ç^4ŽÚ3>¶´Øk«á + ¶ˆÈ[„YÇ–«ÂR¢[. ;bbKO˜ ±7Æ‹‹Œ-¬$F+~׊ZlÙ÷L»Ä4/-ÎS6[& ó-Èê@Èy‹LR6[P”Kš tŽ-ЯrÏf[Z’ÄÓ‹Þ±e«ÖÁÍbK·ÍÑe[ ð™q¾¨‹±e]eŒ-´âÂV,.-Jäú°êG0!&7Þgž2‹Ø2åŽ%±%U¢Ý@Ìì±%ÝèÊ Ä|*,ÿØr¦4bÌh[TaÅ"±8]&±¥MÌi„‰™mésècA±ù¨˜´ºŠŒ-¬$F+~׊َʵC¿µaõ`EìÎ"1G0æÄë<…E-;„§µa‚c‹”3še1årÿÌÀ¿mQ…!L˜õ"±»aaÛ"±Ä‚‰¡6Ûâ)Ì?¶äV¬§{aŒ-¬$F+ÒŠ$Fb$Fb$Fb$Fb$Fb/'ÖPøÿó)‰‘‰‘‰‘‰‘‰‘Øâ?¸Qíà4‚ IEND®B`‚urwid-2.6.16/docs/tutorial/smenu2.png000066400000000000000000000017371470350774000174710ustar00rootroot00000000000000‰PNG  IHDRØ¥÷l.bKGDÿ‡Ì¿ pHYs  ÒÝ~üƒIDATxÚíár„ „óþï™÷èŒs* •d»öG9m=w¾%“kEô÷õÕ~lðYP$Fb$Fb$Fb$Fb$Fbæ Än¾íšgVV—˜ÿÚ²gœ™˜4ÈHÂ]X71Y_mæ\l~-î™`ŽIÆO$Õ¼ ²=£+ ÓLØq0Á»+ hE,1dtñ & ’ VœcŽýÖ`Û°d¼7¬ø”˜%s˜­{­(ƒ ë'†3W/1¦-s(ë $Fb$Fb$Fb$Fb$Fbs£¿kÅÓ£ûãØå+•$ý¤©ì>11^‚­+ÞÄDMafµe‰LF!&JÙ]bbî9L«-bòVö˜XEXö2~brGöxŽI[ØZD+ËEéz¥%LlbŽe qŠõp¿Uq‹sl?Ô+¬¥ëÑÊ£vƒ^ýö;}¨OXíââÁo,Ê kÅWV›ÿˆÓ–9”ù¥-Ь"Ó줦-ŠîÀq&&Ú&Yo˜t‹ÁÒ–Ü–Á,¨´¥)LÂÒ–ýº·ÜdoL¿;y–¶h«+Ì÷.m© x’L[î ‚t±Ú2¯2V[hʼn­hDŵGxÏ_öèÑ€Ó–üG°cÓ 0dÚ’ÜzσԜ§úK”²€´¥>ÐR™ÂO™oÚ""£óN[® Kª-2Eµå¢0ñ#öBÚÒæBlÄ´eþjË׊ vE˜ ¶¹Yq|a­ø;]~òýÕábœ¢bj¼K2ŽG«þÄTFf5c='¦U_……ÎKõ%¦¶-’öŠ §®ÄÂämaÏç˜ÞzP﨨7‰aƒGÏ}LïÍ1l¸ïZy7è-*öh .ï•Ç8׊Ÿ$Æ zeýÕèŸ!±.Á…˜ÜxŸ÷”uW[^_mÙw®ŽÌ?äbÖèj˶·òÄbºƒ«-aÆcF·jKMØvÅYѯÚÒ$a0Õ–†°€9ª¶è1*nZâ"«-\+NHŒVü® ©¬„õ†•/Á‹Ø&±@0îÄ*}@a¨&±Ó‚ðÔ&:aÚršcÅNà_TóÙS˜Â„y7‰Ø!„á?’jÓ¤ ",>m9Z±œ­ø cÚµâ„ÄhEZ‘ÄHŒÄHŒÄHŒÄHŒÄHlpb…ßð?Ÿ’‰‘‰‘‰‘‰‘‰M>øNp¤j¼,¡IEND®B`‚urwid-2.6.16/docs/tutorial/smenu3.png000066400000000000000000000010611470350774000174600ustar00rootroot00000000000000‰PNG  IHDRØ¥÷l.bKGDÿ‡Ì¿ pHYs  ÒÝ~üÕIDATxÚíÝán‚0Ðûþïyßc1ÓXÁÒ–˜Nür¾¶ýXD>¿zdgƒn¡ˆ#FŒ1bĈ[,ÙÝoÛVÅ;'+G$FŒ1bĈ#F¬k1UTEbĈ#FŒ1bÄSEU$FŒ1bĈ#F¬q1U첊1zÜpŽ]Ÿè±MÞ;þþl±ƒM_‡Žð‡Å#vœîÁŽÏ±×ŽWsbþ†]¯vÅú`,~²·gbg²ýË}LçÁ†]1~Íò`åÈKç:wUÜ,×óäç`y4Øùb‹Á†–ÆB½V#Åö.~I,gtÛÅZšcŸ¹gŽå5bÓõí-ÙxÁœ4°´*ÎX\B답½¹Wì@LU‘1bĈ#FŒ±ÆÅ¶‹Æ¶jUl?ØÁ*>7?øìÙ³!– §È‹ƒEÖËh!ØðZbÅ`yi²Šb¥`—./YU¬,/Ÿf5çØz¿0ɪ®ŠÙ†XÖ¿Že s,O¸Ž/Ðye®Úwílî»óô=’ù1bĈ#FŒ±,¦ŠªHŒ1bĈ#FŒXãbª¨ŠÄˆ#FŒ1bĈ5.VHØÇÀ>%FŒ1bĈ#Fìæƒbv±e(=IEND®B`‚urwid-2.6.16/docs/tutorial/urwid_attr.py000066400000000000000000000007701470350774000203040ustar00rootroot00000000000000from __future__ import annotations import urwid def exit_on_q(key: str) -> None: if key in {"q", "Q"}: raise urwid.ExitMainLoop() palette = [ ("banner", "black", "light gray"), ("streak", "black", "dark red"), ("bg", "black", "dark blue"), ] txt = urwid.Text(("banner", " Hello World "), align="center") map1 = urwid.AttrMap(txt, "streak") fill = urwid.Filler(map1) map2 = urwid.AttrMap(fill, "bg") loop = urwid.MainLoop(map2, palette, unhandled_input=exit_on_q) loop.run() urwid-2.6.16/docs/tutorial/urwid_attr.py.xdotool000066400000000000000000000002441470350774000217670ustar00rootroot00000000000000windowsize --usehints $RXVTWINDOWID 21 7 windowsize --usehints $RXVTWINDOWID 10 9 windowsize --usehints $RXVTWINDOWID 30 3 windowsize --usehints $RXVTWINDOWID 15 2 urwid-2.6.16/docs/tutorial/urwid_attr1.png000066400000000000000000000003771470350774000205240ustar00rootroot00000000000000‰PNG  IHDR½iÊš PLTEÍÍååå¶<% pHYs  ÒÝ~ü™IDATXÃíÒA à …á­—èÑz‰7 {ñ>#x€,´:ÅE[b¡M!‹÷m"âdYˆˆˆˆÎä:`æ6V]~ ˆðjkÔÙ£ôùY ÀSàªö@ª¾¸Ø&cÞ |Mbïn›$D´×ýÞOŽ”êöàCðøèG°No)n¾Ø-…Ð@›É‡ý ˆˆˆˆÎäQÉ”¾ÀïIEND®B`‚urwid-2.6.16/docs/tutorial/urwid_attr2.png000066400000000000000000000003701470350774000205160ustar00rootroot00000000000000‰PNG  IHDRZ‡ëf¦` PLTEÍÍååå¶<% pHYs  ÒÝ~ü’IDATHÇíÑ1„ …ah³Þð‘ŒýXpŽ@Áì@aD4 ‰ÅóWæ 1øœ&˲¬Á>Ú‚sYæ_péÎÁ ~ßRq+—Ä®x Ëù'÷òpc¾%'p¹Ïè>¯øª7DÙ9?4îw9ï,µë÷êž±q*Þž×_ØíûáÕû— óòÎüön–eYÖ_ögv.$h‘hIEND®B`‚urwid-2.6.16/docs/tutorial/urwid_attr3.png000066400000000000000000000003731470350774000205220ustar00rootroot00000000000000‰PNG  IHDR-"m PLTEÍÍååå¶<% pHYs  ÒÝ~ü•IDATHÇí’1Â0 E}D.Á½X¾¥xO‡ÞÇÜ •jK0„dDéâ7$_Vòd+!J’$!:¿á‚\_ŽŸö”0* ÊÍïUömF¯G%P_Ÿ>H27—˜ ÷á KHbMËxKõÖsY‚l%:rt[7|!QüV2M‰„_GV6RzPôÊò÷o„$I’„èðbô…TÙ°IEND®B`‚urwid-2.6.16/docs/tutorial/urwid_attr4.png000066400000000000000000000003571470350774000205250ustar00rootroot00000000000000‰PNG  IHDR‡{àå PLTEÍåååÍô>6t pHYs  ÒÝ~ü‰IDAT8ËíÑ1 Ã0 P_¢÷êòÖî ¹z¢3ZIÐR»4é˜j°?âa!œRº`¥®Éë+B( ÕèŠk„õ[‰óEèk'¤dcÁÓÑ*ÙIc»¥±¬Ñ­ñ˜#¡ª‹²Ç’Z­¾[=õo[ÏO§wƒà'öw>pHºß²™$ý ¾ÓÚŠ>WœüG÷®Òä`óßÿr8ùwø+~àŸÅs寴ÛüPý¡¾ô3±¥lcM}·@iµœPñ³-<·T™‡Ó³±D!*7€Ì|Ò§q–Љ5œÐ¿kå¸büÌ|Vž˜fö®~ÒÆ†Î’áL…£ ɧÉá=q%Ýo´þR~BîG)Ó«€%œ?ŠìëÔŽ2…´¿pΪ(KòšbÔÃ$@Ÿ$‰M˜Q(kŠåà/|ÎôqâÀ¿ZQ:ŽTÒú(3"m¾!ôoà¥x¢|€z˜£4§s^Ž(qyiÂ' ÃÁÞ”ƒV—å¯pÎpÚIû̃¯õ|6c~ý™[^À)J‡Ž©£—»¨‹yi¢õõá€þB=øð@˜v“%Ì þ´ù–û2óEèUšìð)Æ8±øÝÜ¦ŽŠ¾*ÃzžF|ºâ'š}ÑññÕÅsBŽ"èsàÖD|øòÏgóøú7ú9ÈîN9+±(!ïsS˜è8±yEÌæ„°²‚@®ÔNDFëÇçÒÂÞ•¼:±Ž!ä ^ÐЊ†}õ®ÝÒ¨Kêæ~€Qååà®ñÝ=< …üÑê’þ+­.ÚÄ. ¨E­ ÿè&LN¯…éä¦þ+­­u.ÑÍN½¦µe+ÙÕE/…1äMíÄÒnKlbœ´¹Ãc>ÝOp’ZƒpP<ÔÇCæ–Ù«ísJ)óÖ)Cv5"ú‘{kLm§“[$ý ‚NúH9ÌYM“w0W¸+…îØ]ùj@N< …Åà‰Â;Œ3«‹X?3èß°püÁfàô…྅I3ì>—ùê|‡´¿DSG¥®U[…#Ä0rŸ?ˆ§pËEaç2ÎoIW@f Á§E®¸JÈÑ(d§ÉÁB}@ÿF¢ÿ=@¡6øFÛ–<ÂD»ûÚê(<„LBDHÆ.ú|ý„Ø-ˆBYfü´Aܨݧy s1œ>±èÜÉaŽBIRô)ƒaÃx0:†l²û}ŽAð¡¯l tÁº‚"éñp†x_ñÝAašìû)D€Ô&=ÆìfbÌпM'_[£ùé=t ¤Kì‚ÄÏ‘ÓÚ2Pds IëÄC¡M`ôTÈ G­ã–RBOô‘JÒ(@.¹–@¡c[ú)8ÖMðAçÁ{M9þ€ÂÓGL!¡ÇŽ)…Òå{iRZŒBP6ìkè\€ RÈM%­¯Cìa§ðs4…:Ç S˜6÷Œú£‘!\ü•ñ'ÙÒHã±Ì+wƒÜ…ù9Š4¢ÏÆøSÞ–®¤W óXE®Mkšü©Aºß†Ÿ}Ášñ\°Â A³´K”øÆ1RŒáÑŸ™‡´²`ó¿ÆÒ“£•66eMPô‡Ï’-ž í3C×þʽ¥ë@Y=MN52 ÓéÝØ@4( c½%*…ƒ’&Köv`$ó•\J¡G¡¼°U<§È˜-ãf'†žuB•R¼Xwœ5v“ k˜zo9Íê’æßAu ¦{é¡Ç¼‰9Ûš@^Ô@R ~/¯áð¼å¬µIêþ0à\± DÃz—ç":qt¾¡ ³ÄÝÓtÎ1Î1c- ˜Ðz6y&fs’¦<”: ÁÍB…Eáç§ð§‡'>zxb¡ %@‡| ›&ŠO׃µ^ïc­¦ÑŒ¢,ìœCÄL¯Xxµ»¡ÇFtÎKl»€$Ótîs!ý6¼‘½„ð¾¶á#Ö›oÓä0ë…ì\”ÃBý¶&fÙþ^¢6ßòž´N0PÁ0ñޢݬËXO™‚¶×»r\£”Ã]­c>Ž…ò«¢b¸~ýY.ð°;#l:­o¨s1`± ÑiðV5l²ðI€ÞÇÇBÑü8>H§^7kë~Ç »¶ Ž@e ü“±2*åÉ;ƒ›F¡è]+½¥z*ÒvÎEChq «Ìg—…Ó{xAÖ>»3… ´"F¦S;Ø2ö—¤GÌñ_è;6É¢¬…2„…üµIÖt{^$œÅ7ê;DbH$k Ä›;mú÷c í;?µ¿ Š#øuÖØLÈá€ä[Zñ£H›o ,ÔØ#Úd9z¢ÇÂ(óâõ“׈û}¢Õe`€i¬!EAIC4^^¸o|Çæ—faÒ,L/ÌE³°ù†9é:ñ"ƒ|Ši¼ßy(,,rRa(Šl¾õ±ØÆlŠeÔž¢W=c¡|= Fmcì,4©ÑH,$‡zà‚ükž…†›Ø_†…Ý+}°Í÷˜#Rnå&Öh}¸úËìî_µ¸r³0Å䮉u­49`)—rüelI+Ÿ…ú˜{g¡-Õ‹<_W¬ÁMåXQ´ò=YXŸÊba1\z‰ƒ°q‡õÑ«eáb Iƒ$M°Ðظka!¶¯ ß9Å„¯¶ŒØÆ†;hæÿÂ8ÆqdØ0T]d–j»™ ¹Ä ï6´b9ßÓÏËÂVþ_#f¡­s¸DŠ—eƒµ µ%´ÒŒf‡oBx»uøAä+Ÿøÿ[Šõm³ÿ"0T‹Žµ·FŽD°jXØÆ°××"|¡¼¿´2—•~’w˜Å^³hMòÐð IØF{@ÿF%€È¥+.¤ gò»#vÆáƒu"RZµ¾°?Fö€ ­¯ã;ä=¦>£½×`ùœ’¤jžV—l-=¢6æhÚ Ýù`©°¼°sé½tއY¬k#‡QïïN°càÎÿ]Ãô.ÖrÓ#vY}‚§Ó>CÈzv3êåeÿ†ßê³Ø Êìà„oC»#R{|Êþk‡\a¡Ç Ðq¼_nl²¶×ÌÄE‡Àf‡îhyö.2óI¹i*¡ÀöAпEá®.9‚ቅåZæðv©‘ቅþèµ °ç¾IEND®B`‚urwid-2.6.16/examples/000077500000000000000000000000001470350774000145655ustar00rootroot00000000000000urwid-2.6.16/examples/asyncio_socket_server.py000077500000000000000000000126741470350774000215570ustar00rootroot00000000000000#!/usr/bin/env python """Demo of using urwid with Python asyncio. """ from __future__ import annotations import asyncio import logging import sys import weakref from datetime import datetime import urwid from urwid.display.raw import Screen logging.basicConfig() loop = asyncio.get_event_loop() # ----------------------------------------------------------------------------- # General-purpose setup code def build_widgets(): input1 = urwid.Edit("What is your name? ") input2 = urwid.Edit("What is your quest? ") input3 = urwid.Edit("What is the capital of Assyria? ") inputs = [input1, input2, input3] def update_clock(widget_ref): widget = widget_ref() if not widget: # widget is dead; the main loop must've been destroyed return widget.set_text(datetime.now().isoformat()) # Schedule us to update the clock again in one second loop.call_later(1, update_clock, widget_ref) clock = urwid.Text("") update_clock(weakref.ref(clock)) return urwid.Filler(urwid.Pile([clock, *inputs]), urwid.TOP) def unhandled(key): if key == "ctrl c": raise urwid.ExitMainLoop # ----------------------------------------------------------------------------- # Demo 1 def demo1(): """Plain old urwid app. Just happens to be run atop asyncio as the event loop. Note that the clock is updated using the asyncio loop directly, not via any of urwid's facilities. """ main_widget = build_widgets() urwid_loop = urwid.MainLoop( main_widget, event_loop=urwid.AsyncioEventLoop(loop=loop), unhandled_input=unhandled, ) urwid_loop.run() # ----------------------------------------------------------------------------- # Demo 2 class AsyncScreen(Screen): """An urwid screen that speaks to an asyncio stream, rather than polling file descriptors. This is fairly limited; it can't, for example, determine the size of the remote screen. Fixing that depends on the nature of the stream. """ def __init__(self, reader, writer, encoding="utf-8"): self.reader = reader self.writer = writer self.encoding = encoding super().__init__(None, None) _pending_task = None def write(self, data): self.writer.write(data.encode(self.encoding)) def flush(self): pass def hook_event_loop(self, event_loop, callback): # Wait on the reader's read coro, and when there's data to read, call # the callback and then wait again def pump_reader(fut=None): if fut is None: # First call, do nothing pass elif fut.cancelled(): # This is in response to an earlier .read() call, so don't # schedule another one! return elif fut.exception(): pass else: try: self.parse_input(event_loop, callback, bytearray(fut.result())) except urwid.ExitMainLoop: # This will immediately close the transport and thus the # connection, which in turn calls connection_lost, which # stops the screen and the loop self.writer.abort() # create_task() schedules a coroutine without using `yield from` or # `await`, which are syntax errors in Pythons before 3.5 self._pending_task = event_loop._loop.create_task(self.reader.read(1024)) self._pending_task.add_done_callback(pump_reader) pump_reader() def unhook_event_loop(self, event_loop): if self._pending_task: self._pending_task.cancel() del self._pending_task class UrwidProtocol(asyncio.Protocol): def connection_made(self, transport): print("Got a client!") self.transport = transport # StreamReader is super convenient here; it has a regular method on our # end (feed_data), and a coroutine on the other end that will # faux-block until there's data to be read. We could also just call a # method directly on the screen, but this keeps the screen somewhat # separate from the protocol. self.reader = asyncio.StreamReader(loop=loop) screen = AsyncScreen(self.reader, transport) main_widget = build_widgets() self.urwid_loop = urwid.MainLoop( main_widget, event_loop=urwid.AsyncioEventLoop(loop=loop), screen=screen, unhandled_input=unhandled, ) self.urwid_loop.start() def data_received(self, data): self.reader.feed_data(data) def connection_lost(self, exc): print("Lost a client...") self.reader.feed_eof() self.urwid_loop.stop() def demo2(): """Urwid app served over the network to multiple clients at once, using an asyncio Protocol. """ coro = loop.create_server(UrwidProtocol, port=12345) loop.run_until_complete(coro) print("OK, good to go! Try this in another terminal (or two):") print() print(" socat TCP:127.0.0.1:12345 STDIN,rawer") print() loop.run_forever() if __name__ == "__main__": if len(sys.argv) == 2: which = sys.argv[1] else: which = None if which == "1": demo1() elif which == "2": demo2() else: print("Please run me with an argument of either 1 or 2.") sys.exit(1) urwid-2.6.16/examples/bigtext.py000077500000000000000000000144021470350774000166110ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid BigText example program # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid example demonstrating use of the BigText widget. """ from __future__ import annotations import typing import urwid import urwid.display.raw if typing.TYPE_CHECKING: from collections.abc import Callable _Wrapped = typing.TypeVar("_Wrapped") class SwitchingPadding(urwid.Padding[_Wrapped]): def padding_values(self, size, focus: bool) -> tuple[int, int]: maxcol = size[0] width, _height = self.original_widget.pack(size, focus=focus) if maxcol > width: self.align = urwid.LEFT else: self.align = urwid.RIGHT return super().padding_values(size, focus) class BigTextDisplay: palette: typing.ClassVar[list[tuple[str, str, str, ...]]] = [ ("body", "black", "light gray", "standout"), ("header", "white", "dark red", "bold"), ("button normal", "light gray", "dark blue", "standout"), ("button select", "white", "dark green"), ("button disabled", "dark gray", "dark blue"), ("edit", "light gray", "dark blue"), ("bigtext", "white", "black"), ("chars", "light gray", "black"), ("exit", "white", "dark cyan"), ] def create_radio_button( self, g: list[urwid.RadioButton], name: str, font: urwid.Font, fn: Callable[[urwid.RadioButton, bool], typing.Any], ) -> urwid.AttrMap: w = urwid.RadioButton(g, name, False, on_state_change=fn) w.font = font w = urwid.AttrMap(w, "button normal", "button select") return w def create_disabled_radio_button(self, name: str) -> urwid.AttrMap: w = urwid.Text(f" {name} (UTF-8 mode required)") w = urwid.AttrMap(w, "button disabled") return w def create_edit( self, label: str, text: str, fn: Callable[[urwid.Edit, str], typing.Any], ) -> urwid.AttrMap: w = urwid.Edit(label, text) urwid.connect_signal(w, "change", fn) fn(w, text) w = urwid.AttrMap(w, "edit") return w def set_font_event(self, w, state: bool) -> None: if state: self.bigtext.set_font(w.font) self.chars_avail.set_text(w.font.characters()) def edit_change_event(self, widget, text: str) -> None: self.bigtext.set_text(text) def setup_view(self) -> tuple[ urwid.Frame[urwid.AttrMap[urwid.ListBox], urwid.AttrMap[urwid.Text], None], urwid.Overlay[urwid.BigText, urwid.Frame[urwid.AttrMap[urwid.ListBox], urwid.AttrMap[urwid.Text], None]], ]: fonts = urwid.get_all_fonts() # setup mode radio buttons self.font_buttons = [] group = [] utf8 = urwid.get_encoding_mode() == "utf8" for name, fontcls in fonts: font = fontcls() if font.utf8_required and not utf8: rb = self.create_disabled_radio_button(name) else: rb = self.create_radio_button(group, name, font, self.set_font_event) if fontcls == urwid.Thin6x6Font: chosen_font_rb = rb exit_font = font self.font_buttons.append(rb) # Create BigText self.bigtext = urwid.BigText("", None) bt = urwid.BoxAdapter( urwid.Filler( urwid.AttrMap( SwitchingPadding(self.bigtext, urwid.LEFT, None), "bigtext", ), urwid.BOTTOM, None, 7, ), 7, ) # Create chars_avail cah = urwid.Text("Characters Available:") self.chars_avail = urwid.Text("", wrap=urwid.ANY) ca = urwid.AttrMap(self.chars_avail, "chars") chosen_font_rb.original_widget.set_state(True) # causes set_font_event call # Create Edit widget edit = self.create_edit("", "Urwid BigText example", self.edit_change_event) # ListBox chars = urwid.Pile([cah, ca]) fonts = urwid.Pile([urwid.Text("Fonts:"), *self.font_buttons], focus_item=1) col = urwid.Columns([(16, chars), fonts], 3, focus_column=1) bt = urwid.Pile([bt, edit], focus_item=1) lines = [bt, urwid.Divider(), col] listbox = urwid.ListBox(urwid.SimpleListWalker(lines)) # Frame w = urwid.Frame( body=urwid.AttrMap(listbox, "body"), header=urwid.AttrMap(urwid.Text("Urwid BigText example program - F8 exits."), "header"), ) # Exit message exit_w = urwid.Overlay( urwid.BigText(("exit", " Quit? "), exit_font), w, urwid.CENTER, None, urwid.MIDDLE, None, ) return w, exit_w def main(self): self.view, self.exit_view = self.setup_view() self.loop = urwid.MainLoop(self.view, self.palette, unhandled_input=self.unhandled_input) self.loop.run() def unhandled_input(self, key: str | tuple[str, int, int, int]) -> bool | None: if key == "f8": self.loop.widget = self.exit_view return True if self.loop.widget != self.exit_view: return None if key in {"y", "Y"}: raise urwid.ExitMainLoop() if key in {"n", "N"}: self.loop.widget = self.view return True return None def main(): BigTextDisplay().main() if __name__ == "__main__": main() urwid-2.6.16/examples/browse.py000077500000000000000000000277421470350774000164570ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid example lazy directory browser / tree view # Copyright (C) 2004-2011 Ian Ward # Copyright (C) 2010 Kirk McDonald # Copyright (C) 2010 Rob Lanphier # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid example lazy directory browser / tree view Features: - custom selectable widgets for files and directories - custom message widgets to identify access errors and empty directories - custom list walker for displaying widgets in a tree fashion - outputs a quoted list of files and directories "selected" on exit """ from __future__ import annotations import itertools import os import re import typing import urwid if typing.TYPE_CHECKING: from collections.abc import Hashable class FlagFileWidget(urwid.TreeWidget): # apply an attribute to the expand/unexpand icons unexpanded_icon = urwid.AttrMap(urwid.TreeWidget.unexpanded_icon, "dirmark") expanded_icon = urwid.AttrMap(urwid.TreeWidget.expanded_icon, "dirmark") def __init__(self, node: urwid.TreeNode) -> None: super().__init__(node) # insert an extra AttrWrap for our own use self._w = urwid.AttrMap(self._w, None) self.flagged = False self.update_w() def selectable(self) -> bool: return True def keypress(self, size, key: str) -> str | None: """allow subclasses to intercept keystrokes""" key = super().keypress(size, key) if key: key = self.unhandled_keys(size, key) return key def unhandled_keys(self, size, key: str) -> str | None: """ Override this method to intercept keystrokes in subclasses. Default behavior: Toggle flagged on space, ignore other keys. """ if key == " ": self.flagged = not self.flagged self.update_w() return None return key def update_w(self) -> None: """Update the attributes of self.widget based on self.flagged.""" if self.flagged: self._w.attr_map = {None: "flagged"} self._w.focus_map = {None: "flagged focus"} else: self._w.attr_map = {None: "body"} self._w.focus_map = {None: "focus"} class FileTreeWidget(FlagFileWidget): """Widget for individual files.""" def __init__(self, node: FileNode) -> None: super().__init__(node) path = node.get_value() add_widget(path, self) def get_display_text(self) -> str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]: return self.get_node().get_key() class EmptyWidget(urwid.TreeWidget): """A marker for expanded directories with no contents.""" def get_display_text(self) -> str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]: return ("flag", "(empty directory)") class ErrorWidget(urwid.TreeWidget): """A marker for errors reading directories.""" def get_display_text(self) -> str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]: return ("error", "(error/permission denied)") class DirectoryWidget(FlagFileWidget): """Widget for a directory.""" def __init__(self, node: DirectoryNode) -> None: super().__init__(node) path = node.get_value() add_widget(path, self) self.expanded = starts_expanded(path) self.update_expanded_icon() def get_display_text(self) -> str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]: node = self.get_node() if node.get_depth() == 0: return "/" return node.get_key() class FileNode(urwid.TreeNode): """Metadata storage for individual files""" def __init__(self, path: str, parent: urwid.ParentNode | None = None) -> None: depth = path.count(dir_sep()) key = os.path.basename(path) super().__init__(path, key=key, parent=parent, depth=depth) def load_parent(self) -> DirectoryNode: parentname, _myname = os.path.split(self.get_value()) parent = DirectoryNode(parentname) parent.set_child_node(self.get_key(), self) return parent def load_widget(self) -> FileTreeWidget: return FileTreeWidget(self) class EmptyNode(urwid.TreeNode): def load_widget(self) -> EmptyWidget: return EmptyWidget(self) class ErrorNode(urwid.TreeNode): def load_widget(self) -> ErrorWidget: return ErrorWidget(self) class DirectoryNode(urwid.ParentNode): """Metadata storage for directories""" def __init__(self, path: str, parent: urwid.ParentNode | None = None) -> None: if path == dir_sep(): depth = 0 key = None else: depth = path.count(dir_sep()) key = os.path.basename(path) super().__init__(path, key=key, parent=parent, depth=depth) def load_parent(self) -> DirectoryNode: parentname, _myname = os.path.split(self.get_value()) parent = DirectoryNode(parentname) parent.set_child_node(self.get_key(), self) return parent def load_child_keys(self): dirs = [] files = [] try: path = self.get_value() # separate dirs and files for a in os.listdir(path): if os.path.isdir(os.path.join(path, a)): dirs.append(a) else: files.append(a) except OSError: depth = self.get_depth() + 1 self._children[None] = ErrorNode(self, parent=self, key=None, depth=depth) return [None] # sort dirs and files dirs.sort(key=alphabetize) files.sort(key=alphabetize) # store where the first file starts self.dir_count = len(dirs) # collect dirs and files together again keys = dirs + files if len(keys) == 0: depth = self.get_depth() + 1 self._children[None] = EmptyNode(self, parent=self, key=None, depth=depth) keys = [None] return keys def load_child_node(self, key) -> EmptyNode | DirectoryNode | FileNode: """Return either a FileNode or DirectoryNode""" index = self.get_child_index(key) if key is None: return EmptyNode(None) path = os.path.join(self.get_value(), key) if index < self.dir_count: return DirectoryNode(path, parent=self) path = os.path.join(self.get_value(), key) return FileNode(path, parent=self) def load_widget(self) -> DirectoryWidget: return DirectoryWidget(self) class DirectoryBrowser: palette: typing.ClassVar[list[tuple[str, str, str, ...]]] = [ ("body", "black", "light gray"), ("flagged", "black", "dark green", ("bold", "underline")), ("focus", "light gray", "dark blue", "standout"), ("flagged focus", "yellow", "dark cyan", ("bold", "standout", "underline")), ("head", "yellow", "black", "standout"), ("foot", "light gray", "black"), ("key", "light cyan", "black", "underline"), ("title", "white", "black", "bold"), ("dirmark", "black", "dark cyan", "bold"), ("flag", "dark gray", "light gray"), ("error", "dark red", "light gray"), ] footer_text: typing.ClassVar[list[tuple[str, str] | str]] = [ ("title", "Directory Browser"), " ", ("key", "UP"), ",", ("key", "DOWN"), ",", ("key", "PAGE UP"), ",", ("key", "PAGE DOWN"), " ", ("key", "SPACE"), " ", ("key", "+"), ",", ("key", "-"), " ", ("key", "LEFT"), " ", ("key", "HOME"), " ", ("key", "END"), " ", ("key", "Q"), ] def __init__(self) -> None: cwd = os.getcwd() store_initial_cwd(cwd) self.header = urwid.Text("") self.listbox = urwid.TreeListBox(urwid.TreeWalker(DirectoryNode(cwd))) self.listbox.offset_rows = 1 self.footer = urwid.AttrMap(urwid.Text(self.footer_text), "foot") self.view = urwid.Frame( urwid.AttrMap( urwid.ScrollBar( self.listbox, thumb_char=urwid.ScrollBar.Symbols.FULL_BLOCK, trough_char=urwid.ScrollBar.Symbols.LITE_SHADE, ), "body", ), header=urwid.AttrMap(self.header, "head"), footer=self.footer, ) def main(self) -> None: """Run the program.""" self.loop = urwid.MainLoop(self.view, self.palette, unhandled_input=self.unhandled_input) self.loop.run() # on exit, write the flagged filenames to the console names = [escape_filename_sh(x) for x in get_flagged_names()] print(" ".join(names)) def unhandled_input(self, k: str | tuple[str, int, int, int]) -> None: # update display of focus directory if k in {"q", "Q"}: raise urwid.ExitMainLoop() def main(): DirectoryBrowser().main() ####### # global cache of widgets _widget_cache = {} def add_widget(path, widget): """Add the widget for a given path""" _widget_cache[path] = widget def get_flagged_names() -> list[str]: """Return a list of all filenames marked as flagged.""" names = [w.get_node().get_value() for w in _widget_cache.values() if w.flagged] return names ###### # store path components of initial current working directory _initial_cwd = [] def store_initial_cwd(name: str) -> None: """Store the initial current working directory path components.""" _initial_cwd.clear() _initial_cwd.extend(name.split(dir_sep())) def starts_expanded(name: str) -> bool: """Return True if directory is a parent of initial cwd.""" if name == "/": return True path_elements = name.split(dir_sep()) if len(path_elements) > len(_initial_cwd): return False return path_elements == _initial_cwd[: len(path_elements)] def escape_filename_sh(name: str) -> str: """Return a hopefully safe shell-escaped version of a filename.""" # check whether we have unprintable characters for ch in name: if ord(ch) < 32: # found one so use the ansi-c escaping return escape_filename_sh_ansic(name) # all printable characters, so return a double-quoted version name = name.replace("\\", "\\\\").replace('"', '\\"').replace("`", "\\`").replace("$", "\\$") return f'"{name}"' def escape_filename_sh_ansic(name: str) -> str: """Return an ansi-c shell-escaped version of a filename.""" out = [] # gather the escaped characters into a list for ch in name: if ord(ch) < 32: out.append(f"\\x{ord(ch):02x}") elif ch == "\\": out.append("\\\\") else: out.append(ch) # slap them back together in an ansi-c quote $'...' return f"$'{''.join(out)}'" SPLIT_RE = re.compile(r"[a-zA-Z]+|\d+") def alphabetize(s: str) -> list[str]: L = [] for isdigit, group in itertools.groupby(SPLIT_RE.findall(s), key=str.isdigit): if isdigit: L.extend(("", int(n)) for n in group) else: L.append(("".join(group).lower(), 0)) return L def dir_sep() -> str: """Return the separator used in this os.""" return getattr(os.path, "sep", "/") if __name__ == "__main__": main() urwid-2.6.16/examples/calc.py000077500000000000000000000652631470350774000160600ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid advanced example column calculator application # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid advanced example column calculator application Features: - multiple separate list boxes within columns - custom edit widget for editing calculator cells - custom parent widget for links to other columns - custom list walker to show and hide cell results as required - custom wrap and align modes for editing right-1 aligned numbers - outputs commands that may be used to recreate expression on exit """ from __future__ import annotations import operator import string import typing import urwid if typing.TYPE_CHECKING: from collections.abc import Hashable # use appropriate Screen class if urwid.display.web.is_web_request(): Screen = urwid.display.web.Screen else: Screen = urwid.display.raw.Screen def div_or_none(a, b): """Divide a by b. Return result or None on divide by zero.""" if b == 0: return None return a / b # operators supported and the functions used to calculate a result OPERATORS = { "+": operator.add, "-": operator.sub, "*": operator.mul, "/": div_or_none, } # the uppercase versions of keys used to switch columns COLUMN_KEYS = list("?ABCDEF") # these lists are used to determine when to display errors EDIT_KEYS = list(OPERATORS.keys()) + COLUMN_KEYS + ["backspace", "delete"] MOVEMENT_KEYS = ["up", "down", "left", "right", "page up", "page down"] # Event text E_no_such_column = "Column %s does not exist." E_no_more_columns = "Maxumum number of columns reached." E_new_col_cell_not_empty = "Column must be started from an empty cell." E_invalid_key = "Invalid key '%s'." E_no_parent_column = "There is no parent column to return to." E_cant_combine = "Cannot combine cells with sub-expressions." E_invalid_in_parent_cell = "Cannot enter numbers into parent cell." E_invalid_in_help_col = [ "Help Column is in focus. Press ", ("key", COLUMN_KEYS[1]), "-", ("key", COLUMN_KEYS[-1]), " to select another column.", ] # Shared layout object CALC_LAYOUT = None class CalcEvent(Exception): """Events triggered by user input.""" attr = "event" def __init__(self, message: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]) -> None: self.message = message def widget(self): """Return a widget containing event information""" text = urwid.Text(self.message, urwid.CENTER) return urwid.AttrMap(text, self.attr) class ColumnDeleteEvent(CalcEvent): """Sent when user wants to delete a column""" attr = "confirm" def __init__(self, letter: str, from_parent=0) -> None: super().__init__(["Press ", ("key", "BACKSPACE"), " again to confirm column removal."]) self.letter = letter class UpdateParentEvent(Exception): """Sent when parent columns may need to be updated.""" class Cell: def __init__(self, op) -> None: self.op = op self.is_top = op is None self.child = None self.setup_edit() self.result = urwid.Text("", layout=CALC_LAYOUT) def show_result(self, next_cell) -> bool: """Return whether this widget should display its result. next_cell -- the cell following self or None""" if self.is_top: return False if next_cell is None: return True return not (self.op == next_cell.op == "+") def setup_edit(self) -> None: """Create the standard edit widget for this cell.""" self.edit = urwid.IntEdit() if not self.is_top: self.edit.set_caption(f"{self.op} ") self.edit.set_layout(None, None, CALC_LAYOUT) def get_value(self) -> int | None: """Return the numeric value of the cell.""" if self.child is not None: return self.child.get_result() return int(f"0{self.edit.edit_text}") def get_result(self) -> int | None: """Return the numeric result of this cell's operation.""" if self.is_top: return self.get_value() if not self.result.text: return None return int(self.result.text) def set_result(self, result: int | None): """Set the numeric result for this cell.""" if result is None: self.result.set_text("") else: self.result.set_text(f"{result:d}") def become_parent(self, column, letter: str) -> None: """Change the edit widget to a parent cell widget.""" self.child = column self.edit = ParentEdit(self.op, letter) def remove_child(self) -> None: """Change the edit widget back to a standard edit widget.""" self.child = None self.setup_edit() def is_empty(self) -> bool: """Return True if the cell is "empty".""" return self.child is None and not self.result.text class ParentEdit(urwid.Edit): """Edit widget modified to link to a child column""" def __init__(self, op, letter: str) -> None: """Use the operator and letter of the child column as caption op -- operator or None letter -- letter of child column remove_fn -- function to call when user wants to remove child function takes no parameters """ super().__init__(layout=CALC_LAYOUT) self.op = op self.set_letter(letter) def set_letter(self, letter: str) -> None: """Set the letter of the child column for display.""" self.letter = letter caption = f"({letter})" if self.op is not None: caption = f"{self.op} {caption}" self.set_caption(caption) def keypress(self, size, key: str) -> str | None: """Disable usual editing, allow only removing of child""" if key == "backspace": raise ColumnDeleteEvent(self.letter, from_parent=True) if key in string.digits: raise CalcEvent(E_invalid_in_parent_cell) return key class CellWalker(urwid.ListWalker): def __init__(self, content): self.content = urwid.MonitoredList(content) self.content.modified = self._modified self.focus = (0, 0) # everyone can share the same divider widget self.div = urwid.Divider("-") def get_cell(self, i): if i < 0 or i >= len(self.content): return None return self.content[i] def _get_at_pos(self, pos): i, sub = pos assert sub in {0, 1, 2} # noqa: S101 # for examples "assert" is acceptable if i < 0 or i >= len(self.content): return None, None if sub == 0: edit = self.content[i].edit return urwid.AttrMap(edit, "edit", "editfocus"), pos if sub == 1: return self.div, pos return self.content[i].result, pos def get_focus(self): return self._get_at_pos(self.focus) def set_focus(self, focus) -> None: self.focus = focus def get_next(self, position): i, sub = position assert sub in {0, 1, 2} # noqa: S101 # for examples "assert" is acceptable if sub == 0: show_result = self.content[i].show_result(self.get_cell(i + 1)) if show_result: return self._get_at_pos((i, 1)) return self._get_at_pos((i + 1, 0)) if sub == 1: return self._get_at_pos((i, 2)) return self._get_at_pos((i + 1, 0)) def get_prev(self, position): i, sub = position assert sub in {0, 1, 2} # noqa: S101 # for examples "assert" is acceptable if sub == 0: if i == 0: return None, None show_result = self.content[i - 1].show_result(self.content[i]) if show_result: return self._get_at_pos((i - 1, 2)) return self._get_at_pos((i - 1, 0)) if sub == 1: return self._get_at_pos((i, 0)) return self._get_at_pos((i, 1)) class CellColumn(urwid.WidgetWrap): def __init__(self, letter: str) -> None: self.walker = CellWalker([Cell(None)]) self.content = self.walker.content self.listbox = urwid.ListBox(self.walker) self.set_letter(letter) super().__init__(self.frame) def set_letter(self, letter: str) -> None: """Set the column header with letter.""" self.letter = letter header = urwid.AttrMap(urwid.Text(["Column ", ("key", letter)], layout=CALC_LAYOUT), "colhead") self.frame = urwid.Frame(self.listbox, header) def keypress(self, size, key: str) -> str | None: key = self.frame.keypress(size, key) if key is None: changed = self.update_results() if changed: raise UpdateParentEvent() return None _f, (i, sub) = self.walker.get_focus() if sub != 0: # f is not an edit widget return key if key in OPERATORS: # move trailing text to new cell below edit = self.walker.get_cell(i).edit cursor_pos = edit.edit_pos tail = edit.edit_text[cursor_pos:] edit.set_edit_text(edit.edit_text[:cursor_pos]) new_cell = Cell(key) new_cell.edit.edit_text = tail self.content[i + 1 : i + 1] = [new_cell] changed = self.update_results() self.move_focus_next(size) self.content[i + 1].edit.set_edit_pos(0) if changed: raise UpdateParentEvent() return None if key == "backspace": # unhandled backspace, we're at beginning of number # append current number to cell above, removing operator above = self.walker.get_cell(i - 1) if above is None: # we're the first cell raise ColumnDeleteEvent(self.letter, from_parent=False) edit = self.walker.get_cell(i).edit # check that we can combine if above.child is not None: # cell above is parent if edit.edit_text: # ..and current not empty, no good raise CalcEvent(E_cant_combine) above_pos = 0 else: # above is normal number cell above_pos = len(above.edit.edit_text) above.edit.set_edit_text(above.edit.edit_text + edit.edit_text) self.move_focus_prev(size) self.content[i - 1].edit.set_edit_pos(above_pos) del self.content[i] changed = self.update_results() if changed: raise UpdateParentEvent() return None if key == "delete": # pull text from next cell into current cell = self.walker.get_cell(i) below = self.walker.get_cell(i + 1) if cell.child is not None: # this cell is a parent raise CalcEvent(E_cant_combine) if below is None: # nothing below return key if below.child is not None: # cell below is a parent raise CalcEvent(E_cant_combine) edit = self.walker.get_cell(i).edit edit.set_edit_text(edit.edit_text + below.edit.edit_text) del self.content[i + 1] changed = self.update_results() if changed: raise UpdateParentEvent() return None return key def move_focus_next(self, size) -> None: _f, (i, _sub) = self.walker.get_focus() assert i < len(self.content) - 1 # noqa: S101 # for examples "assert" is acceptable ni = i while ni == i: self.frame.keypress(size, "down") _nf, (ni, _nsub) = self.walker.get_focus() def move_focus_prev(self, size) -> None: _f, (i, _sub) = self.walker.get_focus() assert i > 0 # noqa: S101 # for examples "assert" is acceptable ni = i while ni == i: self.frame.keypress(size, "up") _nf, (ni, _nsub) = self.walker.get_focus() def update_results(self, start_from=None) -> bool: """Update column. Return True if final result changed. start_from -- Cell to start updating from or None to start from the current focus (default None) """ if start_from is None: _f, (i, _sub) = self.walker.get_focus() else: i = self.content.index(start_from) if i is None: return False focus_cell = self.walker.get_cell(i) if focus_cell.is_top: x = focus_cell.get_value() else: last_cell = self.walker.get_cell(i - 1) x = last_cell.get_result() if x is not None and focus_cell.op is not None: x = OPERATORS[focus_cell.op](x, focus_cell.get_value()) focus_cell.set_result(x) for cell in self.content[i + 1 :]: if cell.op is None: x = None if x is not None: x = OPERATORS[cell.op](x, cell.get_value()) if cell.get_result() == x: return False cell.set_result(x) return True def create_child(self, letter): """Return (parent cell,child column) or None,None on failure.""" _f, (i, sub) = self.walker.get_focus() if sub != 0: # f is not an edit widget return None, None cell = self.walker.get_cell(i) if cell.child is not None: raise CalcEvent(E_new_col_cell_not_empty) if cell.edit.edit_text: raise CalcEvent(E_new_col_cell_not_empty) child = CellColumn(letter) cell.become_parent(child, letter) return cell, child def is_empty(self) -> bool: """Return True if this column is empty.""" return len(self.content) == 1 and self.content[0].is_empty() def get_expression(self) -> str: """Return the expression as a printable string.""" lines = [] for c in self.content: if c.op is not None: # only applies to first cell lines.append(c.op) if c.child is not None: lines.append(f"({c.child.get_expression()})") else: lines.append(f"{c.get_value():d}") return "".join(lines) def get_result(self): """Return the result of the last cell in the column.""" return self.content[-1].get_result() class HelpColumn(urwid.Widget): _selectable = True _sizing = frozenset((urwid.BOX,)) help_text = [ # noqa: RUF012 # text layout typing is too complex ("title", "Column Calculator"), "", ["Numbers: ", ("key", "0"), "-", ("key", "9")], "", ["Operators: ", ("key", "+"), ", ", ("key", "-"), ", ", ("key", "*"), " and ", ("key", "/")], "", ["Editing: ", ("key", "BACKSPACE"), " and ", ("key", "DELETE")], "", [ "Movement: ", ("key", "UP"), ", ", ("key", "DOWN"), ", ", ("key", "LEFT"), ", ", ("key", "RIGHT"), ", ", ("key", "PAGE UP"), " and ", ("key", "PAGE DOWN"), ], "", ["Sub-expressions: ", ("key", "("), " and ", ("key", ")")], "", ["Columns: ", ("key", COLUMN_KEYS[0]), " and ", ("key", COLUMN_KEYS[1]), "-", ("key", COLUMN_KEYS[-1])], "", ["Exit: ", ("key", "Q")], "", "", [ "Column Calculator does operations in the order they are ", "typed, not by following usual precedence rules. ", "If you want to calculate ", ("key", "12 - 2 * 3"), " with the multiplication happening before the ", "subtraction you must type ", ("key", "12 - (2 * 3)"), " instead.", ], ] def __init__(self) -> None: super().__init__() self.head = urwid.AttrMap(urwid.Text(["Help Column ", ("key", "?")], layout=CALC_LAYOUT), "help") self.foot = urwid.AttrMap(urwid.Text(["[text continues.. press ", ("key", "?"), " then scroll]"]), "helpnote") self.items = [urwid.Text(x) for x in self.help_text] self.listbox = urwid.ListBox(urwid.SimpleListWalker(self.items)) self.body = urwid.AttrMap(self.listbox, "help") self.frame = urwid.Frame(self.body, header=self.head) def render(self, size, focus: bool = False) -> urwid.Canvas: maxcol, maxrow = size head_rows = self.head.rows((maxcol,)) if "bottom" in self.listbox.ends_visible((maxcol, maxrow - head_rows)): self.frame.footer = None else: self.frame.footer = self.foot return self.frame.render((maxcol, maxrow), focus) def keypress(self, size, key: str) -> str | None: return self.frame.keypress(size, key) class CalcDisplay: palette: typing.ClassVar[list[tuple[str, str, str, ...]]] = [ ("body", "white", "dark blue"), ("edit", "yellow", "dark blue"), ("editfocus", "yellow", "dark cyan", "bold"), ("key", "dark cyan", "light gray", ("standout", "underline")), ("title", "white", "light gray", ("bold", "standout")), ("help", "black", "light gray", "standout"), ("helpnote", "dark green", "light gray"), ("colhead", "black", "light gray", "standout"), ("event", "light red", "black", "standout"), ("confirm", "yellow", "black", "bold"), ] def __init__(self) -> None: self.columns = urwid.Columns([HelpColumn(), CellColumn("A")], 1) self.columns.focus_position = 1 view = urwid.AttrMap(self.columns, "body") self.view = urwid.Frame(view) # for showing messages self.col_link = {} def main(self) -> None: self.loop = urwid.MainLoop(self.view, self.palette, screen=Screen(), input_filter=self.input_filter) self.loop.run() # on exit write the formula and the result to the console expression, result = self.get_expression_result() print("Paste this expression into a new Column Calculator session to continue editing:") print(expression) print("Result:", result) def input_filter(self, data, raw_input): if "q" in data or "Q" in data: raise urwid.ExitMainLoop() # handle other keystrokes for k in data: try: self.wrap_keypress(k) self.event = None self.view.footer = None except CalcEvent as e: # noqa: PERF203 # display any message self.event = e self.view.footer = e.widget() # remove all input from further processing by MainLoop return [] def wrap_keypress(self, key: str) -> None: """Handle confirmation and throw event on bad input.""" try: key = self.keypress(key) except ColumnDeleteEvent as e: if e.letter == COLUMN_KEYS[1]: # cannot delete the first column, ignore key return if not self.column_empty(e.letter) and not isinstance(self.event, ColumnDeleteEvent): # need to get two in a row, so check last event ask for confirmation raise self.delete_column(e.letter) except UpdateParentEvent: self.update_parent_columns() return if key is None: return if self.columns.focus_position == 0 and key not in {"up", "down", "page up", "page down"}: raise CalcEvent(E_invalid_in_help_col) if key not in EDIT_KEYS and key not in MOVEMENT_KEYS: raise CalcEvent(E_invalid_key % key.upper()) def keypress(self, key: str) -> str | None: """Handle a keystroke.""" self.loop.process_input([key]) if isinstance(key, tuple): # ignore mouse events return None if key.upper() in COLUMN_KEYS: # column switch i = COLUMN_KEYS.index(key.upper()) if i >= len(self.columns): raise CalcEvent(E_no_such_column % key.upper()) self.columns.focus_position = i return None if key == "(": # open a new column if len(self.columns) >= len(COLUMN_KEYS): raise CalcEvent(E_no_more_columns) i = self.columns.focus_position if i == 0: # makes no sense in help column return key col = self.columns.contents[i][0] new_letter = COLUMN_KEYS[len(self.columns)] parent, child = col.create_child(new_letter) if child is None: # something invalid in focus return key self.columns.contents.append((child, (urwid.WEIGHT, 1, False))) self.set_link(parent, col, child) self.columns.focus_position = len(self.columns) - 1 return None if key == ")": i = self.columns.focus_position if i == 0: # makes no sense in help column return key col = self.columns.contents[i][0] parent, pcol = self.get_parent(col) if parent is None: # column has no parent raise CalcEvent(E_no_parent_column) new_i = next(iter(idx for idx, (w, _) in enumerate(self.columns.contents) if w == pcol)) self.columns.focus_position = new_i return None return key def set_link(self, parent, pcol, child): """Store the link between a parent cell and child column. parent -- parent Cell object pcol -- CellColumn where parent resides child -- child CellColumn object""" self.col_link[child] = parent, pcol def get_parent(self, child): """Return the parent and parent column for a given column.""" return self.col_link.get(child, (None, None)) def column_empty(self, letter) -> bool: """Return True if the column passed is empty.""" return self.columns.contents[COLUMN_KEYS.index(letter)][0].is_empty() def delete_column(self, letter) -> None: """Delete the column with the given letter.""" i = COLUMN_KEYS.index(letter) col = self.columns.contents[i][0] parent, pcol = self.get_parent(col) f = self.columns.focus_position if f == i: # move focus to the parent column f = next(iter(idx for idx, (w, _) in enumerate(self.columns.contents) if w == pcol)) self.columns.focus_position = f parent.remove_child() pcol.update_results(parent) del self.columns.contents[i] # delete children of this column keep_right_cols = [] remove_cols = [col] for rcol, _ in self.columns.contents[i:]: parent, pcol = self.get_parent(rcol) if pcol in remove_cols: remove_cols.append(rcol) else: keep_right_cols.append(rcol) for rc in remove_cols: # remove the links del self.col_link[rc] # keep only the non-children self.columns.contents[i:] = [(w, (urwid.WEIGHT, 1, False)) for w in keep_right_cols] # fix the letter assignments for j in range(i, len(self.columns)): col = self.columns.contents[j][0] # fix the column heading col.set_letter(COLUMN_KEYS[j]) parent, pcol = self.get_parent(col) # fix the parent cell parent.edit.set_letter(COLUMN_KEYS[j]) def update_parent_columns(self) -> None: """Update the parent columns of the current focus column.""" f = self.columns.focus_position col = self.columns.contents[f][0] while 1: parent, pcol = self.get_parent(col) if pcol is None: return changed = pcol.update_results(start_from=parent) if not changed: return col = pcol def get_expression_result(self): """Return (expression, result) as strings.""" col = self.columns.contents[1][0] return col.get_expression(), f"{col.get_result():d}" class CalcNumLayout(urwid.TextLayout): """ TextLayout class for bottom-right aligned numbers with a space on the last line for the cursor. """ def layout(self, text, width: int, align, wrap): """ Return layout structure for calculator number display. """ lt = len(text) + 1 # extra space for cursor remaining = lt % width # remaining segment not full width wide linestarts = range(remaining, lt, width) layout = [] if linestarts: if remaining: # right-align the remaining segment on 1st line layout.append([(width - remaining, None), (remaining, 0, remaining)]) # fill all but the last line for x in linestarts[:-1]: layout.append([(width, x, x + width)]) # noqa: PERF401 s = linestarts[-1] # add the last line with a cursor hint layout.append([(width - 1, s, lt - 1), (0, lt - 1)]) elif lt - 1: # all fits on one line, so right align the text # with a cursor hint at the end layout.append([(width - lt, None), (lt - 1, 0, lt - 1), (0, lt - 1)]) else: # nothing on the line, right align a cursor hint layout.append([(width - 1, None), (0, 0)]) return layout def main() -> None: """Launch Column Calculator.""" global CALC_LAYOUT # noqa: PLW0603 # pylint: disable=global-statement CALC_LAYOUT = CalcNumLayout() urwid.display.web.set_preferences("Column Calculator") # try to handle short web requests quickly if urwid.display.web.handle_short_request(): return CalcDisplay().main() if __name__ == "__main__" or urwid.display.web.is_web_request(): main() urwid-2.6.16/examples/dialog.py000077500000000000000000000263061470350774000164100ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid example similar to dialog(1) program # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid example similar to dialog(1) program """ from __future__ import annotations import sys import typing import urwid if typing.TYPE_CHECKING: from collections.abc import Hashable class DialogExit(Exception): pass class DialogDisplay: palette: typing.ClassVar[list[tuple[str, str, str, ...]]] = [ ("body", "black", "light gray", "standout"), ("border", "black", "dark blue"), ("shadow", "white", "black"), ("selectable", "black", "dark cyan"), ("focus", "white", "dark blue", "bold"), ("focustext", "light gray", "dark blue"), ] def __init__(self, text, height: int | str, width: int | str, body=None) -> None: width = int(width) if width <= 0: width = (urwid.RELATIVE, 80) height = int(height) if height <= 0: height = (urwid.RELATIVE, 80) self.body = body if body is None: # fill space with nothing body = urwid.Filler(urwid.Divider(), urwid.TOP) self.frame = urwid.Frame(body, focus_part="footer") if text is not None: self.frame.header = urwid.Pile([urwid.Text(text), urwid.Divider()]) w = self.frame # pad area around listbox w = urwid.Padding(w, urwid.LEFT, left=2, right=2) w = urwid.Filler(w, urwid.TOP, urwid.RELATIVE_100, top=1, bottom=1) w = urwid.AttrMap(w, "body") # "shadow" effect w = urwid.Columns([w, (2, urwid.AttrMap(urwid.Filler(urwid.Text(("border", " ")), urwid.TOP), "shadow"))]) w = urwid.Frame(w, footer=urwid.AttrMap(urwid.Text(("border", " ")), "shadow")) # outermost border area w = urwid.Padding(w, urwid.CENTER, width) w = urwid.Filler(w, urwid.MIDDLE, height) w = urwid.AttrMap(w, "border") self.view = w def add_buttons(self, buttons) -> None: lines = [] for name, exitcode in buttons: b = urwid.Button(name, self.button_press) b.exitcode = exitcode b = urwid.AttrMap(b, "selectable", "focus") lines.append(b) self.buttons = urwid.GridFlow(lines, 10, 3, 1, urwid.CENTER) self.frame.footer = urwid.Pile([urwid.Divider(), self.buttons], focus_item=1) def button_press(self, button) -> typing.NoReturn: raise DialogExit(button.exitcode) def main(self) -> tuple[int, str]: self.loop = urwid.MainLoop(self.view, self.palette) try: self.loop.run() except DialogExit as e: return self.on_exit(e.args[0]) def on_exit(self, exitcode: int) -> tuple[int, str]: return exitcode, "" class InputDialogDisplay(DialogDisplay): def __init__(self, text, height: int | str, width: int | str) -> None: self.edit = urwid.Edit() body = urwid.ListBox(urwid.SimpleListWalker([self.edit])) body = urwid.AttrMap(body, "selectable", "focustext") super().__init__(text, height, width, body) self.frame.focus_position = "body" def unhandled_key(self, size, k: str) -> None: if k in {"up", "page up"}: self.frame.focus_position = "body" if k in {"down", "page down"}: self.frame.focus_position = "footer" if k == "enter": # pass enter to the "ok" button self.frame.focus_position = "footer" self.view.keypress(size, k) def on_exit(self, exitcode: int) -> tuple[int, str]: return exitcode, self.edit.get_edit_text() class TextDialogDisplay(DialogDisplay): def __init__(self, file: str, height: int | str, width: int | str) -> None: with open(file, encoding="utf-8") as f: lines = [urwid.Text(line.rstrip()) for line in f] # read the whole file (being slow, not lazy this time) body = urwid.ListBox(urwid.SimpleListWalker(lines)) body = urwid.AttrMap(body, "selectable", "focustext") super().__init__(None, height, width, body) def unhandled_key(self, size, k: str) -> None: if k in {"up", "page up", "down", "page down"}: self.frame.focus_position = "body" self.view.keypress(size, k) self.frame.focus_position = "footer" class ListDialogDisplay(DialogDisplay): def __init__( self, text, height: int | str, width: int | str, constr, items, has_default: bool, ) -> None: j = [] if has_default: k, tail = 3, () else: k, tail = 2, ("no",) while items: j.append(items[:k] + tail) items = items[k:] lines = [] self.items = [] for tag, item, default in j: w = constr(tag, default == "on") self.items.append(w) w = urwid.Columns([(12, w), urwid.Text(item)], 2) w = urwid.AttrMap(w, "selectable", "focus") lines.append(w) lb = urwid.ListBox(urwid.SimpleListWalker(lines)) lb = urwid.AttrMap(lb, "selectable") super().__init__(text, height, width, lb) self.frame.focus_position = "body" def unhandled_key(self, size, k: str) -> None: if k in {"up", "page up"}: self.frame.focus_position = "body" if k in {"down", "page down"}: self.frame.focus_position = "footer" if k == "enter": # pass enter to the "ok" button self.frame.focus_position = "footer" self.buttons.focus_position = 0 self.view.keypress(size, k) def on_exit(self, exitcode: int) -> tuple[int, str]: """Print the tag of the item selected.""" if exitcode != 0: return exitcode, "" s = "" for i in self.items: if i.get_state(): s = i.get_label() break return exitcode, s class CheckListDialogDisplay(ListDialogDisplay): def on_exit(self, exitcode: int) -> tuple[int, str]: """ Mimic dialog(1)'s --checklist exit. Put each checked item in double quotes with a trailing space. """ if exitcode != 0: return exitcode, "" labels = [i.get_label() for i in self.items if i.get_state()] return exitcode, "".join(f'"{tag}" ' for tag in labels) class MenuItem(urwid.Text): """A custom widget for the --menu option""" def __init__(self, label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]) -> None: super().__init__(label) self.state = False def selectable(self) -> bool: return True def keypress(self, size: tuple[int] | tuple[()], key: str) -> str | None: if key == "enter": self.state = True raise DialogExit(0) return key def mouse_event( self, size: tuple[int] | tuple[()], event: str, button: int, col: int, row: int, focus: bool, ) -> bool | None: if event == "mouse release": self.state = True raise DialogExit(0) return False def get_state(self) -> bool: return self.state def get_label(self) -> str: """Just alias to text.""" return self.text def do_checklist( text, height: int, width: int, list_height: int, *items, ) -> CheckListDialogDisplay: def constr(tag, state: bool) -> urwid.CheckBox: return urwid.CheckBox(tag, state) d = CheckListDialogDisplay(text, height, width, constr, items, True) d.add_buttons([("OK", 0), ("Cancel", 1)]) return d def do_inputbox(text, height: int, width: int) -> InputDialogDisplay: d = InputDialogDisplay(text, height, width) d.add_buttons([("Exit", 0)]) return d def do_menu( text, height: int, width: int, menu_height: int, *items, ) -> ListDialogDisplay: def constr(tag, state: bool) -> MenuItem: return MenuItem(tag) d = ListDialogDisplay(text, height, width, constr, items, False) d.add_buttons([("OK", 0), ("Cancel", 1)]) return d def do_msgbox(text, height: int, width: int) -> DialogDisplay: d = DialogDisplay(text, height, width) d.add_buttons([("OK", 0)]) return d def do_radiolist( text, height: int, width: int, list_height: int, *items, ) -> ListDialogDisplay: radiolist = [] def constr( # pylint: disable=dangerous-default-value tag, state: bool, radiolist: list[urwid.RadioButton] = radiolist, ) -> urwid.RadioButton: return urwid.RadioButton(radiolist, tag, state) d = ListDialogDisplay(text, height, width, constr, items, True) d.add_buttons([("OK", 0), ("Cancel", 1)]) return d def do_textbox(file: str, height: int, width: int) -> TextDialogDisplay: d = TextDialogDisplay(file, height, width) d.add_buttons([("Exit", 0)]) return d def do_yesno(text, height: int, width: int) -> DialogDisplay: d = DialogDisplay(text, height, width) d.add_buttons([("Yes", 0), ("No", 1)]) return d MODES = { # pylint: disable=consider-using-namedtuple-or-dataclass # made before argparse in stdlib "--checklist": (do_checklist, "text height width list-height [ tag item status ] ..."), "--inputbox": (do_inputbox, "text height width"), "--menu": (do_menu, "text height width menu-height [ tag item ] ..."), "--msgbox": (do_msgbox, "text height width"), "--radiolist": (do_radiolist, "text height width list-height [ tag item status ] ..."), "--textbox": (do_textbox, "file height width"), "--yesno": (do_yesno, "text height width"), } def show_usage(): """ Display a helpful usage message. """ modelist = sorted((mode, help_mode) for (mode, (fn, help_mode)) in MODES.items()) sys.stdout.write( __doc__ + "\n".join([f"{mode:<15} {help_mode}" for (mode, help_mode) in modelist]) + """ height and width may be set to 0 to auto-size. list-height and menu-height are currently ignored. status may be either on or off. """ ) def main() -> None: if len(sys.argv) < 2 or sys.argv[1] not in MODES: show_usage() return # Create a DialogDisplay instance fn, _help_mode = MODES[sys.argv[1]] d = fn(*sys.argv[2:]) # Run it exitcode, exitstring = d.main() # Exit if exitstring: sys.stderr.write(f"{exitstring}\n") sys.exit(exitcode) if __name__ == "__main__": main() urwid-2.6.16/examples/edit.py000077500000000000000000000175661470350774000161060ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid example lazy text editor suitable for tabbed and format=flowed text # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid example lazy text editor suitable for tabbed and flowing text Features: - custom list walker for lazily loading text file Usage: edit.py """ from __future__ import annotations import sys import typing import urwid class LineWalker(urwid.ListWalker): """ListWalker-compatible class for lazily reading file contents.""" def __init__(self, name: str) -> None: # do not overcomplicate example self.file = open(name, encoding="utf-8") # noqa: SIM115 # pylint: disable=consider-using-with self.lines = [] self.focus = 0 def __del__(self) -> None: self.file.close() def get_focus(self): return self._get_at_pos(self.focus) def set_focus(self, focus) -> None: self.focus = focus self._modified() def get_next(self, position: int) -> tuple[urwid.Edit, int] | tuple[None, None]: return self._get_at_pos(position + 1) def get_prev(self, position: int) -> tuple[urwid.Edit, int] | tuple[None, None]: return self._get_at_pos(position - 1) def read_next_line(self) -> str: """Read another line from the file.""" next_line = self.file.readline() if not next_line or next_line[-1:] != "\n": # no newline on last line of file self.file = None else: # trim newline characters next_line = next_line[:-1] expanded = next_line.expandtabs() edit = urwid.Edit("", expanded, allow_tab=True) edit.edit_pos = 0 edit.original_text = next_line self.lines.append(edit) return next_line def _get_at_pos(self, pos: int) -> tuple[urwid.Edit, int] | tuple[None, None]: """Return a widget for the line number passed.""" if pos < 0: # line 0 is the start of the file, no more above return None, None if len(self.lines) > pos: # we have that line so return it return self.lines[pos], pos if self.file is None: # file is closed, so there are no more lines return None, None assert pos == len(self.lines), "out of order request?" # noqa: S101 # "assert" is ok in examples self.read_next_line() return self.lines[-1], pos def split_focus(self) -> None: """Divide the focus edit widget at the cursor location.""" focus = self.lines[self.focus] pos = focus.edit_pos edit = urwid.Edit("", focus.edit_text[pos:], allow_tab=True) edit.original_text = "" focus.set_edit_text(focus.edit_text[:pos]) edit.edit_pos = 0 self.lines.insert(self.focus + 1, edit) def combine_focus_with_prev(self) -> None: """Combine the focus edit widget with the one above.""" above, _ = self.get_prev(self.focus) if above is None: # already at the top return focus = self.lines[self.focus] above.set_edit_pos(len(above.edit_text)) above.set_edit_text(above.edit_text + focus.edit_text) del self.lines[self.focus] self.focus -= 1 def combine_focus_with_next(self) -> None: """Combine the focus edit widget with the one below.""" below, _ = self.get_next(self.focus) if below is None: # already at bottom return focus = self.lines[self.focus] focus.set_edit_text(focus.edit_text + below.edit_text) del self.lines[self.focus + 1] class EditDisplay: palette: typing.ClassVar[list[tuple[str, str, str, ...]]] = [ ("body", "default", "default"), ("foot", "dark cyan", "dark blue", "bold"), ("key", "light cyan", "dark blue", "underline"), ] footer_text = ( "foot", [ "Text Editor ", ("key", "F5"), " save ", ("key", "F8"), " quit", ], ) def __init__(self, name: str) -> None: self.save_name = name self.walker = LineWalker(name) self.listbox = urwid.ListBox(self.walker) self.footer = urwid.AttrMap(urwid.Text(self.footer_text), "foot") self.view = urwid.Frame(urwid.AttrMap(self.listbox, "body"), footer=self.footer) def main(self) -> None: self.loop = urwid.MainLoop(self.view, self.palette, unhandled_input=self.unhandled_keypress) self.loop.run() def unhandled_keypress(self, k: str | tuple[str, int, int, int]) -> bool | None: """Last resort for keypresses.""" if k == "f5": self.save_file() elif k == "f8": raise urwid.ExitMainLoop() elif k == "delete": # delete at end of line self.walker.combine_focus_with_next() elif k == "backspace": # backspace at beginning of line self.walker.combine_focus_with_prev() elif k == "enter": # start new line self.walker.split_focus() # move the cursor to the new line and reset pref_col self.loop.process_input(["down", "home"]) elif k == "right": w, pos = self.walker.get_focus() w, pos = self.walker.get_next(pos) if w: self.listbox.set_focus(pos, "above") self.loop.process_input(["home"]) elif k == "left": w, pos = self.walker.get_focus() w, pos = self.walker.get_prev(pos) if w: self.listbox.set_focus(pos, "below") self.loop.process_input(["end"]) else: return None return True def save_file(self) -> None: """Write the file out to disk.""" lines = [] walk = self.walker for edit in walk.lines: # collect the text already stored in edit widgets if edit.original_text.expandtabs() == edit.edit_text: lines.append(edit.original_text) else: lines.append(re_tab(edit.edit_text)) # then the rest while walk.file is not None: lines.append(walk.read_next_line()) # write back to disk with open(self.save_name, "w", encoding="utf-8") as outfile: prefix = "" for line in lines: outfile.write(prefix + line) prefix = "\n" def re_tab(s) -> str: """Return a tabbed string from an expanded one.""" line = [] p = 0 for i in range(8, len(s), 8): if s[i - 2 : i] == " ": # collapse two or more spaces into a tab line.append(f"{s[p:i].rstrip()}\t") p = i if p == 0: return s line.append(s[p:]) return "".join(line) def main() -> None: try: name = sys.argv[1] # do not overcomplicate example assert open(name, "ab") # noqa: SIM115,S101 # pylint: disable=consider-using-with except OSError: sys.stderr.write(__doc__) return EditDisplay(name).main() if __name__ == "__main__": main() urwid-2.6.16/examples/fib.py000077500000000000000000000101611470350774000157010ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid example fibonacci sequence viewer / unbounded data demo # Copyright (C) 2004-2007 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid example fibonacci sequence viewer / unbounded data demo Features: - custom list walker class for browsing infinite set - custom wrap mode "numeric" for wrapping numbers to right and bottom """ from __future__ import annotations import typing import urwid if typing.TYPE_CHECKING: from typing_extensions import Literal class FibonacciWalker(urwid.ListWalker): """ListWalker-compatible class for browsing fibonacci set. positions returned are (value at position-1, value at position) tuples. """ def __init__(self) -> None: self.focus = (0, 1) self.numeric_layout = NumericLayout() def _get_at_pos(self, pos: tuple[int, int]) -> tuple[urwid.Text, tuple[int, int]]: """Return a widget and the position passed.""" return urwid.Text(f"{pos[1]:d}", layout=self.numeric_layout), pos def get_focus(self) -> tuple[urwid.Text, tuple[int, int]]: return self._get_at_pos(self.focus) def set_focus(self, focus) -> None: self.focus = focus self._modified() def get_next(self, position) -> tuple[urwid.Text, tuple[int, int]]: a, b = position focus = b, a + b return self._get_at_pos(focus) def get_prev(self, position) -> tuple[urwid.Text, tuple[int, int]]: a, b = position focus = b - a, a return self._get_at_pos(focus) def main() -> None: palette = [ ("body", "black", "dark cyan", "standout"), ("foot", "light gray", "black"), ("key", "light cyan", "black", "underline"), ( "title", "white", "black", ), ] footer_text = [ ("title", "Fibonacci Set Viewer"), " ", ("key", "UP"), ", ", ("key", "DOWN"), ", ", ("key", "PAGE UP"), " and ", ("key", "PAGE DOWN"), " move view ", ("key", "Q"), " exits", ] def exit_on_q(key: str | tuple[str, int, int, int]) -> None: if key in {"q", "Q"}: raise urwid.ExitMainLoop() listbox = urwid.ListBox(FibonacciWalker()) footer = urwid.AttrMap(urwid.Text(footer_text), "foot") view = urwid.Frame(urwid.AttrMap(listbox, "body"), footer=footer) loop = urwid.MainLoop(view, palette, unhandled_input=exit_on_q) loop.run() class NumericLayout(urwid.TextLayout): """ TextLayout class for bottom-right aligned numbers """ def layout( self, text: str | bytes, width: int, align: Literal["left", "center", "right"] | urwid.Align, wrap: Literal["any", "space", "clip", "ellipsis"] | urwid.WrapMode, ) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]: """ Return layout structure for right justified numbers. """ lt = len(text) r = lt % width # remaining segment not full width wide if r: return [ [(width - r, None), (r, 0, r)], # right-align the remaining segment on 1st line *([(width, x, x + width)] for x in range(r, lt, width)), # fill the rest of the lines ] return [[(width, x, x + width)] for x in range(0, lt, width)] if __name__ == "__main__": main() urwid-2.6.16/examples/graph.py000077500000000000000000000274551470350774000162600ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid graphics example program # Copyright (C) 2004-2011 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid example demonstrating use of the BarGraph widget and creating a floating-window appearance. Also shows use of alarms to create timed animation. """ from __future__ import annotations import math import time import typing import urwid UPDATE_INTERVAL = 0.2 def sin100(x): """ A sin function that returns values between 0 and 100 and repeats after x == 100. """ return 50 + 50 * math.sin(x * math.pi / 50) class GraphModel: """ A class responsible for storing the data that will be displayed on the graph, and keeping track of which mode is enabled. """ data_max_value = 100 def __init__(self): data = [ ("Saw", list(range(0, 100, 2)) * 2), ("Square", [0] * 30 + [100] * 30), ("Sine 1", [sin100(x) for x in range(100)]), ("Sine 2", [(sin100(x) + sin100(x * 2)) / 2 for x in range(100)]), ("Sine 3", [(sin100(x) + sin100(x * 3)) / 2 for x in range(100)]), ] self.modes = [] self.data = {} for m, d in data: self.modes.append(m) self.data[m] = d def get_modes(self): return self.modes def set_mode(self, m) -> None: self.current_mode = m def get_data(self, offset, r): """ Return the data in [offset:offset+r], the maximum value for items returned, and the offset at which the data repeats. """ lines = [] d = self.data[self.current_mode] while r: offset %= len(d) segment = d[offset : offset + r] r -= len(segment) offset += len(segment) lines += segment return lines, self.data_max_value, len(d) class GraphView(urwid.WidgetWrap): """ A class responsible for providing the application's interface and graph display. """ palette: typing.ClassVar[tuple[str, str, str, ...]] = [ ("body", "black", "light gray", "standout"), ("header", "white", "dark red", "bold"), ("screen edge", "light blue", "dark cyan"), ("main shadow", "dark gray", "black"), ("line", "black", "light gray", "standout"), ("bg background", "light gray", "black"), ("bg 1", "black", "dark blue", "standout"), ("bg 1 smooth", "dark blue", "black"), ("bg 2", "black", "dark cyan", "standout"), ("bg 2 smooth", "dark cyan", "black"), ("button normal", "light gray", "dark blue", "standout"), ("button select", "white", "dark green"), ("line", "black", "light gray", "standout"), ("pg normal", "white", "black", "standout"), ("pg complete", "white", "dark magenta"), ("pg smooth", "dark magenta", "black"), ] graph_samples_per_bar = 10 graph_num_bars = 5 graph_offset_per_second = 5 def __init__(self, controller): self.controller = controller self.started = True self.start_time = None self.offset = 0 self.last_offset = None super().__init__(self.main_window()) def get_offset_now(self): if self.start_time is None: return 0 if not self.started: return self.offset tdelta = time.time() - self.start_time return int(self.offset + (tdelta * self.graph_offset_per_second)) def update_graph(self, force_update=False): o = self.get_offset_now() if o == self.last_offset and not force_update: return False self.last_offset = o gspb = self.graph_samples_per_bar r = gspb * self.graph_num_bars d, max_value, repeat = self.controller.get_data(o, r) lines = [] for n in range(self.graph_num_bars): value = sum(d[n * gspb : (n + 1) * gspb]) / gspb # toggle between two bar types if n & 1: lines.append([0, value]) else: lines.append([value, 0]) self.graph.set_data(lines, max_value) # also update progress if (o // repeat) & 1: # show 100% for first half, 0 for second half if o % repeat > repeat // 2: prog = 0 else: prog = 1 else: prog = float(o % repeat) / repeat self.animate_progress.current = prog return True def on_animate_button(self, button): """Toggle started state and button text.""" if self.started: # stop animation button.base_widget.set_label("Start") self.offset = self.get_offset_now() self.started = False self.controller.stop_animation() else: button.base_widget.set_label("Stop") self.started = True self.start_time = time.time() self.controller.animate_graph() def on_reset_button(self, w): self.offset = 0 self.start_time = time.time() self.update_graph(True) def on_mode_button(self, button, state): """Notify the controller of a new mode setting.""" if state: # The new mode is the label of the button self.controller.set_mode(button.get_label()) self.last_offset = None def on_mode_change(self, m): """Handle external mode change by updating radio buttons.""" for rb in self.mode_buttons: if rb.base_widget.label == m: rb.base_widget.set_state(True, do_callback=False) break self.last_offset = None def on_unicode_checkbox(self, w, state): self.graph = self.bar_graph(state) self.graph_wrap._w = self.graph self.animate_progress = self.progress_bar(state) self.animate_progress_wrap._w = self.animate_progress self.update_graph(True) def main_shadow(self, w): """Wrap a shadow and background around widget w.""" bg = urwid.AttrMap(urwid.SolidFill("â–’"), "screen edge") shadow = urwid.AttrMap(urwid.SolidFill(" "), "main shadow") bg = urwid.Overlay( shadow, bg, align=urwid.LEFT, width=urwid.RELATIVE_100, valign=urwid.TOP, height=urwid.RELATIVE_100, left=3, right=1, top=2, bottom=1, ) w = urwid.Overlay( w, bg, align=urwid.LEFT, width=urwid.RELATIVE_100, valign=urwid.TOP, height=urwid.RELATIVE_100, left=2, right=3, top=1, bottom=2, ) return w def bar_graph(self, smooth=False): satt = None if smooth: satt = {(1, 0): "bg 1 smooth", (2, 0): "bg 2 smooth"} w = urwid.BarGraph(["bg background", "bg 1", "bg 2"], satt=satt) return w def button(self, t, fn): w = urwid.Button(t, fn) w = urwid.AttrMap(w, "button normal", "button select") return w def radio_button(self, g, label, fn): w = urwid.RadioButton(g, label, False, on_state_change=fn) w = urwid.AttrMap(w, "button normal", "button select") return w def progress_bar(self, smooth=False): if smooth: return urwid.ProgressBar("pg normal", "pg complete", 0, 1, "pg smooth") return urwid.ProgressBar("pg normal", "pg complete", 0, 1) def exit_program(self, w): raise urwid.ExitMainLoop() def graph_controls(self): modes = self.controller.get_modes() # setup mode radio buttons self.mode_buttons = [] group = [] for m in modes: rb = self.radio_button(group, m, self.on_mode_button) self.mode_buttons.append(rb) # setup animate button self.animate_button = self.button("", self.on_animate_button) self.on_animate_button(self.animate_button) self.offset = 0 self.animate_progress = self.progress_bar() animate_controls = urwid.GridFlow( [ self.animate_button, self.button("Reset", self.on_reset_button), ], 9, 2, 0, urwid.CENTER, ) if urwid.get_encoding_mode() == "utf8": unicode_checkbox = urwid.CheckBox("Enable Unicode Graphics", on_state_change=self.on_unicode_checkbox) else: unicode_checkbox = urwid.Text("UTF-8 encoding not detected") self.animate_progress_wrap = urwid.WidgetWrap(self.animate_progress) lines = [ urwid.Text("Mode", align=urwid.CENTER), *self.mode_buttons, urwid.Divider(), urwid.Text("Animation", align=urwid.CENTER), animate_controls, self.animate_progress_wrap, urwid.Divider(), urwid.LineBox(unicode_checkbox), urwid.Divider(), self.button("Quit", self.exit_program), ] w = urwid.ListBox(urwid.SimpleListWalker(lines)) return w def main_window(self): self.graph = self.bar_graph() self.graph_wrap = urwid.WidgetWrap(self.graph) vline = urwid.AttrMap(urwid.SolidFill("│"), "line") c = self.graph_controls() w = urwid.Columns([(urwid.WEIGHT, 2, self.graph_wrap), (1, vline), c], dividechars=1, focus_column=2) w = urwid.Padding(w, urwid.LEFT, left=1) w = urwid.AttrMap(w, "body") w = urwid.LineBox(w) w = urwid.AttrMap(w, "line") w = self.main_shadow(w) return w class GraphController: """ A class responsible for setting up the model and view and running the application. """ def __init__(self): self.animate_alarm = None self.model = GraphModel() self.view = GraphView(self) # use the first mode as the default mode = self.get_modes()[0] self.model.set_mode(mode) # update the view self.view.on_mode_change(mode) self.view.update_graph(True) def get_modes(self): """Allow our view access to the list of modes.""" return self.model.get_modes() def set_mode(self, m) -> None: """Allow our view to set the mode.""" self.model.set_mode(m) self.view.update_graph(True) def get_data(self, offset, data_range): """Provide data to our view for the graph.""" return self.model.get_data(offset, data_range) def main(self): self.loop = urwid.MainLoop(self.view, self.view.palette) self.loop.run() def animate_graph(self, loop=None, user_data=None): """update the graph and schedule the next update""" self.view.update_graph() self.animate_alarm = self.loop.set_alarm_in(UPDATE_INTERVAL, self.animate_graph) def stop_animation(self): """stop animating the graph""" if self.animate_alarm: self.loop.remove_alarm(self.animate_alarm) self.animate_alarm = None def main(): GraphController().main() if __name__ == "__main__": main() urwid-2.6.16/examples/input_test.py000077500000000000000000000113251470350774000173420ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid keyboard input test app # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Keyboard test application """ from __future__ import annotations import argparse import logging import urwid if urwid.display.web.is_web_request(): Screen = urwid.display.web.Screen loop_cls = urwid.SelectEventLoop else: event_loops: dict[str, type[urwid.EventLoop] | None] = { "none": None, "select": urwid.SelectEventLoop, "asyncio": urwid.AsyncioEventLoop, } if hasattr(urwid, "TornadoEventLoop"): event_loops["tornado"] = urwid.TornadoEventLoop if hasattr(urwid, "GLibEventLoop"): event_loops["glib"] = urwid.GLibEventLoop if hasattr(urwid, "TwistedEventLoop"): event_loops["twisted"] = urwid.TwistedEventLoop if hasattr(urwid, "TrioEventLoop"): event_loops["trio"] = urwid.TrioEventLoop if hasattr(urwid, "ZMQEventLoop"): event_loops["zmq"] = urwid.ZMQEventLoop parser = argparse.ArgumentParser(description="Input test") parser.add_argument( "argc", help="Positional arguments ('r' for raw display)", metavar="", nargs="*", default=(), ) group = parser.add_argument_group("Advanced Options") group.add_argument( "--event-loop", choices=event_loops, default="none", help="Event loop to use ('none' = use the default)", ) group.add_argument("--debug-log", action="store_true", help="Enable debug logging") args = parser.parse_args() if not hasattr(urwid.display, "curses") or "r" in args.argc: Screen = urwid.display.raw.Screen else: Screen = urwid.display.curses.Screen loop_cls = event_loops[args.event_loop] if args.debug_log: logging.basicConfig( level=logging.DEBUG, filename="debug.log", format=( "%(levelname)1.1s %(asctime)s | %(threadName)s | %(name)s \n" "\t%(message)s\n" "-------------------------------------------------------------------------------" ), datefmt="%d-%b-%Y %H:%M:%S", force=True, ) logging.captureWarnings(True) def key_test(): screen = Screen() header = urwid.AttrMap( urwid.Text("Values from get_input(). Q exits."), "header", ) lw = urwid.SimpleListWalker([]) listbox = urwid.AttrMap( urwid.ListBox(lw), "listbox", ) top = urwid.Frame(listbox, header) def input_filter(keys, raw): if "q" in keys or "Q" in keys: raise urwid.ExitMainLoop t = [] for k in keys: if isinstance(k, tuple): out = [] for v in k: if out: out += [", "] out += [("key", repr(v))] t += ["(", *out, ")"] else: t += ["'", ("key", k), "' "] rawt = urwid.Text(", ".join(f"{r:d}" for r in raw)) if t: lw.append(urwid.Columns([(urwid.WEIGHT, 2, urwid.Text(t)), rawt])) listbox.original_widget.set_focus(len(lw) - 1, "above") return keys loop = urwid.MainLoop( top, [ ("header", "black", "dark cyan", "standout"), ("key", "yellow", "dark blue", "bold"), ("listbox", "light gray", "black"), ], screen, input_filter=input_filter, event_loop=loop_cls() if loop_cls is not None else None, ) old = () try: old = screen.tty_signal_keys("undefined", "undefined", "undefined", "undefined", "undefined") loop.run() finally: if old: screen.tty_signal_keys(*old) def main(): urwid.display.web.set_preferences("Input Test") if urwid.display.web.handle_short_request(): return key_test() if __name__ == "__main__" or urwid.display.web.is_web_request(): main() urwid-2.6.16/examples/lcd_cf635.py000077500000000000000000000234741470350774000166240ustar00rootroot00000000000000#!/usr/bin/env python """ The crystalfontz 635 has these characters in ROM: ....X. ...... ...... ...XX. .XXXXX ..XXX. ..XXX. .XXXXX .XXXXX .XXXX. .XXXXX .XXXXX ..XXX. .XXXXX .XXXXX ...XX. .XXXXX ..XXX. ....X. ...... ...... ...... ...... ...... 0x11 0xd0 0xbb By adding the characters in CGRAM below we can use them as part of a horizontal slider control, selected check box and selected radio button respectively. """ from __future__ import annotations import sys import typing import urwid if typing.TYPE_CHECKING: from collections.abc import Callable from typing_extensions import Literal CGRAM = """ ...... ...... ...... ...... ..X... ...... ...... ...... XXXXXX XXXXXX XXXXXX XXXXXX X.XX.. .XXXXX ..XXX. .....X ...... XX.... XXXX.. XXXXXX X.XXX. .X...X .X...X ....XX ...... XX.... XXXX.. XXXXXX X.XXXX .X...X .X...X .X.XX. ...... XX.... XXXX.. XXXXXX X.XXX. .X...X .X...X .XXX.. XXXXXX XXXXXX XXXXXX XXXXXX X.XX.. .XXXXX ..XXX. ..X... ...... ...... ...... ...... ..X... ...... ...... ...... ...... ...... ...... ...... ...... ...... ...... ...... """ def program_cgram(screen_inst: urwid.display.lcd.CF635Screen) -> None: """ Load the character data """ # convert .'s and X's above into integer data cbuf = [[] for x in range(8)] for row in CGRAM.strip().split("\n"): rowsegments = row.strip().split() for num, r in enumerate(rowsegments): accum = 0 for c in r: accum = (accum << 1) + (c == "X") cbuf[num].append(accum) for num, cdata in enumerate(cbuf): screen_inst.program_cgram(num, cdata) class LCDCheckBox(urwid.CheckBox): """ A check box+label that uses only one character for the check box, including custom CGRAM character """ states: typing.ClassVar[dict[bool, urwid.SelectableIcon]] = { True: urwid.SelectableIcon("\xd0"), False: urwid.SelectableIcon("\x05"), } reserve_columns = 1 class LCDRadioButton(urwid.RadioButton): """ A radio button+label that uses only one character for the radio button, including custom CGRAM character """ states: typing.ClassVar[dict[bool, urwid.SelectableIcon]] = { True: urwid.SelectableIcon("\xbb"), False: urwid.SelectableIcon("\x06"), } reserve_columns = 1 class LCDProgressBar(urwid.Widget): """ The "progress bar" used by the horizontal slider for this device, using custom CGRAM characters """ segments = "\x00\x01\x02\x03" _sizing = frozenset([urwid.Sizing.FLOW]) def __init__(self, data_range, value) -> None: super().__init__() self.range = data_range self.value = value def rows(self, size, focus=False) -> int: return 1 def render(self, size, focus=False): """ Draw the bar with self.segments where [0] is empty and [-1] is completely full """ (maxcol,) = size steps = self.get_steps(size) filled = urwid.int_scale(self.value, self.range, steps) full_segments = int(filled / (len(self.segments) - 1)) last_char = filled % (len(self.segments) - 1) + 1 s = ( self.segments[-1] * full_segments + self.segments[last_char] + self.segments[0] * (maxcol - full_segments - 1) ) return urwid.Text(s).render(size) def move_position(self, size, direction): """ Update and return the value one step +ve or -ve, based on the size of the displayed bar. direction -- 1 for +ve, 0 for -ve """ steps = self.get_steps(size) filled = urwid.int_scale(self.value, self.range, steps) filled += 2 * direction - 1 value = urwid.int_scale(filled, steps, self.range) value = max(0, min(self.range - 1, value)) if value != self.value: self.value = value self._invalidate() return value def get_steps(self, size): """ Return the number of steps available given size for rendering the bar and number of segments we can draw. """ (maxcol,) = size return maxcol * (len(self.segments) - 1) class LCDHorizontalSlider(urwid.WidgetWrap[urwid.Columns]): """ A slider control using custom CGRAM characters """ def __init__(self, data_range, value, callback): self.bar = LCDProgressBar(data_range, value) cols = urwid.Columns( [ (1, urwid.SelectableIcon("\x11")), self.bar, (1, urwid.SelectableIcon("\x04")), ] ) super().__init__(cols) self.callback = callback def keypress(self, size, key: str): # move the slider based on which arrow is focused if key == "enter": # use the correct size for adjusting the bar self.bar.move_position((self._w.column_widths(size)[1],), self._w.get_focus_column() != 0) self.callback(self.bar.value) return None return super().keypress(size, key) class MenuOption(urwid.Button): """ A menu option, indicated with a single arrow character """ def __init__(self, label, submenu): super().__init__("") # use a Text widget for label, we want the cursor # on the arrow not the label self._label = urwid.Text("") self.set_label(label) self._w = urwid.Columns([(1, urwid.SelectableIcon("\xdf")), self._label]) urwid.connect_signal(self, "click", lambda option: show_menu(submenu)) def keypress(self, size, key: str): if key == "right": key = "enter" return super().keypress(size, key) class Menu(urwid.ListBox): def __init__(self, widgets): self.menu_parent = None super().__init__(urwid.SimpleListWalker(widgets)) def keypress(self, size, key: str): """ Go back to the previous menu on cancel button (mapped to esc) """ key = super().keypress(size, key) if key in {"left", "esc"} and self.menu_parent: show_menu(self.menu_parent) return None return key def build_menus(): cursor_option_group = [] def cursor_option(label: str, style: Literal[1, 2, 3, 4]) -> LCDRadioButton: """A radio button that sets the cursor style""" def on_change(b, state): if state: screen.set_cursor_style(style) b = LCDRadioButton(cursor_option_group, label, screen.cursor_style == style) urwid.connect_signal(b, "change", on_change) return b def display_setting(label: str, data_range: int, fn: Callable[[int], None]) -> urwid.Columns: slider = LCDHorizontalSlider(data_range, data_range / 2, fn) return urwid.Columns( [ urwid.Text(label), (10, slider), ] ) def led_custom(index: Literal[0, 1, 2, 3]) -> urwid.Columns: def exp_scale_led(rg: Literal[0, 1]) -> Callable[[int], None]: """ apply an exponential transformation to values sent so that apparent brightness increases in a natural way. """ return lambda value: screen.set_led_pin( index, rg, [0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 29, 38, 48, 61, 79, 100][value], ) return urwid.Columns( [ (2, urwid.Text(f"{index:d}R")), LCDHorizontalSlider(18, 0, exp_scale_led(0)), (2, urwid.Text(" G")), LCDHorizontalSlider(18, 0, exp_scale_led(1)), ] ) menu_structure = [ ( "Display Settings", [ display_setting("Brightness", 101, screen.set_backlight), display_setting("Contrast", 76, lambda x: screen.set_lcd_contrast(x + 75)), ], ), ( "Cursor Settings", [ cursor_option("Block", screen.CURSOR_BLINKING_BLOCK), cursor_option("Underscore", screen.CURSOR_UNDERSCORE), cursor_option("Block + Underscore", screen.CURSOR_BLINKING_BLOCK_UNDERSCORE), cursor_option("Inverting Block", screen.CURSOR_INVERTING_BLINKING_BLOCK), ], ), ( "LEDs", [ led_custom(0), led_custom(1), led_custom(2), led_custom(3), ], ), ( "About this Demo", [ urwid.Text( "This is a demo of Urwid's CF635Display " "module. If you need an interface for a limited " "character display device this should serve as a " "good example for implementing your own display " "module and menu-driven application." ), ], ), ] def build_submenu(ms): """ Recursive menu building from structure above """ options = [] submenus = [] for opt in ms: # shortform for MenuOptions if isinstance(opt, tuple): name, sub = opt submenu = build_submenu(sub) opt = MenuOption(name, submenu) # noqa: PLW2901 submenus.append(submenu) options.append(opt) menu = Menu(options) for s in submenus: s.menu_parent = menu return menu return build_submenu(menu_structure) screen = urwid.display.lcd.CF635Screen(sys.argv[1]) # set up our font program_cgram(screen) loop = urwid.MainLoop(build_menus(), screen=screen) # FIXME: want screen to know it is in narrow mode, or better yet, # do the unicode conversion for us urwid.set_encoding("narrow") def show_menu(menu): loop.widget = menu loop.run() urwid-2.6.16/examples/palette_test.py000077500000000000000000000525301470350774000176440ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid Palette Test. Showing off highcolor support # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Palette test. Shows the available foreground and background settings in monochrome, 16 color, 88 color, 256 color, and 24-bit (true) color modes. """ from __future__ import annotations import re import typing import urwid if typing.TYPE_CHECKING: from typing_extensions import Literal CHART_TRUE = """ #e50000#e51000#e52000#e53000#e54000#e55000#e56000#e57000#e58000#e59000\ #e5a000#e5b100#e5c100#e5d100#e5e100#d9e500#c9e500#b9e500#a9e500#99e500\ #89e500#79e500#68e500#58e500#48e500#38e500#28e500#18e500#08e500#00e507\ #00e517#00e527#00e538#00e548#00e558#00e568#00e578#00e588#00e598#00e5a8\ #00e5b8#00e5c8#00e5d8#00e1e5#00d1e5#00c1e5#00b1e5#00a1e5#0091e5#0081e5\ #0071e5#0061e5#0051e5#0040e5#0030e5#0020e5#0010e5#0000e5#0f00e5#1f00e5\ #2f00e5#3f00e5#4f00e5#5f00e5#7000e5#8000e5#9000e5#a000e5#b000e5#c000e5\ #d000e5#e000e5#e500da#e500ca#e500b9#e500a9#e50099#e50089 #da0000#da0f00#da1e00#da2e00#da3d00#da4c00#da5c00#da6b00#da7a00#da8a00\ #da9900#daa800#dab800#dac700#dad600#cfda00#c0da00#b0da00#a1da00#92da00\ #82da00#73da00#64da00#54da00#45da00#35da00#26da00#17da00#07da00#00da07\ #00da16#00da26#00da35#00da44#00da54#00da63#00da72#00da82#00da91#00daa0\ #00dab0#00dabf#00dace#00d7da#00c8da#00b8da#00a9da#0099da#008ada#007bda\ #006bda#005cda#004dda#003dda#002eda#001fda#000fda#0000da#0e00da#1e00da\ #2d00da#3c00da#4c00da#5b00da#6a00da#7a00da#8900da#9800da#a800da#b700da\ #c600da#d600da#da00cf#da00c0#da00b1#da00a1#da0092#da0083 #d00000#d00e00#d01d00#d02b00#d03a00#d04800#d05700#d06600#d07400#d08300\ #d09100#d0a000#d0af00#d0bd00#d0cc00#c5d000#b6d000#a8d000#99d000#8ad000\ #7cd000#6dd000#5fd000#50d000#41d000#33d000#24d000#16d000#07d000#00d007\ #00d015#00d024#00d032#00d041#00d04f#00d05e#00d06d#00d07b#00d08a#00d098\ #00d0a7#00d0b6#00d0c4#00ccd0#00bed0#00afd0#00a1d0#0092d0#0083d0#0075d0\ #0066d0#0058d0#0049d0#003ad0#002cd0#001dd0#000fd0#0000d0#0e00d0#1c00d0\ #2b00d0#3900d0#4800d0#5600d0#6500d0#7400d0#8200d0#9100d0#9f00d0#ae00d0\ #bd00d0#cb00d0#d000c5#d000b7#d000a8#d00099#d0008b#d0007c #c50000#c50d00#c51b00#c52900#c53700#c54500#c55300#c56000#c56e00#c57c00\ #c58a00#c59800#c5a600#c5b300#c5c100#bbc500#adc500#9fc500#91c500#83c500\ #75c500#68c500#5ac500#4cc500#3ec500#30c500#22c500#15c500#07c500#00c506\ #00c514#00c522#00c530#00c53e#00c54b#00c559#00c567#00c575#00c583#00c591\ #00c59e#00c5ac#00c5ba#00c2c5#00b4c5#00a6c5#0098c5#008ac5#007dc5#006fc5\ #0061c5#0053c5#0045c5#0037c5#002ac5#001cc5#000ec5#0000c5#0d00c5#1b00c5\ #2800c5#3600c5#4400c5#5200c5#6000c5#6e00c5#7c00c5#8900c5#9700c5#a500c5\ #b300c5#c100c5#c500bb#c500ad#c5009f#c50092#c50084#c50076 #ba0000#ba0d00#ba1a00#ba2700#ba3400#ba4100#ba4e00#ba5b00#ba6800#ba7500\ #ba8200#ba8f00#ba9c00#baaa00#bab700#b0ba00#a3ba00#96ba00#89ba00#7cba00\ #6fba00#62ba00#55ba00#48ba00#3bba00#2eba00#20ba00#13ba00#06ba00#00ba06\ #00ba13#00ba20#00ba2d#00ba3a#00ba47#00ba54#00ba61#00ba6e#00ba7c#00ba89\ #00ba96#00baa3#00bab0#00b7ba#00aaba#009dba#0090ba#0083ba#0076ba#0069ba\ #005cba#004eba#0041ba#0034ba#0027ba#001aba#000dba#0000ba#0c00ba#1900ba\ #2600ba#3300ba#4000ba#4e00ba#5b00ba#6800ba#7500ba#8200ba#8f00ba#9c00ba\ #a900ba#b600ba#ba00b1#ba00a4#ba0097#ba008a#ba007d#ba006f #af0000#af0c00#af1800#af2400#af3100#af3d00#af4900#af5600#af6200#af6e00\ #af7b00#af8700#af9300#afa000#afac00#a6af00#9aaf00#8eaf00#81af00#75af00\ #69af00#5caf00#50af00#44af00#37af00#2baf00#1faf00#12af00#06af00#00af05\ #00af12#00af1e#00af2a#00af37#00af43#00af4f#00af5c#00af68#00af74#00af81\ #00af8d#00af99#00afa6#00adaf#00a0af#0094af#0088af#007baf#006faf#0063af\ #0056af#004aaf#003eaf#0031af#0025af#0019af#000caf#0000af#0b00af#1800af\ #2400af#3000af#3d00af#4900af#5500af#6200af#6e00af#7a00af#8700af#9300af\ #9f00af#ac00af#af00a7#af009a#af008e#af0082#af0075#af0069 #a50000#a50b00#a51700#a52200#a52e00#a53900#a54500#a55100#a55c00#a56800\ #a57300#a57f00#a58a00#a59600#a5a200#9ca500#90a500#85a500#79a500#6ea500\ #62a500#57a500#4ba500#3fa500#34a500#28a500#1da500#11a500#06a500#00a505\ #00a511#00a51c#00a528#00a533#00a53f#00a54b#00a556#00a562#00a56d#00a579\ #00a584#00a590#00a59c#00a2a5#0096a5#008ba5#007fa5#0074a5#0068a5#005da5\ #0051a5#0045a5#003aa5#002ea5#0023a5#0017a5#000ca5#0000a5#0b00a5#1600a5\ #2200a5#2d00a5#3900a5#4500a5#5000a5#5c00a5#6700a5#7300a5#7e00a5#8a00a5\ #9600a5#a100a5#a5009c#a50091#a50085#a5007a#a5006e#a50063 #9a0000#9a0a00#9a1500#9a2000#9a2b00#9a3600#9a4000#9a4b00#9a5600#9a6100\ #9a6c00#9a7700#9a8100#9a8c00#9a9700#929a00#879a00#7c9a00#719a00#679a00\ #5c9a00#519a00#469a00#3b9a00#309a00#269a00#1b9a00#109a00#059a00#009a05\ #009a10#009a1a#009a25#009a30#009a3b#009a46#009a50#009a5b#009a66#009a71\ #009a7c#009a87#009a91#00979a#008d9a#00829a#00779a#006c9a#00619a#00569a\ #004c9a#00419a#00369a#002b9a#00209a#00169a#000b9a#00009a#0a009a#15009a\ #20009a#2a009a#35009a#40009a#4b009a#56009a#61009a#6b009a#76009a#81009a\ #8c009a#97009a#9a0092#9a0087#9a007d#9a0072#9a0067#9a005c #8f0000#8f0a00#8f1400#8f1e00#8f2800#8f3200#8f3c00#8f4600#8f5000#8f5a00\ #8f6400#8f6e00#8f7800#8f8200#8f8c00#888f00#7e8f00#748f00#698f00#5f8f00\ #558f00#4b8f00#418f00#378f00#2d8f00#238f00#198f00#0f8f00#058f00#008f04\ #008f0e#008f18#008f23#008f2d#008f37#008f41#008f4b#008f55#008f5f#008f69\ #008f73#008f7d#008f87#008d8f#00838f#00798f#006f8f#00658f#005b8f#00508f\ #00468f#003c8f#00328f#00288f#001e8f#00148f#000a8f#00008f#09008f#13008f\ #1d008f#27008f#31008f#3c008f#46008f#50008f#5a008f#64008f#6e008f#78008f\ #82008f#8c008f#8f0088#8f007e#8f0074#8f006a#8f0060#8f0056 #840000#840900#841200#841b00#842500#842e00#843700#844100#844a00#845300\ #845d00#846600#846f00#847900#848200#7d8400#748400#6b8400#628400#588400\ #4f8400#468400#3c8400#338400#2a8400#208400#178400#0e8400#048400#008404\ #00840d#008417#008420#008429#008433#00843c#008445#00844f#008458#008461\ #00846a#008474#00847d#008284#007984#007084#006684#005d84#005484#004a84\ #004184#003884#002e84#002584#001c84#001284#000984#000084#080084#120084\ #1b0084#240084#2e0084#370084#400084#4a0084#530084#5c0084#660084#6f0084\ #780084#820084#84007e#840074#84006b#840062#840059#84004f #7a0000#7a0800#7a1100#7a1900#7a2200#7a2a00#7a3300#7a3b00#7a4400#7a4d00\ #7a5500#7a5e00#7a6600#7a6f00#7a7700#737a00#6b7a00#627a00#5a7a00#517a00\ #487a00#407a00#377a00#2f7a00#267a00#1e7a00#157a00#0d7a00#047a00#007a04\ #007a0c#007a15#007a1d#007a26#007a2e#007a37#007a40#007a48#007a51#007a59\ #007a62#007a6a#007a73#00787a#006f7a#00677a#005e7a#00557a#004d7a#00447a\ #003c7a#00337a#002b7a#00227a#001a7a#00117a#00087a#00007a#08007a#10007a\ #19007a#21007a#2a007a#33007a#3b007a#44007a#4c007a#55007a#5d007a#66007a\ #6f007a#77007a#7a0074#7a006b#7a0062#7a005a#7a0051#7a0049 #6f0000#6f0700#6f0f00#6f1700#6f1f00#6f2700#6f2e00#6f3600#6f3e00#6f4600\ #6f4e00#6f5500#6f5d00#6f6500#6f6d00#696f00#616f00#596f00#526f00#4a6f00\ #426f00#3a6f00#326f00#2b6f00#236f00#1b6f00#136f00#0b6f00#046f00#006f03\ #006f0b#006f13#006f1b#006f23#006f2a#006f32#006f3a#006f42#006f4a#006f51\ #006f59#006f61#006f69#006d6f#00656f#005e6f#00566f#004e6f#00466f#003e6f\ #00366f#002f6f#00276f#001f6f#00176f#000f6f#00086f#00006f#07006f#0f006f\ #17006f#1e006f#26006f#2e006f#36006f#3e006f#46006f#4d006f#55006f#5d006f\ #65006f#6d006f#6f0069#6f0062#6f005a#6f0052#6f004a#6f0042 #640000#640700#640e00#641500#641c00#642300#642a00#643100#643800#643f00\ #644600#644d00#645400#645b00#646200#5f6400#586400#516400#4a6400#436400\ #3c6400#356400#2e6400#266400#1f6400#186400#116400#0a6400#036400#006403\ #00640a#006411#006418#00641f#006426#00642d#006434#00643b#006442#006449\ #006451#006458#00645f#006364#005c64#005464#004d64#004664#003f64#003864\ #003164#002a64#002364#001c64#001564#000e64#000764#000064#060064#0d0064\ #140064#1b0064#230064#2a0064#310064#380064#3f0064#460064#4d0064#540064\ #5b0064#620064#64005f#640058#640051#64004a#640043#64003c #590000#590600#590c00#591200#591900#591f00#592500#592c00#593200#593800\ #593f00#594500#594b00#595100#595800#555900#4e5900#485900#425900#3c5900\ #355900#2f5900#295900#225900#1c5900#165900#0f5900#095900#035900#005903\ #005909#00590f#005915#00591c#005922#005928#00592f#005935#00593b#005942\ #005948#00594e#005955#005859#005259#004b59#004559#003f59#003859#003259\ #002c59#002659#001f59#001959#001359#000c59#000659#000059#060059#0c0059\ #120059#180059#1f0059#250059#2b0059#320059#380059#3e0059#450059#4b0059\ #510059#580059#590055#59004f#590048#590042#59003c#590035 #4f0000#4f0500#4f0b00#4f1000#4f1600#4f1b00#4f2100#4f2600#4f2c00#4f3100\ #4f3700#4f3d00#4f4200#4f4800#4f4d00#4b4f00#454f00#3f4f00#3a4f00#344f00\ #2f4f00#294f00#244f00#1e4f00#194f00#134f00#0d4f00#084f00#024f00#004f02\ #004f08#004f0d#004f13#004f18#004f1e#004f23#004f29#004f2f#004f34#004f3a\ #004f3f#004f45#004f4a#004d4f#00484f#00424f#003d4f#00374f#00324f#002c4f\ #00274f#00214f#001b4f#00164f#00104f#000b4f#00054f#00004f#05004f#0a004f\ #10004f#16004f#1b004f#21004f#26004f#2c004f#31004f#37004f#3c004f#42004f\ #47004f#4d004f#4f004b#4f0045#4f0040#4f003a#4f0035#4f002f #440000#440400#440900#440e00#441300#441800#441c00#442100#442600#442b00\ #443000#443400#443900#443e00#444300#404400#3c4400#374400#324400#2d4400\ #284400#244400#1f4400#1a4400#154400#104400#0c4400#074400#024400#004402\ #004407#00440b#004410#004415#00441a#00441f#004423#004428#00442d#004432\ #004437#00443b#004440#004344#003e44#003944#003444#003044#002b44#002644\ #002144#001c44#001844#001344#000e44#000944#000444#000044#040044#090044\ #0e0044#130044#170044#1c0044#210044#260044#2b0044#2f0044#340044#390044\ #3e0044#430044#440041#44003c#440037#440032#44002d#440029 #390000#390400#390800#390c00#391000#391400#391800#391c00#392000#392400\ #392800#392c00#393000#393400#393800#363900#323900#2e3900#2a3900#263900\ #223900#1e3900#1a3900#163900#123900#0e3900#0a3900#063900#023900#003901\ #003905#00390a#00390e#003912#003916#00391a#00391e#003922#003926#00392a\ #00392e#003932#003936#003839#003439#003039#002c39#002839#002439#002039\ #001c39#001839#001439#001039#000c39#000839#000439#000039#030039#070039\ #0b0039#100039#140039#180039#1c0039#200039#240039#280039#2c0039#300039\ #340039#380039#390036#390032#39002e#39002a#390026#390022 #2e0000#2e0300#2e0600#2e0900#2e0d00#2e1000#2e1300#2e1700#2e1a00#2e1d00\ #2e2000#2e2400#2e2700#2e2a00#2e2e00#2c2e00#292e00#252e00#222e00#1f2e00\ #1c2e00#182e00#152e00#122e00#0e2e00#0b2e00#082e00#052e00#012e00#002e01\ #002e04#002e08#002e0b#002e0e#002e12#002e15#002e18#002e1b#002e1f#002e22\ #002e25#002e29#002e2c#002e2e#002a2e#00272e#00242e#00212e#001d2e#001a2e\ #00172e#00132e#00102e#000d2e#000a2e#00062e#00032e#00002e#03002e#06002e\ #09002e#0d002e#10002e#13002e#16002e#1a002e#1d002e#20002e#24002e#27002e\ #2a002e#2d002e#2e002c#2e0029#2e0026#2e0022#2e001f#2e001c #240000#240200#240500#240700#240a00#240c00#240f00#241100#241400#241600\ #241900#241b00#241e00#242100#242300#222400#1f2400#1d2400#1a2400#182400\ #152400#132400#102400#0e2400#0b2400#082400#062400#032400#012400#002401\ #002403#002406#002408#00240b#00240d#002410#002413#002415#002418#00241a\ #00241d#00241f#002422#002324#002124#001e24#001c24#001924#001624#001424\ #001124#000f24#000c24#000a24#000724#000524#000224#000024#020024#040024\ #070024#0a0024#0c0024#0f0024#110024#140024#160024#190024#1b0024#1e0024\ #200024#230024#240022#24001f#24001d#24001a#240018#240015 #190000#190100#190300#190500#190700#190800#190a00#190c00#190e00#191000\ #191100#191300#191500#191700#191900#181900#161900#141900#121900#111900\ #0f1900#0d1900#0b1900#091900#081900#061900#041900#021900#001900#001900\ #001902#001904#001906#001908#001909#00190b#00190d#00190f#001910#001912\ #001914#001916#001918#001919#001719#001519#001319#001119#001019#000e19\ #000c19#000a19#000919#000719#000519#000319#000119#000019#010019#030019\ #050019#070019#080019#0a0019#0c0019#0e0019#100019#110019#130019#150019\ #170019#180019#190018#190016#190014#190012#190011#19000f """ # raise Exception(CHART_TRUE) CHART_256 = """ brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_ yellow_ light_red light_magenta light_blue light_cyan light_green #00f#06f#08f#0af#0df#0ff black_______ dark_gray___ #60f#00d#06d#08d#0ad#0dd#0fd light_gray__ white_______ #80f#60d#00a#06a#08a#0aa#0da#0fa #a0f#80d#60a#008#068#088#0a8#0d8#0f8 #d0f#a0d#80d#608#006#066#086#0a6#0d6#0f6 #f0f#d0d#a0a#808#606#000#060#080#0a0#0d0#0f0#0f6#0f8#0fa#0fd#0ff #f0d#d0a#a08#806#600#660#680#6a0#6d0#6f0#6f6#6f8#6fa#6fd#6ff#0df #f0a#d08#a06#800#860#880#8a0#8d0#8f0#8f6#8f8#8fa#8fd#8ff#6df#0af #f08#d06#a00#a60#a80#aa0#ad0#af0#af6#af8#afa#afd#aff#8df#6af#08f #f06#d00#d60#d80#da0#dd0#df0#df6#df8#dfa#dfd#dff#adf#8af#68f#06f #f00#f60#f80#fa0#fd0#ff0#ff6#ff8#ffa#ffd#fff#ddf#aaf#88f#66f#00f #fd0#fd6#fd8#fda#fdd#fdf#daf#a8f#86f#60f #66d#68d#6ad#6dd #fa0#fa6#fa8#faa#fad#faf#d8f#a6f#80f #86d#66a#68a#6aa#6da #f80#f86#f88#f8a#f8d#f8f#d6f#a0f #a6d#86a#668#688#6a8#6d8 #f60#f66#f68#f6a#f6d#f6f#d0f #d6d#a6a#868#666#686#6a6#6d6#6d8#6da#6dd #f00#f06#f08#f0a#f0d#f0f #d6a#a68#866#886#8a6#8d6#8d8#8da#8dd#6ad #d68#a66#a86#aa6#ad6#ad8#ada#add#8ad#68d #d66#d86#da6#dd6#dd8#dda#ddd#aad#88d#66d g78_g82_g85_g89_g93_g100 #da6#da8#daa#dad#a8d#86d g52_g58_g62_g66_g70_g74_ #88a#8aa #d86#d88#d8a#d8d#a6d g27_g31_g35_g38_g42_g46_g50_ #a8a#888#8a8#8aa #d66#d68#d6a#d6d g0__g3__g7__g11_g15_g19_g23_ #a88#aa8#aaa#88a #a88#a8a """ CHART_88 = """ brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_ yellow_ light_red light_magenta light_blue light_cyan light_green #00f#08f#0cf#0ff black_______ dark_gray___ #80f#00c#08c#0cc#0fc light_gray__ white_______ #c0f#80c#008#088#0c8#0f8 #f0f#c0c#808#000#080#0c0#0f0#0f8#0fc#0ff #88c#8cc #f0c#c08#800#880#8c0#8f0#8f8#8fc#8ff#0cf #c8c#888#8c8#8cc #f08#c00#c80#cc0#cf0#cf8#cfc#cff#8cf#08f #c88#cc8#ccc#88c #f00#f80#fc0#ff0#ff8#ffc#fff#ccf#88f#00f #c88#c8c #fc0#fc8#fcc#fcf#c8f#80f #f80#f88#f8c#f8f#c0f g62_g74_g82_g89_g100 #f00#f08#f0c#f0f g0__g19_g35_g46_g52 """ CHART_16 = """ brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_ yellow_ light_red light_magenta light_blue light_cyan light_green black_______ dark_gray___ light_gray__ white_______ """ ATTR_RE = re.compile("(?P[ \n]*)(?P(?:#[0-9A-Fa-f]{6})|(?:#[0-9A-Fa-f]{3})|(?:[^ \n]+))") LONG_ATTR = 7 SHORT_ATTR = 4 # length of short high-colour descriptions which may # be packed one after the next def parse_chart(chart: str, convert): """ Convert string chart into text markup with the correct attributes. chart -- palette chart as a string convert -- function that converts a single palette entry to an (attr, text) tuple, or None if no match is found """ out = [] for match in ATTR_RE.finditer(chart): if match.group("whitespace"): out.append(match.group("whitespace")) entry = match.group("entry") entry = entry.replace("_", " ") while entry: if chart == CHART_TRUE and len(entry) == LONG_ATTR: attrtext = convert(entry[:LONG_ATTR]) elen = LONG_ATTR else: elen = SHORT_ATTR attrtext = convert(entry[:SHORT_ATTR]) # try the first four characters if attrtext: entry = entry[elen:].strip() else: # try the whole thing attrtext = convert(entry.strip()) assert attrtext, f"Invalid palette entry: {entry!r}" # noqa: S101 # "assert" is ok for examples elen = len(entry) entry = "" attr, text = attrtext if chart == CHART_TRUE: out.append((attr, "â–„")) else: out.append((attr, text.ljust(elen))) return out def foreground_chart(chart: str, background, colors: Literal[1, 16, 88, 256, 16777216]): """ Create text markup for a foreground colour chart chart -- palette chart as string background -- colour to use for background of chart colors -- number of colors (88 or 256) """ def convert_foreground(entry): try: attr = urwid.AttrSpec(entry, background, colors) except urwid.AttrSpecError: return None return attr, entry return parse_chart(chart, convert_foreground) def background_chart(chart: str, foreground, colors: Literal[1, 16, 88, 256, 16777216]): """ Create text markup for a background colour chart chart -- palette chart as string foreground -- colour to use for foreground of chart colors -- number of colors (88 or 256) This will remap 8 <= colour < 16 to high-colour versions in the hopes of greater compatibility """ def convert_background(entry): try: attr = urwid.AttrSpec(foreground, entry, colors) except urwid.AttrSpecError: return None # fix 8 <= colour < 16 if colors > 16 and attr.background_basic and attr.background_number >= 8: # use high-colour with same number entry = f"h{attr.background_number:d}" attr = urwid.AttrSpec(foreground, entry, colors) return attr, entry return parse_chart(chart, convert_background) def main() -> None: palette = [ ("header", "black,underline", "light gray", "standout,underline", "black,underline", "#88a"), ("panel", "light gray", "dark blue", "", "#ffd", "#00a"), ("focus", "light gray", "dark cyan", "standout", "#ff8", "#806"), ] screen = urwid.display.raw.Screen() screen.register_palette(palette) lb = urwid.SimpleListWalker([]) chart_offset = None # offset of chart in lb list mode_radio_buttons = [] chart_radio_buttons = [] def fcs(widget) -> urwid.AttrMap: # wrap widgets that can take focus return urwid.AttrMap(widget, None, "focus") def set_mode(colors: int, is_foreground_chart: bool) -> None: # set terminal mode and redraw chart screen.set_terminal_properties(colors) screen.reset_default_terminal_palette() chart_fn = (background_chart, foreground_chart)[is_foreground_chart] if colors == 1: lb[chart_offset] = urwid.Divider() else: chart: str = {16: CHART_16, 88: CHART_88, 256: CHART_256, 2**24: CHART_TRUE}[colors] txt = chart_fn(chart, "default", colors) lb[chart_offset] = urwid.Text(txt, wrap=urwid.CLIP) def on_mode_change(colors: int, rb: urwid.RadioButton, state: bool) -> None: # if this radio button is checked if state: is_foreground_chart = chart_radio_buttons[0].state set_mode(colors, is_foreground_chart) def mode_rb(text: str, colors: int, state: bool = False) -> urwid.AttrMap: # mode radio buttons rb = urwid.RadioButton(mode_radio_buttons, text, state) urwid.connect_signal(rb, "change", on_mode_change, user_args=(colors,)) return fcs(rb) def on_chart_change(rb: urwid.RadioButton, state: bool) -> None: # handle foreground check box state change set_mode(screen.colors, state) def click_exit(button) -> typing.NoReturn: raise urwid.ExitMainLoop() lb.extend( [ urwid.AttrMap(urwid.Text("Urwid Palette Test"), "header"), urwid.AttrMap( urwid.Columns( [ urwid.Pile( [ mode_rb("Monochrome", 1), mode_rb("16-Color", 16, True), mode_rb("88-Color", 88), mode_rb("256-Color", 256), mode_rb("24-bit Color", 2**24), ] ), urwid.Pile( [ fcs(urwid.RadioButton(chart_radio_buttons, "Foreground Colors", True, on_chart_change)), fcs(urwid.RadioButton(chart_radio_buttons, "Background Colors")), urwid.Divider(), fcs(urwid.Button("Exit", click_exit)), ] ), ] ), "panel", ), ] ) chart_offset = len(lb) lb.extend([urwid.Divider()]) # placeholder for the chart set_mode(16, True) # displays the chart def unhandled_input(key: str | tuple[str, int, int, int]) -> None: if key in {"Q", "q", "esc"}: raise urwid.ExitMainLoop() urwid.MainLoop(urwid.ListBox(lb), screen=screen, unhandled_input=unhandled_input).run() if __name__ == "__main__": main() urwid-2.6.16/examples/pop_up.py000077500000000000000000000030521470350774000164440ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import annotations import typing import urwid class PopUpDialog(urwid.WidgetWrap): """A dialog that appears with nothing but a close button""" signals: typing.ClassVar[list[str]] = ["close"] def __init__(self): close_button = urwid.Button("that's pretty cool") urwid.connect_signal(close_button, "click", lambda button: self._emit("close")) pile = urwid.Pile( [ urwid.Text("^^ I'm attached to the widget that opened me. Try resizing the window!\n"), close_button, ] ) super().__init__(urwid.AttrMap(urwid.Filler(pile), "popbg")) class ThingWithAPopUp(urwid.PopUpLauncher): def __init__(self) -> None: super().__init__(urwid.Button("click-me")) urwid.connect_signal(self.original_widget, "click", lambda button: self.open_pop_up()) def create_pop_up(self) -> PopUpDialog: pop_up = PopUpDialog() urwid.connect_signal(pop_up, "close", lambda button: self.close_pop_up()) return pop_up def get_pop_up_parameters(self): return {"left": 0, "top": 1, "overlay_width": 32, "overlay_height": 7} def keypress(self, size: tuple[int], key: str) -> str | None: parsed = super().keypress(size, key) if parsed in {"q", "Q"}: raise urwid.ExitMainLoop("Done") return parsed fill = urwid.Filler(urwid.Padding(ThingWithAPopUp(), urwid.CENTER, 15)) loop = urwid.MainLoop(fill, [("popbg", "white", "dark blue")], pop_ups=True) loop.run() urwid-2.6.16/examples/subproc.py000077500000000000000000000020651470350774000166220ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import annotations import os import subprocess import sys import urwid factor_me = 362923067964327863989661926737477737673859044111968554257667 run_me = os.path.join(os.path.dirname(sys.argv[0]), "subproc2.py") output_widget = urwid.Text(f"Factors of {factor_me:d}:\n") edit_widget = urwid.Edit("Type anything or press enter to exit:") frame_widget = urwid.Frame( header=edit_widget, body=urwid.Filler(output_widget, valign=urwid.BOTTOM), focus_part="header", ) def exit_on_enter(key: str | tuple[str, int, int, int]) -> None: if key == "enter": raise urwid.ExitMainLoop() loop = urwid.MainLoop(frame_widget, unhandled_input=exit_on_enter) def received_output(data: bytes) -> bool: output_widget.set_text(output_widget.text + data.decode("utf8")) return True write_fd = loop.watch_pipe(received_output) with subprocess.Popen( # noqa: S603 ["python", "-u", run_me, str(factor_me)], # noqa: S607 # Example can be insecure stdout=write_fd, close_fds=True, ) as proc: loop.run() urwid-2.6.16/examples/subproc2.py000066400000000000000000000003001470350774000166670ustar00rootroot00000000000000# this is part of the subproc.py example from __future__ import annotations import sys num = int(sys.argv[1]) for c in range(1, 10000000): if num % c == 0: print("factor:", c) urwid-2.6.16/examples/terminal.py000077500000000000000000000052261470350774000167620ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid terminal emulation widget example app # Copyright (C) 2010 aszlig # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import typing from contextlib import suppress import urwid def main() -> None: urwid.set_encoding("utf8") term = urwid.Terminal(None, encoding="utf-8") size_widget = urwid.Text("") mainframe = urwid.LineBox( urwid.Pile( [ (urwid.WEIGHT, 70, term), (1, urwid.Filler(size_widget)), (1, urwid.Filler(urwid.Edit("focus test edit: "))), ] ), ) def set_title(widget, title: str) -> None: mainframe.set_title(title) def execute_quit(*args, **kwargs) -> typing.NoReturn: raise urwid.ExitMainLoop() def handle_key(key: str | tuple[str, int, int, int]) -> None: if key in {"q", "Q"}: execute_quit() def handle_resize(widget, size: tuple[int, int]) -> None: size_widget.set_text(f"Terminal size: [{size[0]}, {size[1]}]") urwid.connect_signal(term, "title", set_title) urwid.connect_signal(term, "closed", execute_quit) with suppress(NameError): urwid.connect_signal(term, "resize", handle_resize) # if using a version of Urwid library where vterm doesn't support # resize, don't register the signal handler. try: # create Screen with bracketed paste mode support enabled bpm_screen = urwid.display.raw.Screen(bracketed_paste_mode=True) # pylint: disable=unexpected-keyword-arg except TypeError: # if using a version of Urwid library that doesn't support # bracketed paste mode, do without it. bpm_screen = urwid.display.raw.Screen() loop = urwid.MainLoop(mainframe, handle_mouse=False, screen=bpm_screen, unhandled_input=handle_key) term.main_loop = loop loop.run() if __name__ == "__main__": main() urwid-2.6.16/examples/tour.py000077500000000000000000000345721470350774000161460ustar00rootroot00000000000000#!/usr/bin/env python # # Urwid tour. It slices, it dices.. # Copyright (C) 2004-2011 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid tour. Shows many of the standard widget types and features. """ from __future__ import annotations import urwid def main(): text_header = "Welcome to the urwid tour! UP / DOWN / PAGE UP / PAGE DOWN scroll. F8 exits." text_intro = [ ("important", "Text"), " widgets are the most common in " "any urwid program. This Text widget was created " "without setting the wrap or align mode, so it " "defaults to left alignment with wrapping on space " "characters. ", ("important", "Change the window width"), " to see how the widgets on this page react. This Text widget is wrapped with a ", ("important", "Padding"), " widget to keep it indented on the left and right.", ] text_right = "This Text widget is right aligned. Wrapped words stay to the right as well. " text_center = "This one is center aligned." text_clip = ( "Text widgets may be clipped instead of wrapped.\n" "Extra text is discarded instead of wrapped to the next line. " "65-> 70-> 75-> 80-> 85-> 90-> 95-> 100>\n" "Newlines embedded in the string are still respected." ) text_right_clip = ( "This is a right aligned and clipped Text widget.\n" "<100 <-95 <-90 <-85 <-80 <-75 <-70 <-65 " "Text will be cut off at the left of this widget." ) text_center_clip = "Center aligned and clipped widgets will have text cut off both sides." text_ellipsis = ( "Text can be clipped using the ellipsis character (…)\n" "Extra text is discarded and a … mark is shown." "50-> 55-> 60-> 65-> 70-> 75-> 80-> 85-> 90-> 95-> 100>\n" ) text_any = ( "The 'any' wrap mode will wrap on any character. This " "mode will not collapse space characters at the end of the " "line but it still honors embedded newline characters.\n" "Like this one." ) text_padding = ( "Padding widgets have many options. This " "is a standard Text widget wrapped with a Padding widget " "with the alignment set to relative 20% and with its width " "fixed at 40." ) text_divider = [ "The ", ("important", "Divider"), " widget repeats the same character across the whole line. It can also add blank lines above and below.", ] text_edit = [ "The ", ("important", "Edit"), " widget is a simple text editing widget. It supports cursor " "movement and tries to maintain the current column when focus " "moves to another edit widget. It wraps and aligns the same " "way as Text widgets.", ] text_edit_cap1 = ("editcp", "This is a caption. Edit here: ") text_edit_text1 = "editable stuff" text_edit_cap2 = ("editcp", "This one supports newlines: ") text_edit_text2 = ( "line one starts them all\n" "== line 2 == with some more text to edit.. words.. whee..\n" "LINE III, the line to end lines one and two, unless you " "change something." ) text_edit_cap3 = ("editcp", "This one is clipped, try editing past the edge: ") text_edit_text3 = "add some text here -> -> -> ...." text_edit_alignments = "Different Alignments:" text_edit_left = "left aligned (default)" text_edit_center = "center aligned" text_edit_right = "right aligned" text_intedit = ("editcp", [("important", "IntEdit"), " allows only numbers: "]) text_edit_padding = ("editcp", "Edit widget within a Padding widget ") text_columns1 = [ ("important", "Columns"), " are used to share horizontal screen space. " "This one splits the space into two parts with " "three characters between each column. The " "contents of each column is a single widget.", ] text_columns2 = [ "When you need to put more than one widget into a column you can use a ", ("important", "Pile"), " to combine two or more widgets.", ] text_col_columns = "Columns may be placed inside other columns." text_col_21 = "Col 2.1" text_col_22 = "Col 2.2" text_col_23 = "Col 2.3" text_column_widths = ( "Columns may also have uneven relative " "weights or fixed widths. Use a minimum width so that " "columns don't become too small." ) text_weight = "Weight %d" text_fixed_9 = "" # should be 9 columns wide text_fixed_14 = "<--Fixed 14-->" # should be 14 columns wide text_edit_col_cap1 = ("editcp", "Edit widget within Columns") text_edit_col_text1 = "here's\nsome\ninfo" text_edit_col_cap2 = ("editcp", "and within Pile ") text_edit_col_text2 = "more" text_edit_col_cap3 = ("editcp", "another ") text_edit_col_text3 = "still more" text_gridflow = [ "A ", ("important", "GridFlow"), " widget " "may be used to display a list of flow widgets with equal " "widths. Widgets that don't fit on the first line will " "flow to the next. This is useful for small widgets that " "you want to keep together such as ", ("important", "Button"), ", ", ("important", "CheckBox"), " and ", ("important", "RadioButton"), " widgets.", ] text_button_list = ["Yes", "No", "Perhaps", "Certainly", "Partially", "Tuesdays Only", "Help"] text_cb_list = ["Wax", "Wash", "Buff", "Clear Coat", "Dry", "Racing Stripe"] text_rb_list = ["Morning", "Afternoon", "Evening", "Weekend"] text_listbox = [ "All these widgets have been displayed with the help of a ", ("important", "ListBox"), " widget. ListBox widgets handle scrolling and changing focus. A ", ("important", "Frame"), " widget is used to keep the instructions at the top of the screen.", ] def button_press(button): frame.footer = urwid.AttrMap(urwid.Text(["Pressed: ", button.get_label()]), "header") radio_button_group = [] blank = urwid.Divider() listbox_content = [ blank, urwid.Padding(urwid.Text(text_intro), left=2, right=2, min_width=20), blank, urwid.Text(text_right, align=urwid.RIGHT), blank, urwid.Text(text_center, align=urwid.CENTER), blank, urwid.Text(text_clip, wrap=urwid.CLIP), blank, urwid.Text(text_right_clip, align=urwid.RIGHT, wrap=urwid.CLIP), blank, urwid.Text(text_center_clip, align=urwid.CENTER, wrap=urwid.CLIP), blank, urwid.Text(text_ellipsis, wrap=urwid.ELLIPSIS), blank, urwid.Text(text_any, wrap=urwid.ANY), blank, urwid.Padding(urwid.Text(text_padding), (urwid.RELATIVE, 20), 40), blank, urwid.AttrMap(urwid.Divider("=", 1), "bright"), urwid.Padding(urwid.Text(text_divider), left=2, right=2, min_width=20), urwid.AttrMap(urwid.Divider("-", 0, 1), "bright"), blank, urwid.Padding(urwid.Text(text_edit), left=2, right=2, min_width=20), blank, urwid.AttrMap(urwid.Edit(text_edit_cap1, text_edit_text1), "editbx", "editfc"), blank, urwid.AttrMap(urwid.Edit(text_edit_cap2, text_edit_text2, multiline=True), "editbx", "editfc"), blank, urwid.AttrMap(urwid.Edit(text_edit_cap3, text_edit_text3, wrap=urwid.CLIP), "editbx", "editfc"), blank, urwid.Text(text_edit_alignments), urwid.AttrMap(urwid.Edit("", text_edit_left, align=urwid.LEFT), "editbx", "editfc"), urwid.AttrMap(urwid.Edit("", text_edit_center, align=urwid.CENTER), "editbx", "editfc"), urwid.AttrMap(urwid.Edit("", text_edit_right, align=urwid.RIGHT), "editbx", "editfc"), blank, urwid.AttrMap(urwid.IntEdit(text_intedit, 123), "editbx", "editfc"), blank, urwid.Padding(urwid.AttrMap(urwid.Edit(text_edit_padding, ""), "editbx", "editfc"), left=10, width=50), blank, blank, urwid.AttrMap( urwid.Columns( [ urwid.Divider("."), urwid.Divider(","), urwid.Divider("."), ] ), "bright", ), blank, urwid.Columns( [ urwid.Padding(urwid.Text(text_columns1), left=2, right=0, min_width=20), urwid.Pile([urwid.Divider("~"), urwid.Text(text_columns2), urwid.Divider("_")]), ], 3, ), blank, blank, urwid.Columns( [ urwid.Text(text_col_columns), urwid.Columns( [ urwid.Text(text_col_21), urwid.Text(text_col_22), urwid.Text(text_col_23), ], 1, ), ], 2, ), blank, urwid.Padding(urwid.Text(text_column_widths), left=2, right=2, min_width=20), blank, urwid.Columns( [ urwid.AttrMap(urwid.Text(text_weight % 1), "reverse"), (urwid.WEIGHT, 2, urwid.Text(text_weight % 2)), (urwid.WEIGHT, 3, urwid.AttrMap(urwid.Text(text_weight % 3), "reverse")), (urwid.WEIGHT, 4, urwid.Text(text_weight % 4)), (urwid.WEIGHT, 5, urwid.AttrMap(urwid.Text(text_weight % 5), "reverse")), (urwid.WEIGHT, 6, urwid.Text(text_weight % 6)), ], 0, min_width=8, ), blank, urwid.Columns( [ (urwid.WEIGHT, 2, urwid.AttrMap(urwid.Text(text_weight % 2), "reverse")), (9, urwid.Text(text_fixed_9)), (urwid.WEIGHT, 3, urwid.AttrMap(urwid.Text(text_weight % 3), "reverse")), (14, urwid.Text(text_fixed_14)), ], 0, min_width=8, ), blank, urwid.Columns( [ urwid.AttrMap(urwid.Edit(text_edit_col_cap1, text_edit_col_text1, multiline=True), "editbx", "editfc"), urwid.Pile( [ urwid.AttrMap(urwid.Edit(text_edit_col_cap2, text_edit_col_text2), "editbx", "editfc"), blank, urwid.AttrMap(urwid.Edit(text_edit_col_cap3, text_edit_col_text3), "editbx", "editfc"), ] ), ], 1, ), blank, urwid.AttrMap( urwid.Columns( [ urwid.Divider("'"), urwid.Divider('"'), urwid.Divider("~"), urwid.Divider('"'), urwid.Divider("'"), ] ), "bright", ), blank, blank, urwid.Padding(urwid.Text(text_gridflow), left=2, right=2, min_width=20), blank, urwid.Padding( urwid.GridFlow( [urwid.AttrMap(urwid.Button(txt, button_press), "buttn", "buttnf") for txt in text_button_list], 13, 3, 1, urwid.LEFT, ), left=4, right=3, min_width=13, ), blank, urwid.Padding( urwid.GridFlow( [urwid.AttrMap(urwid.CheckBox(txt), "buttn", "buttnf") for txt in text_cb_list], 10, 3, 1, urwid.LEFT, ), left=4, right=3, min_width=10, ), blank, urwid.Padding( urwid.GridFlow( [urwid.AttrMap(urwid.RadioButton(radio_button_group, txt), "buttn", "buttnf") for txt in text_rb_list], 13, 3, 1, urwid.LEFT, ), left=4, right=3, min_width=13, ), blank, blank, urwid.Padding(urwid.Text(text_listbox), left=2, right=2, min_width=20), blank, blank, ] header = urwid.AttrMap(urwid.Text(text_header), "header") listbox = urwid.ListBox(urwid.SimpleListWalker(listbox_content)) scrollable = urwid.ScrollBar( listbox, trough_char=urwid.ScrollBar.Symbols.LITE_SHADE, ) frame = urwid.Frame(urwid.AttrMap(scrollable, "body"), header=header) palette = [ ("body", "black", "light gray", "standout"), ("reverse", "light gray", "black"), ("header", "white", "dark red", "bold"), ("important", "dark blue", "light gray", ("standout", "underline")), ("editfc", "white", "dark blue", "bold"), ("editbx", "light gray", "dark blue"), ("editcp", "black", "light gray", "standout"), ("bright", "dark gray", "light gray", ("bold", "standout")), ("buttn", "black", "dark cyan"), ("buttnf", "white", "dark blue", "bold"), ] # use appropriate Screen class if urwid.display.web.is_web_request(): screen = urwid.display.web.Screen() else: screen = urwid.display.raw.Screen() def unhandled(key: str | tuple[str, int, int, int]) -> None: if key == "f8": raise urwid.ExitMainLoop() urwid.MainLoop(frame, palette, screen, unhandled_input=unhandled).run() def setup(): urwid.display.web.set_preferences("Urwid Tour") # try to handle short web requests quickly if urwid.display.web.handle_short_request(): return main() if __name__ == "__main__" or urwid.display.web.is_web_request(): setup() urwid-2.6.16/examples/treesample.py000077500000000000000000000114231470350774000173040ustar00rootroot00000000000000#!/usr/bin/env python # # Trivial data browser # This version: # Copyright (C) 2010 Rob Lanphier # Derived from browse.py in urwid distribution # Copyright (C) 2004-2007 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid example lazy directory browser / tree view Features: - custom selectable widgets for files and directories - custom message widgets to identify access errors and empty directories - custom list walker for displaying widgets in a tree fashion """ from __future__ import annotations import typing import urwid if typing.TYPE_CHECKING: from collections.abc import Hashable, Iterable class ExampleTreeWidget(urwid.TreeWidget): """Display widget for leaf nodes""" def get_display_text(self) -> str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]: return self.get_node().get_value()["name"] class ExampleNode(urwid.TreeNode): """Data storage object for leaf nodes""" def load_widget(self) -> ExampleTreeWidget: return ExampleTreeWidget(self) class ExampleParentNode(urwid.ParentNode): """Data storage object for interior/parent nodes""" def load_widget(self) -> ExampleTreeWidget: return ExampleTreeWidget(self) def load_child_keys(self) -> Iterable[int]: data = self.get_value() return range(len(data["children"])) def load_child_node(self, key) -> ExampleParentNode | ExampleNode: """Return either an ExampleNode or ExampleParentNode""" childdata = self.get_value()["children"][key] childdepth = self.get_depth() + 1 if "children" in childdata: childclass = ExampleParentNode else: childclass = ExampleNode return childclass(childdata, parent=self, key=key, depth=childdepth) class ExampleTreeBrowser: palette: typing.ClassVar[tuple[str, str, str, ...]] = [ ("body", "black", "light gray"), ("focus", "light gray", "dark blue", "standout"), ("head", "yellow", "black", "standout"), ("foot", "light gray", "black"), ("key", "light cyan", "black", "underline"), ("title", "white", "black", "bold"), ("flag", "dark gray", "light gray"), ("error", "dark red", "light gray"), ] footer_text: typing.ClassVar[list[tuple[str, str] | str]] = [ ("title", "Example Data Browser"), " ", ("key", "UP"), ",", ("key", "DOWN"), ",", ("key", "PAGE UP"), ",", ("key", "PAGE DOWN"), " ", ("key", "+"), ",", ("key", "-"), " ", ("key", "LEFT"), " ", ("key", "HOME"), " ", ("key", "END"), " ", ("key", "Q"), ] def __init__(self, data=None) -> None: self.topnode = ExampleParentNode(data) self.listbox = urwid.TreeListBox(urwid.TreeWalker(self.topnode)) self.listbox.offset_rows = 1 self.header = urwid.Text("") self.footer = urwid.AttrMap(urwid.Text(self.footer_text), "foot") self.view = urwid.Frame( urwid.AttrMap(self.listbox, "body"), header=urwid.AttrMap(self.header, "head"), footer=self.footer, ) def main(self) -> None: """Run the program.""" self.loop = urwid.MainLoop(self.view, self.palette, unhandled_input=self.unhandled_input) self.loop.run() def unhandled_input(self, k: str | tuple[str, int, int, int]) -> None: if k in {"q", "Q"}: raise urwid.ExitMainLoop() def get_example_tree(): """generate a quick 100 leaf tree for demo purposes""" retval = {"name": "parent", "children": []} for i in range(10): retval["children"].append({"name": f"child {i!s}"}) retval["children"][i]["children"] = [] for j in range(10): retval["children"][i]["children"].append({"name": "grandchild " + str(i) + "." + str(j)}) return retval def main() -> None: sample = get_example_tree() ExampleTreeBrowser(sample).main() if __name__ == "__main__": main() urwid-2.6.16/examples/twisted_serve_ssh.py000066400000000000000000000346141470350774000207130ustar00rootroot00000000000000""" Twisted integration for Urwid. This module allows you to serve Urwid applications remotely over ssh. The idea is that the server listens as an SSH server, and each connection is routed by Twisted to urwid, and the urwid UI is routed back to the console. The concept was a bit of a head-bender for me, but really we are just sending escape codes and the what-not back to the console over the shell that ssh has created. This is the same service as provided by the UI components in twisted.conch.insults.window, except urwid has more features, and seems more mature. This module is not highly configurable, and the API is not great, so don't worry about just using it as an example and copy-pasting. Process ------- TODO: - better gpm tracking: there is no place for os.Popen in a Twisted app I think. Copyright: 2010, Ali Afshar License: MIT Portions Copyright: 2010, Ian Ward Licence: LGPL """ from __future__ import annotations import typing from twisted.application.internet import TCPServer from twisted.application.service import Application from twisted.conch.insults.insults import ServerProtocol, TerminalProtocol from twisted.conch.interfaces import IConchUser, ISession from twisted.conch.manhole_ssh import ( ConchFactory, TerminalRealm, TerminalSession, TerminalSessionTransport, TerminalUser, ) from twisted.cred.portal import Portal from twisted.python.components import Adapter, Componentized from zope.interface import Attribute, Interface, implementer import urwid from urwid.display.raw import Screen if typing.TYPE_CHECKING: from twisted.cred.checkers import ICredentialsChecker class IUrwidUi(Interface): """Toplevel urwid widget""" toplevel = Attribute("Urwid Toplevel Widget") palette = Attribute("Urwid Palette") screen = Attribute("Urwid Screen") loop = Attribute("Urwid Main Loop") def create_urwid_toplevel(): """Create a toplevel widget.""" def create_urwid_mainloop(): """Create the urwid main loop.""" class IUrwidMind(Interface): ui = Attribute("") terminalProtocol = Attribute("") terminal = Attribute("") checkers = Attribute("") avatar = Attribute("The avatar") def push(data): """Push data""" def draw(): """Refresh the UI""" class UrwidUi: def __init__(self, urwid_mind): self.mind = urwid_mind self.toplevel = self.create_urwid_toplevel() self.palette = self.create_urwid_palette() self.screen = TwistedScreen(self.mind.terminalProtocol) self.loop = self.create_urwid_mainloop() def create_urwid_toplevel(self): raise NotImplementedError def create_urwid_palette(self): return def create_urwid_mainloop(self): evl = urwid.TwistedEventLoop(manage_reactor=False) loop = urwid.MainLoop( self.toplevel, screen=self.screen, event_loop=evl, unhandled_input=self.mind.unhandled_key, palette=self.palette, ) self.screen.loop = loop loop.run() return loop class UnhandledKeyHandler: def __init__(self, mind): self.mind = mind def push(self, key): if isinstance(key, tuple): return None f = getattr(self, f"key_{key.replace(' ', '_')}", None) if f is None: return None return f(key) def key_ctrl_c(self, key): self.mind.terminal.loseConnection() @implementer(IUrwidMind) class UrwidMind(Adapter): cred_checkers: typing.ClassVar[list[ICredentialsChecker]] = [] ui = None ui_factory = None unhandled_key_factory = UnhandledKeyHandler @property def avatar(self): return IConchUser(self.original) def set_terminalProtocol(self, terminalProtocol): self.terminalProtocol = terminalProtocol self.terminal = terminalProtocol.terminal self.unhandled_key_handler = self.unhandled_key_factory(self) self.unhandled_key = self.unhandled_key_handler.push self.ui = self.ui_factory(self) def push(self, data): self.ui.screen.push(data) def draw(self): self.ui.loop.draw_screen() class TwistedScreen(Screen): """A Urwid screen which knows about the Twisted terminal protocol that is driving it. A Urwid screen is responsible for: 1. Input 2. Output Input is achieved in normal urwid by passing a list of available readable file descriptors to the event loop for polling/selecting etc. In the Twisted situation, this is not necessary because Twisted polls the input descriptors itself. Urwid allows this by being driven using the main loop instance's `process_input` method which is triggered on Twisted protocol's standard `dataReceived` method. """ def __init__(self, terminalProtocol): # We will need these later self.terminalProtocol = terminalProtocol self.terminal = terminalProtocol.terminal super().__init__() self.colors = 16 self._pal_escape = {} self.bright_is_bold = True self.register_palette_entry(None, "black", "white") urwid.signals.connect_signal(self, urwid.UPDATE_PALETTE_ENTRY, self._on_update_palette_entry) # Don't need to wait for anything to start self._started = True # Urwid Screen API def get_cols_rows(self) -> tuple[int, int]: """Get the size of the terminal as (cols, rows)""" return self.terminalProtocol.width, self.terminalProtocol.height def draw_screen(self, size: tuple[int, int], canvas: urwid.Canvas) -> None: """Render a canvas to the terminal. The canvas contains all the information required to render the Urwid UI. The content method returns a list of rows as (attr, cs, text) tuples. This very simple implementation iterates each row and simply writes it out. """ (_maxcol, _maxrow) = size # self.terminal.eraseDisplay() lasta = None for i, row in enumerate(canvas.content()): self.terminal.cursorPosition(0, i) for attr, _cs, text in row: if attr != lasta: text = f"{self._attr_to_escape(attr)}{text}" # noqa: PLW2901 lasta = attr # if cs or attr: # print(cs, attr) self.write(text) cursor = canvas.get_cursor() if cursor is not None: self.terminal.cursorPosition(*cursor) # XXX from base screen def set_mouse_tracking(self, enable=True): """ Enable (or disable) mouse tracking. After calling this function get_input will include mouse click events along with keystrokes. """ if enable: self.write(urwid.escape.MOUSE_TRACKING_ON) else: self.write(urwid.escape.MOUSE_TRACKING_OFF) # twisted handles polling, so we don't need the loop to do it, we just # push what we get to the loop from dataReceived. def hook_event_loop(self, event_loop, callback): self._urwid_callback = callback self._evl = event_loop def unhook_event_loop(self, event_loop): pass # Do nothing here either. Not entirely sure when it gets called. def get_input(self, raw_keys=False): return def get_available_raw_input(self): data = self._data self._data = [] return data # Twisted driven def push(self, data): """Receive data from Twisted and push it into the urwid main loop. We must here: 1. filter the input data against urwid's input filter. 2. Calculate escapes and other clever things using urwid's `escape.process_keyqueue`. 3. Pass the calculated keys as a list to the Urwid main loop. 4. Redraw the screen """ self._data = list(map(ord, data)) self.parse_input(self._evl, self._urwid_callback) self.loop.draw_screen() # Convenience def write(self, data): self.terminal.write(data) # Private def _on_update_palette_entry(self, name, *attrspecs): # copy the attribute to a dictionary containing the escape sequences self._pal_escape[name] = self._attrspec_to_escape(attrspecs[{16: 0, 1: 1, 88: 2, 256: 3}[self.colors]]) def _attr_to_escape(self, a): if a in self._pal_escape: return self._pal_escape[a] if isinstance(a, urwid.AttrSpec): return self._attrspec_to_escape(a) # undefined attributes use default/default # TODO: track and report these return self._attrspec_to_escape(urwid.AttrSpec("default", "default")) def _attrspec_to_escape(self, a): """ Convert AttrSpec instance a to an escape sequence for the terminal >>> s = Screen() >>> s.set_terminal_properties(colors=256) >>> a2e = s._attrspec_to_escape >>> a2e(s.AttrSpec('brown', 'dark green')) '\\x1b[0;33;42m' >>> a2e(s.AttrSpec('#fea,underline', '#d0d')) '\\x1b[0;38;5;229;4;48;5;164m' """ if a.foreground_high: fg = f"38;5;{a.foreground_number:d}" elif a.foreground_basic: if a.foreground_number > 7: if self.bright_is_bold: fg = f"1;{a.foreground_number - 8 + 30:d}" else: fg = f"{a.foreground_number - 8 + 90:d}" else: fg = f"{a.foreground_number + 30:d}" else: fg = "39" st = "1;" * a.bold + "4;" * a.underline + "7;" * a.standout if a.background_high: bg = f"48;5;{a.background_number:d}" elif a.background_basic: if a.background_number > 7: # this doesn't work on most terminals bg = f"{a.background_number - 8 + 100:d}" else: bg = f"{a.background_number + 40:d}" else: bg = "49" return f"{urwid.escape.ESC}[0;{fg};{st}{bg}m" class UrwidTerminalProtocol(TerminalProtocol): """A terminal protocol that knows to proxy input and receive output from Urwid. This integrates with the TwistedScreen in a 1:1. """ def __init__(self, urwid_mind): self.urwid_mind = urwid_mind self.width = 80 self.height = 24 def connectionMade(self): self.urwid_mind.set_terminalProtocol(self) self.terminalSize(self.height, self.width) def terminalSize(self, height, width): """Resize the terminal.""" self.width = width self.height = height self.urwid_mind.ui.loop.screen_size = None self.terminal.eraseDisplay() self.urwid_mind.draw() def dataReceived(self, data): """Received data from the connection. This overrides the default implementation which parses and passes to the keyReceived method. We don't do that here, and must not do that so that Urwid can get the right juice (which includes things like mouse tracking). Instead we just pass the data to the screen instance's dataReceived, which handles the proxying to Urwid. """ self.urwid_mind.push(data) def _unhandled_input(self, data): # evil proceed = True if hasattr(self.urwid_toplevel, "app"): proceed = self.urwid_toplevel.app.unhandled_input(self, data) if not proceed: return if data == "ctrl c": self.terminal.loseConnection() class UrwidServerProtocol(ServerProtocol): def dataReceived(self, data): self.terminalProtocol.dataReceived(data) class UrwidUser(TerminalUser): """A terminal user that remembers its avatarId The default implementation doesn't """ def __init__(self, original, avatarId): super().__init__(original, avatarId) self.avatarId = avatarId class UrwidTerminalSession(TerminalSession): """A terminal session that remembers the avatar and chained protocol for later use. And implements a missing method for changed Window size. Note: This implementation assumes that each SSH connection will only request a single shell, which is not an entirely safe assumption, but is by far the most common case. """ def openShell(self, proto): """Open a shell.""" self.chained_protocol = UrwidServerProtocol(UrwidTerminalProtocol, IUrwidMind(self.original)) TerminalSessionTransport(proto, self.chained_protocol, IConchUser(self.original), self.height, self.width) def windowChanged(self, dimensions): """Called when the window size has changed.""" (h, w, _x, _y) = dimensions self.chained_protocol.terminalProtocol.terminalSize(h, w) class UrwidRealm(TerminalRealm): """Custom terminal realm class-configured to use our custom Terminal User Terminal Session. """ def __init__(self, mind_factory): super().__init__() self.mind_factory = mind_factory def _getAvatar(self, avatarId): comp = Componentized() user = UrwidUser(comp, avatarId) comp.setComponent(IConchUser, user) sess = UrwidTerminalSession(comp) comp.setComponent(ISession, sess) mind = self.mind_factory(comp) comp.setComponent(IUrwidMind, mind) return user def requestAvatar(self, avatarId, mind, *interfaces): for i in interfaces: if i is IConchUser: return (IConchUser, self._getAvatar(avatarId), lambda: None) raise NotImplementedError() def create_server_factory(urwid_mind_factory): """Convenience to create a server factory with a portal that uses a realm serving a given urwid widget against checkers provided. """ rlm = UrwidRealm(urwid_mind_factory) ptl = Portal(rlm, urwid_mind_factory.cred_checkers) return ConchFactory(ptl) def create_service(urwid_mind_factory, port, *args, **kw): """Convenience to create a service for use in tac-ish situations.""" f = create_server_factory(urwid_mind_factory) return TCPServer(port, f, *args, **kw) def create_application(application_name, urwid_mind_factory, port, *args, **kw): """Convenience to create an application suitable for tac file""" application = Application(application_name) svc = create_service(urwid_mind_factory, 6022) svc.setServiceParent(application) return application urwid-2.6.16/examples/twisted_serve_ssh.tac000066400000000000000000000016621470350774000210270ustar00rootroot00000000000000# encoding: utf-8 """ Example application for integrating serving a Urwid application remotely. Run this application with:: twistd -ny twisted_serve_ssh.tac Then in another terminal run:: ssh -p 6022 user@localhost (The password is 'pw' without the quotes.) Note: To use this in real life, you must use some real checker. """ from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse import urwid from twisted_serve_ssh import UrwidMind, UrwidUi, create_application class HelloUi(UrwidUi): def create_urwid_toplevel(self): txt = urwid.Edit('Hello World?\n ') txt2 = urwid.Edit('Hello World?\n ') fill = urwid.Filler(urwid.Pile([txt, txt2]), 'top') return fill class HelloMind(UrwidMind): ui_factory = HelloUi cred_checkers = [InMemoryUsernamePasswordDatabaseDontUse(user='pw')] application = create_application('TXUrwid Demo', HelloMind, 6022) # vim: ft=python urwid-2.6.16/isort-requirements.txt000066400000000000000000000000211470350774000173620ustar00rootroot00000000000000 isort == 5.13.2 urwid-2.6.16/pylint-requirements.txt000066400000000000000000000000161470350774000175450ustar00rootroot00000000000000pylint==3.3.1 urwid-2.6.16/pyproject.toml000066400000000000000000000166171470350774000156760ustar00rootroot00000000000000[build-system] # Minimum requirements for the build system to execute. # PEP 508 specifications for PEP 518. requires = [ "setuptools >= 61.0.0", "setuptools_scm[toml]>=7.0", "wheel", ] build-backend="setuptools.build_meta" [project] name = "urwid" description = "A full-featured console (xterm et al.) user interface library" requires-python = ">=3.7.0" keywords = ["curses", "ui", "widget", "scroll", "listbox", "user interface", "text layout", "console", "ncurses"] license={text="LGPL-2.1-only"} # Use SPDX classifier readme = {file = "README.rst", content-type = "text/x-rst"} authors=[{name="Ian Ward", email="ian@excess.org"}] dynamic = ["classifiers", "version", "dependencies"] [project.urls] "Homepage" = "https://urwid.org/" "Documentation" = "https://urwid.org/manual/index.html" "Repository" = "https://github.com/urwid/urwid" "Bug Tracker" = "https://github.com/urwid/urwid/issues" [tool.setuptools] platforms=["unix-like"] zip-safe = false [tool.setuptools.packages.find] exclude = [ "doc", "docs", "examples", "test", "tests", "bin", ".*" ] namespaces = false [tool.setuptools.dynamic] dependencies = {file = ["requirements.txt"]} classifiers = {file = ["classifiers.txt"]} [project.optional-dependencies] curses = ["windows-curses;sys_platform=='win32'"] glib = ["PyGObject"] tornado = ["tornado>=5.0"] trio = ["trio>=0.22.0", "exceptiongroup"] twisted = ["twisted"] zmq = ["zmq"] # for lcd_display serial = ["pyserial"] lcd = ["pyserial"] [tool.distutils.bdist_wheel] universal = 0 [tool.setuptools_scm] write_to = "urwid/version.py" [tool.cibuildwheel] # Disable building PyPy wheels on all platforms # Disable musllinux as not popular platform skip = ["pp*", "*-musllinux_*"] [tool.isort] profile = "black" line_length = 120 [tool.black] line-length = 120 [tool.pytest.ini_options] minversion = "6.0" addopts = "-vvvv --doctest-modules -s --cov=urwid" doctest_optionflags = ["ELLIPSIS", "IGNORE_EXCEPTION_DETAIL"] testpaths = ["urwid"] [tool.coverage.run] omit = ["tests/*", ".tox/*", "setup.py"] branch = true [tool.coverage.report] exclude_lines = [ # Have to re-enable the standard pragma "pragma: no cover", # Don't complain about missing debug-only code: "def __repr__", "def __rich_repr__", # own repr construct magic "def _repr_words", "def _repr_attrs", # Don't complain if tests don't hit defensive assertion code: "raise NotImplementedError", # Exclude methods marked as abstract "@abstractmethod", "@abc.abstractmethod", # Exclude import statements "^from\b", "^import\b", # Exclude variable declarations that are executed when file is loaded "^[a-zA-Z_]+\b\\s=", # Code for static analysis is never covered: "if typing.TYPE_CHECKING:", "@typing.overload", "@overload", # Protocols "@runtime_checkable", "@typing.runtime_checkable", "class \\w*\\(Protocol\\):", "class \\w*\\(typing.Protocol\\):", # Fallback code with no installed deps is almost impossible to cover properly "except ImportError:", # Don't complain if non-runnable code isn't run: "if __name__ == .__main__.:", # OS Specific "if platform.system()", "if IS_WINDOWS", "if os.name", "if sys.platform", # Manual test code "class \\w*test:", "def \\wtest():" ] [tool.mypy] show_error_context = true show_column_numbers = true show_error_codes = true pretty = true [[tool.mypy.overrides]] module = [ "gi.*", "trio.*", "serial.*" ] ignore_missing_imports = true [tool.ruff] line-length = 120 output-format = "full" target-version = "py37" extend-exclude = ["tests"] [tool.ruff.lint] extend-select = [ "E", "W", # also pycodestyle warnings "PYI", # flake8-pyi "ASYNC", # flake8-async "FA", # from __future__ import annotations "SLOT", # flake8-slots "TCH", # flake8-type-checking "S", # flake8-bandit "A", # flake8-builtins "B", "T10", "EXE", # flake8-bugbear, flake8-debugger, flake8-executable "ISC", # flake8-implicit-str-concat "RET", "SIM", "C4", # flake8-return, flake8-simplify, flake8-comprehensions "ICN", "PGH", # flake8-import-conventions, pygrep-hooks "Q", # quotes "FLY", # Flynt "TRY", "UP", "I", "PL", "PERF", "RUF", # tryceratops, pyupgrade, isort, pylint + perflint, Ruff-specific ] extend-ignore = [ "PLR6301", "E203", # refactor rules (too many statements/arguments/branches) "PLR0904", "PLR0911", "PLR0912", "PLR0913", "PLR0914", "PLR0915", "PLR0917", "PLR2004", "PLC0415", # We have a lot of imports outside of top-level "RET504", # Unnecessary variable assignment before return statement "SIM108", # Use ternary operator, "TRY003", #long messages prepare outside, ... ] [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401", "SIM105"] [tool.ruff.lint.isort] known-third-party = [] [tool.ruff.lint.pydocstyle] convention = "pep257" [tool.ruff.lint.pylint] allow-dunder-method-names = [ "__rich_repr__", # class generation "_contents__getitem__", "_contents__setitem__", "_contents__delitem__", ] [tool.pylint] extension-pkg-whitelist = [] ignore = ["CVS", "_version.py"] jobs = 0 py-version = "3.7" load-plugins = [ "pylint.extensions.overlapping_exceptions", "pylint.extensions.check_elif", "pylint.extensions.for_any_all", "pylint.extensions.code_style", "pylint.extensions.redefined_variable_type", "pylint.extensions.dict_init_mutate", "pylint.extensions.typing", "pylint.extensions.dunder", ] enable = "all" disable = [ "locally-disabled", "file-ignored", "suppressed-message", "similarities", "too-many-ancestors", "too-few-public-methods", "too-many-public-methods", "too-many-return-statements", "too-many-branches", "too-many-arguments", "too-many-locals", "too-many-statements", "too-many-instance-attributes", "too-many-lines", "too-many-nested-blocks", "too-many-positional-arguments", "broad-except", "broad-exception-raised", "logging-fstring-interpolation", "logging-format-interpolation", "fixme", "invalid-name", "import-outside-toplevel", "cyclic-import", "missing-docstring", "use-implicit-booleaness-not-comparison-to-zero", "unused-argument", "unused-private-member", "arguments-differ", "consider-using-assignment-expr", "superfluous-parens", # Magic part "invalid-overridden-method", "attribute-defined-outside-init", # old bad practice, fixed step-by-step "redefined-variable-type", # makes not happy typechecking, but super common # too complex union "use-implicit-booleaness-not-comparison-to-string", # iterable modification during iteration "consider-using-enumerate", ] max-line-length = 120 reports = false [tool.pylint.basic] good-names = [ "i", "j", "k", "ex", "Run", "_", "bar", # we draw bars ] [tool.pylint.classes] exclude-protected = [ "_asdict", "_fields", "_replace", "_source", "_make", "os._exit", # API "_original_widget", "_invalidate", "_repr_attrs", "_repr_words", "_w", ] [tool.pylint.format] ignore-long-lines = "^(\\s*(# )??)|(.+# type: ignore\\[[\\w\\-\\,]+\\])$" [tool.pylint.dunder] good-dunder-names = [ "__rich_repr__", # class generation "_contents__getitem__", "_contents__setitem__", "_contents__delitem__", ] [tool.refurb] python_version = "3.7" ignore = ["FURB104", "FURB144", "FURB146"] [[tool.refurb.amend]] path = "urwid/__init__.py" ignore = ["FURB107"] [[tool.refurb.amend]] path = "urwid/display/__init__.py" ignore = ["FURB107"] [[tool.refurb.amend]] path = "urwid/event_loop/__init__.py" ignore = ["FURB107"] urwid-2.6.16/requirements.txt000066400000000000000000000000311470350774000162250ustar00rootroot00000000000000typing-extensions wcwidthurwid-2.6.16/ruff-requirements.txt000066400000000000000000000000141470350774000171660ustar00rootroot00000000000000ruff==0.6.9 urwid-2.6.16/setup.py000066400000000000000000000021671470350774000144670ustar00rootroot00000000000000# Urwid setup.py exports the useful bits # Copyright (C) 2004-2014 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations from setuptools import setup setup( name="urwid", url="https://urwid.org/", python_requires=">3.7", setup_requires=[ "setuptools >= 61.0.0", "setuptools_scm[toml]>=7.0", "wheel", ], ) urwid-2.6.16/test_requirements.txt000066400000000000000000000002151470350774000172700ustar00rootroot00000000000000tornado>=5 twisted trio zmq exceptiongroup;python_version<'3.11' windows-curses;sys_platform=="win32" pyserial # for test run coverage[toml] urwid-2.6.16/tests/000077500000000000000000000000001470350774000141115ustar00rootroot00000000000000urwid-2.6.16/tests/__init__.py000066400000000000000000000000001470350774000162100ustar00rootroot00000000000000urwid-2.6.16/tests/test_canvas.py000066400000000000000000000513621470350774000170040ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid from urwid import canvas from urwid.util import get_encoding class CanvasCacheTest(unittest.TestCase): def setUp(self): # purge the cache urwid.CanvasCache._widgets.clear() def cct(self, widget, size, focus, expected): with self.subTest(widget=widget, size=size, focus=focus, expected=expected): got = urwid.CanvasCache.fetch(widget, urwid.Widget, size, focus) self.assertEqual(expected, got, f"got: {got} expected: {expected}") def test1(self): a = urwid.Text("") b = urwid.Text("") blah = urwid.TextCanvas() blah.finalize(a, (10, 1), False) blah2 = urwid.TextCanvas() blah2.finalize(a, (15, 1), False) bloo = urwid.TextCanvas() bloo.finalize(b, (20, 2), True) urwid.CanvasCache.store(urwid.Widget, blah) urwid.CanvasCache.store(urwid.Widget, blah2) urwid.CanvasCache.store(urwid.Widget, bloo) self.cct(a, (10, 1), False, blah) self.cct(a, (15, 1), False, blah2) self.cct(a, (15, 1), True, None) self.cct(a, (10, 2), False, None) self.cct(b, (20, 2), True, bloo) self.cct(b, (21, 2), True, None) urwid.CanvasCache.invalidate(a) self.cct(a, (10, 1), False, None) self.cct(a, (15, 1), False, None) self.cct(b, (20, 2), True, bloo) class CanvasTest(unittest.TestCase): def test_basic_info(self): """Test str and repr methods for debugging purposes.""" string = "Hello World!" rendered = urwid.Text(string).render(()) self.assertEqual(string, str(rendered)) self.assertEqual( f"", repr(rendered), ) def test_composite_basic_info(self): """Composite canvas contain info about canvas inside. Use canvas caching feature for test. """ string = "Hello World!" widget = urwid.Text(string) rendered_widget = widget.render(()) disabled = urwid.WidgetDisable(widget) rendered = disabled.render(()) self.assertEqual( f"", repr(rendered), ) def ct(self, text, attr, exp_content): with self.subTest(text=text, attr=attr, exp_content=exp_content): c = urwid.TextCanvas([t.encode("iso8859-1") for t in text], attr) content = list(c.content()) self.assertEqual(content, exp_content, f"got: {content!r} expected: {exp_content!r}") def ct2(self, text, attr, left, top, cols, rows, def_attr, exp_content): c = urwid.TextCanvas([t.encode("iso8859-1") for t in text], attr) content = list(c.content(left, top, cols, rows, def_attr)) self.assertEqual(content, exp_content, f"got: {content!r} expected: {exp_content!r}") def test1(self): self.ct(["Hello world"], None, [[(None, None, b"Hello world")]]) self.ct(["Hello world"], [[("a", 5)]], [[("a", None, b"Hello"), (None, None, b" world")]]) self.ct(["Hi", "There"], None, [[(None, None, b"Hi ")], [(None, None, b"There")]]) def test2(self): self.ct2( ["Hello"], None, 0, 0, 5, 1, None, [[(None, None, b"Hello")]], ) self.ct2( ["Hello"], None, 1, 0, 4, 1, None, [[(None, None, b"ello")]], ) self.ct2( ["Hello"], None, 0, 0, 4, 1, None, [[(None, None, b"Hell")]], ) self.ct2( ["Hi", "There"], None, 1, 0, 3, 2, None, [[(None, None, b"i ")], [(None, None, b"her")]], ) self.ct2( ["Hi", "There"], None, 0, 0, 5, 1, None, [[(None, None, b"Hi ")]], ) self.ct2( ["Hi", "There"], None, 0, 1, 5, 1, None, [[(None, None, b"There")]], ) class ShardBodyTest(unittest.TestCase): def sbt(self, shards, shard_tail, expected): result = canvas.shard_body(shards, shard_tail, False) assert result == expected, f"got: {result!r} expected: {expected!r}" def sbttail(self, num_rows, sbody, expected): result = canvas.shard_body_tail(num_rows, sbody) assert result == expected, f"got: {result!r} expected: {expected!r}" def sbtrow(self, sbody, expected): result = list(canvas.shard_body_row(sbody)) assert result == expected, f"got: {result!r} expected: {expected!r}" def test1(self): cviews = [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 5, None, "bar")] self.sbt( cviews, [], [(0, None, (0, 0, 10, 5, None, "foo")), (0, None, (0, 0, 5, 5, None, "bar"))], ) self.sbt( cviews, [(0, 3, None, (0, 0, 5, 8, None, "baz"))], [ (3, None, (0, 0, 5, 8, None, "baz")), (0, None, (0, 0, 10, 5, None, "foo")), (0, None, (0, 0, 5, 5, None, "bar")), ], ) self.sbt( cviews, [(10, 3, None, (0, 0, 5, 8, None, "baz"))], [ (0, None, (0, 0, 10, 5, None, "foo")), (3, None, (0, 0, 5, 8, None, "baz")), (0, None, (0, 0, 5, 5, None, "bar")), ], ) self.sbt( cviews, [(15, 3, None, (0, 0, 5, 8, None, "baz"))], [ (0, None, (0, 0, 10, 5, None, "foo")), (0, None, (0, 0, 5, 5, None, "bar")), (3, None, (0, 0, 5, 8, None, "baz")), ], ) def test2(self): sbody = [ (0, None, (0, 0, 10, 5, None, "foo")), (0, None, (0, 0, 5, 5, None, "bar")), (3, None, (0, 0, 5, 8, None, "baz")), ] self.sbttail(5, sbody, []) self.sbttail( 3, sbody, [ (0, 3, None, (0, 0, 10, 5, None, "foo")), (0, 3, None, (0, 0, 5, 5, None, "bar")), (0, 6, None, (0, 0, 5, 8, None, "baz")), ], ) sbody = [ (0, None, (0, 0, 10, 3, None, "foo")), (0, None, (0, 0, 5, 5, None, "bar")), (3, None, (0, 0, 5, 9, None, "baz")), ] self.sbttail( 3, sbody, [(10, 3, None, (0, 0, 5, 5, None, "bar")), (0, 6, None, (0, 0, 5, 9, None, "baz"))], ) def test3(self): self.sbtrow( [ (0, None, (0, 0, 10, 5, None, "foo")), (0, None, (0, 0, 5, 5, None, "bar")), (3, None, (0, 0, 5, 8, None, "baz")), ], [20], ) self.sbtrow( [ (0, iter("foo"), (0, 0, 10, 5, None, "foo")), (0, iter("bar"), (0, 0, 5, 5, None, "bar")), (3, iter("zzz"), (0, 0, 5, 8, None, "baz")), ], ["f", "b", "z"], ) class ShardsTrimTest(unittest.TestCase): def sttop(self, shards, top, expected): result = canvas.shards_trim_top(shards, top) assert result == expected, f"got: {result!r} expected: {expected!r}" def strows(self, shards, rows, expected): result = canvas.shards_trim_rows(shards, rows) assert result == expected, f"got: {result!r} expected: {expected!r}" def stsides(self, shards, left, cols, expected): result = canvas.shards_trim_sides(shards, left, cols) assert result == expected, f"got: {result!r} expected: {expected!r}" def test1(self): shards = [(5, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 5, None, "bar")])] self.sttop(shards, 2, [(3, [(0, 2, 10, 3, None, "foo"), (0, 2, 5, 3, None, "bar")])]) self.strows(shards, 2, [(2, [(0, 0, 10, 2, None, "foo"), (0, 0, 5, 2, None, "bar")])]) shards = [(5, [(0, 0, 10, 5, None, "foo")]), (3, [(0, 0, 10, 3, None, "bar")])] self.sttop(shards, 2, [(3, [(0, 2, 10, 3, None, "foo")]), (3, [(0, 0, 10, 3, None, "bar")])]) self.sttop(shards, 5, [(3, [(0, 0, 10, 3, None, "bar")])]) self.sttop(shards, 7, [(1, [(0, 2, 10, 1, None, "bar")])]) self.strows(shards, 7, [(5, [(0, 0, 10, 5, None, "foo")]), (2, [(0, 0, 10, 2, None, "bar")])]) self.strows(shards, 5, [(5, [(0, 0, 10, 5, None, "foo")])]) self.strows(shards, 4, [(4, [(0, 0, 10, 4, None, "foo")])]) shards = [(5, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 8, None, "baz")]), (3, [(0, 0, 10, 3, None, "bar")])] self.sttop( shards, 2, [ (3, [(0, 2, 10, 3, None, "foo"), (0, 2, 5, 6, None, "baz")]), (3, [(0, 0, 10, 3, None, "bar")]), ], ) self.sttop(shards, 5, [(3, [(0, 0, 10, 3, None, "bar"), (0, 5, 5, 3, None, "baz")])]) self.sttop(shards, 7, [(1, [(0, 2, 10, 1, None, "bar"), (0, 7, 5, 1, None, "baz")])]) self.strows( shards, 7, [ (5, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 7, None, "baz")]), (2, [(0, 0, 10, 2, None, "bar")]), ], ) self.strows(shards, 5, [(5, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 5, None, "baz")])]) self.strows(shards, 4, [(4, [(0, 0, 10, 4, None, "foo"), (0, 0, 5, 4, None, "baz")])]) def test2(self): shards = [(5, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 5, None, "bar")])] self.stsides(shards, 0, 15, [(5, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 5, None, "bar")])]) self.stsides(shards, 6, 9, [(5, [(6, 0, 4, 5, None, "foo"), (0, 0, 5, 5, None, "bar")])]) self.stsides(shards, 6, 6, [(5, [(6, 0, 4, 5, None, "foo"), (0, 0, 2, 5, None, "bar")])]) self.stsides(shards, 0, 10, [(5, [(0, 0, 10, 5, None, "foo")])]) self.stsides(shards, 10, 5, [(5, [(0, 0, 5, 5, None, "bar")])]) self.stsides(shards, 1, 7, [(5, [(1, 0, 7, 5, None, "foo")])]) shards = [(5, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 8, None, "baz")]), (3, [(0, 0, 10, 3, None, "bar")])] self.stsides( shards, 0, 15, [(5, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 8, None, "baz")]), (3, [(0, 0, 10, 3, None, "bar")])], ) self.stsides( shards, 2, 13, [(5, [(2, 0, 8, 5, None, "foo"), (0, 0, 5, 8, None, "baz")]), (3, [(2, 0, 8, 3, None, "bar")])], ) self.stsides( shards, 2, 10, [(5, [(2, 0, 8, 5, None, "foo"), (0, 0, 2, 8, None, "baz")]), (3, [(2, 0, 8, 3, None, "bar")])], ) self.stsides( shards, 2, 8, [(5, [(2, 0, 8, 5, None, "foo")]), (3, [(2, 0, 8, 3, None, "bar")])], ) self.stsides( shards, 2, 6, [(5, [(2, 0, 6, 5, None, "foo")]), (3, [(2, 0, 6, 3, None, "bar")])], ) self.stsides(shards, 10, 5, [(8, [(0, 0, 5, 8, None, "baz")])]) self.stsides(shards, 11, 3, [(8, [(1, 0, 3, 8, None, "baz")])]) class ShardsJoinTest(unittest.TestCase): def sjt(self, shard_lists, expected): result = canvas.shards_join(shard_lists) assert result == expected, f"got: {result!r} expected: {expected!r}" def test(self): shards1 = [(5, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 8, None, "baz")]), (3, [(0, 0, 10, 3, None, "bar")])] shards2 = [(3, [(0, 0, 10, 3, None, "aaa")]), (5, [(0, 0, 10, 5, None, "bbb")])] shards3 = [ (3, [(0, 0, 10, 3, None, "111")]), (2, [(0, 0, 10, 3, None, "222")]), (3, [(0, 0, 10, 3, None, "333")]), ] self.sjt([shards1], shards1) self.sjt( [shards1, shards2], [ (3, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 8, None, "baz"), (0, 0, 10, 3, None, "aaa")]), (2, [(0, 0, 10, 5, None, "bbb")]), (3, [(0, 0, 10, 3, None, "bar")]), ], ) self.sjt( [shards1, shards3], [ (3, [(0, 0, 10, 5, None, "foo"), (0, 0, 5, 8, None, "baz"), (0, 0, 10, 3, None, "111")]), (2, [(0, 0, 10, 3, None, "222")]), (3, [(0, 0, 10, 3, None, "bar"), (0, 0, 10, 3, None, "333")]), ], ) self.sjt( [shards1, shards2, shards3], [ ( 3, [ (0, 0, 10, 5, None, "foo"), (0, 0, 5, 8, None, "baz"), (0, 0, 10, 3, None, "aaa"), (0, 0, 10, 3, None, "111"), ], ), (2, [(0, 0, 10, 5, None, "bbb"), (0, 0, 10, 3, None, "222")]), (3, [(0, 0, 10, 3, None, "bar"), (0, 0, 10, 3, None, "333")]), ], ) class CanvasJoinTest(unittest.TestCase): def cjtest(self, desc, l, expected): l = [(c, None, False, n) for c, n in l] result = list(urwid.CanvasJoin(l).content()) assert result == expected, f"{desc} expected {expected!r}, got {result!r}" def test(self): C = urwid.TextCanvas hello = C([b"hello"]) there = C([b"there"], [[("a", 5)]]) a = C([b"a"]) hi = C([b"hi"]) how = C([b"how"], [[("a", 1)]]) dy = C([b"dy"]) how_you = C([b"how", b"you"]) self.cjtest("one", [(hello, 5)], [[(None, None, b"hello")]]) self.cjtest( "two", [(hello, 5), (there, 5)], [[(None, None, b"hello"), ("a", None, b"there")]], ) self.cjtest( "two space", [(hello, 7), (there, 5)], [[(None, None, b"hello"), (None, None, b" "), ("a", None, b"there")]], ) self.cjtest( "three space", [(hi, 4), (how, 3), (dy, 2)], [ [(None, None, b"hi"), (None, None, b" "), ("a", None, b"h"), (None, None, b"ow"), (None, None, b"dy")], ], ) self.cjtest( "four space", [(a, 2), (hi, 3), (dy, 3), (a, 1)], [ [ (None, None, b"a"), (None, None, b" "), (None, None, b"hi"), (None, None, b" "), (None, None, b"dy"), (None, None, b" "), (None, None, b"a"), ] ], ) self.cjtest( "pile 2", [(how_you, 4), (hi, 2)], [ [(None, None, b"how"), (None, None, b" "), (None, None, b"hi")], [(None, None, b"you"), (None, None, b" "), (None, None, b" ")], ], ) self.cjtest( "pile 2r", [(hi, 4), (how_you, 3)], [ [(None, None, b"hi"), (None, None, b" "), (None, None, b"how")], [(None, None, b" "), (None, None, b"you")], ], ) class CanvasOverlayTest(unittest.TestCase): def setUp(self) -> None: self.old_encoding = get_encoding() def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def cotest(self, desc, bgt, bga, fgt, fga, l, r, et): with self.subTest(desc): bgt = bgt.encode("iso8859-1") fgt = fgt.encode("iso8859-1") bg = urwid.CompositeCanvas(urwid.TextCanvas([bgt], [bga])) fg = urwid.CompositeCanvas(urwid.TextCanvas([fgt], [fga])) bg.overlay(fg, l, 0) result = list(bg.content()) assert result == et, f"{desc} expected {et!r}, got {result!r}" def test1(self): self.cotest( "left", "qxqxqxqx", [], "HI", [], 0, 6, [[(None, None, b"HI"), (None, None, b"qxqxqx")]], ) self.cotest( "right", "qxqxqxqx", [], "HI", [], 6, 0, [[(None, None, b"qxqxqx"), (None, None, b"HI")]], ) self.cotest( "center", "qxqxqxqx", [], "HI", [], 3, 3, [[(None, None, b"qxq"), (None, None, b"HI"), (None, None, b"xqx")]], ) self.cotest( "center2", "qxqxqxqx", [], "HI ", [], 2, 2, [[(None, None, b"qx"), (None, None, b"HI "), (None, None, b"qx")]], ) self.cotest( "full", "rz", [], "HI", [], 0, 0, [[(None, None, b"HI")]], ) def test2(self): self.cotest( "same", "asdfghjkl", [("a", 9)], "HI", [("a", 2)], 4, 3, [[("a", None, b"asdf"), ("a", None, b"HI"), ("a", None, b"jkl")]], ) self.cotest( "diff", "asdfghjkl", [("a", 9)], "HI", [("b", 2)], 4, 3, [[("a", None, b"asdf"), ("b", None, b"HI"), ("a", None, b"jkl")]], ) self.cotest( "None end", "asdfghjkl", [("a", 9)], "HI ", [("a", 2)], 2, 3, [[("a", None, b"as"), ("a", None, b"HI"), (None, None, b" "), ("a", None, b"jkl")]], ) self.cotest( "float end", "asdfghjkl", [("a", 3)], "HI", [("a", 2)], 4, 3, [[("a", None, b"asd"), (None, None, b"f"), ("a", None, b"HI"), (None, None, b"jkl")]], ) self.cotest( "cover 2", "asdfghjkl", [("a", 5), ("c", 4)], "HI", [("b", 2)], 4, 3, [[("a", None, b"asdf"), ("b", None, b"HI"), ("c", None, b"jkl")]], ) self.cotest( "cover 2-2", "asdfghjkl", [("a", 4), ("d", 1), ("e", 1), ("c", 3)], "HI", [("b", 2)], 4, 3, [[("a", None, b"asdf"), ("b", None, b"HI"), ("c", None, b"jkl")]], ) def test3(self): urwid.set_encoding("euc-jp") self.cotest( "db0", "\xA1\xA1\xA1\xA1\xA1\xA1", [], "HI", [], 2, 2, [[(None, None, b"\xA1\xA1"), (None, None, b"HI"), (None, None, b"\xA1\xA1")]], ) self.cotest( "db1", "\xA1\xA1\xA1\xA1\xA1\xA1", [], "OHI", [], 1, 2, [[(None, None, b" "), (None, None, b"OHI"), (None, None, b"\xA1\xA1")]], ) self.cotest( "db2", "\xA1\xA1\xA1\xA1\xA1\xA1", [], "OHI", [], 2, 1, [[(None, None, b"\xA1\xA1"), (None, None, b"OHI"), (None, None, b" ")]], ) self.cotest( "db3", "\xA1\xA1\xA1\xA1\xA1\xA1", [], "OHIO", [], 1, 1, [[(None, None, b" "), (None, None, b"OHIO"), (None, None, b" ")]], ) class CanvasPadTrimTest(unittest.TestCase): def cptest(self, desc, ct, ca, l, r, et): with self.subTest(desc): ct = ct.encode("iso8859-1") c = urwid.CompositeCanvas(urwid.TextCanvas([ct], [ca])) c.pad_trim_left_right(l, r) result = list(c.content()) self.assertEqual(result, et, f"{desc} expected {et!r}, got {result!r}") def test1(self): self.cptest("none", "asdf", [], 0, 0, [[(None, None, b"asdf")]]) self.cptest("left pad", "asdf", [], 2, 0, [[(None, None, b" "), (None, None, b"asdf")]]) self.cptest("right pad", "asdf", [], 0, 2, [[(None, None, b"asdf"), (None, None, b" ")]]) def test2(self): self.cptest("left trim", "asdf", [], -2, 0, [[(None, None, b"df")]]) self.cptest("right trim", "asdf", [], 0, -2, [[(None, None, b"as")]]) urwid-2.6.16/tests/test_columns.py000066400000000000000000001060651470350774000172120ustar00rootroot00000000000000from __future__ import annotations import typing import unittest import urwid from tests.util import SelectableText if typing.TYPE_CHECKING: from collections.abc import Collection from typing_extensions import Literal class ColumnsTest(unittest.TestCase): def test_basic_sizing(self) -> None: box_only = urwid.SolidFill("#") flow_only = urwid.ProgressBar(None, None) fixed_only = urwid.BigText("0", urwid.Thin3x3Font()) flow_fixed = urwid.Text("text") with self.subTest("No sizing calculation possible"), self.assertWarns(urwid.widget.ColumnsWarning) as ctx: widget = urwid.Columns(((urwid.PACK, fixed_only), box_only), box_columns=(1,)) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW)), widget.sizing()) for description, kwargs in ( ("BOX-only widget", {"widget_list": (box_only,)}), ('BOX-only widget with "get height from max"', {"widget_list": (box_only,), "box_columns": (0,)}), ("No FLOW - BOX only", {"widget_list": (box_only, urwid.SolidFill(" ")), "box_columns": (0, 1)}), ("GIVEN BOX only -> BOX only", {"widget_list": ((5, box_only),), "box_columns": (0,)}), ): with self.subTest(description): widget = urwid.Columns(**kwargs) self.assertEqual(frozenset((urwid.BOX,)), widget.sizing()) cols, rows = 2, 2 self.assertEqual((cols, rows), widget.pack((cols, rows))) canvas = widget.render((cols, rows)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("FLOW-only"): widget = urwid.Columns((flow_only,)) self.assertEqual(frozenset((urwid.FLOW,)), widget.sizing()) cols, rows = 2, 1 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest('BOX/FLOW by "box_columns": can be rendered as box only as fallback'): widget = urwid.Columns((flow_only, box_only), box_columns=(1,)) self.assertEqual(frozenset((urwid.FLOW,)), widget.sizing()) cols, rows = 2, 1 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual((cols, rows), widget.pack((cols, rows))) canvas = widget.render((cols, rows)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("FLOW/FIXED"): widget = urwid.Columns((flow_fixed,)) self.assertEqual(frozenset((urwid.FLOW, urwid.FIXED)), widget.sizing()) cols, rows = 4, 1 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) cols, rows = 2, 2 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("FIXED only"): widget = urwid.Columns(((urwid.PACK, fixed_only),)) self.assertEqual(frozenset((urwid.FLOW, urwid.FIXED)), widget.sizing()) cols, rows = 3, 3 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("FIXED only use FLOW methods - drop & replace by solid"): widget = urwid.Columns(((urwid.PACK, fixed_only),)) self.assertEqual(frozenset((urwid.FLOW, urwid.FIXED)), widget.sizing()) cols, rows = 2, 1 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual(" ", str(canvas)) with self.subTest("FIXED only use FLOW methods - add empty space"): widget = urwid.Columns(((urwid.PACK, fixed_only),)) self.assertEqual(frozenset((urwid.FLOW, urwid.FIXED)), widget.sizing()) cols, rows = 5, 3 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( ( "┌─┠", "│ │ ", "└─┘ ", ), canvas.decoded_text, ) with self.subTest("GIVEN BOX + FLOW/FIXED, but other widgets do not support box"): widget = urwid.Columns((flow_fixed, (3, box_only)), box_columns=(1,)) self.assertEqual(frozenset((urwid.FLOW, urwid.FIXED)), widget.sizing()) cols, rows = 7, 1 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) cols, rows = 4, 4 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual([b"t###", b"e###", b"x###", b"t###"], canvas.text) cols, rows = 2, 2 self.assertEqual((cols, rows), widget.pack((cols, rows))) canvas = widget.render((cols, rows)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual([b"te", b"xt"], canvas.text) with self.subTest("Wrong sizing -> fallback to historic hardcoded"): with self.assertWarns(urwid.widget.ColumnsWarning) as ctx: widget = urwid.Columns(((urwid.PACK, box_only),)) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW)), widget.sizing()) self.assertEqual( f"Sizing combination of widget {box_only} (position=0) not supported: PACK box=False", str(ctx.warnings[0].message), ) with self.subTest("Wrong sizing -> fallback to historic hardcoded 2"): with self.assertWarns(urwid.widget.ColumnsWarning) as ctx: widget = urwid.Columns(((urwid.WEIGHT, 1, fixed_only),)) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW)), widget.sizing()) self.assertEqual( f"Sizing combination of widget {fixed_only} (position=0) not supported: WEIGHT box=False", str(ctx.warnings[0].message), ) with self.subTest( 'BOX not added to "box_columns" but widget handled as FLOW', ), self.assertWarns( urwid.widget.ColumnsWarning, ) as ctx: self.maxDiff = None contents = ( (urwid.WEIGHT, 1, urwid.SolidFill()), (urwid.FIXED, 10, urwid.Button("OK", align=urwid.CENTER)), (urwid.WEIGHT, 1, urwid.SolidFill()), (urwid.FIXED, 10, urwid.Button("Cancel", align=urwid.CENTER)), (urwid.WEIGHT, 1, urwid.SolidFill()), ) widget = urwid.Columns(contents) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW)), widget.sizing()) self.assertEqual( "Columns widget contents flags not allow to determine supported render kind:\n" "BOX WEIGHT, FIXED|FLOW GIVEN\n" "Using fallback hardcoded BOX|FLOW sizing kind.", str(ctx.warnings[0].message), ) cols, rows = 30, 1 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(rows, canvas.rows()) self.assertEqual(cols, canvas.cols()) self.assertEqual([b" < OK > < Cancel > "], canvas.text) self.assertEqual( "Widgets in columns [0, 2, 4] " f"({[elem[-1] for elem in contents[:6:2]]}) " f'are BOX widgets not marked "box_columns" ' f"while FLOW render is requested (size=(30,))", str(ctx.warnings[2].message), ) self.assertEqual("OK", widget.focus.label) self.assertIsNone(widget.keypress((cols,), "right")) self.assertEqual( "Widgets in columns [0, 2, 4] " f"({[elem[-1] for elem in contents[:6:2]]}) " f'are BOX widgets not marked "box_columns" ' f"while FLOW render is requested (size=(30,))", str(ctx.warnings[3].message), ) self.assertEqual("Cancel", widget.focus.label) def test_pack_render_fixed(self) -> None: """Cover weighted FIXED/FLOW widgets pack. One of the popular use-cases: rows of buttons/radiobuttons/checkboxes in pop-up windows. """ widget = urwid.Columns( (urwid.Button(label, align=urwid.CENTER) for label in ("OK", "Cancel", "Help")), dividechars=1, ) self.assertEqual((32, 1), widget.pack(())) self.assertEqual([b"< OK > < Cancel > < Help >"], widget.render(()).text) def test_render_fixed_consistency(self) -> None: """Widgets supporting FIXED should be rendered with PACK side the same way as not FIXED.""" widget = urwid.Columns(((urwid.PACK, urwid.Text("Prefix:")), (urwid.PACK, urwid.Button("Btn"))), dividechars=1) cols, _ = widget.pack(()) self.assertEqual(((7, 7), (1, 1), ((), ())), widget.get_column_sizes(())) self.assertEqual(((7, 7), (1, 1), ((7,), (7,))), widget.get_column_sizes((cols,))) self.assertEqual(("Prefix: < Btn >",), widget.render(()).decoded_text) self.assertEqual(("Prefix: < Btn >",), widget.render((cols,)).decoded_text) def test_render_pack_item_not_fit(self): items = urwid.Text("123"), urwid.Text("456") widget = urwid.Columns((urwid.PACK, item) for item in items) # Make width < widget fixed pack width = items[0].pack(())[0] - 1 height = items[0].rows((width,)) self.assertEqual((width, height), widget.pack((width,))) canvas = widget.render((width,)) # Rendered should be not empty self.assertEqual(items[0].render((width,)).decoded_text, canvas.decoded_text) self.assertEqual(width, canvas.cols()) def test_pack_render_broken_sizing(self) -> None: use_sizing = frozenset((urwid.BOX,)) class BrokenSizing(urwid.Text): def sizing(self) -> frozenset[urwid.Sizing]: return use_sizing with self.assertWarns(urwid.widget.ColumnsWarning) as ctx: element = BrokenSizing("Test") widget = urwid.Columns(((urwid.PACK, element),)) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW)), widget.sizing()) self.assertEqual( f"Sizing combination of widget {element} (position=0) not supported: PACK box=False", str(ctx.warnings[0].message), ) with self.assertWarns(urwid.widget.ColumnsWarning) as ctx: canvas = widget.render((10,)) self.assertEqual([b"Test "], canvas.text) self.assertEqual( f"Unusual widget {element} sizing for {urwid.PACK} (box=False). " f"Assuming wrong sizing and using FLOW for width calculation", str(ctx.warnings[0].message), ) def test_pack_render_skip_widget(self): widget = urwid.Columns(((0, urwid.Text("Ignore\nme")), (urwid.Text("Count")))) cols, rows = 5, 1 self.assertEqual((cols, rows), widget.pack(())) self.assertEqual((cols, rows), widget.pack((cols,))) self.assertEqual("Count", str(widget.render(()))) self.assertEqual("Count", str(widget.render((cols,)))) def test_pack_flow_with_fixed_item(self): widget = urwid.Columns( ( (urwid.PACK, urwid.BigText("3", urwid.Thin3x3Font())), (urwid.Text("Multi\nline\ntext\n???")), ), dividechars=1, ) with self.subTest("Skip"): self.assertEqual(3, widget.rows((3,))) self.assertEqual((3, 3), widget.pack((3,))) self.assertEqual( ( "┌─â”", " ─┤", "└─┘", ), widget.render((3,)).decoded_text, ) with self.subTest("Fit all"): self.assertEqual(4, widget.rows((9,))) self.assertEqual((9, 4), widget.pack((9,))) self.assertEqual( ( "┌─┠Multi", " ─┤ line ", "└─┘ text ", " ??? ", ), widget.render((9,)).decoded_text, ) with self.subTest("Use SolidCanvas instead"): self.assertEqual(1, widget.rows((2,))) self.assertEqual((2, 1), widget.pack((2,))) self.assertEqual((" ",), widget.render((2,)).decoded_text) def test_pack_render_empty_widget(self): widget = urwid.Columns(()) self.assertEqual(frozenset((urwid.FLOW, urwid.BOX)), widget.sizing()) cols, rows = 10, 1 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(rows, canvas.rows()) self.assertEqual((" ",), canvas.decoded_text) def test_not_a_widget(self): class NotAWidget: __slots__ = ("name", "symbol") def __init__(self, name: str, symbol: bytes) -> None: self.name = name self.symbol = symbol def __repr__(self) -> str: return f"{self.__class__.__name__}(name={self.name!r}, symbol={self.symbol!r})" def selectable(self) -> bool: return False def rows(self, max_col_row: tuple[int], focus: bool = False) -> int: return 1 def render(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> urwid.Canvas: maxcol = max_col_row[0] line = self.symbol * maxcol if len(max_col_row) == 1: return urwid.TextCanvas((line,), maxcol=maxcol) return urwid.TextCanvas((line,) * max_col_row[1], maxcol=maxcol) with self.subTest("Box"), self.assertWarns(urwid.widget.ColumnsWarning) as ctx: items = (NotAWidget("First", b"*"), NotAWidget("Second", b"^")) widget = urwid.Columns(items) self.assertEqual(("**^^", "**^^"), widget.render((4, 2)).decoded_text) self.assertEqual(f"{items[0]!r} is not a Widget", str(ctx.warnings[0].message)) self.assertEqual(f"{items[1]!r} is not a Widget", str(ctx.warnings[1].message)) with self.subTest("Flow"), self.assertWarns(urwid.widget.ColumnsWarning) as ctx: items = (NotAWidget("First", b"*"), NotAWidget("Second", b"^")) widget = urwid.Columns(items) self.assertEqual(("***^^^",), widget.render((6,)).decoded_text) self.assertEqual(f"{items[0]!r} is not a Widget", str(ctx.warnings[0].message)) self.assertEqual(f"{items[1]!r} is not a Widget", str(ctx.warnings[1].message)) def test_zero_width_column(self): elem_1 = urwid.BoxAdapter(urwid.SolidFill("#"), 2) elem_2 = urwid.BoxAdapter(urwid.SolidFill("*"), 4) widget = urwid.Columns((elem_1, (0, elem_2))) self.assertEqual((3, 2), widget.pack((3,))) canvas = widget.render((3,)) self.assertEqual(2, canvas.rows()) self.assertEqual(3, canvas.cols()) self.assertEqual([b"###", b"###"], canvas.text) def test_focus_column(self): button_1 = urwid.Button("Selectable") button_2 = urwid.Button("Other selectable") widget_list = [ (urwid.PACK, urwid.Text("Non selectable")), (urwid.PACK, button_1), (urwid.PACK, button_2), (urwid.PACK, urwid.Text("The end")), ] with self.subTest("Not provided -> select first selectable"): widget = urwid.Columns(widget_list) self.assertEqual(1, widget.focus_position) widget.keypress((), "left") self.assertEqual(1, widget.focus_position) with self.subTest("Exact index"): widget = urwid.Columns(widget_list, focus_column=2) self.assertEqual(2, widget.focus_position) widget.keypress((), "left") self.assertEqual(1, widget.focus_position) with self.subTest("Exact widget"): widget = urwid.Columns(widget_list, focus_column=button_2) self.assertEqual(2, widget.focus_position) self.assertEqual(button_2, widget.focus) widget.keypress((), "left") self.assertEqual(1, widget.focus_position) def test_pack_not_enough_info(self): """Test special case for not official fixed pack and render support.""" widget = urwid.Columns( ( (urwid.WEIGHT, 16, urwid.SolidFill(urwid.SolidFill.Symbols.LITE_SHADE)), (10, urwid.Button("First")), urwid.SolidFill(urwid.SolidFill.Symbols.LITE_SHADE), (10, urwid.Button("Second")), (urwid.WEIGHT, 16, urwid.SolidFill(urwid.SolidFill.Symbols.LITE_SHADE)), ), box_columns=(0, 2, 4), ) cols, rows = 53, 1 self.assertEqual(frozenset((urwid.FLOW,)), widget.sizing()) self.assertEqual((cols, rows), widget.pack(())) self.assertEqual( ("â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘< First >â–‘< Second >â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘",), widget.render(()).decoded_text, ) def test_no_height(self): widget = urwid.Columns( ( (urwid.WEIGHT, 16, urwid.SolidFill(urwid.SolidFill.Symbols.LITE_SHADE)), urwid.SolidFill(urwid.SolidFill.Symbols.LITE_SHADE), (urwid.WEIGHT, 16, urwid.SolidFill(urwid.SolidFill.Symbols.LITE_SHADE)), ), box_columns=(0, 1, 2), ) self.assertEqual(frozenset((urwid.BOX,)), widget.sizing()) with self.assertRaises(urwid.widget.ColumnsError): widget.pack(()) def assert_column_widths( self, expected: Collection[int], widget: urwid.Columns, size: tuple[int, int] | tuple[int] | tuple[()], description: str, ) -> None: column_widths, _, _ = widget.get_column_sizes(size) self.assertEqual(expected, column_widths, f"{description} expected {expected}, got {column_widths}") def test_widths(self): x = urwid.Text("") # sample "column" self.assert_column_widths((20,), urwid.Columns([x]), (20,), "simple 1") self.assert_column_widths((10, 10), urwid.Columns([x, x]), (20,), "simple 2") self.assert_column_widths((10, 9), urwid.Columns([x, x], 1), (20,), "simple 2+1") self.assert_column_widths((6, 6, 6), urwid.Columns([x, x, x], 1), (20,), "simple 3+1") self.assert_column_widths((5, 6, 5), urwid.Columns([x, x, x], 2), (20,), "simple 3+2") self.assert_column_widths((6, 6, 5), urwid.Columns([x, x, x], 2), (21,), "simple 3+2") simple_4 = urwid.Columns([x, x, x, x], 1) for expected, cols, description in ( ((6, 5, 6, 5), 25, "simple 4+1"), ((1, 1, 1, 1), 7, "squish 4+1"), ((1, 2, 1), 6, "squish 4+1"), ((2, 1), 4, "squish 4+1"), ): with self.subTest(description): self.assert_column_widths(expected, simple_4, (cols,), description) fixed = urwid.Columns([("fixed", 4, x), ("fixed", 6, x), ("fixed", 2, x)], 1) for expected, size, description in ( ((4, 6, 2), (), "FIXED"), ((4, 6, 2), (25,), "fixed 3"), ((4, 6), (13,), "fixed 3 cut"), ((4,), (10,), "fixed 3 cut2"), ): with self.subTest(description): self.assert_column_widths(expected, fixed, size, description) mixed = urwid.Columns([("weight", 2, x), ("fixed", 5, x), x, ("weight", 3, x)], 1) for expected, cols, description in ( ((2, 5, 1, 3), 14, "mixed 4"), ((1, 5, 1, 2), 12, "mixed 4 a"), ((2, 5, 1), 10, "mixed 4 b"), ((4, 5, 2, 6), 20, "mixed 4 c"), ): with self.subTest(description): self.assert_column_widths(expected, mixed, (cols,), description) def test_widths_focus_end(self): x = urwid.Text("") # sample "column" self.assert_column_widths( (10, 10), urwid.Columns([x, x], focus_column=1), (20,), "end simple 2", ) self.assert_column_widths( (10, 9), urwid.Columns([x, x], 1, 1), (20,), "end simple 2+1", ) self.assert_column_widths( (6, 6, 6), urwid.Columns([x, x, x], 1, 2), (20,), "end simple 3+1", ) self.assert_column_widths( (5, 6, 5), urwid.Columns([x, x, x], 2, 2), (20,), "end simple 3+2", ) self.assert_column_widths( (6, 6, 5), urwid.Columns([x, x, x], 2, 2), (21,), "end simple 3+2", ) simple_4 = urwid.Columns([x, x, x, x], 1, 3) for expected, cols, description in ( ((6, 5, 6, 5), 25, "end simple 4+1"), ((1, 1, 1, 1), 7, "end squish 4+1"), ((0, 1, 2, 1), 6, "end squish 4+1"), ((0, 0, 2, 1), 4, "end squish 4+1"), ): with self.subTest(description): self.assert_column_widths(expected, simple_4, (cols,), description) fixed = urwid.Columns([("fixed", 4, x), ("fixed", 6, x), ("fixed", 2, x)], 1, 2) for expected, size, description in ( ((4, 6, 2), (), "FIXED"), ((4, 6, 2), (25,), "end fixed 3"), ((0, 6, 2), (13,), "end fixed 3 cut"), ((0, 0, 2), (8,), "end fixed 3 cut2"), ): with self.subTest(description): self.assert_column_widths(expected, fixed, size, description) mixed = urwid.Columns([("weight", 2, x), ("fixed", 5, x), x, ("weight", 3, x)], 1, 3) for expected, cols, description in ( ((2, 5, 1, 3), 14, "end mixed 4"), ((1, 5, 1, 2), 12, "end mixed 4 a"), ((0, 5, 1, 2), 10, "end mixed 4 b"), ((4, 5, 2, 6), 20, "end mixed 4 c"), ): with self.subTest(description): self.assert_column_widths(expected, mixed, (cols,), description) def check_move_cursor( self, expected: bool, widget: urwid.Columns, size: tuple[int, int] | tuple[int] | tuple[()], col: int | Literal["left", "right"], row: int, focus_position: int, pref_col: int | Literal["left", "right"] | None, description: str, ) -> None: moved = widget.move_cursor_to_coords(size, col, row) self.assertEqual(expected, moved, f"{description} expected {expected!r}, got {moved!r}") self.assertEqual( focus_position, widget.focus_position, f"{description} expected focus_position {focus_position!r}, got {widget.focus_position!r}", ) w_pref_col = widget.get_pref_col(size) self.assertEqual( pref_col, w_pref_col, f"{description} expected pref_col {pref_col!r}, got {w_pref_col!r}", ) def test_move_cursor(self): e, s, x = urwid.Edit("", ""), SelectableText(""), urwid.Text("") self.check_move_cursor( False, urwid.Columns([x, x, x], 1), (20,), 9, 0, 0, None, "nothing selectable", ) mid = urwid.Columns([x, s, x], 1) self.check_move_cursor( True, mid, (20,), 9, 0, 1, 9, "dead on", ) self.check_move_cursor( True, mid, (20,), 6, 0, 1, 6, "l edge", ) self.check_move_cursor( True, mid, (20,), 13, 0, 1, 13, "r edge", ) self.check_move_cursor( True, mid, (20,), 2, 0, 1, 2, "l off", ) self.check_move_cursor( True, mid, (20,), 17, 0, 1, 17, "r off", ) self.check_move_cursor( True, urwid.Columns([x, x, s], 1), (20,), 2, 0, 2, 2, "l off 2", ) self.check_move_cursor( True, urwid.Columns([s, x, x], 1), (20,), 17, 0, 0, 17, "r off 2", ) self.check_move_cursor( True, urwid.Columns([s, s, x], 1), (20,), 6, 0, 0, 6, "l between", ) self.check_move_cursor( True, urwid.Columns([x, s, s], 1), (20,), 13, 0, 1, 13, "r between", ) l_2 = urwid.Columns([s, s, x], 2) self.check_move_cursor( True, l_2, (22,), 6, 0, 0, 6, "l between 2l", ) self.check_move_cursor( True, l_2, (22,), 7, 0, 1, 7, "l between 2r", ) r_2 = urwid.Columns([x, s, s], 2) self.check_move_cursor( True, r_2, (22,), 14, 0, 1, 14, "r between 2l", ) self.check_move_cursor( True, r_2, (22,), 15, 0, 2, 15, "r between 2r", ) # unfortunate pref_col shifting edge = urwid.Columns([x, e, x], 1) self.check_move_cursor( True, edge, (20,), 6, 0, 1, 7, "l e edge", ) self.check_move_cursor( True, edge, (20,), 13, 0, 1, 12, "r e edge", ) # 'left'/'right' special cases full = urwid.Columns([e, e, e]) self.check_move_cursor( True, full, (12,), "right", 0, 2, "right", "l e edge", ) self.check_move_cursor( True, full, (12,), "left", 0, 0, "left", "r e edge", ) def test_init_with_a_generator(self): urwid.Columns(urwid.Text(c) for c in "ABC") def test_old_attributes(self): c = urwid.Columns([urwid.Text("a"), urwid.SolidFill("x")], box_columns=[1]) with self.assertWarns(PendingDeprecationWarning): self.assertEqual(c.box_columns, [1]) with self.assertWarns(PendingDeprecationWarning): c.box_columns = [] self.assertEqual(c.box_columns, []) def test_box_column(self): c = urwid.Columns([urwid.Filler(urwid.Edit()), urwid.Text("")], box_columns=[0]) c.keypress((10,), "x") c.get_cursor_coords((10,)) c.move_cursor_to_coords((10,), 0, 0) c.mouse_event((10,), "foo", 1, 0, 0, True) c.get_pref_col((10,)) def test_length(self): columns = urwid.Columns(urwid.Text(c) for c in "ABC") self.assertEqual(3, len(columns)) self.assertEqual(3, len(columns.contents)) def test_common(self): t1 = urwid.Text("one") t2 = urwid.Text("two") t3 = urwid.Text("three") sf = urwid.SolidFill("x") c = urwid.Columns([]) with self.subTest("Focus"): self.assertEqual(c.focus, None) self.assertRaises(IndexError, lambda: getattr(c, "focus_position")) self.assertRaises(IndexError, lambda: setattr(c, "focus_position", None)) self.assertRaises(IndexError, lambda: setattr(c, "focus_position", 0)) with self.subTest("Contents change"): c.contents = [ (t1, ("pack", None, False)), (t2, ("weight", 1, False)), (sf, ("weight", 2, True)), (t3, ("given", 10, False)), ] c.focus_position = 1 del c.contents[0] self.assertEqual(c.focus_position, 0) c.contents[0:0] = [(t3, ("given", 10, False)), (t2, ("weight", 1, False))] c.contents.insert(3, (t1, ("pack", None, False))) self.assertEqual(c.focus_position, 2) with self.subTest("Contents change validation"): c.contents.clear() self.assertRaises(urwid.ColumnsError, lambda: c.contents.append(t1)) self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, None))) self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, "given"))) self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, ("given", None)))) # Incorrect kind self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, ("what", 1, False)))) # Incorrect box field self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, ("given", 1, None)))) # Incorrect size type self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, ("given", (), False)))) # Incorrect size self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, ("given", -1, False)))) # Float and int weight accepted c.contents.append((t1, ("weight", 1, False))) c.contents.append((t2, ("weight", 0.5, False))) self.assertEqual(("one two",), c.render(()).decoded_text) def test_focus_position(self): t1 = urwid.Text("one") t2 = urwid.Text("two") c = urwid.Columns([t1, t2]) self.assertEqual(c.focus, t1) self.assertEqual(c.focus_position, 0) c.focus_position = 1 self.assertEqual(c.focus, t2) self.assertEqual(c.focus_position, 1) c.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(c, "focus_position", -1)) self.assertRaises(IndexError, lambda: setattr(c, "focus_position", 2)) def test_deprecated(self): t1 = urwid.Text("one") t2 = urwid.Text("two") sf = urwid.SolidFill("x") # old methods: c = urwid.Columns([t1, ("weight", 3, t2), sf], box_columns=[2]) with self.subTest("Focus"): c.set_focus(0) self.assertRaises(IndexError, lambda: c.set_focus(-1)) self.assertRaises(IndexError, lambda: c.set_focus(3)) c.set_focus(t2) self.assertEqual(c.focus_position, 1) self.assertRaises(ValueError, lambda: c.set_focus("nonexistant")) with self.subTest("Contents"): self.assertEqual(c.widget_list, [t1, t2, sf]) self.assertEqual(c.column_types, [("weight", 1), ("weight", 3), ("weight", 1)]) self.assertEqual(c.box_columns, [2]) with self.subTest("Contents change"): c.widget_list = [t2, t1, sf] self.assertEqual(c.widget_list, [t2, t1, sf]) self.assertEqual(c.box_columns, [2]) self.assertEqual( c.contents, [(t2, ("weight", 1, False)), (t1, ("weight", 3, False)), (sf, ("weight", 1, True))], ) self.assertEqual(c.focus_position, 1) # focus unchanged c.column_types = [("flow", None), ("weight", 2), ("fixed", 5)] # use the old name self.assertEqual(c.column_types, [("flow", None), ("weight", 2), ("fixed", 5)]) self.assertEqual( c.contents, [(t2, ("pack", None, False)), (t1, ("weight", 2, False)), (sf, ("given", 5, True))], ) self.assertEqual(c.focus_position, 1) # focus unchanged with self.subTest("Contents change 2"): c.widget_list = [t1] self.assertEqual(len(c.contents), 1) self.assertEqual(c.focus_position, 0) c.widget_list.extend([t2, t1]) self.assertEqual(len(c.contents), 3) self.assertEqual(c.column_types, [("flow", None), ("weight", 1), ("weight", 1)]) c.column_types[:] = [("weight", 2)] self.assertEqual(len(c.contents), 1) def test_regression_columns_different_height(self): size = (20, 5) box_w = urwid.SolidFill("#") f_f_widget = urwid.Text("Fixed/Flow") box_flow = urwid.LineBox(urwid.Filler(f_f_widget, valign=urwid.TOP)) self.assertIn(urwid.BOX, box_w.sizing()) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW)), box_flow.sizing()) with self.subTest("BoxFlow weight"): widget = urwid.Columns(((1, box_w), box_flow)) self.assertEqual( ( "#┌─────────────────â”", "#│Fixed/Flow │", "#│ │", "#│ │", "#└─────────────────┘", ), widget.render(size, False).decoded_text, ) with self.subTest("BoxFlow GIVEN"): widget = urwid.Columns((box_w, (12, box_flow))) self.assertEqual( ( "########┌──────────â”", "########│Fixed/Flow│", "########│ │", "########│ │", "########└──────────┘", ), widget.render(size, False).decoded_text, ) urwid-2.6.16/tests/test_container.py000066400000000000000000000117461470350774000175150ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid class WidgetSquishTest(unittest.TestCase): def wstest(self, w): c = w.render((80, 0), focus=False) assert c.rows() == 0 c = w.render((80, 0), focus=True) assert c.rows() == 0 c = w.render((80, 1), focus=False) assert c.rows() == 1 c = w.render((0, 25), focus=False) c = w.render((1, 25), focus=False) def fwstest(self, w): def t(cols: int, focus: bool): wrows = w.rows((cols,), focus) c = w.render((cols,), focus) self.assertEqual(c.rows(), wrows, f"Canvas rows {c.rows()} != widget rows {wrows}") if focus and hasattr(w, "get_cursor_coords"): gcc = w.get_cursor_coords((cols,)) self.assertEqual(c.cursor, gcc, f"Canvas cursor {c.cursor} != widget cursor {gcc}") for cols, focus in ((0, False), (1, False), (0, True), (1, True)): with self.subTest(f"{w.__class__.__name__} cols={cols} and focus={focus}"): t(cols, focus) def test_listbox(self): self.wstest(urwid.ListBox(urwid.SimpleListWalker([]))) self.wstest(urwid.ListBox(urwid.SimpleListWalker([urwid.Text("hello")]))) def test_bargraph(self): self.wstest(urwid.BarGraph(["foo", "bar"])) def test_graphvscale(self): self.wstest(urwid.GraphVScale([(0, "hello")], 1)) self.wstest(urwid.GraphVScale([(5, "hello")], 1)) def test_solidfill(self): self.wstest(urwid.SolidFill()) def test_filler(self): self.wstest(urwid.Filler(urwid.Text("hello"))) def test_overlay(self): self.wstest( urwid.Overlay( urwid.BigText("hello", urwid.Thin6x6Font()), urwid.SolidFill(), "center", None, "middle", None ) ) self.wstest(urwid.Overlay(urwid.Text("hello"), urwid.SolidFill(), "center", ("relative", 100), "middle", None)) def test_frame(self): self.wstest(urwid.Frame(urwid.SolidFill())) self.wstest(urwid.Frame(urwid.SolidFill(), header=urwid.Text("hello"))) self.wstest(urwid.Frame(urwid.SolidFill(), header=urwid.Text("hello"), footer=urwid.Text("hello"))) def test_pile(self): self.wstest(urwid.Pile([urwid.SolidFill()])) self.wstest(urwid.Pile([("flow", urwid.Text("hello"))])) self.wstest(urwid.Pile([])) def test_columns(self): self.wstest(urwid.Columns([urwid.SolidFill()])) self.wstest(urwid.Columns([(4, urwid.SolidFill())])) def test_buttons(self): self.fwstest(urwid.Button("hello")) self.fwstest(urwid.RadioButton([], "hello")) def testFocus(self): expect_focused = urwid.Button("Focused") pile = urwid.Pile((urwid.Button("First"), expect_focused, urwid.Button("Last")), focus_item=expect_focused) self.assertEqual(1, pile.focus_position) self.assertEqual(expect_focused, pile.focus) class CommonContainerTest(unittest.TestCase): def test_list_box(self): lb = urwid.ListBox(urwid.SimpleFocusListWalker([])) self.assertEqual(lb.focus, None) self.assertRaises(IndexError, lambda: getattr(lb, "focus_position")) self.assertRaises(IndexError, lambda: setattr(lb, "focus_position", None)) self.assertRaises(IndexError, lambda: setattr(lb, "focus_position", 0)) t1 = urwid.Text("one") t2 = urwid.Text("two") lb = urwid.ListBox(urwid.SimpleListWalker([t1, t2])) self.assertEqual(lb.focus, t1) self.assertEqual(lb.focus_position, 0) lb.focus_position = 1 self.assertEqual(lb.focus, t2) self.assertEqual(lb.focus_position, 1) lb.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(lb, "focus_position", -1)) self.assertRaises(IndexError, lambda: setattr(lb, "focus_position", 2)) def test_focus_path(self): # big tree of containers t = urwid.Text("x") e = urwid.Edit("?") c = urwid.Columns([t, e, t, t]) p = urwid.Pile([t, t, c, t]) a = urwid.AttrMap(p, "gets ignored") s = urwid.SolidFill("/") o = urwid.Overlay(e, s, "center", "pack", "middle", "pack") lb = urwid.ListBox(urwid.SimpleFocusListWalker([t, a, o, t])) lb.focus_position = 1 g = urwid.GridFlow([t, t, t, t, e, t], 10, 0, 0, "left") g.focus_position = 4 f = urwid.Frame(lb, header=t, footer=g) self.assertEqual(f.get_focus_path(), ["body", 1, 2, 1]) f.set_focus_path(["footer"]) # same as f.focus_position = 'footer' self.assertEqual(f.get_focus_path(), ["footer", 4]) f.set_focus_path(["body", 1, 2, 2]) self.assertEqual(f.get_focus_path(), ["body", 1, 2, 2]) self.assertRaises(IndexError, lambda: f.set_focus_path([0, 1, 2])) self.assertRaises(IndexError, lambda: f.set_focus_path(["body", 2, 2])) f.set_focus_path(["body", 2]) # focus the overlay self.assertEqual(f.get_focus_path(), ["body", 2, 1]) urwid-2.6.16/tests/test_doctests.py000066400000000000000000000041151470350774000173530ustar00rootroot00000000000000from __future__ import annotations import doctest import sys import unittest import urwid import urwid.numedit def load_tests(loader: unittest.TestLoader, tests: unittest.BaseTestSuite, ignore) -> unittest.BaseTestSuite: module_doctests = [ urwid.widget.attr_map, urwid.widget.attr_wrap, urwid.widget.bar_graph, urwid.widget.big_text, urwid.widget.box_adapter, urwid.widget.columns, urwid.widget.constants, urwid.widget.container, urwid.widget.divider, urwid.widget.edit, urwid.widget.filler, urwid.widget.frame, urwid.widget.grid_flow, urwid.widget.line_box, urwid.widget.overlay, urwid.widget.padding, urwid.widget.pile, urwid.widget.popup, urwid.widget.progress_bar, urwid.widget.solid_fill, urwid.widget.text, urwid.widget.widget, urwid.widget.widget_decoration, urwid.widget.wimp, urwid.widget.monitored_list, urwid.display.common, urwid.display.raw, urwid.event_loop.main_loop, urwid.numedit, urwid.raw_display, urwid.font, "urwid.split_repr", # override function with same name urwid.util, urwid.signals, ] try: module_doctests.append(urwid.display.curses) except (AttributeError, NameError): pass # Curses is not loaded option_flags = doctest.ELLIPSIS | doctest.DONT_ACCEPT_TRUE_FOR_1 | doctest.IGNORE_EXCEPTION_DETAIL for m in module_doctests: tests.addTests(doctest.DocTestSuite(m, optionflags=option_flags)) try: from urwid import curses_display except ImportError: pass # do not run tests else: tests.addTests(doctest.DocTestSuite(curses_display, optionflags=option_flags)) if sys.platform == "win32": tests.addTests(doctest.DocTestSuite("urwid.display._win32_raw_display", optionflags=option_flags)) else: tests.addTests(doctest.DocTestSuite("urwid.display._posix_raw_display", optionflags=option_flags)) return tests urwid-2.6.16/tests/test_escapes.py000066400000000000000000000145361470350774000171560ustar00rootroot00000000000000#!/usr/bin/python """ Tests covering escape sequences processing """ from __future__ import annotations import unittest from urwid.display import escape class InputEscapeSequenceParserTest(unittest.TestCase): """Tests for parser of input escape sequences""" def test_bare_escape(self): codes = [27] expected = ["esc"] actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertListEqual(expected, actual) self.assertListEqual([], rest) def test_meta(self): codes = [27, ord("4"), ord("2")] expected = ["meta 4"] actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertListEqual(expected, actual) self.assertListEqual([ord("2")], rest) def test_shift_arrows(self): codes = [27, ord("["), ord("a")] expected = ["shift up"] actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertListEqual(expected, actual) self.assertListEqual([], rest) def test_ctrl_pgup(self): codes = [27, 91, 53, 59, 53, 126] expected = ["ctrl page up"] actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertListEqual(expected, actual) self.assertListEqual([], rest) def test_esc_meta_1(self): codes = [27, 27, 49] expected = ["esc", "meta 1"] actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertListEqual(expected, actual) self.assertListEqual([], rest) def test_midsequence(self): # '[11~' is F1, '[12~' is F2, etc codes = [27, ord("["), ord("1")] with self.assertRaises(escape.MoreInputRequired): escape.process_keyqueue(codes, more_available=True) actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertListEqual(["meta ["], actual) self.assertListEqual([ord("1")], rest) def test_mouse_press(self): codes = [27, 91, 77, 32, 41, 48] expected = [("mouse press", 1.0, 8, 15)] actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertListEqual(expected, actual) self.assertListEqual([], rest) def test_bug_104(self): """GH #104: click-Esc & Esc-click crashes urwid apps""" codes = [27, 27, 91, 77, 32, 127, 59] expected = ["esc", ("mouse press", 1.0, 94, 26)] actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertListEqual(expected, actual) self.assertListEqual([], rest) codes = [27, 27, 91, 77, 35, 120, 59] expected = ["esc", ("mouse release", 0, 87, 26)] actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertListEqual(expected, actual) self.assertListEqual([], rest) def test_functional_keys(self): """Test for functional keys F1-F4, F5-F12.""" def check_key(expected: str, codes: list[int]) -> None: actual, rest = escape.process_keyqueue(codes, more_available=False) self.assertEqual( [expected], actual, f"Codes {codes!r} ({[chr(code) for code in codes]}) was not decoded to {expected!r}", ) # F1-F4 for num in range(1, 5): codes = [27, ord("O"), 79 + num] check_key(f"f{num}", codes) # F5-F8 for num, offset in enumerate((0, 2, 3, 4), start=5): codes = [27, ord("["), ord("1"), 53 + offset, ord("~")] check_key(f"f{num}", codes) # F9-F12 for num, offset in enumerate((0, 1, 3, 4), start=9): codes = [27, ord("["), ord("2"), 48 + offset, ord("~")] check_key(f"f{num}", codes) def test_functional_keys_mods_f1_f4(self): """Test for modifiers handling for functional keys F1-F4.""" prefixes = ("O", "[1;") masks = "12345678" letters = "PQRS" for prefix in prefixes: encoded_prefix = tuple(ord(element) for element in prefix) for num, letter in enumerate(letters, start=1): for mask in masks: codes = [27, *encoded_prefix, ord(mask), ord(letter)] actual, rest = escape.process_keyqueue(codes, more_available=False) expected = escape.escape_modifier(mask) + f"f{num}" self.assertEqual( [expected], actual, f"Codes {codes!r} ({[chr(code) for code in codes]}) was not decoded to {expected!r}", ) def test_functional_keys_mods_simple_f1_20(self): """Test for modifiers handling for functional keys F1-F20.""" masks = "12345678" numbers = (11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 31, 32, 33, 34) for num, number in enumerate(numbers, start=1): encoded_number = tuple(ord(element) for element in str(number)) for mask in masks: codes = [27, ord("["), *encoded_number, ord(";"), ord(mask), ord("~")] actual, rest = escape.process_keyqueue(codes, more_available=False) expected = escape.escape_modifier(mask) + f"f{num}" self.assertEqual( [expected], actual, f"Codes {codes!r} ({[chr(code) for code in codes]}) was not decoded to {expected!r}", ) def test_sgrmouse(self): prefix = (27, ord("["), ord("<")) x = 4 y = 8 coord = (ord(f"{x + 1}"), ord(";"), ord(f"{y + 1}")) action = ord("M") modifiers = ((0, ""), (8, "meta "), (16, "ctrl "), (24, "meta ctrl ")) for key, code in enumerate((0b0, 0b1, 0b10, 0b1000000, 0b1000001), start=1): for mod_code, mod in modifiers: key_code = tuple(ord(element) for element in str(code | mod_code)) codes = [*prefix, *key_code, ord(";"), *coord, action] actual, rest = escape.process_keyqueue(codes, more_available=False) expected = [(mod + "mouse press", key, x, y)] self.assertEqual( expected, actual, f"Codes {codes!r} ({[chr(code) for code in codes]}) was not decoded to {expected!r}", ) urwid-2.6.16/tests/test_event_loops.py000066400000000000000000000257461470350774000200750ustar00rootroot00000000000000from __future__ import annotations import asyncio import concurrent.futures import socket import sys import typing import unittest import urwid if typing.TYPE_CHECKING: from concurrent.futures import Future from types import TracebackType try: import gi.repository except ImportError: GLIB_AVAILABLE = False else: GLIB_AVAILABLE = True try: import tornado except ImportError: TORNADO_AVAILABLE = False else: TORNADO_AVAILABLE = True try: import twisted except ImportError: TWISTED_AVAILABLE = False else: TWISTED_AVAILABLE = True try: import trio except ImportError: TRIO_AVAILABLE = False else: TRIO_AVAILABLE = True try: import zmq except ImportError: ZMQ_AVAILABLE = False else: ZMQ_AVAILABLE = True IS_WINDOWS = sys.platform == "win32" class ClosingSocketPair(typing.ContextManager[typing.Tuple[socket.socket, socket.socket]]): __slots__ = ("rd_s", "wr_s") def __init__(self) -> None: self.rd_s: socket.socket | None = None self.wr_s: socket.socket | None = None def __enter__(self) -> tuple[socket.socket, socket.socket]: self.rd_s, self.wr_s = socket.socketpair() return self.rd_s, self.wr_s def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: if self.rd_s is not None: self.rd_s.close() if self.wr_s is not None: self.wr_s.close() class EventLoopTestMixin: evl: urwid.EventLoop def test_event_loop(self): evl: urwid.EventLoop = self.evl out = [] rd: socket.socket wr: socket.socket def step1(): out.append("writing") wr.send(b"hi") def step2(): out.append(rd.recv(2).decode("ascii")) raise urwid.ExitMainLoop with ClosingSocketPair() as (rd, wr): _handle = evl.alarm(0, step1) _handle = evl.watch_file(rd.fileno(), step2) evl.run() self.assertEqual(out, ["writing", "hi"]) def test_remove_alarm(self): evl: urwid.EventLoop = self.evl handle = evl.alarm(50, lambda: None) def step1(): self.assertTrue(evl.remove_alarm(handle)) self.assertFalse(evl.remove_alarm(handle)) raise urwid.ExitMainLoop evl.alarm(0, step1) evl.run() def test_remove_watch_file(self): evl: urwid.EventLoop = self.evl with ClosingSocketPair() as (rd, _wr): handle = evl.watch_file(rd.fileno(), lambda: None) def step1(): self.assertTrue(evl.remove_watch_file(handle)) self.assertFalse(evl.remove_watch_file(handle)) raise urwid.ExitMainLoop evl.alarm(0, step1) evl.run() _expected_idle_handle = 1 def test_run(self): evl: urwid.EventLoop = self.evl out = [] wr: socket.socket rd: socket.socket def say_hello(): out.append("hello") def say_waiting(): out.append("waiting") def exit_clean() -> typing.NoReturn: out.append("clean exit") raise urwid.ExitMainLoop def exit_error() -> typing.NoReturn: 1 / 0 with ClosingSocketPair() as (rd, wr): self.assertEqual(wr.send(b"data"), 4) _handle = evl.alarm(0.01, exit_clean) _handle = evl.alarm(0.005, say_hello) idle_handle = evl.enter_idle(say_waiting) if self._expected_idle_handle is not None: self.assertEqual(idle_handle, 1) evl.run() self.assertTrue("waiting" in out, out) self.assertTrue("hello" in out, out) self.assertTrue("clean exit" in out, out) handle = evl.watch_file(rd.fileno(), exit_clean) del out[:] evl.run() self.assertEqual(["clean exit"], out) self.assertTrue(evl.remove_watch_file(handle)) _handle = evl.alarm(0, exit_error) self.assertRaises(ZeroDivisionError, evl.run) _handle = evl.watch_file(rd.fileno(), exit_error) self.assertRaises(ZeroDivisionError, evl.run) def test_run_in_executor(self): out: list[str] = [] with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: def start(): out.append("started") self.evl.run_in_executor(executor, func).add_done_callback(callback) def func() -> bool: out.append("future called") return True def callback(fut: Future) -> None: out.append(f"callback called with future outcome: {fut.result()}") def exit_clean() -> typing.NoReturn: out.append("clean exit") raise urwid.ExitMainLoop _handle = self.evl.alarm(0.1, exit_clean) _handle = self.evl.alarm(0.005, start) self.evl.run() self.assertEqual( [ "started", "future called", "callback called with future outcome: True", "clean exit", ], out, ) class SelectEventLoopTest(unittest.TestCase, EventLoopTestMixin): def setUp(self): self.evl = urwid.SelectEventLoop() @unittest.skipIf(IS_WINDOWS, "Windows is temporary not supported by AsyncioEventLoop.") class AsyncioEventLoopTest(unittest.TestCase, EventLoopTestMixin): def setUp(self): self.evl = urwid.AsyncioEventLoop() _expected_idle_handle = None def test_error(self): evl = self.evl evl.alarm(0, lambda: 1 / 0) # Simulate error in event loop self.assertRaises(ZeroDivisionError, evl.run) @unittest.skipIf( sys.implementation.name == "pypy", "Well known dead wait (lock?) on pypy.", ) def test_coroutine_error(self): evl = self.evl async def error_coro(): result = 1 / 0 # Simulate error in coroutine return result asyncio.ensure_future(error_coro(), loop=asyncio.get_event_loop_policy().get_event_loop()) self.assertRaises(ZeroDivisionError, evl.run) @unittest.skipUnless(GLIB_AVAILABLE, "GLIB unavailable") class GLibEventLoopTest(unittest.TestCase, EventLoopTestMixin): def setUp(self): self.evl = urwid.GLibEventLoop() def test_error(self): evl = self.evl evl.alarm(0, lambda: 1 / 0) # Simulate error in event loop self.assertRaises(ZeroDivisionError, evl.run) @unittest.skipUnless(TORNADO_AVAILABLE, "Tornado not available") @unittest.skipIf( IS_WINDOWS, "Windows is temporary not supported by TornadoEventLoop due to race conditions.", ) class TornadoEventLoopTest(unittest.TestCase, EventLoopTestMixin): def setUp(self): from tornado.ioloop import IOLoop self.evl = urwid.TornadoEventLoop(IOLoop()) _expected_idle_handle = None @unittest.skipUnless(TWISTED_AVAILABLE, "Twisted is not available") @unittest.skipIf( IS_WINDOWS, "Windows is temporary not supported by TwistedEventLoop due to race conditions.", ) class TwistedEventLoopTest(unittest.TestCase, EventLoopTestMixin): def setUp(self): self.evl = urwid.TwistedEventLoop() # can't restart twisted reactor, so use shortened tests def test_event_loop(self): pass def test_remove_alarm(self): pass def test_remove_watch_file(self): pass def test_run(self): from twisted.internet import threads evl = self.evl out = [] wr: socket.socket rd: socket.socket def step2(): out.append(rd.recv(2).decode("ascii")) def say_hello(): out.append("hello") def say_waiting(): out.append("waiting") def test_remove_alarm(): handle = evl.alarm(50, lambda: None) self.assertTrue(evl.remove_alarm(handle)) self.assertFalse(evl.remove_alarm(handle)) out.append("remove_alarm ok") def test_remove_watch_file(): with ClosingSocketPair() as (rd_, _wr): handle = evl.watch_file(rd_.fileno(), lambda: None) self.assertTrue(evl.remove_watch_file(handle)) self.assertFalse(evl.remove_watch_file(handle)) out.append("remove_watch_file ok") def test_threaded(): out.append("schedule to thread") threads.deferToThread(func).addCallback(callback) def func() -> bool: out.append("future called") return True def callback(result: bool) -> None: out.append(f"callback called with future outcome: {result}") def exit_clean() -> typing.NoReturn: out.append("clean exit") raise urwid.ExitMainLoop def exit_error() -> typing.NoReturn: 1 / 0 with ClosingSocketPair() as (rd, wr): self.assertEqual(wr.send(b"data"), 4) _handle = evl.watch_file(rd.fileno(), step2) _handle = evl.alarm(0.1, exit_clean) _handle = evl.alarm(0.05, say_hello) _handle = evl.alarm(0.06, test_remove_alarm) _handle = evl.alarm(0.07, test_remove_watch_file) _handle = evl.alarm(0.08, test_threaded) self.assertEqual(evl.enter_idle(say_waiting), 1) evl.run() self.assertIn("da", out) self.assertIn("ta", out) self.assertIn("hello", out) self.assertIn("remove_alarm ok", out) self.assertIn("clean exit", out) self.assertIn("schedule to thread", out) self.assertIn("future called", out) self.assertIn("callback called with future outcome: True", out) def test_error(self): evl = self.evl evl.alarm(0, lambda: 1 / 0) # Simulate error in event loop self.assertRaises(ZeroDivisionError, evl.run) @unittest.skip("not implemented for twisted event loop") def test_run_in_executor(self): """Not implemented for twisted event loop.""" @unittest.skipUnless(TRIO_AVAILABLE, "Trio not available") @unittest.skipIf( IS_WINDOWS, "Windows is temporary not supported by TrioEventLoop due to race conditions.", ) class TrioEventLoopTest(unittest.TestCase, EventLoopTestMixin): def setUp(self): self.evl = urwid.TrioEventLoop() _expected_idle_handle = None def test_error(self): evl = self.evl evl.alarm(0, lambda: 1 / 0) # Simulate error in event loop self.assertRaises(ZeroDivisionError, evl.run) @unittest.skip("not implemented for trio event loop") def test_run_in_executor(self): """Not implemented for trio event loop.""" @unittest.skipUnless(ZMQ_AVAILABLE, "ZMQ is not available") @unittest.skipIf(IS_WINDOWS, "ZMQEventLoop is not supported under windows") class ZMQEventLoopTest(unittest.TestCase, EventLoopTestMixin): def setUp(self): self.evl = urwid.ZMQEventLoop() urwid-2.6.16/tests/test_filler.py000066400000000000000000000150371470350774000170050ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid class FillerTest(unittest.TestCase): def ftest( self, desc: str, valign, height, maxrow: int, top: int, bottom: int, min_height: int | None = None, ) -> None: with self.subTest(desc): f = urwid.Filler(None, valign, height, min_height) t, b = f.filler_values((20, maxrow), False) self.assertEqual( (t, b), (top, bottom), f"{desc} expected {top, bottom} but got {t, b}", ) def fetest(self, desc: str, valign, height) -> None: with self.subTest(desc): self.assertRaises(urwid.FillerError, urwid.Filler, None, valign, height) def test_create(self): self.fetest("invalid pad", 6, 5) self.fetest("invalid pad type", ("bad", 2), 5) self.fetest("invalid width", "middle", "42") self.fetest("invalid width type", "middle", ("gouranga", 4)) self.fetest("invalid combination", ("relative", 20), ("fixed bottom", 4)) self.fetest("invalid combination 2", ("relative", 20), ("fixed top", 4)) def test_values(self): self.ftest("top align 5 7", "top", 5, 7, 0, 2) self.ftest("top align 7 7", "top", 7, 7, 0, 0) self.ftest("top align 9 7", "top", 9, 7, 0, 0) self.ftest("bottom align 5 7", "bottom", 5, 7, 2, 0) self.ftest("middle align 5 7", "middle", 5, 7, 1, 1) self.ftest("fixed top", ("fixed top", 3), 5, 10, 3, 2) self.ftest("fixed top reduce", ("fixed top", 3), 8, 10, 2, 0) self.ftest("fixed top shrink", ("fixed top", 3), 18, 10, 0, 0) self.ftest( "fixed top, bottom", ("fixed top", 3), ("fixed bottom", 4), 17, 3, 4, ) self.ftest( "fixed top, bottom, min_width", ("fixed top", 3), ("fixed bottom", 4), 10, 3, 2, 5, ) self.ftest( "fixed top, bottom, min_width 2", ("fixed top", 3), ("fixed bottom", 4), 10, 2, 0, 8, ) self.ftest("fixed bottom", ("fixed bottom", 3), 5, 10, 2, 3) self.ftest("fixed bottom reduce", ("fixed bottom", 3), 8, 10, 0, 2) self.ftest("fixed bottom shrink", ("fixed bottom", 3), 18, 10, 0, 0) self.ftest( "fixed bottom, top", ("fixed bottom", 3), ("fixed top", 4), 17, 4, 3, ) self.ftest( "fixed bottom, top, min_height", ("fixed bottom", 3), ("fixed top", 4), 10, 2, 3, 5, ) self.ftest( "fixed bottom, top, min_height 2", ("fixed bottom", 3), ("fixed top", 4), 10, 0, 2, 8, ) self.ftest("relative 30", ("relative", 30), 5, 10, 1, 4) self.ftest("relative 50", ("relative", 50), 5, 10, 2, 3) self.ftest("relative 130 edge", ("relative", 130), 5, 10, 5, 0) self.ftest("relative -10 edge", ("relative", -10), 4, 10, 0, 6) self.ftest("middle relative 70", "middle", ("relative", 70), 10, 1, 2) self.ftest( "middle relative 70 grow 8", "middle", ("relative", 70), 10, 1, 1, 8, ) def test_repr(self): self.assertEqual( ">", repr(urwid.Filler(urwid.Text("hai"))), ) def test_sizing(self): with self.subTest("Flow supported for PACK height (flow widget)"): widget = urwid.Filler(urwid.Text("Some text")) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW)), widget.sizing()) cols, rows = 10, 1 self.assertEqual((cols, rows), widget.pack((cols,))) # top and bottom are 0 self.assertEqual((cols, rows), widget.pack((cols, rows))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) canvas = widget.render((cols, rows)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("Flow supported for GIVEN height (box widget)"): widget = urwid.Filler(urwid.SolidFill("#"), height=3) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW)), widget.sizing()) cols, rows = 5, 3 self.assertEqual((cols, rows), widget.pack((cols,))) # top and bottom are 0 self.assertEqual((cols, rows), widget.pack((cols, rows))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) canvas = widget.render((cols, rows)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("Flow is not supported for RELATIVE scenarios"): widget = urwid.Filler(urwid.SolidFill(""), height=(urwid.RELATIVE, 10)) cols = 10 with self.assertRaises(urwid.widget.WidgetError) as ctx: widget.pack((cols,)) self.assertEqual( f"Cannot pack (maxcol,) size, this is not a flow widget: {widget!r}", str(ctx.exception), ) with self.assertRaises(urwid.widget.WidgetError) as ctx: widget.render((cols,)) self.assertEqual( f"Cannot pack (maxcol,) size, this is not a flow widget: {widget!r}", str(ctx.exception), ) def test_render_focused_not_fit(self): """Test that a focused widget will be shown and top trimmed if not enough height.""" widget = urwid.Filler( urwid.Pile( ( urwid.Text("First"), urwid.Text("Second"), urwid.Text("Third"), urwid.Button("Selectable"), urwid.Text("Last"), ), ) ) canvas = widget.render((14, 3), focus=True) self.assertEqual( [ b"Second ", b"Third ", b"< Selectable >", ], canvas.text, ) urwid-2.6.16/tests/test_floatedit.py000066400000000000000000000026551470350774000175050ustar00rootroot00000000000000import unittest from decimal import Decimal from itertools import product from urwid.numedit import FloatEdit class FloatEditNoPreservePrecicionTest(unittest.TestCase): def test(self): for v in ("1.01", "2.5", "300", "4.100", "5.001"): f = FloatEdit(preserveSignificance=False) f.set_edit_text(v) print(f.value(), Decimal(v)) self.assertEqual(Decimal(v), f.value()) def test_int(self): corner_value = "1" # just int f = FloatEdit(preserveSignificance=False) f.set_edit_text(corner_value) self.assertEqual(Decimal(corner_value), f.value()) def test_no_post_dot(self): corner_value = "1." # forced float 1.0 with trimmed end f = FloatEdit(preserveSignificance=False) f.set_edit_text(corner_value) self.assertEqual(Decimal(corner_value), f.value()) class FloatEditPreservePrecicionTest(unittest.TestCase): def test(self): for v, sig, sep in product(("1.010", "2.500", "300.000", "4.100", "5.001"), (0, 1, 2, 3), (".", ",")): precision_template = "0." + "0" * sig expected = Decimal(v).quantize(Decimal(precision_template)) f = FloatEdit(preserveSignificance=True, default=precision_template, decimalSeparator=sep) f.set_edit_text(v.replace(".", sep)) self.assertEqual(expected, f.value(), f"{f.value()} != {expected} (significance={sig!r})") urwid-2.6.16/tests/test_font.py000066400000000000000000000016111470350774000164670ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid from urwid.util import get_encoding class TestFontRender(unittest.TestCase): def setUp(self) -> None: self.old_encoding = get_encoding() urwid.set_encoding("utf-8") def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def test_001_basic(self): font = urwid.Thin3x3Font() rendered = b"\n".join(font.render("1").text).decode() expected = " â” \n │ \n â”´ " self.assertEqual(expected, rendered) def test_002_non_rect(self): """Test non rect symbol, which causes spaces based padding. Lines as bytes should be not equal length. """ font = urwid.Thin3x3Font() rendered = b"\n".join(font.render("2").text).decode() expected = "┌─â”\n┌─┘\n└─ " self.assertEqual(expected, rendered) urwid-2.6.16/tests/test_frame.py000066400000000000000000000107171470350774000166220ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid class FrameTest(unittest.TestCase): def ftbtest(self, desc: str, focus_part, header_rows, footer_rows, size, focus, top, bottom): class FakeWidget: def __init__(self, rows, want_focus): self.ret_rows = rows self.want_focus = want_focus def rows(self, size, focus=False): assert self.want_focus == focus return self.ret_rows with self.subTest(desc): header = footer = None if header_rows: header = FakeWidget(header_rows, focus and focus_part == "header") if footer_rows: footer = FakeWidget(footer_rows, focus and focus_part == "footer") f = urwid.Frame(urwid.SolidFill(), header, footer, focus_part) rval = f.frame_top_bottom(size, focus) exp = (top, bottom), (header_rows, footer_rows) self.assertEqual(exp, rval) def test(self): self.ftbtest("simple", "body", 0, 0, (9, 10), True, 0, 0) self.ftbtest("simple h", "body", 3, 0, (9, 10), True, 3, 0) self.ftbtest("simple f", "body", 0, 3, (9, 10), True, 0, 3) self.ftbtest("simple hf", "body", 3, 3, (9, 10), True, 3, 3) self.ftbtest("almost full hf", "body", 4, 5, (9, 10), True, 4, 5) self.ftbtest("full hf", "body", 5, 5, (9, 10), True, 4, 5) self.ftbtest("x full h+1f", "body", 6, 5, (9, 10), False, 4, 5) self.ftbtest("full h+1f", "body", 6, 5, (9, 10), True, 4, 5) self.ftbtest("full hf+1", "body", 5, 6, (9, 10), True, 3, 6) self.ftbtest("F full h+1f", "footer", 6, 5, (9, 10), True, 5, 5) self.ftbtest("F full hf+1", "footer", 5, 6, (9, 10), True, 4, 6) self.ftbtest("F full hf+5", "footer", 5, 11, (9, 10), True, 0, 10) self.ftbtest("full hf+5", "body", 5, 11, (9, 10), True, 0, 9) self.ftbtest("H full hf+1", "header", 5, 6, (9, 10), True, 5, 5) self.ftbtest("H full h+1f", "header", 6, 5, (9, 10), True, 6, 4) self.ftbtest("H full h+5f", "header", 11, 5, (9, 10), True, 10, 0) def test_common(self): s1 = urwid.SolidFill("1") f = urwid.Frame(s1) self.assertEqual(f.focus, s1) self.assertEqual(f.focus_position, "body") self.assertRaises(IndexError, lambda: setattr(f, "focus_position", None)) self.assertRaises(IndexError, lambda: setattr(f, "focus_position", "header")) t1 = urwid.Text("one") t2 = urwid.Text("two") t3 = urwid.Text("three") f = urwid.Frame(s1, t1, t2, "header") self.assertEqual(f.focus, t1) self.assertEqual(f.focus_position, "header") f.focus_position = "footer" self.assertEqual(f.focus, t2) self.assertEqual(f.focus_position, "footer") self.assertRaises(IndexError, lambda: setattr(f, "focus_position", -1)) self.assertRaises(IndexError, lambda: setattr(f, "focus_position", 2)) del f.contents["footer"] self.assertEqual(f.footer, None) self.assertEqual(f.focus_position, "body") f.contents.update(footer=(t3, None), header=(t2, None)) self.assertEqual(f.header, t2) self.assertEqual(f.footer, t3) def set1(): f.contents["body"] = t1 self.assertRaises(urwid.FrameError, set1) def set2(): f.contents["body"] = (t1, "given") self.assertRaises(urwid.FrameError, set2) def test_focus(self): header = urwid.Text("header") body = urwid.ListBox((urwid.Text("first"), urwid.Text("second"))) footer = urwid.Text("footer") with self.subTest("default"): widget = urwid.Frame(body, header, footer) self.assertEqual(body, widget.focus) self.assertEqual("body", widget.focus_part) with self.subTest("body"): widget = urwid.Frame(body, header, footer, focus_part=body) self.assertEqual(body, widget.focus) self.assertEqual("body", widget.focus_part) with self.subTest("header"): widget = urwid.Frame(body, header, footer, focus_part=header) self.assertEqual(header, widget.focus) self.assertEqual("header", widget.focus_part) with self.subTest("footer"): widget = urwid.Frame(body, header, footer, focus_part=footer) self.assertEqual(footer, widget.focus) self.assertEqual("footer", widget.focus_part) urwid-2.6.16/tests/test_graphics.py000066400000000000000000000106341470350774000173260ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid from urwid.util import get_encoding from urwid.widget import bar_graph class BarGraphTest(unittest.TestCase): def bgtest(self, desc, data, top, widths, maxrow, exp): rval = bar_graph.calculate_bargraph_display(data, top, widths, maxrow) assert rval == exp, f"{desc} expected {exp!r}, got {rval!r}" def test1(self): self.bgtest("simplest", [[0]], 5, [1], 1, [(1, [(0, 1)])]), self.bgtest( "simpler", [[0], [0]], 5, [1, 2], 5, [(5, [(0, 3)])], ) self.bgtest( "simple", [[5]], 5, [1], 1, [(1, [(1, 1)])], ) self.bgtest( "2col-1", [[2], [0]], 5, [1, 2], 5, [(3, [(0, 3)]), (2, [(1, 1), (0, 2)])], ) self.bgtest( "2col-2", [[0], [2]], 5, [1, 2], 5, [(3, [(0, 3)]), (2, [(0, 1), (1, 2)])], ) self.bgtest( "2col-3", [[2], [3]], 5, [1, 2], 5, [(2, [(0, 3)]), (1, [(0, 1), (1, 2)]), (2, [(1, 3)])], ) self.bgtest( "3col-1", [[5], [3], [0]], 5, [2, 1, 1], 5, [(2, [(1, 2), (0, 2)]), (3, [(1, 3), (0, 1)])], ) self.bgtest( "3col-2", [[4], [4], [4]], 5, [2, 1, 1], 5, [(1, [(0, 4)]), (4, [(1, 4)])], ) self.bgtest( "3col-3", [[1], [2], [3]], 5, [2, 1, 1], 5, [(2, [(0, 4)]), (1, [(0, 3), (1, 1)]), (1, [(0, 2), (1, 2)]), (1, [(1, 4)])], ) self.bgtest( "3col-4", [[4], [2], [4]], 5, [1, 2, 1], 5, [(1, [(0, 4)]), (2, [(1, 1), (0, 2), (1, 1)]), (2, [(1, 4)])], ) def test2(self): self.bgtest( "simple1a", [[2, 0], [2, 1]], 2, [1, 1], 2, [(1, [(1, 2)]), (1, [(1, 1), (2, 1)])], ) self.bgtest( "simple1b", [[2, 1], [2, 0]], 2, [1, 1], 2, [(1, [(1, 2)]), (1, [(2, 1), (1, 1)])], ) self.bgtest("cross1a", [[2, 2], [1, 2]], 2, [1, 1], 2, [(2, [(2, 2)])]) self.bgtest("cross1b", [[1, 2], [2, 2]], 2, [1, 1], 2, [(2, [(2, 2)])]) self.bgtest( "mix1a", [[3, 2, 1], [2, 2, 2], [1, 2, 3]], 3, [1, 1, 1], 3, [(1, [(1, 1), (0, 1), (3, 1)]), (1, [(2, 1), (3, 2)]), (1, [(3, 3)])], ) self.bgtest( "mix1b", [[1, 2, 3], [2, 2, 2], [3, 2, 1]], 3, [1, 1, 1], 3, [(1, [(3, 1), (0, 1), (1, 1)]), (1, [(3, 2), (2, 1)]), (1, [(3, 3)])], ) class SmoothBarGraphTest(unittest.TestCase): def setUp(self) -> None: self.old_encoding = get_encoding() urwid.set_encoding("utf-8") def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def sbgtest(self, desc, data, top, exp): g = urwid.BarGraph(["black", "red", "blue"], None, {(1, 0): "red/black", (2, 1): "blue/red"}) g.set_data(data, top) rval = g.calculate_display((5, 3)) assert rval == exp, f"{desc} expected {exp!r}, got {rval!r}" def test1(self): self.sbgtest( "simple", [[3]], 5, [(1, [(0, 5)]), (1, [((1, 0, 6), 5)]), (1, [(1, 5)])], ) self.sbgtest( "boring", [[4, 2]], 6, [(1, [(0, 5)]), (1, [(1, 5)]), (1, [(2, 5)])], ) self.sbgtest( "two", [[4], [2]], 6, [(1, [(0, 5)]), (1, [(1, 3), (0, 2)]), (1, [(1, 5)])], ) self.sbgtest( "twos", [[3], [4]], 6, [(1, [(0, 5)]), (1, [((1, 0, 4), 3), (1, 2)]), (1, [(1, 5)])], ) self.sbgtest( "twof", [[4], [3]], 6, [(1, [(0, 5)]), (1, [(1, 3), ((1, 0, 4), 2)]), (1, [(1, 5)])], ) urwid-2.6.16/tests/test_grid_flow.py000066400000000000000000000175501470350774000175060ustar00rootroot00000000000000from __future__ import annotations import unittest from unittest import mock import urwid class GridFlowTest(unittest.TestCase): def test_fixed(self): grid = urwid.GridFlow( (urwid.Button(tag, align=urwid.CENTER) for tag in ("OK", "Cancel", "Help")), cell_width=10, h_sep=1, v_sep=1, align=urwid.CENTER, ) self.assertEqual(frozenset((urwid.FIXED, urwid.FLOW)), grid.sizing()) cols, rows = 32, 1 self.assertEqual((cols, rows), grid.pack(())) canvas = grid.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual([b"< OK > < Cancel > < Help >"], canvas.text) def test_default_focus(self): with self.subTest("Simple"): grid = urwid.GridFlow( (urwid.Button("btn"), urwid.Button("btn")), cell_width=10, h_sep=1, v_sep=1, align=urwid.CENTER, ) self.assertTrue(grid.selectable()) self.assertEqual(0, grid.focus_position) with self.subTest("Simple not selectable"): grid = urwid.GridFlow( (urwid.Text("btn"), urwid.Text("btn")), cell_width=10, h_sep=1, v_sep=1, align=urwid.CENTER, ) self.assertFalse(grid.selectable()) self.assertEqual(0, grid.focus_position) with self.subTest("Explicit index"): grid = urwid.GridFlow( (urwid.Button("btn"), urwid.Button("btn")), cell_width=10, h_sep=1, v_sep=1, align=urwid.CENTER, focus=1, ) self.assertEqual(1, grid.focus_position) with self.subTest("Explicit widget"): btn2 = urwid.Button("btn 2") grid = urwid.GridFlow( (urwid.Button("btn"), btn2), cell_width=10, h_sep=1, v_sep=1, align=urwid.CENTER, focus=btn2, ) self.assertEqual(1, grid.focus_position) with self.subTest("Selectable not first"): grid = urwid.GridFlow( (urwid.Text("text"), urwid.Button("btn")), cell_width=10, h_sep=1, v_sep=1, align=urwid.CENTER, ) self.assertEqual(1, grid.focus_position) def test_cell_width(self): gf = urwid.GridFlow([], 5, 0, 0, "left") self.assertEqual(gf.cell_width, 5) def test_not_fit(self): """Test scenario with maxcol < cell_width (special case, warning will be produced).""" widget = urwid.GridFlow( [urwid.Button("first"), urwid.Button("second")], 5, 0, 0, "left", ) size = (3,) with self.assertWarns(urwid.widget.GridFlowWarning): canvas = widget.render(size) self.assertEqual( ("< f", " i", " r", " s", " t", "< s", " e", " c", " o", " n", " d"), canvas.decoded_text, ) def test_multiline(self): """Test widgets fit only with multiple lines""" widget = urwid.GridFlow( [urwid.Button("first"), urwid.Button("second")], 10, 0, 0, "left", ) size = (10,) canvas = widget.render(size) self.assertEqual( ("< first >", "< second >"), canvas.decoded_text, ) def test_multiline_2(self): """Test widgets fit only with multiple lines""" widget = urwid.GridFlow( [urwid.Button("first"), urwid.Button("second"), urwid.Button("third"), urwid.Button("forth")], 10, 0, 0, "left", ) size = (20,) canvas = widget.render(size) self.assertEqual( ( "< first >< second >", "< third >< forth >", ), canvas.decoded_text, ) def test_basics(self): repr(urwid.GridFlow([], 5, 0, 0, "left")) # should not fail def test_v_sep(self): gf = urwid.GridFlow([urwid.Text("test")], 10, 3, 1, "center") self.assertEqual(gf.rows((40,), False), 1) def test_keypress_v_sep_0(self): """ Ensure proper keypress handling when v_sep is 0. https://github.com/urwid/urwid/issues/387 """ call_back = mock.Mock() button = urwid.Button("test", call_back) gf = urwid.GridFlow([button], 10, 3, v_sep=0, align="center") self.assertEqual(gf.keypress((20,), "enter"), None) call_back.assert_called_with(button) def test_length(self): grid = urwid.GridFlow((urwid.Text(c) for c in "ABC"), 1, 0, 0, "left") self.assertEqual(3, len(grid)) self.assertEqual(3, len(grid.contents)) def test_common(self): gf = urwid.GridFlow([], 5, 1, 0, "left") with self.subTest("Focus"): self.assertEqual(gf.focus, None) self.assertEqual(gf.contents, []) self.assertRaises(IndexError, lambda: getattr(gf, "focus_position")) self.assertRaises(IndexError, lambda: setattr(gf, "focus_position", None)) self.assertRaises(IndexError, lambda: setattr(gf, "focus_position", 0)) with self.subTest("Contents options"): self.assertEqual(gf.options(), ("given", 5)) self.assertEqual(gf.options(width_amount=9), ("given", 9)) self.assertRaises(urwid.GridFlowError, lambda: gf.options("pack", None)) def test_focus_position(self): t1 = urwid.Text("one") t2 = urwid.Text("two") gf = urwid.GridFlow([t1, t2], 5, 1, 0, "left") self.assertEqual(gf.focus, t1) self.assertEqual(gf.focus_position, 0) self.assertEqual(gf.contents, [(t1, ("given", 5)), (t2, ("given", 5))]) gf.focus_position = 1 self.assertEqual(gf.focus, t2) self.assertEqual(gf.focus_position, 1) gf.contents.insert(0, (t2, ("given", 5))) self.assertEqual(gf.focus_position, 2) self.assertRaises(urwid.GridFlowError, lambda: gf.contents.append(())) self.assertRaises(urwid.GridFlowError, lambda: gf.contents.insert(1, (t1, ("pack", None)))) gf.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(gf, "focus_position", -1)) self.assertRaises(IndexError, lambda: setattr(gf, "focus_position", 3)) def test_deprecated(self): t1 = urwid.Text("one") t2 = urwid.Text("two") gf = urwid.GridFlow([t1, t2], 5, 1, 0, "left") # old methods: gf.set_focus(0) self.assertRaises(IndexError, lambda: gf.set_focus(-1)) self.assertRaises(IndexError, lambda: gf.set_focus(3)) gf.set_focus(t1) self.assertEqual(gf.focus_position, 0) self.assertRaises(ValueError, lambda: gf.set_focus("nonexistant")) def test_empty(self): """Test behaviour of empty widget.""" grid = urwid.GridFlow( (), cell_width=10, h_sep=1, v_sep=1, align=urwid.CENTER, ) self.assertEqual(frozenset((urwid.FLOW,)), grid.sizing(), "Empty grid can not be handled as FIXED") rows = 1 with self.subTest("Flow"): maxcol = 1 self.assertEqual((maxcol, rows), grid.pack((maxcol,))) rendered = grid.render((maxcol,)) self.assertEqual(maxcol, rendered.cols()) self.assertEqual(rows, rendered.rows()) with self.subTest("Fixed"): maxcol = 0 self.assertEqual((maxcol, rows), grid.pack(())) with self.assertRaises(ValueError): grid.render(()) urwid-2.6.16/tests/test_line_box.py000066400000000000000000000147751470350774000173370ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid from urwid.util import get_encoding class LineBoxTest(unittest.TestCase): def setUp(self) -> None: self.old_encoding = get_encoding() urwid.set_encoding("utf-8") def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def test_linebox_pack(self): # Bug #346 'pack' Padding does not run with LineBox t = urwid.Text("AAA\nCCC\nDDD") size = t.pack() l = urwid.LineBox(t) self.assertEqual(l.pack()[0], size[0] + 2) self.assertEqual(l.pack()[1], size[1] + 2) def test_border(self): wrapped = urwid.Text("Text\non\nfour\nlines", align=urwid.CENTER) l = urwid.LineBox(wrapped, tlcorner="â•­", trcorner="â•®", blcorner="â•°", brcorner="╯") cols, rows = 7, 6 self.assertEqual((cols, rows), l.pack(())) canvas = l.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "╭─────╮", "│ Text│", "│ on │", "│ four│", "│lines│", "╰─────╯", ], [line.decode("utf-8") for line in canvas.text], ) def test_header(self): wrapped = urwid.Text("Some text") l = urwid.LineBox(wrapped, title="Title", tlcorner="â•­", trcorner="â•®", blcorner="â•°", brcorner="╯") cols, rows = 11, 3 self.assertEqual((cols, rows), l.pack(())) canvas = l.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "╭─ Title ─╮", "│Some text│", "╰─────────╯", ], [line.decode("utf-8") for line in canvas.text], ) l.set_title("New") self.assertEqual( [ "╭── New ──╮", "│Some text│", "╰─────────╯", ], [line.decode("utf-8") for line in l.render(()).text], ) def test_negative(self): wrapped = urwid.Text("") with self.assertRaises(ValueError) as ctx: l = urwid.LineBox(wrapped, title="Title", tline="") self.assertEqual("Cannot have a title when tline is empty string", str(ctx.exception)) l = urwid.LineBox(wrapped, tline="") with self.assertRaises(ValueError) as ctx: l.set_title("Fail") self.assertEqual("Cannot set title when tline is unset", str(ctx.exception)) def test_partial_contour(self): def mark_pressed(btn: urwid.Button) -> None: nonlocal pressed pressed = True pressed = False wrapped = urwid.Button("Wrapped", on_press=mark_pressed) with self.subTest("No top line -> no top"): l = urwid.LineBox(wrapped, tline="") cols, rows = 13, 2 self.assertEqual((cols, rows), l.pack(())) canvas = l.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "│< Wrapped >│", "└───────────┘", ], [line.decode("utf-8") for line in canvas.text], ) self.assertIsNone(l.keypress((), "enter")) self.assertTrue(pressed) pressed = False with self.subTest("No right side elements -> no side"): l = urwid.LineBox(wrapped, rline="") cols, rows = 12, 3 self.assertEqual((cols, rows), l.pack(())) canvas = l.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "┌───────────", "│< Wrapped >", "└───────────", ], [line.decode("utf-8") for line in canvas.text], ) self.assertIsNone(l.keypress((), "enter")) self.assertTrue(pressed) pressed = False with self.subTest("No left side elements -> no side"): l = urwid.LineBox(wrapped, lline="") cols, rows = 12, 3 self.assertEqual((cols, rows), l.pack(())) canvas = l.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "───────────â”", "< Wrapped >│", "───────────┘", ], [line.decode("utf-8") for line in canvas.text], ) self.assertIsNone(l.keypress((), "enter")) self.assertTrue(pressed) pressed = False with self.subTest("No bottom line -> no bottom"): l = urwid.LineBox(wrapped, bline="") cols, rows = 13, 2 self.assertEqual((cols, rows), l.pack(())) canvas = l.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "┌───────────â”", "│< Wrapped >│", ], [line.decode("utf-8") for line in canvas.text], ) self.assertIsNone(l.keypress((), "enter")) self.assertTrue(pressed) pressed = False def test_columns_of_lineboxes(self): # BUG #748 # Using PACK: width of widget 1 is 4, width of widget 2 is 5. # With equal weight widget 1 will be rendered also 5. columns = urwid.Columns( [ (urwid.PACK, urwid.LineBox(urwid.Text("lol"), rline="", trcorner="", brcorner="")), (urwid.PACK, urwid.LineBox(urwid.Text("wtf"), tlcorner="┬", blcorner="â”´")), ] ) canvas = columns.render(()) self.assertEqual( [ "┌───┬───â”", "│lol│wtf│", "└───┴───┘", ], [line.decode("utf-8") for line in canvas.text], ) urwid-2.6.16/tests/test_listbox.py000066400000000000000000001255441470350774000172210ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid from tests.util import SelectableText class ListBoxCalculateVisibleTest(unittest.TestCase): def cvtest( self, desc: str, body, focus: int, offset_rows: int, inset_fraction, exp_offset_inset: int, exp_cur: tuple[int, int] | None, ) -> None: with self.subTest(desc): lbox = urwid.ListBox(urwid.SimpleListWalker(body)) lbox.body.set_focus(focus) lbox.offset_rows = offset_rows lbox.inset_fraction = inset_fraction middle, top, bottom = lbox.calculate_visible((4, 5), focus=1) offset_inset, focus_widget, focus_pos, _ign, cursor = middle if cursor is not None: x, y = cursor y += offset_inset cursor = x, y self.assertEqual(exp_offset_inset, offset_inset) self.assertEqual(exp_cur, cursor) def test1_simple(self): T = urwid.Text l = [T(""), T(""), T("\n"), T("\n\n"), T("\n"), T(""), T("")] self.cvtest( "simple top position", l, 3, 0, (0, 1), 0, None, ) self.cvtest( "simple middle position", l, 3, 1, (0, 1), 1, None, ) self.cvtest( "simple bottom position", l, 3, 2, (0, 1), 2, None, ) self.cvtest( "straddle top edge", l, 3, 0, (1, 2), -1, None, ) self.cvtest( "straddle bottom edge", l, 3, 4, (0, 1), 4, None, ) self.cvtest( "off bottom edge", l, 3, 5, (0, 1), 4, None, ) self.cvtest( "way off bottom edge", l, 3, 100, (0, 1), 4, None, ) self.cvtest( "gap at top", l, 0, 2, (0, 1), 0, None, ) self.cvtest( "gap at top and off bottom edge", l, 2, 5, (0, 1), 2, None, ) self.cvtest( "gap at bottom", l, 6, 1, (0, 1), 4, None, ) self.cvtest( "gap at bottom and straddling top edge", l, 4, 0, (1, 2), 1, None, ) self.cvtest( "gap at bottom cannot completely fill", [T(""), T(""), T("")], 1, 0, (0, 1), 1, None, ) self.cvtest( "gap at top and bottom", [T(""), T(""), T("")], 1, 2, (0, 1), 1, None, ) def test2_cursor(self): T, E = urwid.Text, urwid.Edit l1 = [T(""), T(""), T("\n"), E("", "\n\nX"), T("\n"), T(""), T("")] l2 = [T(""), T(""), T("\n"), E("", "YY\n\n"), T("\n"), T(""), T("")] l2[3].set_edit_pos(2) self.cvtest( "plain cursor in view", l1, 3, 1, (0, 1), 1, (1, 3), ) self.cvtest( "cursor off top", l2, 3, 0, (1, 3), 0, (2, 0), ) self.cvtest( "cursor further off top", l2, 3, 0, (2, 3), 0, (2, 0), ) self.cvtest( "cursor off bottom", l1, 3, 3, (0, 1), 2, (1, 4), ) self.cvtest( "cursor way off bottom", l1, 3, 100, (0, 1), 2, (1, 4), ) def test_sized(self): lbox = urwid.ListBox(urwid.SimpleListWalker([urwid.Text(str(num)) for num in range(5)])) self.assertEqual(5, len(lbox)) def test_not_sized(self): class TestWalker(urwid.ListWalker): @property def contents(self): return self @staticmethod def next_position(position: int) -> tuple[urwid.Text, int]: return urwid.Text(str(position)), position @staticmethod def prev_position(position: int) -> tuple[urwid.Text, int]: return urwid.Text(str(position)), position lbox = urwid.ListBox(TestWalker()) with self.assertRaises(AttributeError) as exc: len(lbox) self.assertEqual(f"{TestWalker.__name__} is not Sized", str(exc.exception)) class ListBoxChangeFocusTest(unittest.TestCase): def cftest( self, desc: str, body, pos: int, offset_inset: int, coming_from, cursor: tuple[int, int] | None, snap_rows, exp_offset_rows: int, exp_inset_fraction, exp_cur: tuple[int, int] | None, ): with self.subTest(desc): lbox = urwid.ListBox(urwid.SimpleListWalker(body)) lbox.change_focus((4, 5), pos, offset_inset, coming_from, cursor, snap_rows) exp = exp_offset_rows, exp_inset_fraction act = lbox.offset_rows, lbox.inset_fraction cursor = None focus_widget, focus_pos = lbox.body.get_focus() if focus_widget.selectable(): if hasattr(focus_widget, "get_cursor_coords"): cursor = focus_widget.get_cursor_coords((4,)) self.assertEqual(exp, act) self.assertEqual(exp_cur, cursor) def test1unselectable(self): T = urwid.Text l = [T("\n"), T("\n\n"), T("\n\n"), T("\n\n"), T("\n")] self.cftest( "simple unselectable", l, 2, 0, None, None, None, 0, (0, 1), None, ) self.cftest( "unselectable", l, 2, 1, None, None, None, 1, (0, 1), None, ) self.cftest( "unselectable off top", l, 2, -2, None, None, None, 0, (2, 3), None, ) self.cftest( "unselectable off bottom", l, 3, 2, None, None, None, 2, (0, 1), None, ) def test2selectable(self): T, S = urwid.Text, SelectableText l = [T("\n"), T("\n\n"), S("\n\n"), T("\n\n"), T("\n")] self.cftest( "simple selectable", l, 2, 0, None, None, None, 0, (0, 1), None, ) self.cftest( "selectable", l, 2, 1, None, None, None, 1, (0, 1), None, ) self.cftest( "selectable at top", l, 2, 0, "below", None, None, 0, (0, 1), None, ) self.cftest( "selectable at bottom", l, 2, 2, "above", None, None, 2, (0, 1), None, ) self.cftest( "selectable off top snap", l, 2, -1, "below", None, None, 0, (0, 1), None, ) self.cftest( "selectable off bottom snap", l, 2, 3, "above", None, None, 2, (0, 1), None, ) self.cftest( "selectable off top no snap", l, 2, -1, "above", None, None, 0, (1, 3), None, ) self.cftest( "selectable off bottom no snap", l, 2, 3, "below", None, None, 3, (0, 1), None, ) def test3large_selectable(self): T, S = urwid.Text, SelectableText l = [T("\n"), S("\n\n\n\n\n\n"), T("\n")] self.cftest( "large selectable no snap", l, 1, -1, None, None, None, 0, (1, 7), None, ) self.cftest( "large selectable snap up", l, 1, -2, "below", None, None, 0, (0, 1), None, ) self.cftest( "large selectable snap up2", l, 1, -2, "below", None, 2, 0, (0, 1), None, ) self.cftest( "large selectable almost snap up", l, 1, -2, "below", None, 1, 0, (2, 7), None, ) self.cftest( "large selectable snap down", l, 1, 0, "above", None, None, 0, (2, 7), None, ) self.cftest( "large selectable snap down2", l, 1, 0, "above", None, 2, 0, (2, 7), None, ) self.cftest( "large selectable almost snap down", l, 1, 0, "above", None, 1, 0, (0, 1), None, ) m = [T("\n\n\n\n"), S("\n\n\n\n\n"), T("\n\n\n\n")] self.cftest( "large selectable outside view down", m, 1, 4, "above", None, None, 0, (0, 1), None, ) self.cftest( "large selectable outside view up", m, 1, -5, "below", None, None, 0, (1, 6), None, ) def test4cursor(self): T, E = urwid.Text, urwid.Edit # ... def test5set_focus_valign(self): T, E = urwid.Text, urwid.Edit lbox = urwid.ListBox(urwid.SimpleFocusListWalker([T(""), T("")])) lbox.set_focus_valign("middle") # TODO: actually test the result class ListBoxRenderTest(unittest.TestCase): def ltest( self, desc: str, body, focus: int, offset_inset_rows: int, exp_text: list[bytes], exp_cur: tuple[int, int] | None, ) -> None: with self.subTest(desc): exp_text = [t.encode("iso8859-1") for t in exp_text] lbox = urwid.ListBox(urwid.SimpleListWalker(body)) lbox.body.set_focus(focus) lbox.shift_focus((4, 10), offset_inset_rows) canvas = lbox.render((4, 5), focus=1) self.assertEqual(exp_text, canvas.text) self.assertEqual(exp_cur, canvas.cursor) def test1_simple(self): T = urwid.Text self.ltest( "simple one text item render", [T("1\n2")], 0, 0, ["1 ", "2 ", " ", " ", " "], None, ) self.ltest( "simple multi text item render off bottom", [T("1"), T("2"), T("3\n4"), T("5"), T("6")], 2, 2, ["1 ", "2 ", "3 ", "4 ", "5 "], None, ) self.ltest( "simple multi text item render off top", [T("1"), T("2"), T("3\n4"), T("5"), T("6")], 2, 1, ["2 ", "3 ", "4 ", "5 ", "6 "], None, ) def test2_trim(self): T = urwid.Text self.ltest( "trim unfocused bottom", [T("1\n2"), T("3\n4"), T("5\n6")], 1, 2, ["1 ", "2 ", "3 ", "4 ", "5 "], None, ) self.ltest( "trim unfocused top", [T("1\n2"), T("3\n4"), T("5\n6")], 1, 1, ["2 ", "3 ", "4 ", "5 ", "6 "], None, ) self.ltest( "trim none full focus", [T("1\n2\n3\n4\n5")], 0, 0, ["1 ", "2 ", "3 ", "4 ", "5 "], None, ) self.ltest( "trim focus bottom", [T("1\n2\n3\n4\n5\n6")], 0, 0, ["1 ", "2 ", "3 ", "4 ", "5 "], None, ) self.ltest( "trim focus top", [T("1\n2\n3\n4\n5\n6")], 0, -1, ["2 ", "3 ", "4 ", "5 ", "6 "], None, ) self.ltest( "trim focus top and bottom", [T("1\n2\n3\n4\n5\n6\n7")], 0, -1, ["2 ", "3 ", "4 ", "5 ", "6 "], None, ) def test3_shift(self): T, E = urwid.Text, urwid.Edit self.ltest( "shift up one fit", [T("1\n2"), T("3"), T("4"), T("5"), T("6")], 4, 5, ["2 ", "3 ", "4 ", "5 ", "6 "], None, ) e = E("", "ab\nc", 1) e.set_edit_pos(2) self.ltest( "shift down one cursor over edge", [e, T("3"), T("4"), T("5\n6")], 0, -1, ["ab ", "c ", "3 ", "4 ", "5 "], (2, 0), ) self.ltest( "shift up one cursor over edge", [T("1\n2"), T("3"), T("4"), E("", "d\ne")], 3, 4, ["2 ", "3 ", "4 ", "d ", "e "], (1, 4), ) self.ltest( "shift none cursor top focus over edge", [E("", "ab\n"), T("3"), T("4"), T("5\n6")], 0, -1, [" ", "3 ", "4 ", "5 ", "6 "], (0, 0), ) e = E("", "abc\nd") e.set_edit_pos(3) self.ltest( "shift none cursor bottom focus over edge", [T("1\n2"), T("3"), T("4"), e], 3, 4, ["1 ", "2 ", "3 ", "4 ", "abc "], (3, 4), ) def test4_really_large_contents(self): T, E = urwid.Text, urwid.Edit self.ltest( "really large edit", [T("hello" * 100)], 0, 0, ["hell", "ohel", "lohe", "lloh", "ello"], None, ) self.ltest( "really large edit", [E("", "hello" * 100)], 0, 0, ["hell", "ohel", "lohe", "lloh", "llo "], (3, 4), ) class ListBoxKeypressTest(unittest.TestCase): def ktest( self, desc: str, key, body, focus: int, offset_inset: int, exp_focus: int, exp_offset_inset: int, exp_cur: tuple[int, int] | None, lbox=None, ) -> tuple[str | None, urwid.ListBox]: if lbox is None: lbox = urwid.ListBox(urwid.SimpleListWalker(body)) lbox.body.set_focus(focus) lbox.shift_focus((4, 10), offset_inset) ret_key = lbox.keypress((4, 5), key) middle, top, bottom = lbox.calculate_visible((4, 5), focus=1) offset_inset, focus_widget, focus_pos, _ign, cursor = middle if cursor is not None: x, y = cursor y += offset_inset cursor = x, y exp = exp_focus, exp_offset_inset act = focus_pos, offset_inset self.assertEqual(exp, act) self.assertEqual(exp_cur, cursor) return ret_key, lbox def test1_up(self): T, S, E = urwid.Text, SelectableText, urwid.Edit self.ktest( "direct selectable both visible", "up", [S(""), S("")], 1, 1, 0, 0, None, ) self.ktest( "selectable skip one all visible", "up", [S(""), T(""), S("")], 2, 2, 0, 0, None, ) key, lbox = self.ktest( "nothing above no scroll", "up", [S("")], 0, 0, 0, 0, None, ) self.assertEqual("up", key) key, lbox = self.ktest( "unselectable above no scroll", "up", [T(""), T(""), S("")], 2, 2, 2, 2, None, ) self.assertEqual("up", key) self.ktest( "unselectable above scroll 1", "up", [T(""), S(""), T("\n\n\n")], 1, 0, 1, 1, None, ) self.ktest( "selectable above scroll 1", "up", [S(""), S(""), T("\n\n\n")], 1, 0, 0, 0, None, ) self.ktest( "selectable above too far", "up", [S(""), T(""), S(""), T("\n\n\n")], 2, 0, 2, 1, None, ) self.ktest( "selectable above skip 1 scroll 1", "up", [S(""), T(""), S(""), T("\n\n\n")], 2, 1, 0, 0, None, ) self.ktest( "tall selectable above scroll 2", "up", [S(""), S("\n"), S(""), T("\n\n\n")], 2, 0, 1, 0, None, ) self.ktest( "very tall selectable above scroll 5", "up", [S(""), S("\n\n\n\n"), S(""), T("\n\n\n\n")], 2, 0, 1, 0, None, ) self.ktest( "very tall selected scroll within 1", "up", [S(""), S("\n\n\n\n\n")], 1, -1, 1, 0, None, ) self.ktest( "edit above pass cursor", "up", [E("", "abc"), E("", "de")], 1, 1, 0, 0, (2, 0), ) key, lbox = self.ktest( "edit too far above pass cursor A", "up", [E("", "abc"), T("\n\n\n\n"), E("", "de")], 2, 4, 1, 0, None, ) self.ktest( "edit too far above pass cursor B", "up", None, None, None, 0, 0, (2, 0), lbox, ) self.ktest( "within focus cursor made not visible", "up", [T("\n\n\n"), E("hi\n", "ab")], 1, 3, 0, 0, None, ) self.ktest( "within focus cursor made not visible (2)", "up", [T("\n\n\n\n"), E("hi\n", "ab")], 1, 3, 0, -1, None, ) self.ktest( "force focus unselectable", "up", [T("\n\n\n\n"), S("")], 1, 4, 0, 0, None, ) self.ktest( "pathological cursor widget", "up", [T("\n"), E("\n\n\n\n\n", "a")], 1, 4, 0, -1, None, ) self.ktest( "unselectable to unselectable", "up", [T(""), T(""), T(""), T(""), T(""), T(""), T("")], 2, 0, 1, 0, None, ) self.ktest( "unselectable over edge to same", "up", [T(""), T("12\n34"), T(""), T(""), T(""), T("")], 1, -1, 1, 0, None, ) key, lbox = self.ktest( "edit short between pass cursor A", "up", [E("", "abcd"), E("", "a"), E("", "def")], 2, 2, 1, 1, (1, 1), ) self.ktest( "edit short between pass cursor B", "up", None, None, None, 0, 0, (3, 0), lbox, ) e = E("", "\n\n\n\n\n") e.set_edit_pos(1) key, lbox = self.ktest( "edit cursor force scroll", "up", [e], 0, -1, 0, 0, (0, 0), ) self.assertEqual(0, lbox.inset_fraction[0]) def test2_down(self): T, S, E = urwid.Text, SelectableText, urwid.Edit self.ktest( "direct selectable both visible", "down", [S(""), S("")], 0, 0, 1, 1, None, ) self.ktest( "selectable skip one all visible", "down", [S(""), T(""), S("")], 0, 0, 2, 2, None, ) key, lbox = self.ktest( "nothing below no scroll", "down", [S("")], 0, 0, 0, 0, None, ) self.assertEqual("down", key) key, lbox = self.ktest( "unselectable below no scroll", "down", [S(""), T(""), T("")], 0, 0, 0, 0, None, ) self.assertEqual("down", key) self.ktest( "unselectable below scroll 1", "down", [T("\n\n\n"), S(""), T("")], 1, 4, 1, 3, None, ) self.ktest( "selectable below scroll 1", "down", [T("\n\n\n"), S(""), S("")], 1, 4, 2, 4, None, ) self.ktest( "selectable below too far", "down", [T("\n\n\n"), S(""), T(""), S("")], 1, 4, 1, 3, None, ) self.ktest( "selectable below skip 1 scroll 1", "down", [T("\n\n\n"), S(""), T(""), S("")], 1, 3, 3, 4, None, ) self.ktest( "tall selectable below scroll 2", "down", [T("\n\n\n"), S(""), S("\n"), S("")], 1, 4, 2, 3, None, ) self.ktest( "very tall selectable below scroll 5", "down", [T("\n\n\n\n"), S(""), S("\n\n\n\n"), S("")], 1, 4, 2, 0, None, ) self.ktest( "very tall selected scroll within 1", "down", [S("\n\n\n\n\n"), S("")], 0, 0, 0, -1, None, ) self.ktest( "edit below pass cursor", "down", [E("", "de"), E("", "abc")], 0, 0, 1, 1, (2, 1), ) key, lbox = self.ktest( "edit too far below pass cursor A", "down", [E("", "de"), T("\n\n\n\n"), E("", "abc")], 0, 0, 1, 0, None, ) self.ktest( "edit too far below pass cursor B", "down", None, None, None, 2, 4, (2, 4), lbox, ) odd_e = E("", "hi\nab") odd_e.set_edit_pos(2) # disble cursor movement in odd_e object odd_e.move_cursor_to_coords = lambda s, c, xy: 0 self.ktest( "within focus cursor made not visible", "down", [odd_e, T("\n\n\n\n")], 0, 0, 1, 1, None, ) self.ktest( "within focus cursor made not visible (2)", "down", [ odd_e, T("\n\n\n\n"), ], 0, 0, 1, 1, None, ) self.ktest( "force focus unselectable", "down", [S(""), T("\n\n\n\n")], 0, 0, 1, 0, None, ) odd_e.set_edit_text("hi\n\n\n\n\n") self.ktest( "pathological cursor widget", "down", [odd_e, T("\n")], 0, 0, 1, 4, None, ) self.ktest( "unselectable to unselectable", "down", [T(""), T(""), T(""), T(""), T(""), T(""), T("")], 4, 4, 5, 4, None, ) self.ktest( "unselectable over edge to same", "down", [T(""), T(""), T(""), T(""), T("12\n34"), T("")], 4, 4, 4, 3, None, ) key, lbox = self.ktest( "edit short between pass cursor A", "down", [E("", "abc"), E("", "a"), E("", "defg")], 0, 0, 1, 1, (1, 1), ) self.ktest( "edit short between pass cursor B", "down", None, None, None, 2, 2, (3, 2), lbox, ) e = E("", "\n\n\n\n\n") e.set_edit_pos(4) key, lbox = self.ktest( "edit cursor force scroll", "down", [e], 0, 0, 0, -1, (0, 4), ) self.assertEqual(1, lbox.inset_fraction[0]) def test3_page_up(self): T, S, E = urwid.Text, SelectableText, urwid.Edit self.ktest( "unselectable aligned to aligned", "page up", [T(""), T("\n"), T("\n\n"), T(""), T("\n"), T("\n\n")], 3, 0, 1, 0, None, ) self.ktest( "unselectable unaligned to aligned", "page up", [T(""), T("\n"), T("\n"), T("\n"), T("\n"), T("\n\n")], 3, -1, 1, 0, None, ) self.ktest( "selectable to unselectable", "page up", [T(""), T("\n"), T("\n"), T("\n"), S("\n"), T("\n\n")], 4, 1, 1, -1, None, ) self.ktest( "selectable to cut off selectable", "page up", [S("\n\n"), T("\n"), T("\n"), S("\n"), T("\n\n")], 3, 1, 0, -1, None, ) self.ktest( "seletable to selectable", "page up", [T("\n\n"), S("\n"), T("\n"), S("\n"), T("\n\n")], 3, 1, 1, 1, None, ) self.ktest( "within very long selectable", "page up", [S(""), S("\n\n\n\n\n\n\n\n"), T("\n")], 1, -6, 1, -1, None, ) e = E("", "\n\nab\n\n\n\n\ncd\n") e.set_edit_pos(11) self.ktest( "within very long cursor widget", "page up", [S(""), e, T("\n")], 1, -6, 1, -2, (2, 0), ) self.ktest( "pathological cursor widget", "page up", [T(""), E("\n\n\n\n\n\n\n\n", "ab"), T("")], 1, -5, 0, 0, None, ) e = E("", "\nab\n\n\n\n\ncd\n") e.set_edit_pos(10) self.ktest( "very long cursor widget snap", "page up", [T(""), e, T("\n")], 1, -5, 1, 0, (2, 1), ) self.ktest( "slight scroll selectable", "page up", [T("\n"), S("\n"), T(""), S(""), T("\n\n\n"), S("")], 5, 4, 3, 0, None, ) self.ktest( "scroll into snap region", "page up", [T("\n"), S("\n"), T(""), T(""), T("\n\n\n"), S("")], 5, 4, 1, 0, None, ) self.ktest( "mid scroll short", "page up", [T("\n"), T(""), T(""), S(""), T(""), T("\n"), S(""), T("\n")], 6, 2, 3, 1, None, ) self.ktest( "mid scroll long", "page up", [T("\n"), S(""), T(""), S(""), T(""), T("\n"), S(""), T("\n")], 6, 2, 1, 0, None, ) self.ktest( "mid scroll perfect", "page up", [T("\n"), S(""), S(""), S(""), T(""), T("\n"), S(""), T("\n")], 6, 2, 2, 0, None, ) self.ktest( "cursor move up fail short", "page up", [T("\n"), T("\n"), E("", "\nab"), T(""), T("")], 2, 1, 2, 4, (0, 4), ) self.ktest( "cursor force fail short", "page up", [T("\n"), T("\n"), E("\n", "ab"), T(""), T("")], 2, 1, 0, 0, None, ) odd_e = E("", "hi\nab") odd_e.set_edit_pos(2) # disble cursor movement in odd_e object odd_e.move_cursor_to_coords = lambda s, c, xy: 0 self.ktest( "cursor force fail long", "page up", [odd_e, T("\n"), T("\n"), T("\n"), S(""), T("\n")], 4, 2, 1, -1, None, ) self.ktest( "prefer not cut off", "page up", [S("\n"), T("\n"), S(""), T("\n\n"), S(""), T("\n")], 4, 2, 2, 1, None, ) self.ktest( "allow cut off", "page up", [S("\n"), T("\n"), T(""), T("\n\n"), S(""), T("\n")], 4, 2, 0, -1, None, ) self.ktest( "at top fail", "page up", [T("\n\n"), T("\n"), T("\n\n\n")], 0, 0, 0, 0, None, ) self.ktest( "all visible fail", "page up", [T("a"), T("\n")], 0, 0, 0, 0, None, ) self.ktest( "current ok fail", "page up", [T("\n\n"), S("hi")], 1, 3, 1, 3, None, ) self.ktest( "all visible choose top selectable", "page up", [T(""), S("a"), S("b"), S("c")], 3, 3, 1, 1, None, ) self.ktest( "bring in edge choose top", "page up", [S("b"), T("-"), S("-"), T("c"), S("d"), T("-")], 4, 3, 0, 0, None, ) self.ktest( "bring in edge choose top selectable", "page up", [T("b"), S("-"), S("-"), T("c"), S("d"), T("-")], 4, 3, 1, 1, None, ) def test4_page_down(self): T, S, E = urwid.Text, SelectableText, urwid.Edit self.ktest( "unselectable aligned to aligned", "page down", [T("\n\n"), T("\n"), T(""), T("\n\n"), T("\n"), T("")], 2, 4, 4, 3, None, ) self.ktest( "unselectable unaligned to aligned", "page down", [T("\n\n"), T("\n"), T("\n"), T("\n"), T("\n"), T("")], 2, 4, 4, 3, None, ) self.ktest( "selectable to unselectable", "page down", [T("\n\n"), S("\n"), T("\n"), T("\n"), T("\n"), T("")], 1, 2, 4, 4, None, ) self.ktest( "selectable to cut off selectable", "page down", [T("\n\n"), S("\n"), T("\n"), T("\n"), S("\n\n")], 1, 2, 4, 3, None, ) self.ktest( "seletable to selectable", "page down", [T("\n\n"), S("\n"), T("\n"), S("\n"), T("\n\n")], 1, 1, 3, 2, None, ) self.ktest( "within very long selectable", "page down", [T("\n"), S("\n\n\n\n\n\n\n\n"), S("")], 1, 2, 1, -3, None, ) e = E("", "\nab\n\n\n\n\ncd\n\n") e.set_edit_pos(2) self.ktest( "within very long cursor widget", "page down", [T("\n"), e, S("")], 1, 2, 1, -2, (1, 4), ) odd_e = E("", "ab\n\n\n\n\n\n\n\n\n") odd_e.set_edit_pos(1) # disble cursor movement in odd_e object odd_e.move_cursor_to_coords = lambda s, c, xy: 0 self.ktest( "pathological cursor widget", "page down", [T(""), odd_e, T("")], 1, 1, 2, 4, None, ) e = E("", "\nab\n\n\n\n\ncd\n") e.set_edit_pos(2) self.ktest( "very long cursor widget snap", "page down", [T("\n"), e, T("")], 1, 2, 1, -3, (1, 3), ) self.ktest( "slight scroll selectable", "page down", [S(""), T("\n\n\n"), S(""), T(""), S("\n"), T("\n")], 0, 0, 2, 4, None, ) self.ktest( "scroll into snap region", "page down", [S(""), T("\n\n\n"), T(""), T(""), S("\n"), T("\n")], 0, 0, 4, 3, None, ) self.ktest( "mid scroll short", "page down", [T("\n"), S(""), T("\n"), T(""), S(""), T(""), T(""), T("\n")], 1, 2, 4, 3, None, ) self.ktest( "mid scroll long", "page down", [T("\n"), S(""), T("\n"), T(""), S(""), T(""), S(""), T("\n")], 1, 2, 6, 4, None, ) self.ktest( "mid scroll perfect", "page down", [T("\n"), S(""), T("\n"), T(""), S(""), S(""), S(""), T("\n")], 1, 2, 5, 4, None, ) e = E("", "hi\nab") e.set_edit_pos(1) self.ktest( "cursor move up fail short", "page down", [T(""), T(""), e, T("\n"), T("\n")], 2, 1, 2, -1, (1, 0), ) odd_e = E("", "hi\nab") odd_e.set_edit_pos(1) # disble cursor movement in odd_e object odd_e.move_cursor_to_coords = lambda s, c, xy: 0 self.ktest( "cursor force fail short", "page down", [T(""), T(""), odd_e, T("\n"), T("\n")], 2, 2, 4, 3, None, ) self.ktest( "cursor force fail long", "page down", [T("\n"), S(""), T("\n"), T("\n"), T("\n"), E("hi\n", "ab")], 1, 2, 4, 4, None, ) self.ktest( "prefer not cut off", "page down", [T("\n"), S(""), T("\n\n"), S(""), T("\n"), S("\n")], 1, 2, 3, 3, None, ) self.ktest( "allow cut off", "page down", [T("\n"), S(""), T("\n\n"), T(""), T("\n"), S("\n")], 1, 2, 5, 4, None, ) self.ktest( "at bottom fail", "page down", [T("\n\n"), T("\n"), T("\n\n\n")], 2, 1, 2, 1, None, ) self.ktest( "all visible fail", "page down", [T("a"), T("\n")], 1, 1, 1, 1, None, ) self.ktest( "current ok fail", "page down", [S("hi"), T("\n\n")], 0, 0, 0, 0, None, ) self.ktest( "all visible choose last selectable", "page down", [S("a"), S("b"), S("c"), T("")], 0, 0, 2, 2, None, ) self.ktest( "bring in edge choose last", "page down", [T("-"), S("d"), T("c"), S("-"), T("-"), S("b")], 1, 1, 5, 4, None, ) self.ktest( "bring in edge choose last selectable", "page down", [T("-"), S("d"), T("c"), S("-"), S("-"), T("b")], 1, 1, 4, 3, None, ) class ZeroHeightContentsTest(unittest.TestCase): def test_listbox_pile(self): lb = urwid.ListBox(urwid.SimpleListWalker([urwid.Pile([])])) size = (2, 5) canvas = lb.render(size, focus=True) self.assertEqual([b" ", b" ", b" ", b" ", b" "], canvas.text) def test_listbox_text_pile_page_down(self): lb = urwid.ListBox(urwid.SimpleListWalker([urwid.Text("above"), urwid.Pile([])])) size = (7, 5) lb.keypress(size, "page down") self.assertEqual(lb.focus_position, 0) lb.keypress(size, "page down") # second one caused ListBox failure self.assertEqual(lb.focus_position, 0) canvas = lb.render(size, focus=True) self.assertEqual([b"above ", b" ", b" ", b" ", b" "], canvas.text) def test_listbox_text_pile_page_up(self): lb = urwid.ListBox(urwid.SimpleListWalker([urwid.Pile([]), urwid.Text("below")])) size = (7, 5) lb.set_focus(1) lb.keypress(size, "page up") self.assertEqual(lb.focus_position, 1) lb.keypress(size, "page up") # second one caused pile failure self.assertEqual(lb.focus_position, 1) canvas = lb.render(size, focus=True) self.assertEqual([b"below ", b" ", b" ", b" ", b" "], canvas.text) def test_listbox_text_pile_down(self): sp = urwid.Pile([]) sp.selectable = lambda: True # abuse our Pile lb = urwid.ListBox(urwid.SimpleListWalker([urwid.Text("above"), sp])) size = (7, 5) lb.keypress(size, "down") self.assertEqual(lb.focus_position, 0) lb.keypress(size, "down") self.assertEqual(lb.focus_position, 0) canvas = lb.render(size, focus=True) self.assertEqual([b"above ", b" ", b" ", b" ", b" "], canvas.text) def test_listbox_text_pile_up(self): sp = urwid.Pile([]) sp.selectable = lambda: True # abuse our Pile lb = urwid.ListBox(urwid.SimpleListWalker([sp, urwid.Text("below")])) size = (7, 5) lb.set_focus(1) lb.keypress(size, "up") self.assertEqual(lb.focus_position, 1) lb.keypress(size, "up") self.assertEqual(lb.focus_position, 1) canvas = lb.render(size, focus=True) self.assertEqual([b"below ", b" ", b" ", b" ", b" "], canvas.text) class ListBoxSetBodyTest(unittest.TestCase): def test_signal_connected(self): lb = urwid.ListBox([]) lb.body = urwid.SimpleListWalker([]) self.assertEqual( lb.body._urwid_signals["modified"][0][1], lb._invalidate, "outdated canvas cache reuse after ListWalker's contents modified", ) class TestListWalkerFromIterable(unittest.TestCase): def test_01_simple_list_walker(self): walker = urwid.SimpleListWalker(str(num) for num in range(5)) self.assertEqual(5, len(walker)) def test_02_simple_focus_list_walker(self): walker = urwid.SimpleFocusListWalker(str(num) for num in range(5)) self.assertEqual(5, len(walker)) urwid-2.6.16/tests/test_main_loop.py000066400000000000000000000074241470350774000175060ustar00rootroot00000000000000from __future__ import annotations import concurrent.futures import os import socket import sys import threading import typing import unittest.mock import urwid if typing.TYPE_CHECKING: from types import TracebackType IS_WINDOWS = sys.platform == "win32" class ClosingTemporaryFilesPair(typing.ContextManager[typing.Tuple[typing.TextIO, typing.TextIO]]): """File pair context manager that closes temporary files on exit. Since `sys.stdout` is TextIO, tests have to use compatible api for the proper behavior imitation. """ __slots__ = ("rd_s", "wr_s", "rd_f", "wr_f") def __init__(self) -> None: self.rd_s: socket.socket | None = None self.wr_s: socket.socket | None = None self.rd_f: typing.TextIO | None = None self.wr_f: typing.TextIO | None = None def __enter__(self) -> tuple[typing.TextIO, typing.TextIO]: self.rd_s, self.wr_s = socket.socketpair() self.rd_f = os.fdopen(self.rd_s.fileno(), "r", encoding="utf-8", closefd=False) self.wr_f = os.fdopen(self.wr_s.fileno(), "w", encoding="utf-8", closefd=False) return self.rd_f, self.wr_f def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: """Close everything explicit without waiting for garbage collected.""" if self.rd_f is not None and not self.rd_f.closed: self.rd_f.close() if self.rd_s is not None: self.rd_s.close() if self.wr_f is not None and not self.wr_f.closed: self.wr_f.close() if self.wr_s is not None: self.wr_s.close() def stop_screen_cb(*_args, **_kwargs) -> typing.NoReturn: raise urwid.ExitMainLoop class TestMainLoop(unittest.TestCase): @unittest.skipIf(IS_WINDOWS, "selectors for pipe are not supported on Windows") def test_watch_pipe(self): """Test watching pipe is stopped on explicit False only.""" evt = threading.Event() # We need thread synchronization outcome: list[bytes] = [] def pipe_cb(data: bytes) -> typing.Any: outcome.append(data) if not evt.is_set(): evt.set() if data == b"false": return False if data == b"true": return True if data == b"null": return None return object() def pipe_writer(fd: int) -> None: os.write(fd, b"something") if evt.wait(0.1): evt.clear() os.write(fd, b"true") if evt.wait(0.1): evt.clear() os.write(fd, b"null") if evt.wait(0.1): evt.clear() os.write(fd, b"false") with ClosingTemporaryFilesPair() as ( rd_r, wr_r, ), ClosingTemporaryFilesPair() as ( rd_w, wr_w, ), concurrent.futures.ThreadPoolExecutor( max_workers=1, ) as executor, unittest.mock.patch( "subprocess.Popen", # we want to be sure that nothing outside is called autospec=True, ): evl = urwid.MainLoop( urwid.SolidFill(), screen=urwid.display.raw.Screen(input=rd_r, output=wr_w), # We need screen which support mocked IO handle_mouse=False, # Less external calls - better ) evl.set_alarm_in(1, stop_screen_cb) pipe_fd = evl.watch_pipe(pipe_cb) executor.submit(pipe_writer, pipe_fd) evl.run() self.assertEqual([b"something", b"true", b"null", b"false"], outcome) not_removed = evl.remove_watch_pipe(pipe_fd) self.assertFalse(not_removed) urwid-2.6.16/tests/test_moved_imports.py000066400000000000000000000010321470350774000204050ustar00rootroot00000000000000from __future__ import annotations import unittest class TestMovedImports(unittest.TestCase): def test_moved_imports_direct(self) -> None: with self.assertWarns(DeprecationWarning): from urwid import web_display from urwid.display import web self.assertIs(web, web_display) def test_moved_imports_nested(self) -> None: from urwid.display import html_fragment from urwid.html_fragment import HtmlGenerator self.assertIs(html_fragment.HtmlGenerator, HtmlGenerator) urwid-2.6.16/tests/test_overlay.py000066400000000000000000000315061470350774000172100ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid class OverlayTest(unittest.TestCase): def test_sizing_flow_fixed(self) -> None: top_w = urwid.Text("Flow and Fixed widget") bottom_w = urwid.SolidFill("#") widgets = {"top_w": top_w, "bottom_w": bottom_w} for description, kwargs, sizing in ( ( "Fixed render no size", { **widgets, "align": urwid.CENTER, "width": None, "valign": urwid.MIDDLE, "height": None, }, frozenset((urwid.BOX, urwid.FIXED)), ), ( "Fixed render + corners", { **widgets, "align": urwid.CENTER, "width": None, "valign": urwid.MIDDLE, "height": None, "left": 1, "right": 1, "top": 1, "bottom": 1, }, frozenset((urwid.BOX, urwid.FIXED)), ), ( "Fixed render from FLOW", { **widgets, "align": urwid.CENTER, "width": 10, "valign": urwid.MIDDLE, "height": None, }, frozenset((urwid.BOX, urwid.FLOW, urwid.FIXED)), ), ( "Fixed render from FLOW + corners", { **widgets, "align": urwid.CENTER, "width": 10, "valign": urwid.MIDDLE, "height": None, "left": 1, "right": 1, "top": 1, "bottom": 1, }, frozenset((urwid.BOX, urwid.FLOW, urwid.FIXED)), ), ): with self.subTest(description): widget = urwid.Overlay(**kwargs) self.assertEqual(sizing, widget.sizing()) def_cols, def_rows = top_w.pack() if kwargs["width"] is None: args_cols = def_cols else: args_cols = kwargs["width"] cols = args_cols + kwargs.get("left", 0) + kwargs.get("right", 0) rows = top_w.rows((args_cols,)) + kwargs.get("top", 0) + kwargs.get("bottom", 0) self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("Fixed Relative"): min_width = 23 relative_width = 90 widget = urwid.Overlay( top_w, bottom_w, align=urwid.CENTER, width=(urwid.RELATIVE, relative_width), valign=urwid.MIDDLE, height=None, min_width=23, ) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW, urwid.FIXED)), widget.sizing()) cols = int(min_width * 100 / relative_width + 0.5) rows = top_w.rows((min_width,)) self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual("#Flow and Fixed widget ##", str(canvas)) with self.subTest("Flow Relative"): cols = 25 min_width = 23 relative_width = 90 widget = urwid.Overlay( top_w, bottom_w, align=urwid.CENTER, width=(urwid.RELATIVE, relative_width), valign=urwid.MIDDLE, height=None, min_width=23, ) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW, urwid.FIXED)), widget.sizing()) rows = top_w.rows((min_width,)) self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual("#Flow and Fixed widget #", str(canvas)) with self.subTest("Fixed Relative + corners"): min_width = 23 relative_width = 90 widget = urwid.Overlay( top_w, bottom_w, align=urwid.CENTER, width=(urwid.RELATIVE, relative_width), valign=urwid.MIDDLE, height=None, min_width=23, top=1, bottom=1, ) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW, urwid.FIXED)), widget.sizing()) cols = int(min_width * 100 / relative_width + 0.5) rows = top_w.rows((min_width,)) + 2 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( "##########################\n" "#Flow and Fixed widget ##\n" "##########################", str(canvas), ) with self.subTest("Fixed Relative + corners"): cols = 25 min_width = 23 relative_width = 90 widget = urwid.Overlay( top_w, bottom_w, align=urwid.CENTER, width=(urwid.RELATIVE, relative_width), valign=urwid.MIDDLE, height=None, min_width=23, top=1, bottom=1, ) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW, urwid.FIXED)), widget.sizing()) rows = top_w.rows((min_width,)) + 2 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( "#########################\n" "#Flow and Fixed widget #\n" "#########################", str(canvas), ) def test_sizing_box_fixed_given(self): top_w = urwid.SolidFill("*") bottom_w = urwid.SolidFill("#") min_width = 5 min_height = 3 widget = urwid.Overlay( top_w, bottom_w, align=urwid.CENTER, width=min_width, valign=urwid.MIDDLE, height=min_height, top=1, bottom=1, left=2, right=2, ) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW, urwid.FIXED)), widget.sizing()) cols = min_width + 4 rows = min_height + 2 for description, call_args in ( ("All GIVEN FIXED", ()), ("ALL GIVEN FLOW", (cols,)), ("ALL GIVEN BOX", (cols, rows)), ): with self.subTest(description): self.assertEqual((cols, rows), widget.pack(call_args)) canvas = widget.render(call_args) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "#########", "##*****##", "##*****##", "##*****##", "#########", ], [line.decode("utf-8") for line in canvas.text], ) def test_sizing_box_fixed_relative(self): top_w = urwid.SolidFill("*") bottom_w = urwid.SolidFill("#") relative_width = 50 relative_height = 50 min_width = 4 min_height = 2 widget = urwid.Overlay( top_w, bottom_w, align=urwid.CENTER, width=(urwid.RELATIVE, relative_width), valign=urwid.MIDDLE, height=(urwid.RELATIVE, relative_height), min_width=min_width, min_height=min_height, top=1, bottom=1, left=2, right=2, ) cols = int(min_width * 100 / relative_width + 0.5) rows = int(min_height * 100 / relative_height + 0.5) for description, call_args in ( ("All GIVEN FIXED", ()), ("ALL GIVEN FLOW", (cols,)), ("ALL GIVEN BOX", (cols, rows)), ): with self.subTest(description): self.assertEqual(frozenset((urwid.BOX, urwid.FLOW, urwid.FIXED)), widget.sizing()) self.assertEqual((cols, rows), widget.pack(call_args)) canvas = widget.render(call_args) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "########", "##****##", "##****##", "########", ], [line.decode("utf-8") for line in canvas.text], ) def test_relative(self): ovl = urwid.Overlay( urwid.Text("aaa"), urwid.SolidFill(urwid.SolidFill.Symbols.LITE_SHADE), width=urwid.PACK, height=urwid.PACK, align=(urwid.RELATIVE, 30), valign=(urwid.RELATIVE, 70), ) self.assertEqual( ovl.contents[1][1], (urwid.RELATIVE, 30, urwid.PACK, None, None, 0, 0, urwid.RELATIVE, 70, urwid.PACK, None, None, 0, 0), ) self.assertEqual( ( "â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", "â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", "â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", "â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", "â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", "â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", "â–‘â–‘â–‘â–‘â–‘aaaâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", "â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", "â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", "â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘", ), ovl.render((20, 10)).decoded_text, ) def test_old_params(self): o1 = urwid.Overlay( urwid.SolidFill("X"), urwid.SolidFill("O"), ("fixed left", 5), ("fixed right", 4), ("fixed top", 3), ("fixed bottom", 2), ) self.assertEqual( o1.contents[1][1], ("left", None, "relative", 100, None, 5, 4, "top", None, "relative", 100, None, 3, 2), ) o2 = urwid.Overlay( urwid.SolidFill("X"), urwid.SolidFill("O"), ("fixed right", 5), ("fixed left", 4), ("fixed bottom", 3), ("fixed top", 2), ) self.assertEqual( o2.contents[1][1], ("right", None, "relative", 100, None, 4, 5, "bottom", None, "relative", 100, None, 2, 3), ) def test_get_cursor_coords(self): self.assertEqual( urwid.Overlay( urwid.Filler(urwid.Edit()), urwid.SolidFill("B"), "right", 1, "bottom", 1, ).get_cursor_coords((2, 2)), (1, 1), ) def test_length(self): ovl = urwid.Overlay( urwid.SolidFill("X"), urwid.SolidFill("O"), "center", ("relative", 20), "middle", ("relative", 20), ) self.assertEqual(2, len(ovl)) self.assertEqual(2, len(ovl.contents)) def test_common(self): s1 = urwid.SolidFill("1") s2 = urwid.SolidFill("2") o = urwid.Overlay(s1, s2, "center", ("relative", 50), "middle", ("relative", 50)) self.assertEqual(o.focus, s1) self.assertEqual(o.focus_position, 1) self.assertRaises(IndexError, lambda: setattr(o, "focus_position", None)) self.assertRaises(IndexError, lambda: setattr(o, "focus_position", 2)) self.assertEqual(o.contents[0], (s2, urwid.Overlay._DEFAULT_BOTTOM_OPTIONS)) self.assertEqual( o.contents[1], (s1, ("center", None, "relative", 50, None, 0, 0, "middle", None, "relative", 50, None, 0, 0)), ) urwid-2.6.16/tests/test_padding.py000066400000000000000000000257211470350774000171370ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid class PaddingTest(unittest.TestCase): def test_sizing(self) -> None: fixed_only = urwid.BigText("3", urwid.HalfBlock5x4Font()) fixed_flow = urwid.Text("Some text", align=urwid.CENTER) flow_only = urwid.ProgressBar(None, None) with self.subTest("Fixed only"): widget = urwid.Padding(fixed_only) self.assertEqual(frozenset((urwid.FIXED,)), widget.sizing()) cols, rows = 5, 4 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "▄▀▀▄ ", " â–„â–€ ", "â–„ â–ˆ ", " ▀▀ ", ], [line.decode("utf-8") for line in canvas.text], ) with self.subTest("Fixed only + relative size & default align"): widget = urwid.Padding(fixed_only, width=(urwid.RELATIVE, 50)) self.assertEqual(frozenset((urwid.FIXED,)), widget.sizing()) cols, rows = 10, 4 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "▄▀▀▄ ", " â–„â–€ ", "â–„ â–ˆ ", " ▀▀ ", ], [line.decode("utf-8") for line in canvas.text], ) with self.subTest("FIXED/FLOW"): widget = urwid.Padding(fixed_flow) self.assertEqual(frozenset((urwid.FIXED, urwid.FLOW)), widget.sizing()) cols, rows = 9, 1 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( ["Some text"], [line.decode("utf-8") for line in canvas.text], ) with self.subTest("FIXED/FLOW + relative size & right align"): widget = urwid.Padding(fixed_flow, width=(urwid.RELATIVE, 65), align=urwid.RIGHT) self.assertEqual(frozenset((urwid.FIXED, urwid.FLOW)), widget.sizing()) cols, rows = 14, 1 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [" Some text"], [line.decode("utf-8") for line in canvas.text], ) with self.subTest("GIVEN FLOW make FIXED"): widget = urwid.Padding(flow_only, width=5, align=urwid.RIGHT, right=1) self.assertEqual(frozenset((urwid.FIXED, urwid.FLOW)), widget.sizing()) cols, rows = 6, 1 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [" 0 % "], [line.decode("utf-8") for line in canvas.text], ) with self.subTest("GIVEN FIXED = error"): widget = urwid.Padding(fixed_only, width=5) with self.assertWarns(urwid.widget.padding.PaddingWarning) as ctx, self.assertRaises(AttributeError): widget.sizing() self.assertEqual( f"WHSettings.GIVEN expect BOX or FLOW widget to be used, but received {fixed_only}", str(ctx.warnings[0].message), ) widget.pack(()) self.assertEqual( f"WHSettings.GIVEN expect BOX or FLOW widget to be used, but received {fixed_only}", str(ctx.warnings[1].message), ) with self.assertRaises(ValueError) as err_ctx: widget.render(()) self.assertEqual( "FixedWidget takes only () for size.passed: (5,)", str(err_ctx.exception), ) def test_fixed(self) -> None: """Test real-world like scenario with padded contents.""" col_list = [ urwid.SolidFill(), urwid.Button("OK", align=urwid.CENTER), urwid.SolidFill(), urwid.Button("Cancel", align=urwid.CENTER), urwid.SolidFill(), ] body = urwid.Pile( ( (urwid.Text("Window content text here and it should not touch line", align=urwid.CENTER)), (urwid.PACK, urwid.Columns(col_list, dividechars=1, box_columns=(0, 2, 4))), ) ) widget = urwid.LineBox( urwid.Pile( ( urwid.Text("Modal window", align=urwid.CENTER), urwid.Divider("─"), urwid.Padding(body, width=urwid.PACK, left=1, right=1), ) ) ) self.assertEqual(frozenset((urwid.FIXED, urwid.FLOW)), widget.sizing()) cols, rows = 57, 6 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "┌───────────────────────────────────────────────────────â”", "│ Modal window │", "│───────────────────────────────────────────────────────│", "│ Window content text here and it should not touch line │", "│ < OK > < Cancel > │", "└───────────────────────────────────────────────────────┘", ], [line.decode("utf-8") for line in canvas.text], ) # Forward keypress self.assertEqual("OK", body.focus.focus.label) widget.keypress((), "right") self.assertEqual("Cancel", body.focus.focus.label) def test_insufficient_space(self): width = 10 widget = urwid.Padding(urwid.Text("Some text"), width=width) with self.assertWarns(urwid.widget.PaddingWarning) as ctx: canvas = widget.render((width - 1,)) self.assertEqual("Some text", str(canvas)) self.assertEqual(width - 1, canvas.cols()) def ptest(self, desc, align, width, maxcol, left, right, min_width=None): p = urwid.Padding(None, align, width, min_width) l, r = p.padding_values((maxcol,), False) assert (l, r) == (left, right), f"{desc} expected {left, right} but got {l, r}" def petest(self, desc, align, width): self.assertRaises(urwid.PaddingError, lambda: urwid.Padding(None, align, width)) def test_create(self): self.petest("invalid pad", 6, 5) self.petest("invalid pad type", ("bad", 2), 5) self.petest("invalid width", "center", "42") self.petest("invalid width type", "center", ("gouranga", 4)) def test_values(self): self.ptest("left align 5 7", "left", 5, 7, 0, 2) self.ptest("left align 7 7", "left", 7, 7, 0, 0) self.ptest("left align 9 7", "left", 9, 7, 0, 0) self.ptest("right align 5 7", "right", 5, 7, 2, 0) self.ptest("center align 5 7", "center", 5, 7, 1, 1) self.ptest("fixed left", ("fixed left", 3), 5, 10, 3, 2) self.ptest("fixed left reduce", ("fixed left", 3), 8, 10, 2, 0) self.ptest("fixed left shrink", ("fixed left", 3), 18, 10, 0, 0) self.ptest("fixed left, right", ("fixed left", 3), ("fixed right", 4), 17, 3, 4) self.ptest("fixed left, right, min_width", ("fixed left", 3), ("fixed right", 4), 10, 3, 2, 5) self.ptest("fixed left, right, min_width 2", ("fixed left", 3), ("fixed right", 4), 10, 2, 0, 8) self.ptest("fixed right", ("fixed right", 3), 5, 10, 2, 3) self.ptest("fixed right reduce", ("fixed right", 3), 8, 10, 0, 2) self.ptest("fixed right shrink", ("fixed right", 3), 18, 10, 0, 0) self.ptest("fixed right, left", ("fixed right", 3), ("fixed left", 4), 17, 4, 3) self.ptest("fixed right, left, min_width", ("fixed right", 3), ("fixed left", 4), 10, 2, 3, 5) self.ptest("fixed right, left, min_width 2", ("fixed right", 3), ("fixed left", 4), 10, 0, 2, 8) self.ptest("relative 30", ("relative", 30), 5, 10, 1, 4) self.ptest("relative 50", ("relative", 50), 5, 10, 2, 3) self.ptest("relative 130 edge", ("relative", 130), 5, 10, 5, 0) self.ptest("relative -10 edge", ("relative", -10), 4, 10, 0, 6) self.ptest("center relative 70", "center", ("relative", 70), 10, 1, 2) self.ptest("center relative 70 grow 8", "center", ("relative", 70), 10, 1, 1, 8) def mctest(self, desc, left, right, size, cx, innercx): class Inner: def __init__(self, desc, innercx): self.desc = desc self.innercx = innercx def move_cursor_to_coords(self, size, cx, cy): assert cx == self.innercx, desc i = Inner(desc, innercx) p = urwid.Padding(i, ("fixed left", left), ("fixed right", right)) p.move_cursor_to_coords(size, cx, 0) def test_cursor(self): self.mctest("cursor left edge", 2, 2, (10, 2), 2, 0) self.mctest("cursor left edge-1", 2, 2, (10, 2), 1, 0) self.mctest("cursor right edge", 2, 2, (10, 2), 7, 5) self.mctest("cursor right edge+1", 2, 2, (10, 2), 8, 5) def test_reduced_padding_cursor(self): # FIXME: This is at least consistent now, but I don't like it. # pack() on an Edit should leave room for the cursor # fixing this gets deep into things like Edit._shift_view_to_cursor # though, so this might not get fixed for a while p = urwid.Padding(urwid.Edit("", ""), width="pack", left=4) self.assertEqual(p.render((10,), True).cursor, None) self.assertEqual(p.get_cursor_coords((10,)), None) self.assertEqual(p.render((4,), True).cursor, None) self.assertEqual(p.get_cursor_coords((4,)), None) p = urwid.Padding(urwid.Edit("", ""), width=("relative", 100), left=4) self.assertEqual(p.render((10,), True).cursor, (4, 0)) self.assertEqual(p.get_cursor_coords((10,)), (4, 0)) self.assertEqual(p.render((4,), True).cursor, None) self.assertEqual(p.get_cursor_coords((4,)), None) urwid-2.6.16/tests/test_pile.py000066400000000000000000000457501470350774000164660ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid from tests.util import SelectableText class PileTest(unittest.TestCase): def test_basic_sizing(self) -> None: box_only = urwid.SolidFill("#") flow_only = urwid.ProgressBar(None, None) fixed_only = urwid.BigText("0", urwid.Thin3x3Font()) flow_fixed = urwid.Text("text") with self.subTest("BOX-only widget"): widget = urwid.Pile((box_only,)) self.assertEqual(frozenset((urwid.BOX,)), widget.sizing()) cols, rows = 2, 2 self.assertEqual((cols, rows), widget.pack((cols, rows))) canvas = widget.render((cols, rows)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("GIVEN BOX -> BOX/FLOW"): widget = urwid.Pile(((2, box_only),)) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW)), widget.sizing()) cols, rows = 2, 5 self.assertEqual((cols, rows), widget.pack((cols, rows))) canvas = widget.render((cols, rows)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) cols, rows = 5, 2 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("FLOW-only"): widget = urwid.Pile((flow_only,)) self.assertEqual(frozenset((urwid.FLOW,)), widget.sizing()) cols, rows = 5, 1 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) with self.subTest("FIXED -> FIXED"): widget = urwid.Pile(((urwid.PACK, fixed_only),)) self.assertEqual(frozenset((urwid.FIXED,)), widget.sizing()) cols, rows = 3, 3 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "┌─â”", "│ │", "└─┘", ], [line.decode("utf-8") for line in canvas.text], ) with self.subTest("FLOW/FIXED -> FLOW/FIXED"): widget = urwid.Pile(((urwid.PACK, flow_fixed),)) self.assertEqual(frozenset((urwid.FLOW, urwid.FIXED)), widget.sizing()) cols, rows = 4, 1 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( ["text"], [line.decode("utf-8") for line in widget.render(()).text], ) cols, rows = 2, 2 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "te", "xt", ], [line.decode("utf-8") for line in canvas.text], ) with self.subTest("FLOW + FLOW/FIXED -> FLOW/FIXED"): widget = urwid.Pile((flow_only, (urwid.PACK, flow_fixed))) self.assertEqual(frozenset((urwid.FLOW, urwid.FIXED)), widget.sizing()) cols, rows = 4, 2 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ " 0 %", "text", ], [line.decode("utf-8") for line in canvas.text], ) cols, rows = 2, 3 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "0 ", "te", "xt", ], [line.decode("utf-8") for line in canvas.text], ) with self.subTest("FLOW + FIXED widgets -> FLOW/FIXED"): widget = urwid.Pile((flow_only, (urwid.PACK, fixed_only))) self.assertEqual(frozenset((urwid.FLOW, urwid.FIXED)), widget.sizing()) cols, rows = 3, 4 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "0 %", "┌─â”", "│ │", "└─┘", ], [line.decode("utf-8") for line in canvas.text], ) cols, rows = 10, 4 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ " 0 % ", "┌─â”", "│ │", "└─┘", ], [line.decode("utf-8") for line in canvas.text], ) with self.subTest("GIVEN BOX + FIXED widgets -> BOX/FLOW/FIXED"): widget = urwid.Pile(((1, box_only), (urwid.PACK, fixed_only), (1, box_only))) self.assertEqual(frozenset((urwid.BOX, urwid.FLOW, urwid.FIXED)), widget.sizing()) cols, rows = 3, 5 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "###", "┌─â”", "│ │", "└─┘", "###", ], [line.decode("utf-8") for line in canvas.text], ) cols, rows = 5, 5 self.assertEqual((cols, rows), widget.pack((cols,))) canvas = widget.render((cols,)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "#####", "┌─â”", "│ │", "└─┘", "#####", ], [line.decode("utf-8") for line in canvas.text], ) cols, rows = 5, 6 self.assertEqual((cols, rows), widget.pack((cols, rows))) canvas = widget.render((cols, rows)) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "#####", "┌─â”", "│ │", "└─┘", "#####", " ", ], [line.decode("utf-8") for line in canvas.text], ) def test_pack_render_fixed(self) -> None: """Potential real world case""" widget = urwid.LineBox( urwid.Pile( ( urwid.Text("Modal window", align=urwid.CENTER), urwid.Divider("─"), urwid.Columns( (urwid.Button(label, align=urwid.CENTER) for label in ("OK", "Cancel", "Help")), dividechars=1, ), ) ) ) cols, rows = 34, 5 self.assertEqual((cols, rows), widget.pack(())) canvas = widget.render(()) self.assertEqual(cols, canvas.cols()) self.assertEqual(rows, canvas.rows()) self.assertEqual( [ "┌────────────────────────────────â”", "│ Modal window │", "│────────────────────────────────│", "│< OK > < Cancel > < Help >│", "└────────────────────────────────┘", ], [line.decode("utf-8") for line in canvas.text], ) self.assertEqual("OK", widget.focus.focus.label) widget.keypress((), "right") self.assertEqual("Cancel", widget.focus.focus.label) def test_not_a_widget(self): class NotAWidget: __slots__ = ("name", "symbol") def __init__(self, name: str, symbol: bytes) -> None: self.name = name self.symbol = symbol def __repr__(self) -> str: return f"{self.__class__.__name__}(name={self.name!r}, symbol={self.symbol!r})" def selectable(self) -> bool: return False def pack(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> int: if len(max_col_row) == 2: return max_col_row return max_col_row[0], self.rows(max_col_row) def rows(self, max_col_row: tuple[int], focus=False) -> int: return 1 def render(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> urwid.Canvas: maxcol = max_col_row[0] line = self.symbol * maxcol if len(max_col_row) == 1: return urwid.TextCanvas((line,), maxcol=maxcol) return urwid.TextCanvas((line,) * max_col_row[1], maxcol=maxcol) with self.subTest("Box"), self.assertWarns(urwid.widget.PileWarning) as ctx: items = (NotAWidget("First", b"*"), NotAWidget("Second", b"^")) widget = urwid.Pile(items) self.assertEqual(("****", "^^^^"), widget.render((4, 2)).decoded_text) self.assertEqual(f"{items[0]!r} is not a Widget", str(ctx.warnings[0].message)) self.assertEqual(f"{items[1]!r} is not a Widget", str(ctx.warnings[1].message)) with self.subTest("Flow"), self.assertWarns(urwid.widget.PileWarning) as ctx: items = (NotAWidget("First", b"*"), NotAWidget("Second", b"^")) widget = urwid.Pile(items) self.assertEqual(("******", "^^^^^^"), widget.render((6,)).decoded_text) self.assertEqual(f"{items[0]!r} is not a Widget", str(ctx.warnings[0].message)) self.assertEqual(f"{items[1]!r} is not a Widget", str(ctx.warnings[1].message)) def ktest(self, desc, contents, focus_item, key, rkey, rfocus, rpref_col): p = urwid.Pile(contents, focus_item) rval = p.keypress((20,), key) assert rkey == rval, f"{desc} key expected {rkey!r} but got {rval!r}" new_focus = contents.index(p.focus) assert new_focus == rfocus, f"{desc} focus expected {rfocus!r} but got {new_focus!r}" new_pref = p.get_pref_col((20,)) assert new_pref == rpref_col, f"{desc} pref_col expected {rpref_col!r} but got {new_pref!r}" def test_select_change(self): self.ktest("simple up", [SelectableText("")], 0, "up", "up", 0, 0) self.ktest("simple down", [SelectableText("")], 0, "down", "down", 0, 0) self.ktest("ignore up", [urwid.Text(""), SelectableText("")], 1, "up", "up", 1, 0) self.ktest("ignore down", [SelectableText(""), urwid.Text("")], 0, "down", "down", 0, 0) self.ktest("step up", [SelectableText(""), SelectableText("")], 1, "up", None, 0, 0) self.ktest("step down", [SelectableText(""), SelectableText("")], 0, "down", None, 1, 0) self.ktest("skip step up", [SelectableText(""), urwid.Text(""), SelectableText("")], 2, "up", None, 0, 0) self.ktest("skip step down", [SelectableText(""), urwid.Text(""), SelectableText("")], 0, "down", None, 2, 0) self.ktest( "pad skip step up", [urwid.Text(""), SelectableText(""), urwid.Text(""), SelectableText("")], 3, "up", None, 1, 0, ) self.ktest( "pad skip step down", [SelectableText(""), urwid.Text(""), SelectableText(""), urwid.Text("")], 0, "down", None, 2, 0, ) self.ktest( "padi skip step up", [SelectableText(""), urwid.Text(""), SelectableText(""), urwid.Text(""), SelectableText("")], 4, "up", None, 2, 0, ) self.ktest( "padi skip step down", [SelectableText(""), urwid.Text(""), SelectableText(""), urwid.Text(""), SelectableText("")], 0, "down", None, 2, 0, ) e = urwid.Edit("", "abcd", edit_pos=1) e.keypress((20,), "right") # set a pref_col self.ktest("pref step up", [SelectableText(""), urwid.Text(""), e], 2, "up", None, 0, 2) self.ktest("pref step down", [e, urwid.Text(""), SelectableText("")], 0, "down", None, 2, 2) z = urwid.Edit("", "1234") self.ktest("prefx step up", [z, urwid.Text(""), e], 2, "up", None, 0, 2) assert z.get_pref_col((20,)) == 2 z = urwid.Edit("", "1234") self.ktest("prefx step down", [e, urwid.Text(""), z], 0, "down", None, 2, 2) assert z.get_pref_col((20,)) == 2 def test_init_with_a_generator(self): urwid.Pile(urwid.Text(c) for c in "ABC") def test_change_focus_with_mouse(self): p = urwid.Pile([urwid.Edit(), urwid.Edit()]) self.assertEqual(p.focus_position, 0) p.mouse_event((10,), "button press", 1, 1, 1, True) self.assertEqual(p.focus_position, 1) def test_zero_weight(self): p = urwid.Pile( [ urwid.SolidFill("a"), ("weight", 0, urwid.SolidFill("d")), ] ) p.render((5, 4)) def test_mouse_event_in_empty_pile(self): p = urwid.Pile([]) p.mouse_event((5,), "button press", 1, 1, 1, False) p.mouse_event((5,), "button press", 1, 1, 1, True) def test_length(self): pile = urwid.Pile(urwid.Text(c) for c in "ABC") self.assertEqual(3, len(pile)) self.assertEqual(3, len(pile.contents)) def test_common(self): t1 = urwid.Text("one") t2 = urwid.Text("two") t3 = urwid.Text("three") sf = urwid.SolidFill("x") p = urwid.Pile([]) with self.subTest("Focus"): self.assertEqual(p.focus, None) self.assertRaises(IndexError, lambda: getattr(p, "focus_position")) self.assertRaises(IndexError, lambda: setattr(p, "focus_position", None)) self.assertRaises(IndexError, lambda: setattr(p, "focus_position", 0)) with self.subTest("Contents change"): p.contents = [(t1, ("pack", None)), (t2, ("pack", None)), (sf, ("given", 3)), (t3, ("pack", None))] p.focus_position = 1 del p.contents[0] self.assertEqual(p.focus_position, 0) p.contents[0:0] = [(t3, ("pack", None)), (t2, ("pack", None))] p.contents.insert(3, (t1, ("pack", None))) self.assertEqual(p.focus_position, 2) with self.subTest("Contents change validation"): p.contents.clear() self.assertRaises(urwid.PileError, lambda: p.contents.append(t1)) self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, None))) self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, "given"))) self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, ("given",)))) # Incorrect kind self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, ("what", 0)))) # incorrect size type self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, ("given", ())))) # incorrect size self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, ("given", -1)))) # Float and int weight accepted p.contents.append((t1, ("weight", 1))) p.contents.append((t2, ("weight", 0.5))) self.assertEqual(("one", "two"), p.render((3,)).decoded_text) def test_focus_position(self): t1 = urwid.Text("one") t2 = urwid.Text("two") p = urwid.Pile([t1, t2]) self.assertEqual(p.focus, t1) self.assertEqual(p.focus_position, 0) p.focus_position = 1 self.assertEqual(p.focus, t2) self.assertEqual(p.focus_position, 1) p.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(p, "focus_position", -1)) self.assertRaises(IndexError, lambda: setattr(p, "focus_position", 2)) def test_deprecated(self): t1 = urwid.Text("one") t2 = urwid.Text("two") p = urwid.Pile([t1, t2]) # old methods: with self.subTest("Focus"): p.set_focus(0) self.assertRaises(IndexError, lambda: p.set_focus(-1)) self.assertRaises(IndexError, lambda: p.set_focus(2)) p.set_focus(t2) self.assertEqual(p.focus_position, 1) self.assertRaises(ValueError, lambda: p.set_focus("nonexistant")) with self.subTest("Contents"): self.assertEqual(p.widget_list, [t1, t2]) self.assertEqual(p.item_types, [("weight", 1), ("weight", 1)]) with self.subTest("Contents change"): p.widget_list = [t2, t1] self.assertEqual(p.widget_list, [t2, t1]) self.assertEqual(p.contents, [(t2, ("weight", 1)), (t1, ("weight", 1))]) self.assertEqual(p.focus_position, 1) # focus unchanged p.item_types = [("flow", None), ("weight", 2)] self.assertEqual(p.item_types, [("flow", None), ("weight", 2)]) self.assertEqual(p.contents, [(t2, ("pack", None)), (t1, ("weight", 2))]) self.assertEqual(p.focus_position, 1) # focus unchanged with self.subTest("Contents change 2"): p.widget_list = [t1] self.assertEqual(len(p.contents), 1) self.assertEqual(p.focus_position, 0) p.widget_list.extend([t2, t1]) self.assertEqual(len(p.contents), 3) self.assertEqual(p.item_types, [("flow", None), ("weight", 1), ("weight", 1)]) p.item_types[:] = [("weight", 2)] self.assertEqual(len(p.contents), 1) urwid-2.6.16/tests/test_raw_display.py000066400000000000000000000007021470350774000200370ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid class TestRawDisplay(unittest.TestCase): def test_attrspec_to_escape(self): s = urwid.display.raw.Screen() s.set_terminal_properties(colors=256) a2e = s._attrspec_to_escape self.assertEqual("\x1b[0;33;42m", a2e(s.AttrSpec("brown", "dark green"))) self.assertEqual("\x1b[0;38;5;229;4;48;5;164m", a2e(s.AttrSpec("#fea,underline", "#d0d"))) urwid-2.6.16/tests/test_scrollable.py000066400000000000000000000532011470350774000176450ustar00rootroot00000000000000from __future__ import annotations import string import typing import unittest import urwid if typing.TYPE_CHECKING: from collections.abc import Iterable LGPL_HEADER = """ Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA """ class TestScrollable(unittest.TestCase): def test_basic(self): """Test basic init and scroll.""" long_content = urwid.Text(LGPL_HEADER) reduced_size = (80, 5) content_size = long_content.pack() widget = urwid.Scrollable(long_content) self.assertEqual(frozenset((urwid.BOX,)), widget.sizing()) cropped_content_canvas = urwid.CompositeCanvas(long_content.render((reduced_size[0],))) cropped_content_canvas.trim_end(content_size[1] - reduced_size[1]) top_decoded = cropped_content_canvas.decoded_text self.assertEqual( top_decoded, widget.render(reduced_size).decoded_text, ) for key_1, key_2 in (("down", "up"), ("page down", "page up"), ("end", "home")): widget.keypress(reduced_size, key_1) self.assertNotEqual(top_decoded, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, key_2) self.assertEqual(top_decoded, widget.render(reduced_size).decoded_text) def test_negative(self): with self.assertRaises(ValueError): urwid.Scrollable(urwid.SolidFill(" ")) class TestScrollBarScrollable(unittest.TestCase): def test_basic(self): """Test basic init and scroll. Unlike `Scrollable`, `ScrollBar` can be also scrolled with a mouse wheel. """ long_content = urwid.Text(LGPL_HEADER) reduced_size = (40, 5) widget = urwid.ScrollBar(urwid.Scrollable(long_content)) self.assertEqual(frozenset((urwid.BOX,)), widget.sizing()) top_position_rendered = ( " â–ˆ", "Copyright (C) ", " ", "This library is free software; you can ", "redistribute it and/or ", ) pos_1_down_rendered = ( "Copyright (C) ", " â–ˆ", "This library is free software; you can ", "redistribute it and/or ", "modify it under the terms of the GNU ", ) self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "down") self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "page down") self.assertEqual( ( "redistribute it and/or ", "modify it under the terms of the GNU â–ˆ", "Lesser General Public ", "License as published by the Free ", "Software Foundation; either ", ), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "page up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "end") self.assertEqual( ( "not, write to the Free Software ", "Foundation, Inc., 51 Franklin Street, ", "Fifth Floor, Boston, MA 02110-1301 ", "USA ", " â–ˆ", ), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "home") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 5, 1, 1, False)) self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 4, 1, 1, False)) self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) def test_alt_symbols(self): long_content = urwid.Text(LGPL_HEADER) reduced_size = (40, 5) widget = urwid.ScrollBar( urwid.Scrollable(long_content), trough_char=urwid.ScrollBar.Symbols.LITE_SHADE, ) self.assertEqual( ( " â–ˆ", "Copyright (C) â–‘", " â–‘", "This library is free software; you can â–‘", "redistribute it and/or â–‘", ), widget.render(reduced_size).decoded_text, ) def test_fixed(self): """Test with fixed wrapped widget.""" widget = urwid.ScrollBar( urwid.Scrollable(urwid.BigText("1", urwid.HalfBlockHeavy6x5Font())), trough_char=urwid.ScrollBar.Symbols.LITE_SHADE, thumb_char=urwid.ScrollBar.Symbols.DARK_SHADE, ) reduced_size = (8, 3) self.assertEqual( ( " â–█▌ â–“", " ▀█▌ â–“", " █▌ â–‘", ), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "page down") self.assertEqual( ( " █▌ â–‘", " █▌ â–“", " ███▌ â–“", ), widget.render(reduced_size).decoded_text, ) def test_negative(self): with self.assertRaises(ValueError): urwid.ScrollBar(urwid.Text(" ")) with self.assertRaises(TypeError): urwid.ScrollBar(urwid.SolidFill(" ")) def test_no_scrollbar(self): """If widget fit without scroll - no scrollbar needed""" widget = urwid.ScrollBar( urwid.Scrollable(urwid.BigText("1", urwid.HalfBlockHeavy6x5Font())), trough_char=urwid.ScrollBar.Symbols.LITE_SHADE, thumb_char=urwid.ScrollBar.Symbols.DARK_SHADE, ) reduced_size = (8, 5) self.assertEqual( ( " â–█▌ ", " ▀█▌ ", " █▌ ", " █▌ ", " ███▌ ", ), widget.render(reduced_size).decoded_text, ) class TestScrollBarListBox(unittest.TestCase): def test_relative_non_selectable(self): widget = urwid.ScrollBar( urwid.ListBox(urwid.SimpleListWalker(urwid.Text(line) for line in LGPL_HEADER.splitlines())) ) reduced_size = (40, 5) top_position_rendered = ( " â–ˆ", "Copyright (C) ", " ", "This library is free software; you can ", "redistribute it and/or ", ) pos_1_down_rendered = ( "Copyright (C) ", " â–ˆ", "This library is free software; you can ", "redistribute it and/or ", "modify it under the terms of the GNU ", ) self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "down") self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "page down") self.assertEqual( ( "modify it under the terms of the GNU ", "Lesser General Public â–ˆ", "License as published by the Free ", "Software Foundation; either ", "version 2.1 of the License, or (at your ", ), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "page up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "end") self.assertEqual( ( "License along with this library; if ", "not, write to the Free Software ", "Foundation, Inc., 51 Franklin Street, ", "Fifth Floor, Boston, MA 02110-1301 ", "USA â–ˆ", ), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "home") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 5, 1, 1, False)) self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 4, 1, 1, False)) self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) def test_empty(self): """Empty widget should be correctly rendered.""" widget = urwid.ScrollBar(urwid.ListBox(urwid.SimpleListWalker(()))) reduced_size = (10, 5) self.assertEqual( ( " ", " ", " ", " ", " ", ), widget.render(reduced_size).decoded_text, ) def trivial_AttrMap(widget): return urwid.AttrMap(widget, {}) class TestScrollableAttrMap(unittest.TestCase): def test_basic(self): """Test basic init and scroll.""" long_content = urwid.Text(LGPL_HEADER) reduced_size = (80, 5) content_size = long_content.pack() widget = urwid.Scrollable(trivial_AttrMap(long_content)) self.assertEqual(frozenset((urwid.BOX,)), widget.sizing()) cropped_content_canvas = urwid.CompositeCanvas(long_content.render((reduced_size[0],))) cropped_content_canvas.trim_end(content_size[1] - reduced_size[1]) top_decoded = cropped_content_canvas.decoded_text self.assertEqual( top_decoded, widget.render(reduced_size).decoded_text, ) for key_1, key_2 in (("down", "up"), ("page down", "page up"), ("end", "home")): widget.keypress(reduced_size, key_1) self.assertNotEqual(top_decoded, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, key_2) self.assertEqual(top_decoded, widget.render(reduced_size).decoded_text) def test_negative(self): with self.assertRaises(ValueError): urwid.Scrollable(trivial_AttrMap(urwid.SolidFill(" "))) class TestScrollBarAttrMap(unittest.TestCase): def test_basic(self): """Test basic init and scroll. Unlike `Scrollable`, `ScrollBar` can be also scrolled with a mouse wheel. """ long_content = urwid.Text(LGPL_HEADER) reduced_size = (40, 5) widget = urwid.ScrollBar(urwid.Scrollable(trivial_AttrMap(long_content))) self.assertEqual(frozenset((urwid.BOX,)), widget.sizing()) top_position_rendered = ( " â–ˆ", "Copyright (C) ", " ", "This library is free software; you can ", "redistribute it and/or ", ) pos_1_down_rendered = ( "Copyright (C) ", " â–ˆ", "This library is free software; you can ", "redistribute it and/or ", "modify it under the terms of the GNU ", ) self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "down") self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "page down") self.assertEqual( ( "redistribute it and/or ", "modify it under the terms of the GNU â–ˆ", "Lesser General Public ", "License as published by the Free ", "Software Foundation; either ", ), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "page up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "end") self.assertEqual( ( "not, write to the Free Software ", "Foundation, Inc., 51 Franklin Street, ", "Fifth Floor, Boston, MA 02110-1301 ", "USA ", " â–ˆ", ), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "home") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 5, 1, 1, False)) self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 4, 1, 1, False)) self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) class TestScrollBarListBoxAttrMap(unittest.TestCase): def test_relative_non_selectable(self): widget = urwid.ScrollBar( trivial_AttrMap( urwid.ListBox(urwid.SimpleListWalker(urwid.Text(line) for line in LGPL_HEADER.splitlines())) ) ) reduced_size = (40, 5) top_position_rendered = ( " â–ˆ", "Copyright (C) ", " ", "This library is free software; you can ", "redistribute it and/or ", ) pos_1_down_rendered = ( "Copyright (C) ", " â–ˆ", "This library is free software; you can ", "redistribute it and/or ", "modify it under the terms of the GNU ", ) self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "down") self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "page down") self.assertEqual( ( "modify it under the terms of the GNU ", "Lesser General Public â–ˆ", "License as published by the Free ", "Software Foundation; either ", "version 2.1 of the License, or (at your ", ), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "page up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "end") self.assertEqual( ( "License along with this library; if ", "not, write to the Free Software ", "Foundation, Inc., 51 Franklin Street, ", "Fifth Floor, Boston, MA 02110-1301 ", "USA â–ˆ", ), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "home") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 5, 1, 1, False)) self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 4, 1, 1, False)) self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) def test_large_non_selectable(self): top = urwid.Text("\n".join(string.ascii_letters)) bottom = urwid.Text("\n".join(string.digits)) widget = urwid.ScrollBar(urwid.ListBox(urwid.SimpleListWalker((top, bottom)))) reduced_size = (3, 5) top_position_rendered = ("a â–ˆ", "b ", "c ", "d ", "e ") pos_1_down_rendered = ("b ", "c â–ˆ", "d ", "e ", "f ") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "down") self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "page down") self.assertEqual( ("f ", "g â–ˆ", "h ", "i ", "j "), widget.render(reduced_size).decoded_text, ) widget.keypress(reduced_size, "page up") self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 5, 1, 1, False)) self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 4, 1, 1, False)) self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text) def test_large_selectable(self): """Input is handled by LineBox and wrapped widgets.""" top = urwid.Edit("\n".join(string.ascii_letters)) bottom = urwid.IntEdit("\n".join(string.digits)) widget = urwid.ScrollBar(urwid.ListBox(urwid.SimpleListWalker((top, bottom)))) reduced_size = (3, 10) top_position_rendered = ("a â–ˆ", "b â–ˆ", "c ", "d ", "e ", "f ", "g ", "h ", "i ", "j ") pos_1_down_rendered = ("0 ", "1 ", "2 ", "3 ", "4 ", "5 ", "6 ", "7 ", "8 â–ˆ", "9 â–ˆ") self.assertEqual((top_position_rendered), widget.render(reduced_size).decoded_text) widget.keypress(reduced_size, "down") self.assertEqual(pos_1_down_rendered, widget.render(reduced_size).decoded_text) def test_hinted_len(self): class HintedWalker(urwid.ListWalker): def __init__(self, items: Iterable[str]) -> None: self.items: tuple[str] = tuple(items) self.focus = 0 self.requested_numbers: set[int] = set() def __length_hint__(self) -> int: return len(self.items) def __getitem__(self, item: int) -> urwid.Text: self.requested_numbers.add(item) return urwid.Text(self.items[item]) def set_focus(self, item: int) -> None: self.focus = item def next_position(self, position: int) -> int: if position + 1 < len(self.items): return position + 1 raise IndexError def prev_position(self, position: int) -> int: if position - 1 >= 0: return position - 1 raise IndexError widget = urwid.ScrollBar(urwid.ListBox(HintedWalker((f"Line {idx:02}") for idx in range(1, 51)))) size = (10, 10) widget.original_widget.focus_position = 19 self.assertEqual( ( "Line 16 ", "Line 17 ", "Line 18 ", "Line 19 â–ˆ", "Line 20 â–ˆ", "Line 21 ", "Line 22 ", "Line 23 ", "Line 24 ", "Line 25 ", ), widget.render(size).decoded_text, ) self.assertNotIn( 30, widget.original_widget.body.requested_numbers, "Requested index out of range [0, last shown]. This means not relative scroll bar built.", ) urwid-2.6.16/tests/test_signals.py000066400000000000000000000042471470350774000171710ustar00rootroot00000000000000import sys import unittest from unittest.mock import Mock from urwid import Edit, Signals, connect_signal, disconnect_signal, emit_signal, register_signal class SiglnalsTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.EmClass = type("EmClass", (object,), {}) register_signal(cls.EmClass, ["change", "test"]) def test_connect(self): obj = Mock() handler = Mock() edit = Edit("") key = connect_signal(edit, "change", handler, user_args=[obj]) self.assertIsNotNone(key) edit.set_edit_text("long test text") handler.assert_called_once_with(obj, edit, "long test text") handler.reset_mock() disconnect_signal(edit, "change", handler, user_args=[obj]) edit.set_edit_text("another text") handler.assert_not_called() @unittest.skipIf(sys.implementation.name == "pypy", "WeakRef works differently on PyPy") def test_weak_del(self): emitter = SiglnalsTest.EmClass() w1 = Mock(name="w1") w2 = Mock(name="w2") w3 = Mock(name="w3") handler1 = Mock(name="handler1") handler2 = Mock(name="handler2") k1 = connect_signal(emitter, "test", handler1, weak_args=[w1], user_args=[42, "abc"]) k2 = connect_signal(emitter, "test", handler2, weak_args=[w2, w3], user_args=[8]) self.assertIsNotNone(k2) emit_signal(emitter, "test", "Foo") handler1.assert_called_once_with(w1, 42, "abc", "Foo") handler2.assert_called_once_with(w2, w3, 8, "Foo") handler1.reset_mock() handler2.reset_mock() del w1 self.assertEqual( len(getattr(emitter, Signals._signal_attr)["test"]), 1, getattr(emitter, Signals._signal_attr)["test"], ) emit_signal(emitter, "test", "Bar") handler1.assert_not_called() handler2.assert_called_once_with(w2, w3, 8, "Bar") handler2.reset_mock() del w3 emit_signal(emitter, "test", "Baz") handler1.assert_not_called() handler2.assert_not_called() self.assertEqual(len(getattr(emitter, Signals._signal_attr)["test"]), 0) del w2 urwid-2.6.16/tests/test_str_util.py000066400000000000000000000024271470350774000173740ustar00rootroot00000000000000from __future__ import annotations import unittest from urwid.display.escape import str_util class DecodeOneTest(unittest.TestCase): def gwt(self, ch, exp_ord, exp_pos): ch = ch.encode("iso8859-1") o, pos = str_util.decode_one(ch, 0) assert o == exp_ord, f" got:{o!r} expected:{exp_ord!r}" assert pos == exp_pos, f" got:{pos!r} expected:{exp_pos!r}" def test1byte(self): self.gwt("ab", ord("a"), 1) self.gwt("\xc0a", ord("?"), 1) # error def test2byte(self): self.gwt("\xc2", ord("?"), 1) # error self.gwt("\xc0\x80", ord("?"), 1) # error self.gwt("\xc2\x80", 0x80, 2) self.gwt("\xdf\xbf", 0x7FF, 2) def test3byte(self): self.gwt("\xe0", ord("?"), 1) # error self.gwt("\xe0\xa0", ord("?"), 1) # error self.gwt("\xe0\x90\x80", ord("?"), 1) # error self.gwt("\xe0\xa0\x80", 0x800, 3) self.gwt("\xef\xbf\xbf", 0xFFFF, 3) def test4byte(self): self.gwt("\xf0", ord("?"), 1) # error self.gwt("\xf0\x90", ord("?"), 1) # error self.gwt("\xf0\x90\x80", ord("?"), 1) # error self.gwt("\xf0\x80\x80\x80", ord("?"), 1) # error self.gwt("\xf0\x90\x80\x80", 0x10000, 4) self.gwt("\xf3\xbf\xbf\xbf", 0xFFFFF, 4) urwid-2.6.16/tests/test_text_layout.py000066400000000000000000000376261470350774000201210ustar00rootroot00000000000000from __future__ import annotations import typing import unittest import urwid from urwid import text_layout from urwid.util import get_encoding, set_temporary_encoding if typing.TYPE_CHECKING: from typing_extensions import Literal class CalcBreaksTest(unittest.TestCase): def cbtest(self, width, exp, mode, text): result = text_layout.default_layout.calculate_text_segments(text, width, mode) self.assertEqual(len(exp), len(result), f"Expected: {exp!r}, got {result!r}") for l, e in zip(result, exp): end = l[-1][-1] self.assertEqual(e, end, f"Expected: {exp!r}, got {result!r}") def validate(self, mode, text, do): for width, exp in do: self.cbtest(width, exp, mode, text) def test_calc_breaks_char(self): self.validate( mode="any", text=b"abfghsdjf askhtrvs\naltjhgsdf ljahtshgf", do=[ (100, [18, 38]), (6, [6, 12, 18, 25, 31, 37, 38]), (10, [10, 18, 29, 38]), ], ) def test_calc_reaks_db_char(self): with urwid.util.set_temporary_encoding("euc-jp"): self.validate( mode="any", text=b"abfgh\xA1\xA1j\xA1\xA1xskhtrvs\naltjhgsdf\xA1\xA1jahtshgf", do=[ (10, [10, 18, 28, 38]), (6, [5, 11, 17, 18, 25, 31, 37, 38]), (100, [18, 38]), ], ) def test_calc_breaks_word(self): self.validate( mode="space", text=b"hello world\nout there. blah", do=[ (10, [5, 11, 22, 27]), (5, [5, 11, 17, 22, 27]), (100, [11, 27]), ], ) def test_calc_breaks_word_2(self): self.validate( mode="space", text=b"A simple set of words, really....", do=[ (10, [8, 15, 22, 33]), (17, [15, 33]), (13, [12, 22, 33]), ], ) def test_calc_breaks_db_word(self): with urwid.util.set_temporary_encoding("euc-jp"): self.validate( mode="space", text=b"hel\xA1\xA1 world\nout-\xA1\xA1tre blah", # tests do=[ (10, [5, 11, 21, 26]), (5, [5, 11, 16, 21, 26]), (100, [11, 26]), ], ) def test_calc_breaks_utf8(self): with urwid.util.set_temporary_encoding("utf-8"): self.validate( mode="space", # As text: "æ›¿æ´¼æ¸Žæºæ½º" text=b"\xe6\x9b\xbf\xe6\xb4\xbc\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba", do=[ (4, [6, 12, 15]), (10, [15]), (5, [6, 12, 15]), ], ) class CalcBreaksCantDisplayTest(unittest.TestCase): def test(self): with set_temporary_encoding("euc-jp"): self.assertRaises( text_layout.CanNotDisplayText, text_layout.default_layout.calculate_text_segments, b"\xA1\xA1", 1, "space", ) with set_temporary_encoding("utf-8"): self.assertRaises( text_layout.CanNotDisplayText, text_layout.default_layout.calculate_text_segments, "颖", 1, "space", ) class SubsegTest(unittest.TestCase): def setUp(self): self.old_encoding = get_encoding() urwid.set_encoding("euc-jp") def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def st(self, seg, text: bytes, start: int, end: int, exp): s = urwid.LayoutSegment(seg) result = s.subseg(text, start, end) self.assertEqual(exp, result, f"Expected {exp!r}, got {result!r}") def test1_padding(self): self.st((10, None), b"", 0, 8, [(8, None)]) self.st((10, None), b"", 2, 10, [(8, None)]) self.st((10, 0), b"", 3, 7, [(4, 0)]) self.st((10, 0), b"", 0, 20, [(10, 0)]) def test2_text(self): self.st((10, 0, b"1234567890"), b"", 0, 8, [(8, 0, b"12345678")]) self.st((10, 0, b"1234567890"), b"", 2, 10, [(8, 0, b"34567890")]) self.st((10, 0, b"12\xA1\xA156\xA1\xA190"), b"", 2, 8, [(6, 0, b"\xA1\xA156\xA1\xA1")]) self.st((10, 0, b"12\xA1\xA156\xA1\xA190"), b"", 3, 8, [(5, 0, b" 56\xA1\xA1")]) self.st((10, 0, b"12\xA1\xA156\xA1\xA190"), b"", 2, 7, [(5, 0, b"\xA1\xA156 ")]) self.st((10, 0, b"12\xA1\xA156\xA1\xA190"), b"", 3, 7, [(4, 0, b" 56 ")]) self.st((10, 0, b"12\xA1\xA156\xA1\xA190"), b"", 0, 20, [(10, 0, b"12\xA1\xA156\xA1\xA190")]) def test3_range(self): t = b"1234567890" self.st((10, 0, 10), t, 0, 8, [(8, 0, 8)]) self.st((10, 0, 10), t, 2, 10, [(8, 2, 10)]) self.st((6, 2, 8), t, 1, 6, [(5, 3, 8)]) self.st((6, 2, 8), t, 0, 5, [(5, 2, 7)]) self.st((6, 2, 8), t, 1, 5, [(4, 3, 7)]) t = b"12\xA1\xA156\xA1\xA190" self.st((10, 0, 10), t, 0, 8, [(8, 0, 8)]) self.st((10, 0, 10), t, 2, 10, [(8, 2, 10)]) self.st((6, 2, 8), t, 1, 6, [(1, 3), (4, 4, 8)]) self.st((6, 2, 8), t, 0, 5, [(4, 2, 6), (1, 6)]) self.st((6, 2, 8), t, 1, 5, [(1, 3), (2, 4, 6), (1, 6)]) class CalcTranslateTest(unittest.TestCase): def setUp(self) -> None: self.old_encoding = get_encoding() urwid.set_encoding("utf-8") def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def check(self, text, mode, width, result_left, result_center, result_right) -> None: with self.subTest("left"): result = urwid.default_layout.layout(text, width, "left", mode) self.assertEqual(result_left, result) with self.subTest("center"): result = urwid.default_layout.layout(text, width, "center", mode) self.assertEqual(result_center, result) with self.subTest("right"): result = urwid.default_layout.layout(text, width, "right", mode) self.assertEqual(result_right, result) def test_calc_translate_char(self): self.check( text="It's out of control!\nYou've got to", mode="any", width=15, result_left=[[(15, 0, 15)], [(5, 15, 20), (0, 20)], [(13, 21, 34), (0, 34)]], result_center=[[(15, 0, 15)], [(5, None), (5, 15, 20), (0, 20)], [(1, None), (13, 21, 34), (0, 34)]], result_right=[[(15, 0, 15)], [(10, None), (5, 15, 20), (0, 20)], [(2, None), (13, 21, 34), (0, 34)]], ) def test_calc_translate_word(self): self.check( text="It's out of control!\nYou've got to", mode="space", width=14, result_left=[ [(11, 0, 11), (0, 11)], [(8, 12, 20), (0, 20)], [(13, 21, 34), (0, 34)], ], result_center=[ [(2, None), (11, 0, 11), (0, 11)], [(3, None), (8, 12, 20), (0, 20)], [(1, None), (13, 21, 34), (0, 34)], ], result_right=[ [(3, None), (11, 0, 11), (0, 11)], [(6, None), (8, 12, 20), (0, 20)], [(1, None), (13, 21, 34), (0, 34)], ], ) def test_calc_translate(self): self.check( text="It's out of control!\nYou've got to ", mode="space", width=14, result_left=[ [(11, 0, 11), (0, 11)], [(8, 12, 20), (0, 20)], [(14, 21, 35), (0, 35)], ], result_center=[ [(2, None), (11, 0, 11), (0, 11)], [(3, None), (8, 12, 20), (0, 20)], [(14, 21, 35), (0, 35)], ], result_right=[ [(3, None), (11, 0, 11), (0, 11)], [(6, None), (8, 12, 20), (0, 20)], [(14, 21, 35), (0, 35)], ], ) def test_calc_translate_word_2(self): self.check( text="It's out of control!\nYou've got to ", mode="space", width=14, result_left=[[(11, 0, 11), (0, 11)], [(8, 12, 20), (0, 20)], [(14, 21, 35), (0, 35)]], result_center=[ [(2, None), (11, 0, 11), (0, 11)], [(3, None), (8, 12, 20), (0, 20)], [(14, 21, 35), (0, 35)], ], result_right=[ [(3, None), (11, 0, 11), (0, 11)], [(6, None), (8, 12, 20), (0, 20)], [(14, 21, 35), (0, 35)], ], ) def test_calc_translate_word_3(self): # As bytes: b'\xe6\x9b\xbf\xe6\xb4\xbc\n\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba' # Decoded as UTF-8: "替洼\næ¸Žæºæ½º" self.check( text=b"\xe6\x9b\xbf\xe6\xb4\xbc\n\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba", width=10, mode="space", result_left=[[(4, 0, 6), (0, 6)], [(6, 7, 16), (0, 16)]], result_center=[[(3, None), (4, 0, 6), (0, 6)], [(2, None), (6, 7, 16), (0, 16)]], result_right=[[(6, None), (4, 0, 6), (0, 6)], [(4, None), (6, 7, 16), (0, 16)]], ) def test_calc_translate_word_3_decoded(self): # As bytes: b'\xe6\x9b\xbf\xe6\xb4\xbc\n\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba' # Decoded as UTF-8: "替洼\næ¸Žæºæ½º" self.check( text="替洼\næ¸Žæºæ½º", width=10, mode="space", result_left=[[(4, 0, 2), (0, 2)], [(6, 3, 6), (0, 6)]], result_center=[[(3, None), (4, 0, 2), (0, 2)], [(2, None), (6, 3, 6), (0, 6)]], result_right=[[(6, None), (4, 0, 2), (0, 2)], [(4, None), (6, 3, 6), (0, 6)]], ) def test_calc_translate_word_4(self): self.check( text=" Die Gedank", width=3, mode="space", result_left=[[(0, 0)], [(3, 1, 4), (0, 4)], [(3, 5, 8)], [(3, 8, 11), (0, 11)]], result_center=[[(2, None), (0, 0)], [(3, 1, 4), (0, 4)], [(3, 5, 8)], [(3, 8, 11), (0, 11)]], result_right=[[(3, None), (0, 0)], [(3, 1, 4), (0, 4)], [(3, 5, 8)], [(3, 8, 11), (0, 11)]], ) def test_calc_translate_word_5(self): self.check( text=" Word.", width=3, mode="space", result_left=[[(3, 0, 3)], [(3, 3, 6), (0, 6)]], result_center=[[(3, 0, 3)], [(3, 3, 6), (0, 6)]], result_right=[[(3, 0, 3)], [(3, 3, 6), (0, 6)]], ) def test_calc_translate_clip(self): self.check( text="It's out of control!\nYou've got to\n\nturn it off!!!", mode="clip", width=14, result_left=[ [(20, 0, 20), (0, 20)], [(13, 21, 34), (0, 34)], [(0, 35)], [(14, 36, 50), (0, 50)], ], result_center=[ [(-3, None), (20, 0, 20), (0, 20)], [(1, None), (13, 21, 34), (0, 34)], [(7, None), (0, 35)], [(14, 36, 50), (0, 50)], ], result_right=[ [(-6, None), (20, 0, 20), (0, 20)], [(1, None), (13, 21, 34), (0, 34)], [(14, None), (0, 35)], [(14, 36, 50), (0, 50)], ], ) def test_calc_translate_clip_2(self): self.check( text="Hello!\nto\nWorld!", mode="clip", width=5, # line width (of first and last lines) minus one result_left=[ [(6, 0, 6), (0, 6)], [(2, 7, 9), (0, 9)], [(6, 10, 16), (0, 16)], ], result_center=[ [(6, 0, 6), (0, 6)], [(2, None), (2, 7, 9), (0, 9)], [(6, 10, 16), (0, 16)], ], result_right=[ [(-1, None), (6, 0, 6), (0, 6)], [(3, None), (2, 7, 9), (0, 9)], [(-1, None), (6, 10, 16), (0, 16)], ], ) def test_calc_translate_cant_display(self): self.check( text="Hello颖", mode="space", width=1, result_left=[[]], result_center=[[]], result_right=[[]], ) class CalcPosTest(unittest.TestCase): def setUp(self): self.text = "A" * 27 self.trans = [[(2, None), (7, 0, 7), (0, 7)], [(13, 8, 21), (0, 21)], [(3, None), (5, 22, 27), (0, 27)]] self.mytests = [ (1, 0, 0), (2, 0, 0), (11, 0, 7), (-3, 1, 8), (-2, 1, 8), (1, 1, 9), (31, 1, 21), (1, 2, 22), (11, 2, 27), ] def tests(self): for x, y, expected in self.mytests: got = text_layout.calc_pos(self.text, self.trans, x, y) self.assertEqual(expected, got, f"{x, y!r} got:{got!r} expected:{expected!r}") class Pos2CoordsTest(unittest.TestCase): pos_list = [5, 9, 20, 26] text = "1234567890" * 3 mytests = [ ([[(15, 0, 15)], [(15, 15, 30), (0, 30)]], [(5, 0), (9, 0), (5, 1), (11, 1)]), ([[(9, 0, 9)], [(12, 9, 21)], [(9, 21, 30), (0, 30)]], [(5, 0), (0, 1), (11, 1), (5, 2)]), ([[(2, None), (15, 0, 15)], [(2, None), (15, 15, 30), (0, 30)]], [(7, 0), (11, 0), (7, 1), (13, 1)]), ([[(3, 6, 9), (0, 9)], [(5, 20, 25), (0, 25)]], [(0, 0), (3, 0), (0, 1), (5, 1)]), ([[(10, 0, 10), (0, 10)]], [(5, 0), (9, 0), (10, 0), (10, 0)]), ] def test(self): for t, answer in self.mytests: for pos, a in zip(self.pos_list, answer): r = text_layout.calc_coords(self.text, t, pos) self.assertEqual(a, r, f"{t!r} got: {r!r} expected: {a!r}") class TestEllipsis(unittest.TestCase): def test_ellipsis_encoding_support(self): widget = urwid.Text("Test label", wrap=urwid.WrapMode.ELLIPSIS) with self.subTest("Unicode"), set_temporary_encoding("utf-8"): widget._invalidate() canvas = widget.render((5,)) self.assertEqual("Test…", str(canvas)) with self.subTest("ascii"), set_temporary_encoding("ascii"): widget._invalidate() canvas = widget.render((5,)) self.assertEqual("Te...", str(canvas)) with self.subTest("ascii not fit"), set_temporary_encoding("ascii"): widget._invalidate() canvas = widget.render((3,)) self.assertEqual("T..", str(canvas)) with self.subTest("ascii nothing fit"), set_temporary_encoding("ascii"): widget._invalidate() canvas = widget.render((1,)) self.assertEqual("T", str(canvas)) class NumericLayout(urwid.TextLayout): """ TextLayout class for bottom-right aligned numbers """ def layout( self, text: str | bytes, width: int, align: Literal["left", "center", "right"] | urwid.Align, wrap: Literal["any", "space", "clip", "ellipsis"] | urwid.WrapMode, ) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]: """ Return layout structure for right justified numbers. """ lt = len(text) r = lt % width # remaining segment not full width wide if r: return [ [(width - r, None), (r, 0, r)], # right-align the remaining segment on 1st line *([(width, x, x + width)] for x in range(r, lt, width)), # fill the rest of the lines ] return [[(width, x, x + width)] for x in range(0, lt, width)] class TestTextLayoutNoPack(unittest.TestCase): def test(self): """Text widget pack should work also with layout not supporting `pack` method.""" widget = urwid.Text("123", layout=NumericLayout()) self.assertEqual((3, 1), widget.pack((3,))) urwid-2.6.16/tests/test_tree.py000066400000000000000000000223641470350774000164700ustar00rootroot00000000000000from __future__ import annotations import typing import unittest import urwid from urwid import TreeNode if typing.TYPE_CHECKING: from collections.abc import Collection, Hashable, Iterable class SelfRegisteringParent(urwid.ParentNode): def __init__( self, value: str, parent: SelfRegisteringParent | None = None, key: Hashable = None, depth: int | None = None, children: Iterable[SelfRegisteringChild | SelfRegisteringParent] = (), ) -> None: super().__init__(value, parent, key, depth) if parent: parent.set_child_node(key, self) self._child_keys = [] for child in children: key = child.get_key() self._children[key] = child self._child_keys.append(key) child.set_parent(self) def load_child_keys(self) -> Collection[Hashable]: return list(self._children) def set_child_node(self, key: Hashable, node: TreeNode) -> None: super().set_child_node(key, node) self._child_keys = self.load_child_keys() def set_parent(self, parent: SelfRegisteringParent) -> None: self._parent = parent class SelfRegisteringChild(urwid.TreeNode): def __init__( self, value: str, parent: SelfRegisteringParent | None = None, key: Hashable | None = None, depth: int | None = None, ) -> None: super().__init__(value, parent, key, depth) if parent: parent.set_child_node(key, self) def set_parent(self, parent: SelfRegisteringParent) -> None: self._parent = parent class TestTree(unittest.TestCase): def test_basic(self): root = SelfRegisteringParent( "root", key="/", children=(SelfRegisteringChild(f"child_{idx}", key=str(idx)) for idx in range(1, 4)), ) widget = urwid.TreeListBox(urwid.TreeWalker(root)) size = (15, 5) expanded = ( "- /: root ", " 1: child_1 ", " 2: child_2 ", " 3: child_3 ", " ", ) collapsed = ( "+ /: root ", *(" " * size[0] for _ in range(size[1] - 1)), ) self.assertEqual(expanded, widget.render(size).decoded_text) widget.keypress(size, "-") self.assertEqual(collapsed, widget.render(size).decoded_text) widget.keypress(size, "+") self.assertEqual(expanded, widget.render(size).decoded_text) widget.keypress(size, "down") self.assertIs(root, widget.focus_position) widget.keypress(size, "-") self.assertEqual(collapsed, widget.render(size).decoded_text) widget.keypress(size, "right") self.assertEqual(expanded, widget.render(size).decoded_text) def test_nested_behavior(self): root = SelfRegisteringParent( "root", key="/", children=( SelfRegisteringParent( f"nested_{idx}", key=f"{idx}/", children=(SelfRegisteringChild(f"child_{idx}{cidx}", key=str(cidx)) for cidx in range(1, 4)), ) for idx in range(1, 4) ), ) widget = urwid.TreeListBox(urwid.TreeWalker(root)) size = (18, 13) expanded = ( "- /: root ", " - 1/: nested_1 ", " 1: child_11 ", " 2: child_12 ", " 3: child_13 ", " - 2/: nested_2 ", " 1: child_21 ", " 2: child_22 ", " 3: child_23 ", " - 3/: nested_3 ", " 1: child_31 ", " 2: child_32 ", " 3: child_33 ", ) collapsed = ( "+ /: root ", *(" " * size[0] for _ in range(size[1] - 1)), ) self.assertEqual(expanded, widget.render(size).decoded_text) widget.keypress(size, "-") self.assertEqual(collapsed, widget.render(size).decoded_text) widget.keypress(size, "+") self.assertEqual(expanded, widget.render(size).decoded_text) widget.keypress(size, "down") widget.keypress(size, "-") self.assertEqual( ( "- /: root ", " + 1/: nested_1 ", " - 2/: nested_2 ", " 1: child_21 ", " 2: child_22 ", " 3: child_23 ", " - 3/: nested_3 ", " 1: child_31 ", " 2: child_32 ", " 3: child_33 ", " ", " ", " ", ), widget.render(size).decoded_text, ) widget.keypress(size, "right") self.assertEqual(expanded, widget.render(size).decoded_text) widget.keypress(size, "left") widget.keypress(size, "-") self.assertEqual(collapsed, widget.render(size).decoded_text) widget.keypress(size, "+") widget.keypress(size, "page down") widget.keypress(size, "-") self.assertEqual( ( "- /: root ", " - 1/: nested_1 ", " 1: child_11 ", " 2: child_12 ", " 3: child_13 ", " - 2/: nested_2 ", " 1: child_21 ", " 2: child_22 ", " 3: child_23 ", " + 3/: nested_3 ", " ", " ", " ", ), widget.render(size).decoded_text, ) def test_deep_nested_collapse_expand(self): root = SelfRegisteringParent( "root", key="/", children=( SelfRegisteringParent( f"nested_{top_idx}", key=f"{top_idx}/", children=( SelfRegisteringParent( f"nested_{top_idx}{first_idx}", key=f"{first_idx}/", children=( SelfRegisteringChild(f"child_{top_idx}{first_idx}{last_idx}", key=str(last_idx)) for last_idx in range(1, 3) ), ) for first_idx in range(1, 3) ), ) for top_idx in range(1, 3) ), ) widget = urwid.TreeListBox(urwid.TreeWalker(root)) size = (21, 15) expanded = ( "- /: root ", " - 1/: nested_1 ", " - 1/: nested_11", " 1: child_111", " 2: child_112", " - 2/: nested_12", " 1: child_121", " 2: child_122", " - 2/: nested_2 ", " - 1/: nested_21", " 1: child_211", " 2: child_212", " - 2/: nested_22", " 1: child_221", " 2: child_222", ) collapsed = ( "+ /: root ", *(" " * size[0] for _ in range(size[1] - 1)), ) collapsed_last = ( "- /: root ", " - 1/: nested_1 ", " - 1/: nested_11", " 1: child_111", " 2: child_112", " - 2/: nested_12", " 1: child_121", " 2: child_122", " - 2/: nested_2 ", " - 1/: nested_21", " 1: child_211", " 2: child_212", " + 2/: nested_22", " ", " ", ) self.assertEqual(expanded, widget.render(size).decoded_text) widget.keypress(size, "page down") widget.keypress(size, "-") self.assertEqual(collapsed_last, widget.render(size).decoded_text) widget.keypress(size, "left") widget.keypress(size, "-") self.assertEqual( ( "- /: root ", " - 1/: nested_1 ", " - 1/: nested_11", " 1: child_111", " 2: child_112", " - 2/: nested_12", " 1: child_121", " 2: child_122", " + 2/: nested_2 ", " ", " ", " ", " ", " ", " ", ), widget.render(size).decoded_text, ) widget.keypress(size, "right") self.assertEqual(collapsed_last, widget.render(size).decoded_text) widget.keypress(size, "home") widget.keypress(size, "-") self.assertEqual(collapsed, widget.render(size).decoded_text) urwid-2.6.16/tests/test_util.py000066400000000000000000000221401470350774000164760ustar00rootroot00000000000000from __future__ import annotations import locale import unittest import urwid from urwid import str_util, util class CalcWidthTest(unittest.TestCase): def setUp(self) -> None: self.old_encoding = util.get_encoding() def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def wtest(self, desc, s, exp): s = s.encode("iso8859-1") result = str_util.calc_width(s, 0, len(s)) assert result == exp, f"{desc} got:{result!r} expected:{exp!r}" def test1(self): util.set_encoding("utf-8") self.wtest("narrow", "hello", 5) self.wtest("wide char", "\xe6\x9b\xbf", 2) self.wtest("invalid", "\xe6", 1) self.wtest("zero width", "\xcc\x80", 0) self.wtest("mixed", "hello\xe6\x9b\xbf\xe6\x9b\xbf", 9) def test2(self): util.set_encoding("euc-jp") self.wtest("narrow", "hello", 5) self.wtest("wide", "\xA1\xA1\xA1\xA1", 4) self.wtest("invalid", "\xA1", 1) class ConvertDecSpecialTest(unittest.TestCase): def setUp(self) -> None: self.old_encoding = util.get_encoding() def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def ctest(self, desc, s, exp, expcs): exp = exp.encode("iso8859-1") util.set_encoding("ascii") c = urwid.Text(s).render((5,)) result = c._text[0] assert result == exp, f"{desc} got:{result!r} expected:{exp!r}" resultcs = c._cs[0] assert resultcs == expcs, f"{desc} got:{resultcs!r} expected:{expcs!r}" def test1(self): self.ctest("no conversion", "hello", "hello", [(None, 5)]) self.ctest("only special", "£££££", "}}}}}", [("0", 5)]) self.ctest("mix left", "££abc", "}}abc", [("0", 2), (None, 3)]) self.ctest("mix right", "abc££", "abc}}", [(None, 3), ("0", 2)]) self.ctest("mix inner", "a££bc", "a}}bc", [(None, 1), ("0", 2), (None, 2)]) self.ctest("mix well", "£a£b£", "}a}b}", [("0", 1), (None, 1), ("0", 1), (None, 1), ("0", 1)]) class WithinDoubleByteTest(unittest.TestCase): def setUp(self): self.old_encoding = util.get_encoding() urwid.set_encoding("euc-jp") def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def wtest(self, s, ls, pos, expected, desc): result = str_util.within_double_byte(s.encode("iso8859-1"), ls, pos) assert result == expected, f"{desc} got:{result!r} expected: {expected!r}" def test1(self): self.wtest("mnopqr", 0, 2, 0, "simple no high bytes") self.wtest("mn\xA1\xA1qr", 0, 2, 1, "simple 1st half") self.wtest("mn\xA1\xA1qr", 0, 3, 2, "simple 2nd half") self.wtest("m\xA1\xA1\xA1\xA1r", 0, 3, 1, "subsequent 1st half") self.wtest("m\xA1\xA1\xA1\xA1r", 0, 4, 2, "subsequent 2nd half") self.wtest("mn\xA1@qr", 0, 3, 2, "simple 2nd half lo") self.wtest("mn\xA1\xA1@r", 0, 4, 0, "subsequent not 2nd half lo") self.wtest("m\xA1\xA1\xA1@r", 0, 4, 2, "subsequent 2nd half lo") def test2(self): self.wtest("\xA1\xA1qr", 0, 0, 1, "begin 1st half") self.wtest("\xA1\xA1qr", 0, 1, 2, "begin 2nd half") self.wtest("\xA1@qr", 0, 1, 2, "begin 2nd half lo") self.wtest("\xA1\xA1\xA1\xA1r", 0, 2, 1, "begin subs. 1st half") self.wtest("\xA1\xA1\xA1\xA1r", 0, 3, 2, "begin subs. 2nd half") self.wtest("\xA1\xA1\xA1@r", 0, 3, 2, "begin subs. 2nd half lo") self.wtest("\xA1@\xA1@r", 0, 3, 2, "begin subs. 2nd half lo lo") self.wtest("@\xA1\xA1@r", 0, 3, 0, "begin subs. not 2nd half lo") def test3(self): self.wtest("abc \xA1\xA1qr", 4, 4, 1, "newline 1st half") self.wtest("abc \xA1\xA1qr", 4, 5, 2, "newline 2nd half") self.wtest("abc \xA1@qr", 4, 5, 2, "newline 2nd half lo") self.wtest("abc \xA1\xA1\xA1\xA1r", 4, 6, 1, "newl subs. 1st half") self.wtest("abc \xA1\xA1\xA1\xA1r", 4, 7, 2, "newl subs. 2nd half") self.wtest("abc \xA1\xA1\xA1@r", 4, 7, 2, "newl subs. 2nd half lo") self.wtest("abc \xA1@\xA1@r", 4, 7, 2, "newl subs. 2nd half lo lo") self.wtest("abc @\xA1\xA1@r", 4, 7, 0, "newl subs. not 2nd half lo") class CalcTextPosTest(unittest.TestCase): def setUp(self) -> None: self.old_encoding = util.get_encoding() def tearDown(self) -> None: urwid.set_encoding(self.old_encoding) def ctptest(self, text, tests): text = text.encode("iso8859-1") for s, e, p, expected in tests: got = str_util.calc_text_pos(text, s, e, p) assert got == expected, f"{s, e, p!r} got:{got!r} expected:{expected!r}" def test1(self): text = "hello world out there" tests = [ (0, 21, 0, (0, 0)), (0, 21, 5, (5, 5)), (0, 21, 21, (21, 21)), (0, 21, 50, (21, 21)), (2, 15, 50, (15, 13)), (6, 21, 0, (6, 0)), (6, 21, 3, (9, 3)), ] self.ctptest(text, tests) def test2_wide(self): util.set_encoding("euc-jp") text = "hel\xA1\xA1 world out there" tests = [ (0, 21, 0, (0, 0)), (0, 21, 4, (3, 3)), (2, 21, 2, (3, 1)), (2, 21, 3, (5, 3)), (6, 21, 0, (6, 0)), ] self.ctptest(text, tests) def test3_utf8(self): util.set_encoding("utf-8") text = "hel\xc4\x83 world \xe2\x81\x81 there" tests = [ (0, 21, 0, (0, 0)), (0, 21, 4, (5, 4)), (2, 21, 1, (3, 1)), (2, 21, 2, (5, 2)), (2, 21, 3, (6, 3)), (6, 21, 7, (15, 7)), (6, 21, 8, (16, 8)), ] self.ctptest(text, tests) def test4_utf8(self): util.set_encoding("utf-8") text = "he\xcc\x80llo \xe6\x9b\xbf world" tests = [ (0, 15, 0, (0, 0)), (0, 15, 1, (1, 1)), (0, 15, 2, (4, 2)), (0, 15, 4, (6, 4)), (8, 15, 0, (8, 0)), (8, 15, 1, (8, 0)), (8, 15, 2, (11, 2)), (8, 15, 5, (14, 5)), ] self.ctptest(text, tests) class TagMarkupTest(unittest.TestCase): mytests = [ ("simple one", "simple one", []), (("blue", "john"), "john", [("blue", 4)]), (["a ", "litt", "le list"], "a little list", []), ( ["mix", [" it", ("high", [" up", ("ital", " a")])], " little"], "mix it up a little", [(None, 6), ("high", 3), ("ital", 2)], ), (["££", "x££"], "££x££", []), ([b"\xc2\x80", b"\xc2\x80"], b"\xc2\x80\xc2\x80", []), ] def test(self): for input, text, attr in self.mytests: restext, resattr = urwid.decompose_tagmarkup(input) assert restext == text, f"got: {restext!r} expected: {text!r}" assert resattr == attr, f"got: {resattr!r} expected: {attr!r}" def test_bad_tuple(self): self.assertRaises(urwid.TagMarkupException, lambda: urwid.decompose_tagmarkup((1, 2, 3))) def test_bad_type(self): self.assertRaises(urwid.TagMarkupException, lambda: urwid.decompose_tagmarkup(5)) class RleTest(unittest.TestCase): def test_rle_prepend(self): rle0 = [("A", 10), ("B", 15)] # the rle functions are mutating, so make a few copies of rle0 rle1, rle2 = rle0[:], rle0[:] util.rle_prepend_modify(rle1, ("A", 3)) util.rle_prepend_modify(rle2, ("X", 2)) self.assertListEqual(rle1, [("A", 13), ("B", 15)]) self.assertListEqual(rle2, [("X", 2), ("A", 10), ("B", 15)]) def test_rle_append(self): rle0 = [("A", 10), ("B", 15)] rle3, rle4 = rle0[:], rle0[:] util.rle_append_modify(rle3, ("B", 5)) util.rle_append_modify(rle4, ("K", 1)) self.assertListEqual(rle3, [("A", 10), ("B", 20)]) self.assertListEqual(rle4, [("A", 10), ("B", 15), ("K", 1)]) class PortabilityTest(unittest.TestCase): def test_locale(self): initial = locale.getlocale() locale.setlocale(locale.LC_ALL, (None, None)) util.detect_encoding() self.assertEqual(locale.getlocale(), (None, None)) try: locale.setlocale(locale.LC_ALL, ("en_US", "UTF-8")) except locale.Error as exc: if "unsupported locale setting" not in str(exc): raise print( f"Locale change impossible, probably locale not supported by system (libc ignores this error).\n" f"{exc}" ) return util.detect_encoding() self.assertEqual(locale.getlocale(), ("en_US", "UTF-8")) try: locale.setlocale(locale.LC_ALL, initial) except locale.Error as exc: if "unsupported locale setting" not in str(exc): raise print( f"Locale restore impossible, probably locale not supported by system (libc ignores this error).\n" f"{exc}" ) class TestEmptyMarkup(unittest.TestCase): def test_001_empty(self): text = urwid.Text("") text.set_text(text.get_text()) self.assertEqual("", text.text) urwid-2.6.16/tests/test_vterm.py000066400000000000000000000343241470350774000166650ustar00rootroot00000000000000# Urwid terminal emulation widget unit tests # Copyright (C) 2010 aszlig # Copyright (C) 2011 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import errno import os import sys import typing import unittest from itertools import dropwhile from time import sleep import urwid from urwid.util import set_temporary_encoding IS_WINDOWS = sys.platform == "win32" class DummyCommand: QUITSTRING = b"|||quit|||" def __init__(self) -> None: self.reader, self.writer = os.pipe() def __call__(self) -> None: # reset stdout = getattr(sys.stdout, "buffer", sys.stdout) stdout.write(b"\x1bc") while True: data = self.read(1024) if self.QUITSTRING == data: break stdout.write(data) stdout.flush() def read(self, size: int) -> bytes: while True: try: return os.read(self.reader, size) except OSError as e: if e.errno != errno.EINTR: raise def write(self, data: bytes) -> None: os.write(self.writer, data) sleep(0.025) def quit(self) -> None: self.write(self.QUITSTRING) @unittest.skipIf(IS_WINDOWS, "Terminal is not supported under windows") class TermTest(unittest.TestCase): def setUp(self) -> None: self.command = DummyCommand() self.term = urwid.Terminal(self.command) self.resize(80, 24) def tearDown(self) -> None: self.command.quit() def connect_signal(self, signal: str): self._sig_response = None def _set_signal_response(widget: urwid.Widget, *args, **kwargs) -> None: self._sig_response = (args, kwargs) self._set_signal_response = _set_signal_response urwid.connect_signal(self.term, signal, self._set_signal_response) def expect_signal(self, *args, **kwargs): self.assertEqual(self._sig_response, (args, kwargs)) def disconnect_signal(self, signal: str) -> None: urwid.disconnect_signal(self.term, signal, self._set_signal_response) def caught_beep(self, obj): self.beeped = True def resize(self, width: int, height: int, soft: bool = False) -> None: self.termsize = (width, height) if not soft: self.term.render(self.termsize, focus=False) def write(self, data: str) -> None: data = data.encode("iso8859-1") self.command.write(data.replace(rb"\e", b"\x1b")) def flush(self) -> None: self.write(chr(0x7F)) @typing.overload def read(self, raw: bool = False, focus: bool = ...) -> bytes: ... @typing.overload def read(self, raw: bool = True, focus: bool = ...) -> list[list[bytes, typing.Any, typing.Any]]: ... def read(self, raw: bool = False, focus: bool = False) -> bytes | list[list[bytes, typing.Any, typing.Any]]: self.term.wait_and_feed() rendered = self.term.render(self.termsize, focus=focus) if raw: is_empty = lambda c: c == (None, None, b" ") content = list(rendered.content()) lines = (tuple(dropwhile(is_empty, reversed(line))) for line in content) return [list(reversed(line)) for line in lines if line] else: content = rendered.text lines = (line.rstrip() for line in content) return b"\n".join(lines).rstrip() def expect( self, what: str | list[tuple[typing.Any, str | None, bytes]], desc: str | None = None, raw: bool = False, focus: bool = False, ) -> None: if not isinstance(what, list): what = what.encode("iso8859-1") got = self.read(raw=raw, focus=focus) if desc is None: desc = "" else: desc += "\n" desc += f"Expected:\n{what!r}\nGot:\n{got!r}" self.assertEqual(got, what, desc) def test_simplestring(self): self.write("hello world") self.expect("hello world") def test_linefeed(self): self.write("hello\x0aworld") self.expect("hello\nworld") def test_linefeed2(self): self.write("aa\b\b\\eDbb") self.expect("aa\nbb") def test_carriage_return(self): self.write("hello\x0dworld") self.expect("world") def test_insertlines(self): self.write("\\e[0;0flast\\e[0;0f\\e[10L\\e[0;0ffirst\nsecond\n\\e[11D") self.expect("first\nsecond\n\n\n\n\n\n\n\n\nlast") def test_deletelines(self): self.write("1\n2\n3\n4\\e[2;1f\\e[2M") self.expect("1\n4") def test_nul(self): self.write("a\0b") self.expect("ab") def test_movement(self): self.write(r"\e[10;20H11\e[10;0f\e[20C\e[K") self.expect("\n" * 9 + " " * 19 + "1") self.write("\\e[A\\e[B\\e[C\\e[D\b\\e[K") self.expect("") self.write(r"\e[50A2") self.expect(" " * 19 + "2") self.write("\b\\e[K\\e[50B3") self.expect("\n" * 23 + " " * 19 + "3") self.write("\b\\e[K" + r"\eM" * 30 + r"\e[100C4") self.expect(" " * 79 + "4") self.write(r"\e[100D\e[K5") self.expect("5") def edgewall(self): edgewall = "1-\\e[1;%(x)df-2\\e[%(y)d;1f3-\\e[%(y)d;%(x)df-4\x0d" self.write(edgewall % {"x": self.termsize[0] - 1, "y": self.termsize[1] - 1}) def test_horizontal_resize(self): self.resize(80, 24) self.edgewall() self.expect("1-" + " " * 76 + "-2" + "\n" * 22 + "3-" + " " * 76 + "-4") self.resize(78, 24, soft=True) self.flush() self.expect("1-" + "\n" * 22 + "3-") self.resize(80, 24, soft=True) self.flush() self.expect("1-" + "\n" * 22 + "3-") def test_vertical_resize(self): self.resize(80, 24) self.edgewall() self.expect("1-" + " " * 76 + "-2" + "\n" * 22 + "3-" + " " * 76 + "-4") for y in range(23, 1, -1): self.resize(80, y, soft=True) self.write(r"\e[%df\e[J3-\e[%d;%df-4" % (y, y, 79)) desc = "try to rescale to 80x%d." % y self.expect("\n" * (y - 2) + "3-" + " " * 76 + "-4", desc) self.resize(80, 24, soft=True) self.flush() self.expect("1-" + " " * 76 + "-2" + "\n" * 22 + "3-" + " " * 76 + "-4") def write_movements(self, arg): fmt = "XXX\n\\e[faaa\\e[Bccc\\e[Addd\\e[Bfff\\e[Cbbb\\e[A\\e[Deee" self.write(fmt.replace(r"\e[", r"\e[" + arg)) def test_defargs(self): self.write_movements("") self.expect("aaa ddd eee\n ccc fff bbb") def test_nullargs(self): self.write_movements("0") self.expect("aaa ddd eee\n ccc fff bbb") def test_erase_line(self): self.write("1234567890\\e[5D\\e[K\n1234567890\\e[5D\\e[1K\naaaaaaaaaaaaaaa\\e[2Ka") self.expect("12345\n 7890\n a") def test_erase_display(self): self.write(r"1234567890\e[5D\e[Ja") self.expect("12345a") self.write(r"98765\e[8D\e[1Jx") self.expect(" x5a98765") def test_scrolling_region_simple(self): # TODO(Aleksei): Issue #544 self.write("\\e[10;20r\\e[10f1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\\e[faa") self.expect("aa" + "\n" * 9 + "2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12") def test_scrolling_region_reverse(self): self.write("\\e[2J\\e[1;2r\\e[5Baaa\r\\eM\\eM\\eMbbb\nXXX") self.expect("\n\nbbb\nXXX\n\naaa") def test_scrolling_region_move(self): self.write("\\e[10;20r\\e[2J\\e[10Bfoo\rbar\rblah\rmooh\r\\e[10Aone\r\\eM\\eMtwo\r\\eM\\eMthree\r\\eM\\eMa") self.expect("ahree\n\n\n\n\n\n\n\n\n\nmooh") def test_scrolling_twice(self): self.write(r"\e[?6h\e[10;20r\e[2;5rtest") self.expect("\ntest") def test_cursor_scrolling_region(self): # TODO(Aleksei): Issue #544 self.write("\\e[?6h\\e[10;20r\\e[10f1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\\e[faa") self.expect("\n" * 9 + "aa\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12") def test_scrolling_region_simple_with_focus(self): # TODO(Aleksei): Issue #544 self.write("\\e[10;20r\\e[10f1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\\e[faa") self.expect("aa" + "\n" * 9 + "2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12", focus=True) def test_scrolling_region_reverse_with_focus(self): self.write("\\e[2J\\e[1;2r\\e[5Baaa\r\\eM\\eM\\eMbbb\nXXX") self.expect("\n\nbbb\nXXX\n\naaa", focus=True) def test_scrolling_region_move_with_focus(self): self.write("\\e[10;20r\\e[2J\\e[10Bfoo\rbar\rblah\rmooh\r\\e[10Aone\r\\eM\\eMtwo\r\\eM\\eMthree\r\\eM\\eMa") self.expect("ahree\n\n\n\n\n\n\n\n\n\nmooh", focus=True) def test_scrolling_twice_with_focus(self): self.write(r"\e[?6h\e[10;20r\e[2;5rtest") self.expect("\ntest", focus=True) def test_cursor_scrolling_region_with_focus(self): # TODO(Aleksei): Issue #544 self.write("\\e[?6h\\e[10;20r\\e[10f1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\\e[faa") self.expect("\n" * 9 + "aa\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12", focus=True) def test_relative_region_jump(self): self.write(r"\e[21H---\e[10;20r\e[?6h\e[18Htest") self.expect("\n" * 19 + "test\n---") def test_set_multiple_modes(self): self.write(r"\e[?6;5htest") self.expect("test") self.assertTrue(self.term.term_modes.constrain_scrolling) self.assertTrue(self.term.term_modes.reverse_video) self.write(r"\e[?6;5l") self.expect("test") self.assertFalse(self.term.term_modes.constrain_scrolling) self.assertFalse(self.term.term_modes.reverse_video) def test_wrap_simple(self): self.write(r"\e[?7h\e[1;%dHtt" % self.term.width) self.expect(" " * (self.term.width - 1) + "t\nt") def test_wrap_backspace_tab(self): self.write("\\e[?7h\\e[1;%dHt\b\b\t\ta" % self.term.width) self.expect(" " * (self.term.width - 1) + "a") def test_cursor_visibility(self): self.write(r"\e[?25linvisible") self.expect("invisible", focus=True) self.assertEqual(self.term.term.cursor, None) self.write("\rvisible\\e[?25h\\e[K") self.expect("visible", focus=True) self.assertNotEqual(self.term.term.cursor, None) def test_get_utf8_len(self): length = self.term.term.get_utf8_len(int("11110000", 2)) self.assertEqual(length, 3) length = self.term.term.get_utf8_len(int("11000000", 2)) self.assertEqual(length, 1) length = self.term.term.get_utf8_len(int("11111101", 2)) self.assertEqual(length, 5) def test_encoding_unicode(self): with set_temporary_encoding("utf-8"): self.write("\\e%G\xe2\x80\x94") self.expect("\xe2\x80\x94") def test_encoding_unicode_ascii(self): with set_temporary_encoding("ascii"): self.write("\\e%G\xe2\x80\x94") self.expect("?") def test_encoding_wrong_unicode(self): with set_temporary_encoding("utf-8"): self.write("\\e%G\xc0\x99") self.expect("") def test_encoding_vt100_graphics(self): with set_temporary_encoding("ascii"): self.write("\\e)0\\e(0\x0fg\x0eg\\e)Bn\\e)0g\\e)B\\e(B\x0fn") self.expect( [[(None, "0", b"g"), (None, "0", b"g"), (None, None, b"n"), (None, "0", b"g"), (None, None, b"n")]], raw=True, ) def test_ibmpc_mapping(self): with set_temporary_encoding("ascii"): self.write("\\e[11m\x18\\e[10m\x18") self.expect([[(None, "U", b"\x18")]], raw=True) self.write("\\ec\\e)U\x0e\x18\x0f\\e[3h\x18\\e[3l\x18") self.expect([[(None, None, b"\x18")]], raw=True) self.write("\\ec\\e[11m\xdb\x18\\e[10m\xdb") self.expect( [[(None, "U", b"\xdb"), (None, "U", b"\x18"), (None, None, b"\xdb")]], raw=True, ) def test_set_title(self): self._the_title = None def _change_title(widget, title): self._the_title = title self.connect_signal("title") self.write("\\e]666parsed right?\\e\\te\\e]0;test title\007st1") self.expect("test1") self.expect_signal("test title") self.write("\\e];stupid title\\e\\\\e[0G\\e[2Ktest2") self.expect("test2") self.expect_signal("stupid title") self.disconnect_signal("title") def test_set_leds(self): self.connect_signal("leds") self.write(r"\e[0qtest1") self.expect("test1") self.expect_signal("clear") self.write(r"\e[3q\e[H\e[Ktest2") self.expect("test2") self.expect_signal("caps_lock") self.disconnect_signal("leds") def test_in_listbox(self): listbox = urwid.ListBox([urwid.BoxAdapter(self.term, 80)]) rendered = listbox.render((80, 24)) def test_bracketed_paste_mode_on(self): self.write(r"\e[?2004htest") self.expect("test") self.assertTrue(self.term.term_modes.bracketed_paste) self.term.keypress(None, "begin paste") self.term.keypress(None, "A") self.term.keypress(None, "end paste") sleep(0.1) self.expect(r"test^[[200~A^[[201~") def test_bracketed_paste_mode_off(self): self.write(r"\e[?2004ltest") self.expect("test") self.assertFalse(self.term.term_modes.bracketed_paste) self.term.keypress(None, "begin paste") self.term.keypress(None, "B") self.term.keypress(None, "end paste") sleep(0.1) self.expect(r"testB") urwid-2.6.16/tests/test_widget.py000066400000000000000000000124731470350774000170140ustar00rootroot00000000000000from __future__ import annotations import unittest import urwid from urwid.util import set_temporary_encoding class TextTest(unittest.TestCase): def setUp(self): self.t = urwid.Text("I walk the\ncity in the night") def test1_wrap(self): expected = [t.encode("iso8859-1") for t in ("I walk the", "city in ", "the night ")] got = self.t.render((10,))._text assert got == expected, f"got: {got!r} expected: {expected!r}" def test2_left(self): self.t.set_align_mode("left") expected = [t.encode("iso8859-1") for t in ("I walk the ", "city in the night ")] got = self.t.render((18,))._text assert got == expected, f"got: {got!r} expected: {expected!r}" def test3_right(self): self.t.set_align_mode("right") expected = [t.encode("iso8859-1") for t in (" I walk the", " city in the night")] got = self.t.render((18,))._text assert got == expected, f"got: {got!r} expected: {expected!r}" def test4_center(self): self.t.set_align_mode("center") expected = [t.encode("iso8859-1") for t in (" I walk the ", " city in the night")] got = self.t.render((18,))._text assert got == expected, f"got: {got!r} expected: {expected!r}" def test5_encode_error(self): with set_temporary_encoding("ascii"): expected = [b"? "] got = urwid.Text("û").render((3,))._text assert got == expected, f"got: {got!r} expected: {expected!r}" class EditTest(unittest.TestCase): def setUp(self): self.t1 = urwid.Edit(b"", "blah blah") self.t2 = urwid.Edit(b"stuff:", "blah blah") self.t3 = urwid.Edit(b"junk:\n", "blah blah\n\nbloo", 1) self.t4 = urwid.Edit("better:") def ktest(self, e, key, expected, pos, desc): got = e.keypress((12,), key) assert got == expected, f"{desc}. got: {got!r} expected:{expected!r}" assert e.edit_pos == pos, f"{desc}. pos: {e.edit_pos!r} expected pos: {pos!r}" def test1_left(self): self.t1.set_edit_pos(0) self.ktest(self.t1, "left", "left", 0, "left at left edge") self.ktest(self.t2, "left", None, 8, "left within text") self.t3.set_edit_pos(10) self.ktest(self.t3, "left", None, 9, "left after newline") def test2_right(self): self.ktest(self.t1, "right", "right", 9, "right at right edge") self.t2.set_edit_pos(8) self.ktest(self.t2, "right", None, 9, "right at right edge-1") self.t3.set_edit_pos(0) self.t3.keypress((12,), "right") assert self.t3.get_pref_col((12,)) == 1 def test3_up(self): self.ktest(self.t1, "up", "up", 9, "up at top") self.t2.set_edit_pos(2) self.t2.keypress((12,), "left") assert self.t2.get_pref_col((12,)) == 7 self.ktest(self.t2, "up", "up", 1, "up at top again") assert self.t2.get_pref_col((12,)) == 7 self.t3.set_edit_pos(10) self.ktest(self.t3, "up", None, 0, "up at top+1") def test4_down(self): self.ktest(self.t1, "down", "down", 9, "down single line") self.t3.set_edit_pos(5) self.ktest(self.t3, "down", None, 10, "down line 1 to 2") self.ktest(self.t3, "down", None, 15, "down line 2 to 3") self.ktest(self.t3, "down", "down", 15, "down at bottom") def test_utf8_input(self): with set_temporary_encoding("utf-8"): self.t1.set_edit_text("") self.t1.keypress((12,), "û") self.assertEqual(self.t1.edit_text, "û".encode()) self.t4.keypress((12,), "û") self.assertEqual(self.t4.edit_text, "û") class EditRenderTest(unittest.TestCase): def rtest(self, w, expected_text, expected_cursor): expected_text = [t.encode("iso8859-1") for t in expected_text] get_cursor = w.get_cursor_coords((4,)) assert get_cursor == expected_cursor, f"got: {get_cursor!r} expected: {expected_cursor!r}" r = w.render((4,), focus=1) text = [t for a, cs, t in [ln[0] for ln in r.content()]] assert text == expected_text, f"got: {text!r} expected: {expected_text!r}" assert r.cursor == expected_cursor, f"got: {r.cursor!r} expected: {expected_cursor!r}" def test1_SpaceWrap(self): w = urwid.Edit("", "blah blah") w.set_edit_pos(0) self.rtest(w, ["blah", "blah"], (0, 0)) w.set_edit_pos(4) self.rtest(w, ["lah ", "blah"], (3, 0)) w.set_edit_pos(5) self.rtest(w, ["blah", "blah"], (0, 1)) w.set_edit_pos(9) self.rtest(w, ["blah", "lah "], (3, 1)) def test2_ClipWrap(self): w = urwid.Edit("", "blah\nblargh", 1) w.set_wrap_mode("clip") w.set_edit_pos(0) self.rtest(w, ["blah", "blar"], (0, 0)) w.set_edit_pos(10) self.rtest(w, ["blah", "argh"], (3, 1)) w.set_align_mode("right") w.set_edit_pos(6) self.rtest(w, ["blah", "larg"], (0, 1)) def test3_AnyWrap(self): w = urwid.Edit("", "blah blah") w.set_wrap_mode("any") self.rtest(w, ["blah", " bla", "h "], (1, 2)) def test4_CursorNudge(self): w = urwid.Edit("", "hi", align="right") w.keypress((4,), "end") self.rtest(w, [" hi "], (3, 0)) w.keypress((4,), "left") self.rtest(w, [" hi"], (3, 0)) urwid-2.6.16/tests/util.py000066400000000000000000000002721470350774000154410ustar00rootroot00000000000000from __future__ import annotations import urwid class SelectableText(urwid.Text): def selectable(self): return True def keypress(self, size, key): return key urwid-2.6.16/tox.ini000066400000000000000000000017441470350774000142700ustar00rootroot00000000000000[tox] envlist = py3{7,8,9,10,11,12,13},pypy3,isort,black,ruff,pylint,refurb skip_missing_interpreters = True [testenv] usedevelop = true deps = -r {toxinidir}/test_requirements.txt commands = coverage run -m unittest discover -s tests -v coverage report [testenv:readme] deps = twine build commands = python -m build twine check {toxinidir}/dist/* [testenv:isort] skip_install = true deps = isort commands = isort . [testenv:black] skip_install = true depends = isort deps = black commands = black . [testenv:ruff] skip_install = true depends = black,isort deps = ruff commands = ruff check . [testenv:docs] deps = -r {toxinidir}/test_requirements.txt PyGObject sphinx sphinx-github-changelog commands = sphinx-build docs/ build/documentation [testenv:pylint] depends = black,isort deps = -r {toxinidir}/test_requirements.txt PyGObject pylint commands = pylint urwid [testenv:refurb] depends = black,isort deps = refurb commands = refurb urwid examples urwid-2.6.16/urwid/000077500000000000000000000000001470350774000141015ustar00rootroot00000000000000urwid-2.6.16/urwid/__init__.py000066400000000000000000000175511470350774000162230ustar00rootroot00000000000000# Urwid __init__.py - all the stuff you're likely to care about # # Copyright (C) 2004-2012 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import importlib import sys import types import typing import warnings from urwid.canvas import ( BlankCanvas, Canvas, CanvasCache, CanvasCombine, CanvasError, CanvasJoin, CanvasOverlay, CompositeCanvas, SolidCanvas, TextCanvas, ) from urwid.command_map import ( ACTIVATE, CURSOR_DOWN, CURSOR_LEFT, CURSOR_MAX_LEFT, CURSOR_MAX_RIGHT, CURSOR_PAGE_DOWN, CURSOR_PAGE_UP, CURSOR_RIGHT, CURSOR_UP, REDRAW_SCREEN, CommandMap, command_map, ) from urwid.font import ( Font, FontRegistry, HalfBlock5x4Font, HalfBlock6x5Font, HalfBlock7x7Font, HalfBlockHeavy6x5Font, Sextant2x2Font, Sextant3x3Font, Thin3x3Font, Thin4x3Font, Thin6x6Font, get_all_fonts, ) from urwid.signals import ( MetaSignals, Signals, connect_signal, disconnect_signal, disconnect_signal_by_key, emit_signal, register_signal, ) from urwid.str_util import calc_text_pos, calc_width, is_wide_char, move_next_char, move_prev_char, within_double_byte from urwid.text_layout import LayoutSegment, StandardTextLayout, TextLayout, default_layout from urwid.util import ( MetaSuper, TagMarkupException, apply_target_encoding, calc_trim_text, decompose_tagmarkup, detected_encoding, get_encoding_mode, int_scale, is_mouse_event, set_encoding, supports_unicode, ) from urwid.version import version as __version__ from urwid.version import version_tuple as __version_tuple__ from . import display, event_loop, widget from .display import ( BLACK, BROWN, DARK_BLUE, DARK_CYAN, DARK_GRAY, DARK_GREEN, DARK_MAGENTA, DARK_RED, DEFAULT, LIGHT_BLUE, LIGHT_CYAN, LIGHT_GRAY, LIGHT_GREEN, LIGHT_MAGENTA, LIGHT_RED, UPDATE_PALETTE_ENTRY, WHITE, YELLOW, AttrSpec, AttrSpecError, BaseScreen, RealTerminal, ScreenError, ) from .event_loop import AsyncioEventLoop, EventLoop, ExitMainLoop, MainLoop, SelectEventLoop from .widget import ( ANY, BOTTOM, BOX, CENTER, CLIP, ELLIPSIS, FIXED, FLOW, GIVEN, LEFT, MIDDLE, PACK, RELATIVE, RELATIVE_100, RIGHT, SPACE, TOP, WEIGHT, Align, AttrMap, AttrMapError, AttrWrap, BarGraph, BarGraphError, BarGraphMeta, BigText, BoxAdapter, BoxAdapterError, BoxWidget, Button, CheckBox, CheckBoxError, Columns, ColumnsError, Divider, Edit, EditError, Filler, FillerError, FixedWidget, FlowWidget, Frame, FrameError, GraphVScale, GridFlow, GridFlowError, IntEdit, LineBox, ListBox, ListBoxError, ListWalker, ListWalkerError, MonitoredFocusList, MonitoredList, Overlay, OverlayError, Padding, PaddingError, ParentNode, Pile, PileError, PopUpLauncher, PopUpTarget, ProgressBar, RadioButton, Scrollable, ScrollBar, SelectableIcon, SimpleFocusListWalker, SimpleListWalker, Sizing, SolidFill, Text, TextError, TreeListBox, TreeNode, TreeWalker, TreeWidget, TreeWidgetError, VAlign, WHSettings, Widget, WidgetContainerMixin, WidgetDecoration, WidgetDisable, WidgetError, WidgetMeta, WidgetPlaceholder, WidgetWrap, WidgetWrapError, WrapMode, delegate_to_widget_mixin, fixed_size, scale_bar_values, ) # Optional event loops with external dependencies try: from .event_loop import TornadoEventLoop except ImportError: pass try: from .event_loop import GLibEventLoop except ImportError: pass try: from .event_loop import TwistedEventLoop except ImportError: pass try: from .event_loop import TrioEventLoop except ImportError: pass # OS Specific if sys.platform != "win32": from .vterm import TermCanvas, TermCharset, Terminal, TermModes, TermScroller # ZMQEventLoop cause interpreter crash on windows try: from .event_loop import ZMQEventLoop except ImportError: pass # Backward compatibility VERSION = __version_tuple__ # Moved modules handling __locals: dict[str, typing.Any] = locals() # use mutable access for pure lazy loading # Backward compatible lazy load with deprecation warnings _moved_warn: dict[str, str] = { "lcd_display": "urwid.display.lcd", "html_fragment": "urwid.display.html_fragment", "web_display": "urwid.display.web", "monitored_list": "urwid.widget.monitored_list", "listbox": "urwid.widget.listbox", "treetools": "urwid.widget.treetools", } # Backward compatible lazy load without any warnings # Before DeprecationWarning need to start PendingDeprecationWarning process. _moved_no_warn: dict[str, str] = { "display_common": "urwid.display.common", "raw_display": "urwid.display.raw", "curses_display": "urwid.display.curses", "escape": "urwid.display.escape", } class _MovedModule(types.ModuleType): """Special class to handle moved modules. PEP-0562 handles moved modules attributes, but unfortunately not handle nested modules access like "from xxx.yyy import zzz" """ __slots__ = ("_moved_from", "_moved_to") def __init__(self, moved_from: str, moved_to: str) -> None: super().__init__(moved_from.join(".")[-1]) self._moved_from = moved_from self._moved_to = moved_to def __getattr__(self, name: str) -> typing.Any: real_module = importlib.import_module(self._moved_to) sys.modules[self._moved_from] = real_module return getattr(real_module, name) class _MovedModuleWarn(_MovedModule): """Special class to handle moved modules. Produce DeprecationWarning messages for imports. """ __slots__ = () def __getattr__(self, name: str) -> typing.Any: warnings.warn( f"{self._moved_from} is moved to {self._moved_to}", DeprecationWarning, stacklevel=2, ) return super().__getattr__(name) for _name, _module in _moved_no_warn.items(): _module_path = f"{__name__}.{_name}" sys.modules[_module_path] = _MovedModule(_module_path, _module) for _name, _module in _moved_warn.items(): _module_path = f"{__name__}.{_name}" sys.modules[_module_path] = _MovedModuleWarn(_module_path, _module) def __getattr__(name: str) -> typing.Any: """Get attributes lazy. :return: attribute by name :raises AttributeError: attribute is not defined for lazy load """ if name in _moved_no_warn: mod = importlib.import_module(_moved_no_warn[name]) __locals[name] = mod return mod if name in _moved_warn: warnings.warn( f"{name} is moved to {_moved_warn[name]}", DeprecationWarning, stacklevel=2, ) mod = importlib.import_module(_moved_warn[name]) __locals[name] = mod return mod raise AttributeError(f"{name} not found in {__package__}") urwid-2.6.16/urwid/canvas.py000066400000000000000000001332271470350774000157360ustar00rootroot00000000000000# Urwid canvas class and functions # Copyright (C) 2004-2011 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import contextlib import dataclasses import typing import warnings import weakref from contextlib import suppress from urwid.str_util import calc_text_pos, calc_width from urwid.text_layout import LayoutSegment, trim_line from urwid.util import ( apply_target_encoding, get_encoding, rle_append_modify, rle_join_modify, rle_len, rle_product, trim_text_attr_cs, ) if typing.TYPE_CHECKING: from collections.abc import Hashable, Iterable, Iterator, Sequence from typing_extensions import Literal from .widget import Widget class CanvasCache: """ Cache for rendered canvases. Automatically populated and accessed by Widget render() MetaClass magic, cleared by Widget._invalidate(). Stores weakrefs to the canvas objects, so an external class must maintain a reference for this cache to be effective. At present the Screen classes store the last topmost canvas after redrawing the screen, keeping the canvases from being garbage collected. _widgets[widget] = {(wcls, size, focus): weakref.ref(canvas), ...} _refs[weakref.ref(canvas)] = (widget, wcls, size, focus) _deps[widget} = [dependent_widget, ...] """ _widgets: typing.ClassVar[ dict[ Widget, dict[ tuple[type[Widget], tuple[int, int] | tuple[int] | tuple[()], bool], weakref.ReferenceType, ], ] ] = {} _refs: typing.ClassVar[ dict[ weakref.ReferenceType, tuple[Widget, type[Widget], tuple[int, int] | tuple[int] | tuple[()], bool], ] ] = {} _deps: typing.ClassVar[dict[Widget, list[Widget]]] = {} hits = 0 fetches = 0 cleanups = 0 @classmethod def store(cls, wcls, canvas: Canvas) -> None: """ Store a weakref to canvas in the cache. wcls -- widget class that contains render() function canvas -- rendered canvas with widget_info (widget, size, focus) """ if not canvas.cacheable: return if not canvas.widget_info: raise TypeError("Can't store canvas without widget_info") widget, size, focus = canvas.widget_info def walk_depends(canv): """ Collect all child widgets for determining who we depend on. """ # FIXME: is this recursion necessary? The cache invalidating might work with only one level. depends = [] for _x, _y, c, _pos in canv.children: if c.widget_info: depends.append(c.widget_info[0]) elif hasattr(c, "children"): depends.extend(walk_depends(c)) return depends # use explicit depends_on if available from the canvas depends_on = getattr(canvas, "depends_on", None) if depends_on is None and hasattr(canvas, "children"): depends_on = walk_depends(canvas) if depends_on: for w in depends_on: if w not in cls._widgets: return for w in depends_on: cls._deps.setdefault(w, []).append(widget) ref = weakref.ref(canvas, cls.cleanup) cls._refs[ref] = (widget, wcls, size, focus) cls._widgets.setdefault(widget, {})[wcls, size, focus] = ref @classmethod def fetch(cls, widget, wcls, size, focus) -> Canvas | None: """ Return the cached canvas or None. widget -- widget object requested wcls -- widget class that contains render() function size, focus -- render() parameters """ cls.fetches += 1 # collect stats sizes = cls._widgets.get(widget, None) if not sizes: return None ref = sizes.get((wcls, size, focus), None) if not ref: return None canv = ref() if canv: cls.hits += 1 # more stats return canv @classmethod def invalidate(cls, widget): """ Remove all canvases cached for widget. """ with contextlib.suppress(KeyError): for ref in cls._widgets[widget].values(): with suppress(KeyError): del cls._refs[ref] del cls._widgets[widget] if widget not in cls._deps: return dependants = cls._deps.get(widget, []) with suppress(KeyError): del cls._deps[widget] for w in dependants: cls.invalidate(w) @classmethod def cleanup(cls, ref: weakref.ReferenceType) -> None: cls.cleanups += 1 # collect stats w = cls._refs.get(ref, None) del cls._refs[ref] if not w: return widget, wcls, size, focus = w sizes = cls._widgets.get(widget, None) if not sizes: return with suppress(KeyError): del sizes[wcls, size, focus] if not sizes: with contextlib.suppress(KeyError): del cls._widgets[widget] del cls._deps[widget] @classmethod def clear(cls) -> None: """ Empty the cache. """ cls._widgets = {} cls._refs = {} cls._deps = {} class CanvasError(Exception): pass class Canvas: """ base class for canvases """ cacheable = True _finalized_error = CanvasError( "This canvas has been finalized. Use CompositeCanvas to wrap this canvas if you need to make changes." ) def __init__(self) -> None: """Base Canvas class""" self._widget_info = None self.coords: dict[str, tuple[int, int, tuple[Widget, int, int]] | tuple[int, int, None]] = {} self.shortcuts: dict[str, str] = {} def finalize( self, widget: Widget, size: tuple[()] | tuple[int] | tuple[int, int], focus: bool, ) -> None: """ Mark this canvas as finalized (should not be any future changes to its content). This is required before caching the canvas. This happens automatically after a widget's 'render call returns the canvas thanks to some metaclass magic. widget -- widget that rendered this canvas size -- size parameter passed to widget's render method focus -- focus parameter passed to widget's render method """ if self.widget_info: raise self._finalized_error self._widget_info = widget, size, focus @property def widget_info(self): return self._widget_info def _get_widget_info(self): warnings.warn( f"Method `{self.__class__.__name__}._get_widget_info` is deprecated, " f"please use property `{self.__class__.__name__}.widget_info`", DeprecationWarning, stacklevel=2, ) return self.widget_info @property def text(self) -> list[bytes]: """ Return the text content of the canvas as a list of strings, one for each row. """ return [b"".join([text for (attr, cs, text) in row]) for row in self.content()] @property def decoded_text(self) -> Sequence[str]: """Decoded text content of the canvas as a sequence of strings, one for each row.""" encoding = get_encoding() return tuple(line.decode(encoding) for line in self.text) def _text_content(self): warnings.warn( f"Method `{self.__class__.__name__}._text_content` is deprecated, " f"please use property `{self.__class__.__name__}.text`", DeprecationWarning, stacklevel=2, ) return self.text def content( self, trim_left: int = 0, trim_top: int = 0, cols: int | None = None, rows: int | None = None, attr=None, ) -> Iterator[list[tuple[object, Literal["0", "U"] | None, bytes]]]: raise NotImplementedError() def cols(self) -> int: raise NotImplementedError() def rows(self) -> int: raise NotImplementedError() def content_delta(self, other: Canvas): raise NotImplementedError() def get_cursor(self) -> tuple[int, int] | None: c = self.coords.get("cursor", None) if not c: return None return c[:2] # trim off data part def set_cursor(self, c: tuple[int, int] | None) -> None: if self.widget_info and self.cacheable: raise self._finalized_error if c is None: with suppress(KeyError): del self.coords["cursor"] return self.coords["cursor"] = (*c, None) # data part cursor = property(get_cursor, set_cursor) def get_pop_up(self) -> tuple[int, int, tuple[Widget, int, int]] | None: c = self.coords.get("pop up", None) if not c: return None return c def set_pop_up(self, w: Widget, left: int, top: int, overlay_width: int, overlay_height: int) -> None: """ This method adds pop-up information to the canvas. This information is intercepted by a PopUpTarget widget higher in the chain to display a pop-up at the given (left, top) position relative to the current canvas. :param w: widget to use for the pop-up :type w: widget :param left: x position for left edge of pop-up >= 0 :type left: int :param top: y position for top edge of pop-up >= 0 :type top: int :param overlay_width: width of overlay in screen columns > 0 :type overlay_width: int :param overlay_height: height of overlay in screen rows > 0 :type overlay_height: int """ if self.widget_info and self.cacheable: raise self._finalized_error self.coords["pop up"] = (left, top, (w, overlay_width, overlay_height)) def translate_coords(self, dx: int, dy: int) -> dict[str, tuple[int, int, tuple[Widget, int, int]]]: """ Return coords shifted by (dx, dy). """ d = {} for name, (x, y, data) in self.coords.items(): d[name] = (x + dx, y + dy, data) return d def __repr__(self) -> str: extra = [""] with contextlib.suppress(BaseException): extra.append(f"cols={self.cols()}") with contextlib.suppress(BaseException): extra.append(f"rows={self.rows()}") if self.cursor: extra.append(f"cursor={self.cursor}") return f"<{self.__class__.__name__} finalized={bool(self.widget_info)}{' '.join(extra)} at 0x{id(self):X}>" def __str__(self) -> str: with contextlib.suppress(BaseException): return "\n".join(self.decoded_text) return repr(self) class TextCanvas(Canvas): """ class for storing rendered text and attributes """ def __init__( self, text: list[bytes] | None = None, attr: list[list[tuple[Hashable | None, int]]] | None = None, cs: list[list[tuple[Literal["0", "U"] | None, int]]] | None = None, cursor: tuple[int, int] | None = None, maxcol: int | None = None, check_width: bool = True, ) -> None: """ text -- list of strings, one for each line attr -- list of run length encoded attributes for text cs -- list of run length encoded character set for text cursor -- (x,y) of cursor or None maxcol -- screen columns taken by this canvas check_width -- check and fix width of all lines in text """ super().__init__() if text is None: text = [] if check_width: widths = [] for t in text: if not isinstance(t, bytes): raise CanvasError( "Canvas text must be plain strings encoded in the screen's encoding", repr(text), ) widths.append(calc_width(t, 0, len(t))) else: if not isinstance(maxcol, int): raise TypeError(maxcol) widths = [maxcol] * len(text) if maxcol is None: if widths: # find maxcol ourselves maxcol = max(widths) else: maxcol = 0 if attr is None: attr = [[] for _ in range(len(text))] if cs is None: cs = [[] for _ in range(len(text))] # pad text and attr to maxcol for i in range(len(text)): w = widths[i] if w > maxcol: raise CanvasError( f"Canvas text is wider than the maxcol specified:\n" f"maxcol={maxcol!r}\n" f"widths={widths!r}\n" f"text={text!r}\n" f"urwid target encoding={get_encoding()}" ) if w < maxcol: text[i] += b"".rjust(maxcol - w) a_gap = len(text[i]) - rle_len(attr[i]) if a_gap < 0: raise CanvasError(f"Attribute extends beyond text \n{text[i]!r}\n{attr[i]!r}") if a_gap: rle_append_modify(attr[i], (None, a_gap)) cs_gap = len(text[i]) - rle_len(cs[i]) if cs_gap < 0: raise CanvasError(f"Character Set extends beyond text \n{text[i]!r}\n{cs[i]!r}") if cs_gap: rle_append_modify(cs[i], (None, cs_gap)) self._attr = attr self._cs = cs self.cursor = cursor self._text = text self._maxcol = maxcol def rows(self) -> int: """Return the number of rows in this canvas.""" return len(self._text) def cols(self) -> int: """Return the screen column width of this canvas.""" return self._maxcol def translated_coords(self, dx: int, dy: int) -> tuple[int, int] | None: """ Return cursor coords shifted by (dx, dy), or None if there is no cursor. """ if self.cursor: x, y = self.cursor return x + dx, y + dy return None def content( self, trim_left: int = 0, trim_top: int = 0, cols: int | None = 0, rows: int | None = 0, attr=None, ) -> Iterator[tuple[object, Literal["0", "U"] | None, bytes]]: """ Return the canvas content as a list of rows where each row is a list of (attr, cs, text) tuples. trim_left, trim_top, cols, rows may be set by CompositeCanvas when rendering a partially obscured canvas. """ maxcol, maxrow = self.cols(), self.rows() if not cols: cols = maxcol - trim_left if not rows: rows = maxrow - trim_top if not ((0 <= trim_left < maxcol) and (cols > 0 and trim_left + cols <= maxcol)): raise ValueError(trim_left) if not ((0 <= trim_top < maxrow) and (rows > 0 and trim_top + rows <= maxrow)): raise ValueError(trim_top) if trim_top or rows < maxrow: text_attr_cs = zip( self._text[trim_top : trim_top + rows], self._attr[trim_top : trim_top + rows], self._cs[trim_top : trim_top + rows], ) else: text_attr_cs = zip(self._text, self._attr, self._cs) for text, a_row, cs_row in text_attr_cs: if trim_left or cols < self._maxcol: text, a_row, cs_row = trim_text_attr_cs( # noqa: PLW2901 text, a_row, cs_row, trim_left, trim_left + cols, ) attr_cs = rle_product(a_row, cs_row) i = 0 row = [] for (a, cs), run in attr_cs: if attr and a in attr: a = attr[a] # noqa: PLW2901 row.append((a, cs, text[i : i + run])) i += run yield row def content_delta(self, other: Canvas): """ Return the differences between other and this canvas. If other is the same object as self this will return no differences, otherwise this is the same as calling content(). """ if other is self: return [self.cols()] * self.rows() return self.content() class BlankCanvas(Canvas): """ a canvas with nothing on it, only works as part of a composite canvas since it doesn't know its own size """ def content( self, trim_left: int = 0, trim_top: int = 0, cols: int | None = 0, rows: int | None = 0, attr=None, ) -> Iterator[list[tuple[object, Literal["0", "U"] | None, bytes]]]: """ return (cols, rows) of spaces with default attributes. """ def_attr = None if attr and None in attr: def_attr = attr[None] line = [(def_attr, None, b"".rjust(cols))] for _ in range(rows): yield line def cols(self) -> typing.NoReturn: raise NotImplementedError("BlankCanvas doesn't know its own size!") def rows(self) -> typing.NoReturn: raise NotImplementedError("BlankCanvas doesn't know its own size!") def content_delta(self, other: Canvas) -> typing.NoReturn: raise NotImplementedError("BlankCanvas doesn't know its own size!") blank_canvas = BlankCanvas() class SolidCanvas(Canvas): """ A canvas filled completely with a single character. """ def __init__(self, fill_char: str | bytes, cols: int, rows: int) -> None: super().__init__() end, col = calc_text_pos(fill_char, 0, len(fill_char), 1) if col != 1: raise ValueError(f"Invalid fill_char: {fill_char!r}") self._text, cs = apply_target_encoding(fill_char[:end]) self._cs = cs[0][0] self.size = cols, rows self.cursor = None def cols(self) -> int: return self.size[0] def rows(self) -> int: return self.size[1] def content( self, trim_left: int = 0, trim_top: int = 0, cols: int | None = None, rows: int | None = None, attr=None, ) -> Iterator[list[tuple[object, Literal["0", "U"] | None, bytes]]]: if cols is None: cols = self.size[0] if rows is None: rows = self.size[1] def_attr = None if attr and None in attr: def_attr = attr[None] line = [(def_attr, self._cs, self._text * cols)] for _ in range(rows): yield line def content_delta(self, other): """ Return the differences between other and this canvas. """ if other is self: return [self.cols()] * self.rows() return self.content() class CompositeCanvas(Canvas): """ class for storing a combination of canvases """ def __init__(self, canv: Canvas = None) -> None: """ canv -- a Canvas object to wrap this CompositeCanvas around. if canv is a CompositeCanvas, make a copy of its contents """ # a "shard" is a (num_rows, list of cviews) tuple, one for # each cview starting in this shard # a "cview" is a tuple that defines a view of a canvas: # (trim_left, trim_top, cols, rows, attr_map, canv) # a "shard tail" is a list of tuples: # (col_gap, done_rows, content_iter, cview) # tuples that define the unfinished cviews that are part of # shards following the first shard. super().__init__() if canv is None: self.shards: list[ tuple[ int, list[tuple[int, int, int, int, dict[Hashable | None, Hashable] | None, Canvas]], ] ] = [] self.children: list[tuple[int, int, Canvas, typing.Any]] = [] else: if hasattr(canv, "shards"): self.shards = canv.shards else: self.shards = [(canv.rows(), [(0, 0, canv.cols(), canv.rows(), None, canv)])] self.children = [(0, 0, canv, None)] self.coords.update(canv.coords) for shortcut in canv.shortcuts: self.shortcuts[shortcut] = "wrap" def __repr__(self) -> str: extra = [""] with contextlib.suppress(BaseException): extra.append(f"cols={self.cols()}") with contextlib.suppress(BaseException): extra.append(f"rows={self.rows()}") if self.cursor: extra.append(f"cursor={self.cursor}") if self.children: extra.append(f"children=({', '.join(repr(canv) for _, _, canv, _ in self.children)})") return f"<{self.__class__.__name__} finalized={bool(self.widget_info)}{' '.join(extra)} at 0x{id(self):X}>" def rows(self) -> int: for r, cv in self.shards: if not isinstance(r, int): raise TypeError(r, cv) return sum(r for r, cv in self.shards) def cols(self) -> int: if not self.shards: return 0 cols = sum(cv[2] for cv in self.shards[0][1]) if not isinstance(cols, int): raise TypeError(cols) return cols def content( self, trim_left: int = 0, trim_top: int = 0, cols: int | None = None, rows: int | None = None, attr=None, ) -> Iterator[list[tuple[object, Literal["0", "U"] | None, bytes]]]: """ Return the canvas content as a list of rows where each row is a list of (attr, cs, text) tuples. """ shard_tail = [] for num_rows, cviews in self.shards: # combine shard and shard tail sbody = shard_body(cviews, shard_tail) # output rows for _ in range(num_rows): yield shard_body_row(sbody) # prepare next shard tail shard_tail = shard_body_tail(num_rows, sbody) def content_delta(self, other: Canvas): """ Return the differences between other and this canvas. """ if not hasattr(other, "shards"): yield from self.content() return shard_tail = [] for num_rows, cviews in shards_delta(self.shards, other.shards): # combine shard and shard tail sbody = shard_body(cviews, shard_tail) # output rows row = [] for _ in range(num_rows): # if whole shard is unchanged, don't keep # calling shard_body_row if len(row) != 1 or not isinstance(row[0], int): row = shard_body_row(sbody) yield row # prepare next shard tail shard_tail = shard_body_tail(num_rows, sbody) def trim(self, top: int, count: int | None = None) -> None: """Trim lines from the top and/or bottom of canvas. top -- number of lines to remove from top count -- number of lines to keep, or None for all the rest """ if top < 0: raise ValueError(f"invalid trim amount {top:d}!") if top >= self.rows(): raise ValueError(f"cannot trim {top:d} lines from {self.rows():d}!") if self.widget_info: raise self._finalized_error if top: self.shards = shards_trim_top(self.shards, top) if count == 0: self.shards = [] elif count is not None: self.shards = shards_trim_rows(self.shards, count) self.coords = self.translate_coords(0, -top) def trim_end(self, end: int) -> None: """Trim lines from the bottom of the canvas. end -- number of lines to remove from the end """ if end <= 0: raise ValueError(f"invalid trim amount {end:d}!") if end > self.rows(): raise ValueError(f"cannot trim {end:d} lines from {self.rows():d}!") if self.widget_info: raise self._finalized_error self.shards = shards_trim_rows(self.shards, self.rows() - end) def pad_trim_left_right(self, left: int, right: int) -> None: """ Pad or trim this canvas on the left and right values > 0 indicate screen columns to pad values < 0 indicate screen columns to trim """ if self.widget_info: raise self._finalized_error shards = self.shards if left < 0 or right < 0: trim_left = max(0, -left) cols = self.cols() - trim_left - max(0, -right) shards = shards_trim_sides(shards, trim_left, cols) rows = self.rows() if left > 0 or right > 0: top_rows, top_cviews = shards[0] if left > 0: new_top_cviews = [(0, 0, left, rows, None, blank_canvas), *top_cviews] else: new_top_cviews = top_cviews.copy() if right > 0: new_top_cviews.append((0, 0, right, rows, None, blank_canvas)) shards = [(top_rows, new_top_cviews)] + shards[1:] self.coords = self.translate_coords(left, 0) self.shards = shards def pad_trim_top_bottom(self, top: int, bottom: int) -> None: """ Pad or trim this canvas on the top and bottom. """ if self.widget_info: raise self._finalized_error orig_shards = self.shards if top < 0 or bottom < 0: trim_top = max(0, -top) rows = self.rows() - trim_top - max(0, -bottom) self.trim(trim_top, rows) cols = self.cols() if top > 0: self.shards = [(top, [(0, 0, cols, top, None, blank_canvas)]), *self.shards] self.coords = self.translate_coords(0, top) if bottom > 0: if orig_shards is self.shards: self.shards = self.shards[:] self.shards.append((bottom, [(0, 0, cols, bottom, None, blank_canvas)])) def overlay(self, other: CompositeCanvas, left: int, top: int) -> None: """Overlay other onto this canvas.""" if self.widget_info: raise self._finalized_error width = other.cols() height = other.rows() right = self.cols() - left - width bottom = self.rows() - top - height if right < 0: raise ValueError(f"top canvas of overlay not the size expected!{(other.cols(), left, right, width)!r}") if bottom < 0: raise ValueError(f"top canvas of overlay not the size expected!{(other.rows(), top, bottom, height)!r}") shards = self.shards top_shards = [] side_shards = self.shards bottom_shards = [] if top: side_shards = shards_trim_top(shards, top) top_shards = shards_trim_rows(shards, top) if bottom: bottom_shards = shards_trim_top(side_shards, height) side_shards = shards_trim_rows(side_shards, height) left_shards = [] right_shards = [] if left > 0: left_shards = [shards_trim_sides(side_shards, 0, left)] if right > 0: right_shards = [shards_trim_sides(side_shards, max(0, left + width), right)] if not self.rows(): middle_shards = [] elif left or right: middle_shards = shards_join((*left_shards, other.shards, *right_shards)) else: middle_shards = other.shards self.shards = top_shards + middle_shards + bottom_shards self.coords.update(other.translate_coords(left, top)) def fill_attr(self, a: Hashable) -> None: """ Apply attribute a to all areas of this canvas with default attribute currently set to None, leaving other attributes intact. """ self.fill_attr_apply({None: a}) def fill_attr_apply(self, mapping: dict[Hashable | None, Hashable]) -> None: """ Apply an attribute-mapping dictionary to the canvas. mapping -- dictionary of original-attribute:new-attribute items """ if self.widget_info: raise self._finalized_error shards = [] for num_rows, original_cviews in self.shards: new_cviews = [] for cv in original_cviews: # cv[4] == attr_map if cv[4] is None: new_cviews.append(cv[:4] + (mapping,) + cv[5:]) else: combined = mapping.copy() combined.update([(k, mapping.get(v, v)) for k, v in cv[4].items()]) new_cviews.append(cv[:4] + (combined,) + cv[5:]) shards.append((num_rows, new_cviews)) self.shards = shards def set_depends(self, widget_list: Sequence[Widget]) -> None: """ Explicitly specify the list of widgets that this canvas depends on. If any of these widgets change this canvas will have to be updated. """ if self.widget_info: raise self._finalized_error self.depends_on = widget_list def shard_body_row(sbody): """ Return one row, advancing the iterators in sbody. ** MODIFIES sbody by calling next() on its iterators ** """ row = [] for _done_rows, content_iter, cview in sbody: if content_iter: row.extend(next(content_iter)) else: # noqa: PLR5501 # pylint: disable=else-if-used # readability # need to skip this unchanged canvas if row and isinstance(row[-1], int): row[-1] += cview[2] else: row.append(cview[2]) return row def shard_body_tail(num_rows: int, sbody): """ Return a new shard tail that follows this shard body. """ shard_tail = [] col_gap = 0 for done_rows, content_iter, cview in sbody: cols, rows = cview[2:4] done_rows += num_rows # noqa: PLW2901 if done_rows == rows: col_gap += cols continue shard_tail.append((col_gap, done_rows, content_iter, cview)) col_gap = 0 return shard_tail def shards_delta(shards, other_shards): """ Yield shards1 with cviews that are the same as shards2 having canv = None. """ # pylint: disable=stop-iteration-return other_shards_iter = iter(other_shards) other_num_rows = other_cviews = None done = other_done = 0 for num_rows, cviews in shards: if other_num_rows is None: other_num_rows, other_cviews = next(other_shards_iter) while other_done < done: other_done += other_num_rows other_num_rows, other_cviews = next(other_shards_iter) if other_done > done: yield (num_rows, cviews) done += num_rows continue # top-aligned shards, compare each cview yield (num_rows, shard_cviews_delta(cviews, other_cviews)) other_done += other_num_rows other_num_rows = None done += num_rows def shard_cviews_delta(cviews, other_cviews): # pylint: disable=stop-iteration-return other_cviews_iter = iter(other_cviews) other_cv = None cols = other_cols = 0 for cv in cviews: if other_cv is None: other_cv = next(other_cviews_iter) while other_cols < cols: other_cols += other_cv[2] other_cv = next(other_cviews_iter) if other_cols > cols: yield cv cols += cv[2] continue # top-left-aligned cviews, compare them if cv[5] is other_cv[5] and cv[:5] == other_cv[:5]: yield cv[:5] + (None,) + cv[6:] else: yield cv other_cols += other_cv[2] other_cv = None cols += cv[2] def shard_body(cviews, shard_tail, create_iter: bool = True, iter_default=None): """ Return a list of (done_rows, content_iter, cview) tuples for this shard and shard tail. If a canvas in cviews is None (eg. when unchanged from shard_cviews_delta()) or if create_iter is False then no iterator is created for content_iter. iter_default is the value used for content_iter when no iterator is created. """ col = 0 body = [] # build the next shard tail cviews_iter = iter(cviews) for col_gap, done_rows, content_iter, tail_cview in shard_tail: while col_gap: try: cview = next(cviews_iter) except StopIteration: break (trim_left, trim_top, cols, rows, attr_map, canv) = cview[:6] col += cols col_gap -= cols # noqa: PLW2901 if col_gap < 0: raise CanvasError("cviews overflow gaps in shard_tail!") if create_iter and canv: new_iter = canv.content(trim_left, trim_top, cols, rows, attr_map) else: new_iter = iter_default body.append((0, new_iter, cview)) body.append((done_rows, content_iter, tail_cview)) for cview in cviews_iter: (trim_left, trim_top, cols, rows, attr_map, canv) = cview[:6] if create_iter and canv: new_iter = canv.content(trim_left, trim_top, cols, rows, attr_map) else: new_iter = iter_default body.append((0, new_iter, cview)) return body def shards_trim_top(shards, top: int): """ Return shards with top rows removed. """ if top <= 0: raise ValueError(top) shard_iter = iter(shards) shard_tail = [] # skip over shards that are completely removed for num_rows, cviews in shard_iter: if top < num_rows: break sbody = shard_body(cviews, shard_tail, False) shard_tail = shard_body_tail(num_rows, sbody) top -= num_rows else: raise CanvasError("tried to trim shards out of existence") sbody = shard_body(cviews, shard_tail, False) shard_tail = shard_body_tail(num_rows, sbody) # trim the top of this shard new_sbody = [(0, content_iter, cview_trim_top(cv, done_rows + top)) for done_rows, content_iter, cv in sbody] sbody = new_sbody new_shards = [(num_rows - top, [cv for done_rows, content_iter, cv in sbody])] # write out the rest of the shards new_shards.extend(shard_iter) return new_shards def shards_trim_rows(shards, keep_rows: int): """ Return the topmost keep_rows rows from shards. """ if keep_rows < 0: raise ValueError(keep_rows) new_shards = [] done_rows = 0 for num_rows, cviews in shards: if done_rows >= keep_rows: break new_cviews = [] for cv in cviews: if cv[3] + done_rows > keep_rows: new_cviews.append(cview_trim_rows(cv, keep_rows - done_rows)) else: new_cviews.append(cv) if num_rows + done_rows > keep_rows: new_shards.append((keep_rows - done_rows, new_cviews)) else: new_shards.append((num_rows, new_cviews)) done_rows += num_rows return new_shards def shards_trim_sides(shards, left: int, cols: int): """ Return shards with starting from column left and cols total width. """ if left < 0: raise ValueError(left) if cols <= 0: raise ValueError(cols) shard_tail = [] new_shards = [] right = left + cols for num_rows, cviews in shards: sbody = shard_body(cviews, shard_tail, False) shard_tail = shard_body_tail(num_rows, sbody) new_cviews = [] col = 0 for done_rows, _content_iter, cv in sbody: cv_cols = cv[2] next_col = col + cv_cols if done_rows or next_col <= left or col >= right: col = next_col continue if col < left: cv = cview_trim_left(cv, left - col) # noqa: PLW2901 col = left if next_col > right: cv = cview_trim_cols(cv, right - col) # noqa: PLW2901 new_cviews.append(cv) col = next_col if not new_cviews: prev_num_rows, prev_cviews = new_shards[-1] new_shards[-1] = (prev_num_rows + num_rows, prev_cviews) else: new_shards.append((num_rows, new_cviews)) return new_shards def shards_join(shard_lists): """ Return the result of joining shard lists horizontally. All shards lists must have the same number of rows. """ shards_iters = [iter(sl) for sl in shard_lists] shards_current = [next(i) for i in shards_iters] new_shards = [] while True: new_cviews = [] num_rows = min(r for r, cv in shards_current) shards_next = [] for rows, cviews in shards_current: if cviews: new_cviews.extend(cviews) shards_next.append((rows - num_rows, None)) shards_current = shards_next new_shards.append((num_rows, new_cviews)) # advance to next shards try: for i in range(len(shards_current)): if shards_current[i][0] > 0: continue shards_current[i] = next(shards_iters[i]) except StopIteration: break return new_shards def cview_trim_rows(cv, rows: int): return cv[:3] + (rows,) + cv[4:] def cview_trim_top(cv, trim: int): return (cv[0], trim + cv[1], cv[2], cv[3] - trim) + cv[4:] def cview_trim_left(cv, trim: int): return (cv[0] + trim, cv[1], cv[2] - trim) + cv[3:] def cview_trim_cols(cv, cols: int): return cv[:2] + (cols,) + cv[3:] def CanvasCombine(canvas_info: Iterable[tuple[Canvas, typing.Any, bool]]) -> CompositeCanvas: """Stack canvases in l vertically and return resulting canvas. :param canvas_info: list of (canvas, position, focus) tuples: position a value that widget.set_focus will accept or None if not allowed focus True if this canvas is the one that would be in focus if the whole widget is in focus """ clist = [(CompositeCanvas(c), p, f) for c, p, f in canvas_info] combined_canvas = CompositeCanvas() shards = [] children = [] row = 0 focus_index = 0 for n, (canv, pos, focus) in enumerate(clist): if focus: focus_index = n children.append((0, row, canv, pos)) shards.extend(canv.shards) combined_canvas.coords.update(canv.translate_coords(0, row)) for shortcut in canv.shortcuts: combined_canvas.shortcuts[shortcut] = pos row += canv.rows() if focus_index: children = [children[focus_index]] + children[:focus_index] + children[focus_index + 1 :] combined_canvas.shards = shards combined_canvas.children = children return combined_canvas def CanvasOverlay(top_c: Canvas, bottom_c: Canvas, left: int, top: int) -> CompositeCanvas: """ Overlay canvas top_c onto bottom_c at position (left, top). """ overlayed_canvas = CompositeCanvas(bottom_c) overlayed_canvas.overlay(top_c, left, top) overlayed_canvas.children = [(left, top, top_c, None), (0, 0, bottom_c, None)] overlayed_canvas.shortcuts = {} # disable background shortcuts for shortcut in top_c.shortcuts: overlayed_canvas.shortcuts[shortcut] = "fg" return overlayed_canvas def CanvasJoin(canvas_info: Iterable[tuple[Canvas, typing.Any, bool, int]]) -> CompositeCanvas: """ Join canvases in l horizontally. Return result. :param canvas_info: list of (canvas, position, focus, cols) tuples: position value that widget.set_focus will accept or None if not allowed focus True if this canvas is the one that would be in focus if the whole widget is in focus cols is the number of screen columns that this widget will require, if larger than the actual canvas.cols() value then this widget will be padded on the right. """ l2 = [] focus_item = 0 maxrow = 0 for n, (canv, pos, focus, cols) in enumerate(canvas_info): rows = canv.rows() pad_right = cols - canv.cols() if focus: focus_item = n maxrow = max(maxrow, rows) l2.append((canv, pos, pad_right, rows)) shard_lists = [] children = [] joined_canvas = CompositeCanvas() col = 0 for canv, pos, pad_right, rows in l2: composite_canvas = CompositeCanvas(canv) if pad_right: composite_canvas.pad_trim_left_right(0, pad_right) if rows < maxrow: composite_canvas.pad_trim_top_bottom(0, maxrow - rows) joined_canvas.coords.update(composite_canvas.translate_coords(col, 0)) for shortcut in composite_canvas.shortcuts: joined_canvas.shortcuts[shortcut] = pos shard_lists.append(composite_canvas.shards) children.append((col, 0, composite_canvas, pos)) col += composite_canvas.cols() if focus_item: children = [children[focus_item]] + children[:focus_item] + children[focus_item + 1 :] joined_canvas.shards = shards_join(shard_lists) joined_canvas.children = children return joined_canvas @dataclasses.dataclass class _AttrWalk: counter: int = 0 # counter for moving through elements of a offset: int = 0 # current offset into text of attr[ak] def apply_text_layout( text: str | bytes, attr: list[tuple[Hashable, int]], ls: list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]], maxcol: int, ) -> TextCanvas: t: list[bytes] = [] a: list[list[tuple[Hashable | None, int]]] = [] c: list[list[tuple[Literal["0", "U"] | None, int]]] = [] aw = _AttrWalk() def arange(start_offs: int, end_offs: int) -> list[tuple[Hashable | None, int]]: """Return an attribute list for the range of text specified.""" if start_offs < aw.offset: aw.counter = 0 aw.offset = 0 o = [] # the loop should run at least once, the '=' part ensures that while aw.offset <= end_offs: if len(attr) <= aw.counter: # run out of attributes o.append((None, end_offs - max(start_offs, aw.offset))) break at, run = attr[aw.counter] if aw.offset + run <= start_offs: # move forward through attr to find start_offs aw.counter += 1 aw.offset += run continue if end_offs <= aw.offset + run: o.append((at, end_offs - max(start_offs, aw.offset))) break o.append((at, aw.offset + run - max(start_offs, aw.offset))) aw.counter += 1 aw.offset += run return o for line_layout in ls: # trim the line to fit within maxcol line_layout = trim_line(line_layout, text, 0, maxcol) # noqa: PLW2901 line = [] linea = [] linec = [] def attrrange(start_offs: int, end_offs: int, destw: int) -> None: """ Add attributes based on attributes between start_offs and end_offs. """ # pylint: disable=cell-var-from-loop if start_offs == end_offs: [(at, run)] = arange(start_offs, end_offs) # pylint: disable=unbalanced-tuple-unpacking rle_append_modify(linea, (at, destw)) # noqa: B023 return if destw == end_offs - start_offs: for at, run in arange(start_offs, end_offs): rle_append_modify(linea, (at, run)) # noqa: B023 return # encoded version has different width o = start_offs for at, run in arange(start_offs, end_offs): if o + run == end_offs: rle_append_modify(linea, (at, destw)) # noqa: B023 return tseg = text[o : o + run] tseg, cs = apply_target_encoding(tseg) segw = rle_len(cs) rle_append_modify(linea, (at, segw)) # noqa: B023 o += run destw -= segw for seg in line_layout: # if seg is None: assert 0, ls s = LayoutSegment(seg) if s.end: tseg, cs = apply_target_encoding(text[s.offs : s.end]) line.append(tseg) attrrange(s.offs, s.end, rle_len(cs)) rle_join_modify(linec, cs) elif s.text: tseg, cs = apply_target_encoding(s.text) line.append(tseg) attrrange(s.offs, s.offs, len(tseg)) rle_join_modify(linec, cs) elif s.offs: if s.sc: line.append(b"".rjust(s.sc)) attrrange(s.offs, s.offs, s.sc) else: line.append(b"".rjust(s.sc)) linea.append((None, s.sc)) linec.append((None, s.sc)) t.append(b"".join(line)) a.append(linea) c.append(linec) return TextCanvas(t, a, c, maxcol=maxcol) urwid-2.6.16/urwid/command_map.py000066400000000000000000000103441470350774000167300ustar00rootroot00000000000000# Urwid CommandMap class # Copyright (C) 2004-2011 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import enum import typing if typing.TYPE_CHECKING: from collections.abc import Iterator from typing_extensions import Self class Command(str, enum.Enum): REDRAW_SCREEN = "redraw screen" UP = "cursor up" DOWN = "cursor down" LEFT = "cursor left" RIGHT = "cursor right" PAGE_UP = "cursor page up" PAGE_DOWN = "cursor page down" MAX_LEFT = "cursor max left" MAX_RIGHT = "cursor max right" ACTIVATE = "activate" MENU = "menu" SELECT_NEXT = "next selectable" SELECT_PREVIOUS = "prev selectable" REDRAW_SCREEN = Command.REDRAW_SCREEN CURSOR_UP = Command.UP CURSOR_DOWN = Command.DOWN CURSOR_LEFT = Command.LEFT CURSOR_RIGHT = Command.RIGHT CURSOR_PAGE_UP = Command.PAGE_UP CURSOR_PAGE_DOWN = Command.PAGE_DOWN CURSOR_MAX_LEFT = Command.MAX_LEFT CURSOR_MAX_RIGHT = Command.MAX_RIGHT ACTIVATE = Command.ACTIVATE class CommandMap(typing.Mapping[str, typing.Union[str, Command, None]]): """ dict-like object for looking up commands from keystrokes Default values (key: command):: 'tab': 'next selectable', 'ctrl n': 'next selectable', 'shift tab': 'prev selectable', 'ctrl p': 'prev selectable', 'ctrl l': 'redraw screen', 'esc': 'menu', 'up': 'cursor up', 'down': 'cursor down', 'left': 'cursor left', 'right': 'cursor right', 'page up': 'cursor page up', 'page down': 'cursor page down', 'home': 'cursor max left', 'end': 'cursor max right', ' ': 'activate', 'enter': 'activate', """ def __iter__(self) -> Iterator[str]: return iter(self._command) def __len__(self) -> int: return len(self._command) _command_defaults: typing.ClassVar[dict[str, str | Command]] = { "tab": Command.SELECT_NEXT, "ctrl n": Command.SELECT_NEXT, "shift tab": Command.SELECT_PREVIOUS, "ctrl p": Command.SELECT_PREVIOUS, "ctrl l": Command.REDRAW_SCREEN, "esc": Command.MENU, "up": Command.UP, "down": Command.DOWN, "left": Command.LEFT, "right": Command.RIGHT, "page up": Command.PAGE_UP, "page down": Command.PAGE_DOWN, "home": Command.MAX_LEFT, "end": Command.MAX_RIGHT, " ": Command.ACTIVATE, "enter": Command.ACTIVATE, } def __init__(self) -> None: self._command = dict(self._command_defaults) def restore_defaults(self) -> None: self._command = dict(self._command_defaults) def __getitem__(self, key: str) -> str | Command | None: return self._command.get(key, None) def __setitem__(self, key, command: str | Command) -> None: self._command[key] = command def __delitem__(self, key: str) -> None: del self._command[key] def clear_command(self, command: str | Command) -> None: dk = [k for k, v in self._command.items() if v == command] for k in dk: del self._command[k] def copy(self) -> Self: """ Return a new copy of this CommandMap, likely so we can modify it separate from a shared one. """ c = self.__class__() c._command = dict(self._command) # pylint: disable=protected-access return c command_map = CommandMap() # shared command mappings urwid-2.6.16/urwid/container.py000066400000000000000000000030651470350774000164410ustar00rootroot00000000000000# Urwid container widget classes # Copyright (C) 2004-2012 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import warnings from urwid.widget import ( Columns, ColumnsError, Frame, FrameError, GridFlow, GridFlowError, Overlay, OverlayError, Pile, PileError, WidgetContainerMixin, ) __all__ = ( "Columns", "ColumnsError", "Frame", "FrameError", "GridFlow", "GridFlowError", "Overlay", "OverlayError", "Pile", "PileError", "WidgetContainerMixin", ) warnings.warn( f"{__name__!r} is not expected to be imported directly. " 'Please use public access from "urwid" package. ' f"Module {__name__!r} is deprecated and will be removed in the future.", DeprecationWarning, stacklevel=3, ) urwid-2.6.16/urwid/decoration.py000066400000000000000000000033541470350774000166070ustar00rootroot00000000000000# Urwid widget decoration classes # Copyright (C) 2004-2012 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import warnings from urwid.widget import ( AttrMap, AttrMapError, AttrWrap, BoxAdapter, BoxAdapterError, Filler, FillerError, Padding, PaddingError, WidgetDecoration, WidgetDisable, WidgetPlaceholder, calculate_top_bottom_filler, normalize_valign, ) __all__ = ( "AttrMap", "AttrMapError", "AttrWrap", "BoxAdapter", "BoxAdapterError", "Filler", "FillerError", "Padding", "PaddingError", "WidgetDecoration", "WidgetDisable", "WidgetPlaceholder", "calculate_top_bottom_filler", "normalize_valign", ) warnings.warn( f"{__name__!r} is not expected to be imported directly. " 'Please use public access from "urwid" package. ' f"Module {__name__!r} is deprecated and will be removed in the future.", DeprecationWarning, stacklevel=3, ) urwid-2.6.16/urwid/display/000077500000000000000000000000001470350774000155465ustar00rootroot00000000000000urwid-2.6.16/urwid/display/__init__.py000066400000000000000000000033041470350774000176570ustar00rootroot00000000000000"""Package with Display implementations for urwid.""" from __future__ import annotations import importlib import typing __all__ = ( "BLACK", "BROWN", "DARK_BLUE", "DARK_CYAN", "DARK_GRAY", "DARK_GREEN", "DARK_MAGENTA", "DARK_RED", "DEFAULT", "LIGHT_BLUE", "LIGHT_CYAN", "LIGHT_GRAY", "LIGHT_GREEN", "LIGHT_MAGENTA", "LIGHT_RED", "UPDATE_PALETTE_ENTRY", "WHITE", "YELLOW", "AttrSpec", "AttrSpecError", "BaseScreen", "RealTerminal", "ScreenError", # Lazy imported "html_fragment", "lcd", "raw", "web", ) from . import raw from .common import ( BLACK, BROWN, DARK_BLUE, DARK_CYAN, DARK_GRAY, DARK_GREEN, DARK_MAGENTA, DARK_RED, DEFAULT, LIGHT_BLUE, LIGHT_CYAN, LIGHT_GRAY, LIGHT_GREEN, LIGHT_MAGENTA, LIGHT_RED, UPDATE_PALETTE_ENTRY, WHITE, YELLOW, AttrSpec, AttrSpecError, BaseScreen, RealTerminal, ScreenError, ) try: from . import curses __all__ += ("curses",) except ImportError: pass # Moved modules handling __locals: dict[str, typing.Any] = locals() # use mutable access for pure lazy loading # Lazy load modules _lazy_load: frozenset[str] = frozenset( ( "html_fragment", "lcd", "web", ) ) def __getattr__(name: str) -> typing.Any: """Get attributes lazy. :return: attribute by name :raises AttributeError: attribute is not defined for lazy load """ if name in _lazy_load: mod = importlib.import_module(f"{__package__}.{name}") __locals[name] = mod return mod raise AttributeError(f"{name} not found in {__package__}") urwid-2.6.16/urwid/display/_posix_raw_display.py000066400000000000000000000353151470350774000220260ustar00rootroot00000000000000# Urwid raw display module # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Direct terminal UI implementation """ from __future__ import annotations import contextlib import fcntl import functools import os import selectors import signal import struct import sys import termios import tty import typing from subprocess import PIPE, Popen from urwid import signals from . import _raw_display_base, escape from .common import INPUT_DESCRIPTORS_CHANGED if typing.TYPE_CHECKING: import socket from collections.abc import Callable from types import FrameType from urwid.event_loop import EventLoop class Screen(_raw_display_base.Screen): def __init__( self, input: typing.TextIO = sys.stdin, # noqa: A002 # pylint: disable=redefined-builtin output: typing.TextIO = sys.stdout, bracketed_paste_mode=False, focus_reporting=False, ) -> None: """Initialize a screen that directly prints escape codes to an output terminal. bracketed_paste_mode -- enable bracketed paste mode in the host terminal. If the host terminal supports it, the application will receive `begin paste` and `end paste` keystrokes when the user pastes text. focus_reporting -- enable focus reporting in the host terminal. If the host terminal supports it, the application will receive `focus in` and `focus out` keystrokes when the application gains and loses focus. """ super().__init__(input, output) self.gpm_mev: Popen | None = None self.gpm_event_pending: bool = False self.bracketed_paste_mode = bracketed_paste_mode self.focus_reporting = focus_reporting # These store the previous signal handlers after setting ours self._prev_sigcont_handler = None self._prev_sigtstp_handler = None self._prev_sigwinch_handler = None def __repr__(self) -> str: return ( f"<{self.__class__.__name__}(" f"input={self._term_input_file}, " f"output={self._term_output_file}, " f"bracketed_paste_mode={self.bracketed_paste_mode}, " f"focus_reporting={self.focus_reporting})>" ) def _sigwinch_handler(self, signum: int = 28, frame: FrameType | None = None) -> None: """ frame -- will always be None when the GLib event loop is being used. """ super()._sigwinch_handler(signum, frame) if callable(self._prev_sigwinch_handler): self._prev_sigwinch_handler(signum, frame) def _sigtstp_handler(self, signum: int, frame: FrameType | None = None) -> None: self.stop() # Restores the previous signal handlers self._prev_sigcont_handler = self.signal_handler_setter(signal.SIGCONT, self._sigcont_handler) # Handled by the previous handler. # If non-default, it may set its own SIGCONT handler which should hopefully call our own. os.kill(os.getpid(), signal.SIGTSTP) def _sigcont_handler(self, signum: int, frame: FrameType | None = None) -> None: """ frame -- will always be None when the GLib event loop is being used. """ self.signal_restore() if callable(self._prev_sigcont_handler): # May set its own SIGTSTP handler which would be stored and replaced in # `signal_init()` (via `start()`). self._prev_sigcont_handler(signum, frame) self.start() self._sigwinch_handler(28, None) def signal_init(self) -> None: """ Called in the startup of run wrapper to set the SIGWINCH and SIGTSTP signal handlers. Override this function to call from main thread in threaded applications. """ self._prev_sigwinch_handler = self.signal_handler_setter(signal.SIGWINCH, self._sigwinch_handler) self._prev_sigtstp_handler = self.signal_handler_setter(signal.SIGTSTP, self._sigtstp_handler) def signal_restore(self) -> None: """ Called in the finally block of run wrapper to restore the SIGTSTP, SIGCONT and SIGWINCH signal handlers. Override this function to call from main thread in threaded applications. """ self.signal_handler_setter(signal.SIGTSTP, self._prev_sigtstp_handler or signal.SIG_DFL) self.signal_handler_setter(signal.SIGCONT, self._prev_sigcont_handler or signal.SIG_DFL) self.signal_handler_setter(signal.SIGWINCH, self._prev_sigwinch_handler or signal.SIG_DFL) def _mouse_tracking(self, enable: bool) -> None: super()._mouse_tracking(enable) if enable: self._start_gpm_tracking() else: self._stop_gpm_tracking() def _start_gpm_tracking(self) -> None: if not os.path.isfile("/usr/bin/mev"): return if not os.environ.get("TERM", "").lower().startswith("linux"): return m = Popen( # noqa: S603 # pylint: disable=consider-using-with ["/usr/bin/mev", "-e", "158"], stdin=PIPE, stdout=PIPE, close_fds=True, encoding="ascii", ) fcntl.fcntl(m.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) self.gpm_mev = m def _stop_gpm_tracking(self) -> None: if not self.gpm_mev: return os.kill(self.gpm_mev.pid, signal.SIGINT) os.waitpid(self.gpm_mev.pid, 0) self.gpm_mev = None def _start(self, alternate_buffer: bool = True) -> None: """ Initialize the screen and input mode. alternate_buffer -- use alternate screen buffer """ if alternate_buffer: self.write(escape.SWITCH_TO_ALTERNATE_BUFFER) self._rows_used = None else: self._rows_used = 0 if self.bracketed_paste_mode: self.write(escape.ENABLE_BRACKETED_PASTE_MODE) if self.focus_reporting: self.write(escape.ENABLE_FOCUS_REPORTING) fd = self._input_fileno() if fd is not None and os.isatty(fd): self._old_termios_settings = termios.tcgetattr(fd) tty.setcbreak(fd) self.signal_init() self._alternate_buffer = alternate_buffer self._next_timeout = self.max_wait if not self._signal_keys_set: self._old_signal_keys = self.tty_signal_keys(fileno=fd) signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED) # restore mouse tracking to previous state self._mouse_tracking(self._mouse_tracking_enabled) return super()._start() def _stop(self) -> None: """ Restore the screen. """ self.clear() if self.bracketed_paste_mode: self.write(escape.DISABLE_BRACKETED_PASTE_MODE) if self.focus_reporting: self.write(escape.DISABLE_FOCUS_REPORTING) signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED) self.signal_restore() self._stop_mouse_restore_buffer() fd = self._input_fileno() if fd is not None and os.isatty(fd): termios.tcsetattr(fd, termios.TCSAFLUSH, self._old_termios_settings) if self._old_signal_keys: self.tty_signal_keys(*self._old_signal_keys, fd) super()._stop() def get_input_descriptors(self) -> list[socket.socket | typing.IO | int]: """ Return a list of integer file descriptors that should be polled in external event loops to check for user input. Use this method if you are implementing your own event loop. This method is only called by `hook_event_loop`, so if you override that, you can safely ignore this. """ if not self._started: return [] fd_list = super().get_input_descriptors() if self.gpm_mev is not None and self.gpm_mev.stdout is not None: fd_list.append(self.gpm_mev.stdout) return fd_list def unhook_event_loop(self, event_loop: EventLoop) -> None: """ Remove any hooks added by hook_event_loop. """ for handle in self._current_event_loop_handles: event_loop.remove_watch_file(handle) if self._input_timeout: event_loop.remove_alarm(self._input_timeout) self._input_timeout = None def hook_event_loop( self, event_loop: EventLoop, callback: Callable[[list[str], list[int]], typing.Any], ) -> None: """ Register the given callback with the event loop, to be called with new input whenever it's available. The callback should be passed a list of processed keys and a list of unprocessed keycodes. Subclasses may wish to use parse_input to wrap the callback. """ if hasattr(self, "get_input_nonblocking"): wrapper = self._make_legacy_input_wrapper(event_loop, callback) else: @functools.wraps(callback) def wrapper() -> tuple[list[str], typing.Any] | None: self.logger.debug('Calling callback for "watch file"') return self.parse_input(event_loop, callback, self.get_available_raw_input()) fds = self.get_input_descriptors() handles = [event_loop.watch_file(fd if isinstance(fd, int) else fd.fileno(), wrapper) for fd in fds] self._current_event_loop_handles = handles def _get_input_codes(self) -> list[int]: return super()._get_input_codes() + self._get_gpm_codes() def _get_gpm_codes(self) -> list[int]: codes = [] try: while self.gpm_mev is not None and self.gpm_event_pending: codes.extend(self._encode_gpm_event()) except OSError as e: if e.args[0] != 11: raise return codes def _read_raw_input(self, timeout: int) -> bytearray: ready = self._wait_for_input_ready(timeout) if self.gpm_mev is not None and self.gpm_mev.stdout.fileno() in ready: self.gpm_event_pending = True fd = self._input_fileno() chars = bytearray() if fd is None or fd not in ready: return chars with selectors.DefaultSelector() as selector: selector.register(fd, selectors.EVENT_READ) input_ready = selector.select(0) while input_ready: chars.extend(os.read(fd, 1024)) input_ready = selector.select(0) return chars def _encode_gpm_event(self) -> list[int]: self.gpm_event_pending = False s = self.gpm_mev.stdout.readline() result = s.split(", ") if len(result) != 6: # unexpected output, stop tracking self._stop_gpm_tracking() signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED) return [] ev, x, y, _ign, b, m = s.split(",") ev = int(ev.split("x")[-1], 16) x = int(x.split(" ")[-1]) y = int(y.lstrip().split(" ")[0]) b = int(b.split(" ")[-1]) m = int(m.split("x")[-1].rstrip(), 16) # convert to xterm-like escape sequence last_state = next_state = self.last_bstate result = [] mod = 0 if m & 1: mod |= 4 # shift if m & 10: mod |= 8 # alt if m & 4: mod |= 16 # ctrl def append_button(b: int) -> None: b |= mod result.extend([27, ord("["), ord("M"), b + 32, x + 32, y + 32]) if ev in {20, 36, 52}: # press if b & 4 and last_state & 1 == 0: append_button(0) next_state |= 1 if b & 2 and last_state & 2 == 0: append_button(1) next_state |= 2 if b & 1 and last_state & 4 == 0: append_button(2) next_state |= 4 elif ev == 146: # drag if b & 4: append_button(0 + escape.MOUSE_DRAG_FLAG) elif b & 2: append_button(1 + escape.MOUSE_DRAG_FLAG) elif b & 1: append_button(2 + escape.MOUSE_DRAG_FLAG) else: # release if b & 4 and last_state & 1: append_button(0 + escape.MOUSE_RELEASE_FLAG) next_state &= ~1 if b & 2 and last_state & 2: append_button(1 + escape.MOUSE_RELEASE_FLAG) next_state &= ~2 if b & 1 and last_state & 4: append_button(2 + escape.MOUSE_RELEASE_FLAG) next_state &= ~4 if ev == 40: # double click (release) if b & 4 and last_state & 1: append_button(0 + escape.MOUSE_MULTIPLE_CLICK_FLAG) if b & 2 and last_state & 2: append_button(1 + escape.MOUSE_MULTIPLE_CLICK_FLAG) if b & 1 and last_state & 4: append_button(2 + escape.MOUSE_MULTIPLE_CLICK_FLAG) elif ev == 52: if b & 4 and last_state & 1: append_button(0 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2) if b & 2 and last_state & 2: append_button(1 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2) if b & 1 and last_state & 4: append_button(2 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2) self.last_bstate = next_state return result def get_cols_rows(self) -> tuple[int, int]: """Return the terminal dimensions (num columns, num rows).""" y, x = super().get_cols_rows() with contextlib.suppress(OSError): # Term size could not be determined if hasattr(self._term_output_file, "fileno"): buf = fcntl.ioctl(self._term_output_file.fileno(), termios.TIOCGWINSZ, b" " * 4) y, x = struct.unpack("hh", buf) # Provide some lightweight fallbacks in case the TIOCWINSZ doesn't # give sane answers if (x <= 0 or y <= 0) and self.term in {"ansi", "vt100"}: y, x = 24, 80 self.maxrow = y return x, y def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() urwid-2.6.16/urwid/display/_raw_display_base.py000066400000000000000000001015001470350774000215640ustar00rootroot00000000000000# Urwid raw display module # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Direct terminal UI implementation """ from __future__ import annotations import abc import contextlib import functools import os import platform import selectors import signal import socket import sys import typing from urwid import signals, str_util, util from . import escape from .common import UNPRINTABLE_TRANS_TABLE, UPDATE_PALETTE_ENTRY, AttrSpec, BaseScreen, RealTerminal if typing.TYPE_CHECKING: from collections.abc import Callable, Iterable from types import FrameType from typing_extensions import Literal from urwid import Canvas, EventLoop IS_WINDOWS = sys.platform == "win32" IS_WSL = (sys.platform == "linux") and ("wsl" in platform.platform().lower()) class Screen(BaseScreen, RealTerminal): def __init__(self, input: typing.IO, output: typing.IO) -> None: # noqa: A002 # pylint: disable=redefined-builtin """Initialize a screen that directly prints escape codes to an output terminal. """ super().__init__() self._partial_codes: list[int] = [] self._pal_escape: dict[str | None, str] = {} self._pal_attrspec: dict[str | None, AttrSpec] = {} self._alternate_buffer: bool = False signals.connect_signal(self, UPDATE_PALETTE_ENTRY, self._on_update_palette_entry) self.colors: Literal[1, 16, 88, 256, 16777216] = 16 # FIXME: detect this self.has_underline = True # FIXME: detect this self.prev_input_resize = 0 self.set_input_timeouts() self.screen_buf = None self._screen_buf_canvas = None self._resized = False self.maxrow = None self._mouse_tracking_enabled = False self.last_bstate = 0 self._setup_G1_done = False self._rows_used = None self._cy = 0 self.term = os.environ.get("TERM", "") self.fg_bright_is_bold = not self.term.startswith("xterm") self.bg_bright_is_blink = self.term == "linux" self.back_color_erase = not self.term.startswith("screen") self.register_palette_entry(None, "default", "default") self._next_timeout = None self.signal_handler_setter = signal.signal # Our connections to the world self._term_output_file = output self._term_input_file = input # pipe for signalling external event loops about resize events self._resize_pipe_rd, self._resize_pipe_wr = socket.socketpair() self._resize_pipe_rd.setblocking(False) def __del__(self) -> None: self._resize_pipe_rd.close() self._resize_pipe_wr.close() def __repr__(self) -> str: return f"<{self.__class__.__name__}(input={self._term_input_file}, output={self._term_output_file})>" def _sigwinch_handler(self, signum: int = 28, frame: FrameType | None = None) -> None: """ frame -- will always be None when the GLib event loop is being used. """ logger = self.logger.getChild("signal_handlers") logger.debug(f"SIGWINCH handler called with signum={signum!r}, frame={frame!r}") if IS_WINDOWS or not self._resized: self._resize_pipe_wr.send(b"R") logger.debug("Sent fake resize input to the pipe") self._resized = True self.screen_buf = None @property def _term_input_io(self) -> typing.IO | None: if hasattr(self._term_input_file, "fileno"): return self._term_input_file return None def _input_fileno(self) -> int | None: """Returns the fileno of the input stream, or None if it doesn't have one. A stream without a fileno can't participate in whatever. """ if hasattr(self._term_input_file, "fileno"): return self._term_input_file.fileno() return None def _on_update_palette_entry(self, name: str | None, *attrspecs: AttrSpec): # copy the attribute to a dictionary containing the escape seqences a: AttrSpec = attrspecs[{16: 0, 1: 1, 88: 2, 256: 3, 2**24: 4}[self.colors]] self._pal_attrspec[name] = a self._pal_escape[name] = self._attrspec_to_escape(a) def set_input_timeouts( self, max_wait: float | None = None, complete_wait: float = 0.125, resize_wait: float = 0.125, ) -> None: """ Set the get_input timeout values. All values are in floating point numbers of seconds. max_wait -- amount of time in seconds to wait for input when there is no input pending, wait forever if None complete_wait -- amount of time in seconds to wait when get_input detects an incomplete escape sequence at the end of the available input resize_wait -- amount of time in seconds to wait for more input after receiving two screen resize requests in a row to stop Urwid from consuming 100% cpu during a gradual window resize operation """ self.max_wait = max_wait if max_wait is not None: if self._next_timeout is None: self._next_timeout = max_wait else: self._next_timeout = min(self._next_timeout, self.max_wait) self.complete_wait = complete_wait self.resize_wait = resize_wait def set_mouse_tracking(self, enable: bool = True) -> None: """ Enable (or disable) mouse tracking. After calling this function get_input will include mouse click events along with keystrokes. """ enable = bool(enable) # noqa: FURB123,RUF100 if enable == self._mouse_tracking_enabled: return self._mouse_tracking(enable) self._mouse_tracking_enabled = enable def _mouse_tracking(self, enable: bool) -> None: if enable: self.write(escape.MOUSE_TRACKING_ON) else: self.write(escape.MOUSE_TRACKING_OFF) @abc.abstractmethod def _start(self, alternate_buffer: bool = True) -> None: """ Initialize the screen and input mode. alternate_buffer -- use alternate screen buffer """ def _stop_mouse_restore_buffer(self) -> None: """Stop mouse tracking and restore the screen.""" self._mouse_tracking(False) move_cursor = "" if self._alternate_buffer: move_cursor = escape.RESTORE_NORMAL_BUFFER elif self.maxrow is not None: move_cursor = escape.set_cursor_position(0, self.maxrow) self.write(self._attrspec_to_escape(AttrSpec("", "")) + escape.SI + move_cursor + escape.SHOW_CURSOR) self.flush() @abc.abstractmethod def _stop(self) -> None: """ Restore the screen. """ def write(self, data): """Write some data to the terminal. You may wish to override this if you're using something other than regular files for input and output. """ self._term_output_file.write(data) def flush(self): """Flush the output buffer. You may wish to override this if you're using something other than regular files for input and output. """ self._term_output_file.flush() @typing.overload def get_input(self, raw_keys: Literal[False]) -> list[str]: ... @typing.overload def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]: ... def get_input(self, raw_keys: bool = False) -> list[str] | tuple[list[str], list[int]]: """Return pending input as a list. raw_keys -- return raw keycodes as well as translated versions This function will immediately return all the input since the last time it was called. If there is no input pending it will wait before returning an empty list. The wait time may be configured with the set_input_timeouts function. If raw_keys is False (default) this function will return a list of keys pressed. If raw_keys is True this function will return a ( keys pressed, raw keycodes ) tuple instead. Examples of keys returned: * ASCII printable characters: " ", "a", "0", "A", "-", "/" * ASCII control characters: "tab", "enter" * Escape sequences: "up", "page up", "home", "insert", "f1" * Key combinations: "shift f1", "meta a", "ctrl b" * Window events: "window resize" When a narrow encoding is not enabled: * "Extended ASCII" characters: "\\xa1", "\\xb2", "\\xfe" When a wide encoding is enabled: * Double-byte characters: "\\xa1\\xea", "\\xb2\\xd4" When utf8 encoding is enabled: * Unicode characters: u"\\u00a5", u'\\u253c" Examples of mouse events returned: * Mouse button press: ('mouse press', 1, 15, 13), ('meta mouse press', 2, 17, 23) * Mouse drag: ('mouse drag', 1, 16, 13), ('mouse drag', 1, 17, 13), ('ctrl mouse drag', 1, 18, 13) * Mouse button release: ('mouse release', 0, 18, 13), ('ctrl mouse release', 0, 17, 23) """ logger = self.logger.getChild("get_input") if not self._started: raise RuntimeError self._wait_for_input_ready(self._next_timeout) keys, raw = self.parse_input(None, None, self.get_available_raw_input()) # Avoid pegging CPU at 100% when slowly resizing if keys == ["window resize"] and self.prev_input_resize: logger.debug('get_input: got "window resize" > 1 times. Enable throttling for resize.') for _ in range(2): self._wait_for_input_ready(self.resize_wait) new_keys, new_raw = self.parse_input(None, None, self.get_available_raw_input()) raw += new_raw if new_keys and new_keys != ["window resize"]: if "window resize" in new_keys: keys = new_keys else: keys.extend(new_keys) break if keys == ["window resize"]: self.prev_input_resize = 2 elif self.prev_input_resize == 2 and not keys: self.prev_input_resize = 1 else: self.prev_input_resize = 0 if raw_keys: return keys, raw return keys def get_input_descriptors(self) -> list[socket.socket | typing.IO | int]: """ Return a list of integer file descriptors that should be polled in external event loops to check for user input. Use this method if you are implementing your own event loop. This method is only called by `hook_event_loop`, so if you override that, you can safely ignore this. """ if not self._started: return [] fd_list: list[socket.socket | typing.IO | int] = [self._resize_pipe_rd] input_io = self._term_input_io if input_io is not None: fd_list.append(input_io) return fd_list _current_event_loop_handles = () @abc.abstractmethod def unhook_event_loop(self, event_loop: EventLoop) -> None: """ Remove any hooks added by hook_event_loop. """ @abc.abstractmethod def hook_event_loop( self, event_loop: EventLoop, callback: Callable[[list[str], list[int]], typing.Any], ) -> None: """ Register the given callback with the event loop, to be called with new input whenever it's available. The callback should be passed a list of processed keys and a list of unprocessed keycodes. Subclasses may wish to use parse_input to wrap the callback. """ _input_timeout = None def _make_legacy_input_wrapper(self, event_loop, callback): """ Support old Screen classes that still have a get_input_nonblocking and expect it to work. """ @functools.wraps(callback) def wrapper(): if self._input_timeout: event_loop.remove_alarm(self._input_timeout) self._input_timeout = None timeout, keys, raw = self.get_input_nonblocking() # pylint: disable=no-member # should we deprecate? if timeout is not None: self._input_timeout = event_loop.alarm(timeout, wrapper) callback(keys, raw) return wrapper def _get_input_codes(self) -> list[int]: return list(self._get_keyboard_codes()) def get_available_raw_input(self) -> list[int]: """ Return any currently available input. Does not block. This method is only used by the default `hook_event_loop` implementation; you can safely ignore it if you implement your own. """ logger = self.logger.getChild("get_available_raw_input") codes = [*self._partial_codes, *self._get_input_codes()] self._partial_codes = [] # clean out the pipe used to signal external event loops # that a resize has occurred with selectors.DefaultSelector() as selector: selector.register(self._resize_pipe_rd, selectors.EVENT_READ) present_resize_flag = selector.select(0) # nonblocking while present_resize_flag: logger.debug("Resize signal received. Cleaning socket.") # Argument "size" is maximum buffer size to read. Since we're emptying, set it reasonably big. self._resize_pipe_rd.recv(128) present_resize_flag = selector.select(0) return codes @typing.overload def parse_input( self, event_loop: None, callback: None, codes: list[int], wait_for_more: bool = ..., ) -> tuple[list[str], list[int]]: ... @typing.overload def parse_input( self, event_loop: EventLoop, callback: None, codes: list[int], wait_for_more: bool = ..., ) -> tuple[list[str], list[int]]: ... @typing.overload def parse_input( self, event_loop: EventLoop, callback: Callable[[list[str], list[int]], typing.Any], codes: list[int], wait_for_more: bool = ..., ) -> None: ... def parse_input( self, event_loop: EventLoop | None, callback: Callable[[list[str], list[int]], typing.Any] | None, codes: list[int], wait_for_more: bool = True, ) -> tuple[list[str], list[int]] | None: """ Read any available input from get_available_raw_input, parses it into keys, and calls the given callback. The current implementation tries to avoid any assumptions about what the screen or event loop look like; it only deals with parsing keycodes and setting a timeout when an incomplete one is detected. `codes` should be a sequence of keycodes, i.e. bytes. A bytearray is appropriate, but beware of using bytes, which only iterates as integers on Python 3. """ logger = self.logger.getChild("parse_input") # Note: event_loop may be None for 100% synchronous support, only used # by get_input. Not documented because you shouldn't be doing it. if self._input_timeout and event_loop: event_loop.remove_alarm(self._input_timeout) self._input_timeout = None original_codes = codes decoded_codes = [] try: while codes: run, codes = escape.process_keyqueue(codes, wait_for_more) decoded_codes.extend(run) except escape.MoreInputRequired: # Set a timer to wait for the rest of the input; if it goes off # without any new input having come in, use the partial input k = len(original_codes) - len(codes) raw_codes = original_codes[:k] self._partial_codes = codes def _parse_incomplete_input(): self._input_timeout = None self._partial_codes = [] self.parse_input(event_loop, callback, codes, wait_for_more=False) if event_loop: self._input_timeout = event_loop.alarm(self.complete_wait, _parse_incomplete_input) else: raw_codes = original_codes self._partial_codes = [] logger.debug(f"Decoded codes: {decoded_codes!r}, raw codes: {raw_codes!r}") if self._resized: decoded_codes.append("window resize") logger.debug('Added "window resize" to the codes') self._resized = False if callback: callback(decoded_codes, raw_codes) return None # For get_input return decoded_codes, raw_codes def _wait_for_input_ready(self, timeout: float | None) -> list[int]: logger = self.logger.getChild("wait_for_input_ready") fd_list = self.get_input_descriptors() logger.debug(f"Waiting for input: descriptors={fd_list!r}, timeout={timeout!r}") with selectors.DefaultSelector() as selector: for fd in fd_list: selector.register(fd, selectors.EVENT_READ) ready = selector.select(timeout) logger.debug(f"Input ready: {ready}") return [event.fd for event, _ in ready] @abc.abstractmethod def _read_raw_input(self, timeout: int) -> Iterable[int]: ... def _get_keyboard_codes(self) -> Iterable[int]: return self._read_raw_input(0) def _setup_G1(self) -> None: """ Initialize the G1 character set to graphics mode if required. """ if self._setup_G1_done: return while True: with contextlib.suppress(OSError): self.write(escape.DESIGNATE_G1_SPECIAL) self.flush() break self._setup_G1_done = True def draw_screen(self, size: tuple[int, int], canvas: Canvas) -> None: """Paint screen with rendered canvas.""" def set_cursor_home() -> str: if not partial_display(): return escape.set_cursor_position(0, 0) return escape.CURSOR_HOME_COL + escape.move_cursor_up(cy) def set_cursor_position(x: int, y: int) -> str: if not partial_display(): return escape.set_cursor_position(x, y) if cy > y: return "\b" + escape.CURSOR_HOME_COL + escape.move_cursor_up(cy - y) + escape.move_cursor_right(x) return "\b" + escape.CURSOR_HOME_COL + escape.move_cursor_down(y - cy) + escape.move_cursor_right(x) def is_blank_row(row: list[tuple[object, Literal["0", "U"] | None], bytes]) -> bool: if len(row) > 1: return False return not row[0][2].strip() def attr_to_escape(a: AttrSpec | str | None) -> str: if a in self._pal_escape: return self._pal_escape[a] if isinstance(a, AttrSpec): return self._attrspec_to_escape(a) if a is None: return self._attrspec_to_escape(AttrSpec("default", "default")) # undefined attributes use default/default self.logger.debug(f"Undefined attribute: {a!r}") return self._attrspec_to_escape(AttrSpec("default", "default")) def using_standout_or_underline(a: AttrSpec | str) -> bool: a = self._pal_attrspec.get(a, a) return isinstance(a, AttrSpec) and (a.standout or a.underline) encoding = util.get_encoding() logger = self.logger.getChild("draw_screen") (maxcol, maxrow) = size if not self._started: raise RuntimeError if maxrow != canvas.rows(): raise ValueError(maxrow) # quick return if nothing has changed if self.screen_buf and canvas is self._screen_buf_canvas: return self._setup_G1() if self._resized: # handle resize before trying to draw screen logger.debug("Not drawing screen: screen resized and resize was not handled") return logger.debug(f"Drawing screen with size {size!r}") last_attributes = None # Default = empty output: list[str] = [escape.HIDE_CURSOR, attr_to_escape(last_attributes)] def partial_display() -> bool: # returns True if the screen is in partial display mode ie. only some rows belong to the display return self._rows_used is not None if not partial_display(): output.append(escape.CURSOR_HOME) if self.screen_buf: osb = self.screen_buf else: osb = [] sb: list[list[tuple[object, Literal["0", "U"] | None, bytes]]] = [] cy = self._cy y = -1 ins = None output.append(set_cursor_home()) cy = 0 first = True last_charset_flag = None for row in canvas.content(): y += 1 if osb and y < len(osb) and osb[y] == row: # this row of the screen buffer matches what is # currently displayed, so we can skip this line sb.append(osb[y]) continue sb.append(row) # leave blank lines off display when we are using # the default screen buffer (allows partial screen) if partial_display() and y > self._rows_used: if is_blank_row(row): continue self._rows_used = y if y or partial_display(): output.append(set_cursor_position(0, y)) # after updating the line we will be just over the # edge, but terminals still treat this as being # on the same line cy = y whitespace_at_end = False if row: a, cs, run = row[-1] if run[-1:] == b" " and self.back_color_erase and not using_standout_or_underline(a): whitespace_at_end = True row = row[:-1] + [(a, cs, run.rstrip(b" "))] # noqa: PLW2901 elif y == maxrow - 1 and maxcol > 1: row, back, ins = self._last_row(row) # noqa: PLW2901 for a, cs, run in row: if not isinstance(run, bytes): # canvases render with bytes raise TypeError(run) if cs != "U": run = run.translate(UNPRINTABLE_TRANS_TABLE) # noqa: PLW2901 if last_attributes != a: output.append(attr_to_escape(a)) last_attributes = a if encoding != "utf-8" and (first or last_charset_flag != cs): if cs not in {None, "0", "U"}: raise ValueError(cs) if last_charset_flag == "U": output.append(escape.IBMPC_OFF) if cs is None: output.append(escape.SI) elif cs == "U": output.append(escape.IBMPC_ON) else: output.append(escape.SO) last_charset_flag = cs output.append(run.decode(encoding, "replace")) first = False if ins: (inserta, insertcs, inserttext) = ins ias = attr_to_escape(inserta) if insertcs not in {None, "0", "U"}: raise ValueError(insertcs) if isinstance(inserttext, bytes): inserttext = inserttext.decode(encoding) output.extend(("\x08" * back, ias)) # pylint: disable=used-before-assignment # defined in `if row` if encoding != "utf-8": if cs is None: icss = escape.SI elif cs == "U": icss = escape.IBMPC_ON else: icss = escape.SO output.append(icss) if not IS_WINDOWS: output += [escape.INSERT_ON, inserttext, escape.INSERT_OFF] else: output += [f"{escape.ESC}[{str_util.calc_width(inserttext, 0, len(inserttext))}@", inserttext] if encoding != "utf-8" and cs == "U": output.append(escape.IBMPC_OFF) if whitespace_at_end: output.append(escape.ERASE_IN_LINE_RIGHT) if canvas.cursor is not None: x, y = canvas.cursor output += [set_cursor_position(x, y), escape.SHOW_CURSOR] self._cy = y if self._resized: # handle resize before trying to draw screen return try: for line in output: if isinstance(line, bytes): line = line.decode(encoding, "replace") # noqa: PLW2901 self.write(line) self.flush() except OSError as e: # ignore interrupted syscall if e.args[0] != 4: raise self.screen_buf = sb self._screen_buf_canvas = canvas def _last_row(self, row: list[tuple[object, Literal["0", "U"] | None, bytes]]) -> tuple[ list[tuple[object, Literal["0", "U"] | None, bytes]], int, tuple[object, Literal["0", "U"] | None, bytes], ]: """On the last row we need to slide the bottom right character into place. Calculate the new line, attr and an insert sequence to do that. eg. last row: XXXXXXXXXXXXXXXXXXXXYZ Y will be drawn after Z, shifting Z into position. """ new_row = row[:-1] z_attr, z_cs, last_text = row[-1] last_cols = str_util.calc_width(last_text, 0, len(last_text)) last_offs, z_col = str_util.calc_text_pos(last_text, 0, len(last_text), last_cols - 1) if last_offs == 0: z_text = last_text del new_row[-1] # we need another segment y_attr, y_cs, nlast_text = row[-2] nlast_cols = str_util.calc_width(nlast_text, 0, len(nlast_text)) z_col += nlast_cols nlast_offs, y_col = str_util.calc_text_pos(nlast_text, 0, len(nlast_text), nlast_cols - 1) y_text = nlast_text[nlast_offs:] if nlast_offs: new_row.append((y_attr, y_cs, nlast_text[:nlast_offs])) else: z_text = last_text[last_offs:] y_attr, y_cs = z_attr, z_cs nlast_cols = str_util.calc_width(last_text, 0, last_offs) nlast_offs, y_col = str_util.calc_text_pos(last_text, 0, last_offs, nlast_cols - 1) y_text = last_text[nlast_offs:last_offs] if nlast_offs: new_row.append((y_attr, y_cs, last_text[:nlast_offs])) new_row.append((z_attr, z_cs, z_text)) return new_row, z_col - y_col, (y_attr, y_cs, y_text) def clear(self) -> None: """ Force the screen to be completely repainted on the next call to draw_screen(). """ self.screen_buf = None def _attrspec_to_escape(self, a: AttrSpec) -> str: """ Convert AttrSpec instance a to an escape sequence for the terminal >>> s = Screen() >>> s.set_terminal_properties(colors=256) >>> a2e = s._attrspec_to_escape >>> a2e(s.AttrSpec('brown', 'dark green')) '\\x1b[0;33;42m' >>> a2e(s.AttrSpec('#fea,underline', '#d0d')) '\\x1b[0;38;5;229;4;48;5;164m' """ if self.term == "fbterm": fg = escape.ESC + f"[1;{a.foreground_number:d}}}" bg = escape.ESC + f"[2;{a.background_number:d}}}" return fg + bg if a.foreground_true: fg = f"38;2;{';'.join(str(part) for part in a.get_rgb_values()[0:3])}" elif a.foreground_high: fg = f"38;5;{a.foreground_number:d}" elif a.foreground_basic: if a.foreground_number > 7: if self.fg_bright_is_bold: fg = f"1;{a.foreground_number - 8 + 30:d}" else: fg = f"{a.foreground_number - 8 + 90:d}" else: fg = f"{a.foreground_number + 30:d}" else: fg = "39" st = ( "1;" * a.bold + "3;" * a.italics + "4;" * a.underline + "5;" * a.blink + "7;" * a.standout + "9;" * a.strikethrough ) if a.background_true: bg = f"48;2;{';'.join(str(part) for part in a.get_rgb_values()[3:6])}" elif a.background_high: bg = f"48;5;{a.background_number:d}" elif a.background_basic: if a.background_number > 7: if self.bg_bright_is_blink: bg = f"5;{a.background_number - 8 + 40:d}" else: # this doesn't work on most terminals bg = f"{a.background_number - 8 + 100:d}" else: bg = f"{a.background_number + 40:d}" else: bg = "49" return f"{escape.ESC}[0;{fg};{st}{bg}m" def set_terminal_properties( self, colors: Literal[1, 16, 88, 256, 16777216] | None = None, bright_is_bold: bool | None = None, has_underline: bool | None = None, ) -> None: """ colors -- number of colors terminal supports (1, 16, 88, 256, or 2**24) or None to leave unchanged bright_is_bold -- set to True if this terminal uses the bold setting to create bright colors (numbers 8-15), set to False if this Terminal can create bright colors without bold or None to leave unchanged has_underline -- set to True if this terminal can use the underline setting, False if it cannot or None to leave unchanged """ if colors is None: colors = self.colors if bright_is_bold is None: bright_is_bold = self.fg_bright_is_bold if has_underline is None: has_underline = self.has_underline if colors == self.colors and bright_is_bold == self.fg_bright_is_bold and has_underline == self.has_underline: return self.colors = colors self.fg_bright_is_bold = bright_is_bold self.has_underline = has_underline self.clear() self._pal_escape = {} for p, v in self._palette.items(): self._on_update_palette_entry(p, *v) def reset_default_terminal_palette(self) -> None: """ Attempt to set the terminal palette to default values as taken from xterm. Uses number of colors from current set_terminal_properties() screen setting. """ if self.colors == 1: return if self.colors == 2**24: colors = 256 else: colors = self.colors def rgb_values(n) -> tuple[int | None, int | None, int | None]: if colors == 16: aspec = AttrSpec(f"h{n:d}", "", 256) else: aspec = AttrSpec(f"h{n:d}", "", colors) return aspec.get_rgb_values()[:3] entries = [(n, *rgb_values(n)) for n in range(min(colors, 256))] self.modify_terminal_palette(entries) def modify_terminal_palette(self, entries: list[tuple[int, int | None, int | None, int | None]]): """ entries - list of (index, red, green, blue) tuples. Attempt to set part of the terminal palette (this does not work on all terminals.) The changes are sent as a single escape sequence so they should all take effect at the same time. 0 <= index < 256 (some terminals will only have 16 or 88 colors) 0 <= red, green, blue < 256 """ if self.term == "fbterm": modify = [f"{index:d};{red:d};{green:d};{blue:d}" for index, red, green, blue in entries] self.write(f"\x1b[3;{';'.join(modify)}}}") else: modify = [f"{index:d};rgb:{red:02x}/{green:02x}/{blue:02x}" for index, red, green, blue in entries] self.write(f"\x1b]4;{';'.join(modify)}\x1b\\") self.flush() # shortcut for creating an AttrSpec with this screen object's # number of colors def AttrSpec(self, fg, bg) -> AttrSpec: return AttrSpec(fg, bg, self.colors) urwid-2.6.16/urwid/display/_web.css000066400000000000000000000005471470350774000172020ustar00rootroot00000000000000body { margin: 8px 8px 8px 8px; border: 0; color: black; background-color: silver; font-family: fixed; overflow: hidden; } form { margin: 0 0 8px 0; } #text { position: relative; background-color: silver; width: 100%; height: 100%; margin: 3px 0 0 0; border: 1px solid #999; } #page { position: relative; width: 100%;height: 100%;} urwid-2.6.16/urwid/display/_web.js000066400000000000000000000312231470350774000170210ustar00rootroot00000000000000// Urwid web (CGI/Asynchronous Javascript) display module // Copyright (C) 2004-2005 Ian Ward // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // Urwid web site: https://urwid.org/ colours = new Object(); colours = { '0': "black", '1': "#c00000", '2': "green", '3': "#804000", '4': "#0000c0", '5': "#c000c0", '6': "teal", '7': "silver", '8': "gray", '9': "#ff6060", 'A': "lime", 'B': "yellow", 'C': "#8080ff", 'D': "#ff40ff", 'E': "aqua", 'F': "white" }; keycodes = new Object(); keycodes = { 8: "backspace", 9: "tab", 13: "enter", 27: "esc", 33: "page up", 34: "page down", 35: "end", 36: "home", 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "delete", 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 120: "f9", 121: "f10", 122: "f11", 123: "f12" }; var conn = null; var char_width = null; var char_height = null; var screen_x = null; var screen_y = null; var urwid_id = null; var send_conn = null; var send_queue_max = 32; var send_queue = new Array(send_queue_max); var send_queue_in = 0; var send_queue_out = 0; var check_font_delay = 1000; var send_more_delay = 100; var poll_again_delay = 500; var document_location = null; var update_method = "multipart"; var sending = false; var lastkeydown = null; function setup_connection() { if (window.XMLHttpRequest) { conn = new XMLHttpRequest(); } else if (window.ActiveXObject) { conn = new ActiveXObject("Microsoft.XMLHTTP"); } if (conn == null) { set_status("Connection Failed"); alert( "Can't figure out how to send request." ); return; } try{ conn.multipart = true; }catch(e){ update_method = "polling"; } conn.onreadystatechange = handle_recv; conn.open("POST", document_location, true); conn.setRequestHeader("X-Urwid-Method",update_method); conn.setRequestHeader("Content-type","text/plain"); conn.send("window resize " +screen_x+" "+screen_y+"\n"); } function do_poll() { if (urwid_id == null){ alert("that's unpossible!"); return; } if (window.XMLHttpRequest) { conn = new XMLHttpRequest(); } else if (window.ActiveXObject) { conn = new ActiveXObject("Microsoft.XMLHTTP"); } conn.onreadystatechange = handle_recv; conn.open("POST", document_location, true); conn.setRequestHeader("X-Urwid-Method","polling"); conn.setRequestHeader("X-Urwid-ID",urwid_id); conn.setRequestHeader("Content-type","text/plain"); conn.send("eh?"); } function handle_recv() { if( ! conn ){ return;} if( conn.readyState != 4) { return; } if( conn.status == 404 && urwid_id != null) { set_status("Connection Closed"); return; } if( conn.status == 403 && update_method == "polling" ) { set_status("Server Refused Connection"); alert("This server does not allow polling clients.\n\n" + "Please use a web browser with multipart support " + "such as Mozilla Firefox"); return; } if( conn.status == 503 ) { set_status("Connection Failed"); alert("The server has reached its maximum number of "+ "connections.\n\nPlease try again later."); return; } if( conn.status != 200) { set_status("Connection Failed"); alert("Error from server: "+conn.statusText); return; } if( urwid_id == null ){ urwid_id = conn.getResponseHeader("X-Urwid-ID"); if( send_queue_in != send_queue_out ){ // keys waiting do_send(); } if(update_method=="polling"){ set_status("Polling"); }else if(update_method=="multipart"){ set_status("Connected"); } } if( conn.responseText == "" ){ if(update_method=="polling"){ poll_again(); } return; // keepalive } if( conn.responseText == "Z" ){ set_status("Connection Closed"); update_method = null; return; } var text = document.getElementById('text'); var last_screen = Array(text.childNodes.length); for( var i=0; i k ){ text.replaceChild(ln, text.childNodes[k]); }else{ text.appendChild(ln); } k = k+1; ln = document.createElement('span'); }else if( f.charAt(0) == "<" ){ line_number = parseInt(f.substr(1)); if( line_number == k ){ k = k +1; continue; } var clone = last_screen[line_number].cloneNode(true); if( text.childNodes.length > k ){ text.replaceChild(clone, text.childNodes[k]); }else{ text.appendChild(clone); } k = k+1; }else{ var span=make_span(f.substr(2),f.charAt(0),f.charAt(1)); ln.appendChild( span ); } } for( var i=k; i < text.childNodes.length; i++ ){ text.removeChild(last_screen[i]); } if(update_method=="polling"){ poll_again(); } } function poll_again(){ if(conn.status == 200){ setTimeout("do_poll();",poll_again_delay); } } function load_web_display(){ if( document.documentURI ){ document_location = document.documentURI; }else{ document_location = document.location; } document.onkeypress = body_keypress; document.onkeydown = body_keydown; document.onresize = body_resize; body_resize(); send_queue_out = send_queue_in; // don't queue the first resize set_status("Connecting"); setup_connection(); setTimeout("check_fontsize();",check_font_delay); } function set_status( status ){ var s = document.getElementById('status'); var t = document.createTextNode(status); s.replaceChild(t, s.firstChild); } function make_span(s, fg, bg){ d = document.createElement('span'); d.style.backgroundColor = colours[bg]; d.style.color = colours[fg]; d.appendChild(document.createTextNode(s)); return d; } function body_keydown(e){ if (conn == null){ return; } if (!e) var e = window.event; if (e.keyCode) code = e.keyCode; else if (e.which) code = e.which; var mod = ""; var key; if( e.ctrlKey ){ mod = "ctrl " + mod; } if( e.altKey || e.metaKey ){ mod = "meta " + mod; } if( e.shiftKey && e.charCode == 0 ){ mod = "shift " + mod; } key = keycodes[code]; if( key != undefined ){ lastkeydown = key; send_key( mod + key ); stop_key_event(e); return false; } } function body_keypress(e){ if (conn == null){ return; } if (!e) var e = window.event; if (e.keyCode) code = e.keyCode; else if (e.which) code = e.which; var mod = ""; var key; if( e.ctrlKey ){ mod = "ctrl " + mod; } if( e.altKey || e.metaKey ){ mod = "meta " + mod; } if( e.shiftKey && e.charCode == 0 ){ mod = "shift " + mod; } if( e.charCode != null && e.charCode != 0 ){ key = String.fromCharCode(e.charCode); }else if( e.charCode == null ){ key = String.fromCharCode(code); }else{ key = keycodes[code]; if( key == undefined || lastkeydown == key ){ lastkeydown = null; stop_key_event(e); return false; } } send_key( mod + key ); stop_key_event(e); return false; } function stop_key_event(e){ e.cancelBubble = true; if( e.stopPropagation ){ e.stopPropagation(); } if( e.preventDefault ){ e.preventDefault(); } } function send_key( key ){ if( (send_queue_in+1)%send_queue_max == send_queue_out ){ // buffer overrun return; } send_queue[send_queue_in] = key; send_queue_in = (send_queue_in+1)%send_queue_max; if( urwid_id != null ){ if (send_conn == undefined || send_conn.ready_state != 4 ){ send_more(); return; } do_send(); } } function do_send() { if( ! urwid_id ){ return; } if( ! update_method ){ return; } // connection closed if( send_queue_in == send_queue_out ){ return; } if( sending ){ //var queue_delta = send_queue_in - send_queue_out; //if( queue_delta < 0 ){ queue_delta += send_queue_max; } //set_status("Sending (queued "+queue_delta+")"); return; } try{ sending = true; //set_status("starting send"); if( send_conn == null ){ if (window.XMLHttpRequest) { send_conn = new XMLHttpRequest(); } else if (window.ActiveXObject) { send_conn = new ActiveXObject("Microsoft.XMLHTTP"); } }else if( send_conn.status != 200) { alert("Error from server: "+send_conn.statusText); return; }else if(send_conn.readyState != 4 ){ alert("not ready on send connection"); return; } } catch(e) { alert(e); sending = false; return; } send_conn.open("POST", document_location, true); send_conn.onreadystatechange = send_handle_recv; send_conn.setRequestHeader("Content-type","text/plain"); send_conn.setRequestHeader("X-Urwid-ID",urwid_id); var tmp_send_queue_in = send_queue_in; var out = null; if( send_queue_out > tmp_send_queue_in ){ out = send_queue.slice(send_queue_out).join("\n") if( tmp_send_queue_in > 0 ){ out += "\n" + send_queue.slice(0,tmp_send_queue_in).join("\n"); } }else{ out = send_queue.slice(send_queue_out, tmp_send_queue_in).join("\n"); } send_queue_out = tmp_send_queue_in; //set_status("Sending"); send_conn.send( out +"\n" ); } function send_handle_recv() { if( send_conn.readyState != 4) { return; } if( send_conn.status == 404) { set_status("Connection Closed"); update_method = null; return; } if( send_conn.status != 200) { alert("Error from server: "+send_conn.statusText); return; } sending = false; if( send_queue_out != send_queue_in ){ send_more(); } } function send_more(){ setTimeout("do_send();",send_more_delay); } function check_fontsize(){ body_resize() setTimeout("check_fontsize();",check_font_delay); } function body_resize(){ var t = document.getElementById('testchar'); var t2 = document.getElementById('testchar2'); var text = document.getElementById('text'); var window_width; var window_height; if (window.innerHeight) { window_width = window.innerWidth; window_height = window.innerHeight; }else{ window_width = document.documentElement.clientWidth; window_height = document.documentElement.clientHeight; //var z = "CI:"; for(var i in bod){z = z + " " + i;} alert(z); } char_width = t.offsetLeft / 44; var avail_width = window_width-18; var avail_width_mod = avail_width % char_width; var x_size = (avail_width - avail_width_mod)/char_width; char_height = t2.offsetTop - t.offsetTop; var avail_height = window_height-text.offsetTop-10; var avail_height_mod = avail_height % char_height; var y_size = (avail_height - avail_height_mod)/char_height; text.style.width = x_size*char_width+"px"; text.style.height = y_size*char_height+"px"; if( screen_x != x_size || screen_y != y_size ){ send_key("window resize "+x_size+" "+y_size); } screen_x = x_size; screen_y = y_size; } urwid-2.6.16/urwid/display/_win32.py000066400000000000000000000124311470350774000172220ustar00rootroot00000000000000from __future__ import annotations import enum import typing from ctypes import POINTER, Structure, Union, windll from ctypes.wintypes import BOOL, CHAR, DWORD, HANDLE, LPDWORD, SHORT, UINT, WCHAR, WORD # https://docs.microsoft.com/de-de/windows/console/getstdhandle STD_INPUT_HANDLE = -10 STD_OUTPUT_HANDLE = -11 # https://docs.microsoft.com/de-de/windows/console/setconsolemode ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 DISABLE_NEWLINE_AUTO_RETURN = 0x0008 ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 ENABLE_WINDOW_INPUT = 0x0008 class COORD(Structure): """https://docs.microsoft.com/en-us/windows/console/coord-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [ ("X", SHORT), ("Y", SHORT), ] class SMALL_RECT(Structure): """https://docs.microsoft.com/en-us/windows/console/small-rect-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [ ("Left", SHORT), ("Top", SHORT), ("Right", SHORT), ("Bottom", SHORT), ] class CONSOLE_SCREEN_BUFFER_INFO(Structure): """https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [ ("dwSize", COORD), ("dwCursorPosition", COORD), ("wAttributes", WORD), ("srWindow", SMALL_RECT), ("dwMaximumWindowSize", COORD), ] class uChar(Union): """https://docs.microsoft.com/en-us/windows/console/key-event-record-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [ ("AsciiChar", CHAR), ("UnicodeChar", WCHAR), ] class KEY_EVENT_RECORD(Structure): """https://docs.microsoft.com/en-us/windows/console/key-event-record-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [ ("bKeyDown", BOOL), ("wRepeatCount", WORD), ("wVirtualKeyCode", WORD), ("wVirtualScanCode", WORD), ("uChar", uChar), ("dwControlKeyState", DWORD), ] class MOUSE_EVENT_RECORD(Structure): """https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [ ("dwMousePosition", COORD), ("dwButtonState", DWORD), ("dwControlKeyState", DWORD), ("dwEventFlags", DWORD), ] class MouseButtonState(enum.IntFlag): """https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str""" FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001 RIGHTMOST_BUTTON_PRESSED = 0x0002 FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004 FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008 FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010 class MouseEventFlags(enum.IntFlag): """https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str""" BUTTON_PRESSED = 0x0000 # Default action, used in examples, but not in official enum MOUSE_MOVED = 0x0001 DOUBLE_CLICK = 0x0002 MOUSE_WHEELED = 0x0004 MOUSE_HWHEELED = 0x0008 class WINDOW_BUFFER_SIZE_RECORD(Structure): """https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [("dwSize", COORD)] class MENU_EVENT_RECORD(Structure): """https://docs.microsoft.com/en-us/windows/console/menu-event-record-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [("dwCommandId", UINT)] class FOCUS_EVENT_RECORD(Structure): """https://docs.microsoft.com/en-us/windows/console/focus-event-record-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [("bSetFocus", BOOL)] class Event(Union): """https://docs.microsoft.com/en-us/windows/console/input-record-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [ ("KeyEvent", KEY_EVENT_RECORD), ("MouseEvent", MOUSE_EVENT_RECORD), ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD), ("MenuEvent", MENU_EVENT_RECORD), ("FocusEvent", FOCUS_EVENT_RECORD), ] class INPUT_RECORD(Structure): """https://docs.microsoft.com/en-us/windows/console/input-record-str""" _fields_: typing.ClassVar[list[tuple[str, type]]] = [("EventType", WORD), ("Event", Event)] class EventType(enum.IntFlag): KEY_EVENT = 0x0001 MOUSE_EVENT = 0x0002 WINDOW_BUFFER_SIZE_EVENT = 0x0004 MENU_EVENT = 0x0008 FOCUS_EVENT = 0x0010 # https://docs.microsoft.com/de-de/windows/console/getstdhandle GetStdHandle = windll.kernel32.GetStdHandle GetStdHandle.argtypes = [DWORD] GetStdHandle.restype = HANDLE # https://docs.microsoft.com/de-de/windows/console/getconsolemode GetConsoleMode = windll.kernel32.GetConsoleMode GetConsoleMode.argtypes = [HANDLE, LPDWORD] GetConsoleMode.restype = BOOL # https://docs.microsoft.com/de-de/windows/console/setconsolemode SetConsoleMode = windll.kernel32.SetConsoleMode SetConsoleMode.argtypes = [HANDLE, DWORD] SetConsoleMode.restype = BOOL # https://docs.microsoft.com/de-de/windows/console/readconsoleinput ReadConsoleInputW = windll.kernel32.ReadConsoleInputW # ReadConsoleInputW.argtypes = [HANDLE, POINTER(INPUT_RECORD), DWORD, LPDWORD] ReadConsoleInputW.restype = BOOL # https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo GetConsoleScreenBufferInfo.argtypes = [HANDLE, POINTER(CONSOLE_SCREEN_BUFFER_INFO)] GetConsoleScreenBufferInfo.restype = BOOL urwid-2.6.16/urwid/display/_win32_raw_display.py000066400000000000000000000221421470350774000216200ustar00rootroot00000000000000# Urwid raw display module # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Direct terminal UI implementation """ from __future__ import annotations import contextlib import functools import logging import selectors import socket import sys import threading import typing from ctypes import byref from ctypes.wintypes import DWORD from urwid import signals from . import _raw_display_base, _win32, escape from .common import INPUT_DESCRIPTORS_CHANGED if typing.TYPE_CHECKING: from collections.abc import Callable from urwid.event_loop import EventLoop class Screen(_raw_display_base.Screen): _term_input_file: socket.socket def __init__( self, input: socket.socket | None = None, # noqa: A002 # pylint: disable=redefined-builtin output: typing.TextIO = sys.stdout, ) -> None: """Initialize a screen that directly prints escape codes to an output terminal. """ if input is None: input, self._send_input = socket.socketpair() # noqa: A001 super().__init__(input, output) _dwOriginalOutMode = None _dwOriginalInMode = None def _start(self, alternate_buffer: bool = True) -> None: """ Initialize the screen and input mode. alternate_buffer -- use alternate screen buffer """ if alternate_buffer: self.write(escape.SWITCH_TO_ALTERNATE_BUFFER) self._rows_used = None else: self._rows_used = 0 handle_out = _win32.GetStdHandle(_win32.STD_OUTPUT_HANDLE) handle_in = _win32.GetStdHandle(_win32.STD_INPUT_HANDLE) self._dwOriginalOutMode = DWORD() self._dwOriginalInMode = DWORD() _win32.GetConsoleMode(handle_out, byref(self._dwOriginalOutMode)) _win32.GetConsoleMode(handle_in, byref(self._dwOriginalInMode)) # TODO: Restore on exit dword_out_mode = DWORD( self._dwOriginalOutMode.value | _win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING | _win32.DISABLE_NEWLINE_AUTO_RETURN ) dword_in_mode = DWORD( self._dwOriginalInMode.value | _win32.ENABLE_WINDOW_INPUT | _win32.ENABLE_VIRTUAL_TERMINAL_INPUT ) ok = _win32.SetConsoleMode(handle_out, dword_out_mode) if not ok: raise RuntimeError(f"ConsoleMode set failed for output. Err: {ok!r}") ok = _win32.SetConsoleMode(handle_in, dword_in_mode) if not ok: raise RuntimeError(f"ConsoleMode set failed for input. Err: {ok!r}") self._alternate_buffer = alternate_buffer self._next_timeout = self.max_wait signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED) # restore mouse tracking to previous state self._mouse_tracking(self._mouse_tracking_enabled) return super()._start() def _stop(self) -> None: """ Restore the screen. """ self.clear() signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED) self._stop_mouse_restore_buffer() handle_out = _win32.GetStdHandle(_win32.STD_OUTPUT_HANDLE) handle_in = _win32.GetStdHandle(_win32.STD_INPUT_HANDLE) ok = _win32.SetConsoleMode(handle_out, self._dwOriginalOutMode) if not ok: raise RuntimeError(f"ConsoleMode set failed for output. Err: {ok!r}") ok = _win32.SetConsoleMode(handle_in, self._dwOriginalInMode) if not ok: raise RuntimeError(f"ConsoleMode set failed for input. Err: {ok!r}") super()._stop() def unhook_event_loop(self, event_loop: EventLoop) -> None: """ Remove any hooks added by hook_event_loop. """ if self._input_thread is not None: self._input_thread.should_exit = True with contextlib.suppress(RuntimeError): self._input_thread.join(5) self._input_thread = None for handle in self._current_event_loop_handles: event_loop.remove_watch_file(handle) if self._input_timeout: event_loop.remove_alarm(self._input_timeout) self._input_timeout = None def hook_event_loop( self, event_loop: EventLoop, callback: Callable[[list[str], list[int]], typing.Any], ) -> None: """ Register the given callback with the event loop, to be called with new input whenever it's available. The callback should be passed a list of processed keys and a list of unprocessed keycodes. Subclasses may wish to use parse_input to wrap the callback. """ self._input_thread = ReadInputThread(self._send_input, lambda: self._sigwinch_handler(28)) self._input_thread.start() if hasattr(self, "get_input_nonblocking"): wrapper = self._make_legacy_input_wrapper(event_loop, callback) else: @functools.wraps(callback) def wrapper() -> tuple[list[str], typing.Any] | None: return self.parse_input(event_loop, callback, self.get_available_raw_input()) fds = self.get_input_descriptors() handles = [event_loop.watch_file(fd if isinstance(fd, int) else fd.fileno(), wrapper) for fd in fds] self._current_event_loop_handles = handles _input_thread: ReadInputThread | None = None def _read_raw_input(self, timeout: int) -> bytearray: ready = self._wait_for_input_ready(timeout) fd = self._input_fileno() chars = bytearray() if fd is None or fd not in ready: return chars with selectors.DefaultSelector() as selector: selector.register(fd, selectors.EVENT_READ) input_ready = selector.select(0) while input_ready: chars.extend(self._term_input_file.recv(1024)) input_ready = selector.select(0) return chars def get_cols_rows(self) -> tuple[int, int]: """Return the terminal dimensions (num columns, num rows).""" y, x = super().get_cols_rows() with contextlib.suppress(OSError): # Term size could not be determined if hasattr(self._term_output_file, "fileno"): if self._term_output_file != sys.stdout: raise RuntimeError("Unexpected terminal output file") handle = _win32.GetStdHandle(_win32.STD_OUTPUT_HANDLE) info = _win32.CONSOLE_SCREEN_BUFFER_INFO() ok = _win32.GetConsoleScreenBufferInfo(handle, byref(info)) if ok: # Fallback will be used in case of term size could not be determined y, x = info.dwSize.Y, info.dwSize.X self.maxrow = y return x, y class ReadInputThread(threading.Thread): name = "urwid Windows input reader" daemon = True should_exit: bool = False def __init__( self, input_socket: socket.socket, resize: Callable[[], typing.Any], ) -> None: self._input = input_socket self._resize = resize self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__) super().__init__() def run(self) -> None: hIn = _win32.GetStdHandle(_win32.STD_INPUT_HANDLE) MAX = 2048 read = DWORD(0) arrtype = _win32.INPUT_RECORD * MAX input_records = arrtype() while True: _win32.ReadConsoleInputW(hIn, byref(input_records), MAX, byref(read)) if self.should_exit: return for i in range(read.value): inp = input_records[i] if inp.EventType == _win32.EventType.KEY_EVENT: if not inp.Event.KeyEvent.bKeyDown: continue input_data = inp.Event.KeyEvent.uChar.UnicodeChar # On Windows atomic press/release of modifier keys produce phantom input with code NULL. # This input cannot be decoded and should be handled as garbage. input_bytes = input_data.encode("utf-8") if input_bytes != b"\x00": self._input.send(input_bytes) elif inp.EventType == _win32.EventType.WINDOW_BUFFER_SIZE_EVENT: self._resize() else: pass # TODO: handle mouse events def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() urwid-2.6.16/urwid/display/common.py000066400000000000000000001151351470350774000174160ustar00rootroot00000000000000# Urwid common display code # Copyright (C) 2004-2011 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import abc import logging import os import sys import typing import warnings from urwid import signals from urwid.util import StoppingContext, int_scale if typing.TYPE_CHECKING: from collections.abc import Iterable, Sequence from typing_extensions import Literal, Self from urwid import Canvas IS_WINDOWS = sys.platform == "win32" # for replacing unprintable bytes with '?' UNPRINTABLE_TRANS_TABLE = b"?" * 32 + bytes(range(32, 256)) # signals sent by BaseScreen UPDATE_PALETTE_ENTRY = "update palette entry" INPUT_DESCRIPTORS_CHANGED = "input descriptors changed" # AttrSpec internal values _BASIC_START = 0 # first index of basic color aliases _CUBE_START = 16 # first index of color cube _CUBE_SIZE_256 = 6 # one side of the color cube _GRAY_SIZE_256 = 24 _GRAY_START_256 = _CUBE_SIZE_256**3 + _CUBE_START _CUBE_WHITE_256 = _GRAY_START_256 - 1 _CUBE_SIZE_88 = 4 _GRAY_SIZE_88 = 8 _GRAY_START_88 = _CUBE_SIZE_88**3 + _CUBE_START _CUBE_WHITE_88 = _GRAY_START_88 - 1 _CUBE_BLACK = _CUBE_START # values copied from xterm 256colres.h: _CUBE_STEPS_256 = [0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF] _GRAY_STEPS_256 = [ 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76, 0x80, 0x84, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE, ] # values copied from xterm 88colres.h: _CUBE_STEPS_88 = [0x00, 0x8B, 0xCD, 0xFF] _GRAY_STEPS_88 = [0x2E, 0x5C, 0x73, 0x8B, 0xA2, 0xB9, 0xD0, 0xE7] # values copied from X11/rgb.txt and XTerm-col.ad: _BASIC_COLOR_VALUES = [ (0, 0, 0), (205, 0, 0), (0, 205, 0), (205, 205, 0), (0, 0, 238), (205, 0, 205), (0, 205, 205), (229, 229, 229), (127, 127, 127), (255, 0, 0), (0, 255, 0), (255, 255, 0), (0x5C, 0x5C, 0xFF), (255, 0, 255), (0, 255, 255), (255, 255, 255), ] _COLOR_VALUES_256 = ( _BASIC_COLOR_VALUES + [(r, g, b) for r in _CUBE_STEPS_256 for g in _CUBE_STEPS_256 for b in _CUBE_STEPS_256] + [(gr, gr, gr) for gr in _GRAY_STEPS_256] ) _COLOR_VALUES_88 = ( _BASIC_COLOR_VALUES + [(r, g, b) for r in _CUBE_STEPS_88 for g in _CUBE_STEPS_88 for b in _CUBE_STEPS_88] + [(gr, gr, gr) for gr in _GRAY_STEPS_88] ) if len(_COLOR_VALUES_256) != 256: raise RuntimeError(_COLOR_VALUES_256) if len(_COLOR_VALUES_88) != 88: raise RuntimeError(_COLOR_VALUES_88) # fmt: off _FG_COLOR_MASK = 0x000000ffffff _BG_COLOR_MASK = 0xffffff000000 _FG_BASIC_COLOR = 0x1000000000000 _FG_HIGH_COLOR = 0x2000000000000 _FG_TRUE_COLOR = 0x4000000000000 _BG_BASIC_COLOR = 0x8000000000000 _BG_HIGH_COLOR = 0x10000000000000 _BG_TRUE_COLOR = 0x20000000000000 _BG_SHIFT = 24 _HIGH_88_COLOR = 0x40000000000000 _HIGH_TRUE_COLOR = 0x80000000000000 _STANDOUT = 0x100000000000000 _UNDERLINE = 0x200000000000000 _BOLD = 0x400000000000000 _BLINK = 0x800000000000000 _ITALICS = 0x1000000000000000 _STRIKETHROUGH = 0x2000000000000000 # fmt: on _FG_MASK = ( _FG_COLOR_MASK | _FG_BASIC_COLOR | _FG_HIGH_COLOR | _STANDOUT | _UNDERLINE | _BLINK | _BOLD | _ITALICS | _STRIKETHROUGH ) _BG_MASK = _BG_COLOR_MASK | _BG_BASIC_COLOR | _BG_HIGH_COLOR DEFAULT = "default" BLACK = "black" DARK_RED = "dark red" DARK_GREEN = "dark green" BROWN = "brown" DARK_BLUE = "dark blue" DARK_MAGENTA = "dark magenta" DARK_CYAN = "dark cyan" LIGHT_GRAY = "light gray" DARK_GRAY = "dark gray" LIGHT_RED = "light red" LIGHT_GREEN = "light green" YELLOW = "yellow" LIGHT_BLUE = "light blue" LIGHT_MAGENTA = "light magenta" LIGHT_CYAN = "light cyan" WHITE = "white" _BASIC_COLORS = [ BLACK, DARK_RED, DARK_GREEN, BROWN, DARK_BLUE, DARK_MAGENTA, DARK_CYAN, LIGHT_GRAY, DARK_GRAY, LIGHT_RED, LIGHT_GREEN, YELLOW, LIGHT_BLUE, LIGHT_MAGENTA, LIGHT_CYAN, WHITE, ] _ATTRIBUTES = { "bold": _BOLD, "italics": _ITALICS, "underline": _UNDERLINE, "blink": _BLINK, "standout": _STANDOUT, "strikethrough": _STRIKETHROUGH, } def _value_lookup_table(values: Sequence[int], size: int) -> list[int]: """ Generate a lookup table for finding the closest item in values. Lookup returns (index into values)+1 values -- list of values in ascending order, all < size size -- size of lookup table and maximum value >>> _value_lookup_table([0, 7, 9], 10) [0, 0, 0, 0, 1, 1, 1, 1, 2, 2] """ middle_values = [0] + [(values[i] + values[i + 1] + 1) // 2 for i in range(len(values) - 1)] + [size] lookup_table = [] for i in range(len(middle_values) - 1): count = middle_values[i + 1] - middle_values[i] lookup_table.extend([i] * count) return lookup_table _CUBE_256_LOOKUP = _value_lookup_table(_CUBE_STEPS_256, 256) _GRAY_256_LOOKUP = _value_lookup_table([0, *_GRAY_STEPS_256, 255], 256) _CUBE_88_LOOKUP = _value_lookup_table(_CUBE_STEPS_88, 256) _GRAY_88_LOOKUP = _value_lookup_table([0, *_GRAY_STEPS_88, 255], 256) # convert steps to values that will be used by string versions of the colors # 1 hex digit for rgb and 0..100 for grayscale _CUBE_STEPS_256_16 = [int_scale(n, 0x100, 0x10) for n in _CUBE_STEPS_256] _GRAY_STEPS_256_101 = [int_scale(n, 0x100, 101) for n in _GRAY_STEPS_256] _CUBE_STEPS_88_16 = [int_scale(n, 0x100, 0x10) for n in _CUBE_STEPS_88] _GRAY_STEPS_88_101 = [int_scale(n, 0x100, 101) for n in _GRAY_STEPS_88] # create lookup tables for 1 hex digit rgb and 0..100 for grayscale values _CUBE_256_LOOKUP_16 = [_CUBE_256_LOOKUP[int_scale(n, 16, 0x100)] for n in range(16)] _GRAY_256_LOOKUP_101 = [_GRAY_256_LOOKUP[int_scale(n, 101, 0x100)] for n in range(101)] _CUBE_88_LOOKUP_16 = [_CUBE_88_LOOKUP[int_scale(n, 16, 0x100)] for n in range(16)] _GRAY_88_LOOKUP_101 = [_GRAY_88_LOOKUP[int_scale(n, 101, 0x100)] for n in range(101)] # The functions _gray_num_256() and _gray_num_88() do not include the gray # values from the color cube so that the gray steps are an even width. # The color cube grays are available by using the rgb functions. Pure # white and black are taken from the color cube, since the gray range does # not include them, and the basic colors are more likely to have been # customized by an end-user. def _gray_num_256(gnum: int) -> int: """Return ths color number for gray number gnum. Color cube black and white are returned for 0 and 25 respectively since those values aren't included in the gray scale. """ # grays start from index 1 gnum -= 1 if gnum < 0: return _CUBE_BLACK if gnum >= _GRAY_SIZE_256: return _CUBE_WHITE_256 return _GRAY_START_256 + gnum def _gray_num_88(gnum: int) -> int: """Return ths color number for gray number gnum. Color cube black and white are returned for 0 and 9 respectively since those values aren't included in the gray scale. """ # gnums start from index 1 gnum -= 1 if gnum < 0: return _CUBE_BLACK if gnum >= _GRAY_SIZE_88: return _CUBE_WHITE_88 return _GRAY_START_88 + gnum def _color_desc_true(num: int) -> str: return f"#{num:06x}" def _color_desc_256(num: int) -> str: """ Return a string description of color number num. 0..15 -> 'h0'..'h15' basic colors (as high-colors) 16..231 -> '#000'..'#fff' color cube colors 232..255 -> 'g3'..'g93' grays >>> _color_desc_256(15) 'h15' >>> _color_desc_256(16) '#000' >>> _color_desc_256(17) '#006' >>> _color_desc_256(230) '#ffd' >>> _color_desc_256(233) 'g7' >>> _color_desc_256(234) 'g11' """ if not 0 <= num < 256: raise ValueError(num) if num < _CUBE_START: return f"h{num:d}" if num < _GRAY_START_256: num -= _CUBE_START b, num = num % _CUBE_SIZE_256, num // _CUBE_SIZE_256 g, num = num % _CUBE_SIZE_256, num // _CUBE_SIZE_256 r = num % _CUBE_SIZE_256 return f"#{_CUBE_STEPS_256_16[r]:x}{_CUBE_STEPS_256_16[g]:x}{_CUBE_STEPS_256_16[b]:x}" return f"g{_GRAY_STEPS_256_101[num - _GRAY_START_256]:d}" def _color_desc_88(num: int) -> str: """ Return a string description of color number num. 0..15 -> 'h0'..'h15' basic colors (as high-colors) 16..79 -> '#000'..'#fff' color cube colors 80..87 -> 'g18'..'g90' grays >>> _color_desc_88(15) 'h15' >>> _color_desc_88(16) '#000' >>> _color_desc_88(17) '#008' >>> _color_desc_88(78) '#ffc' >>> _color_desc_88(81) 'g36' >>> _color_desc_88(82) 'g45' """ if not 0 < num < 88: raise ValueError(num) if num < _CUBE_START: return f"h{num:d}" if num < _GRAY_START_88: num -= _CUBE_START b, num = num % _CUBE_SIZE_88, num // _CUBE_SIZE_88 g, r = num % _CUBE_SIZE_88, num // _CUBE_SIZE_88 return f"#{_CUBE_STEPS_88_16[r]:x}{_CUBE_STEPS_88_16[g]:x}{_CUBE_STEPS_88_16[b]:x}" return f"g{_GRAY_STEPS_88_101[num - _GRAY_START_88]:d}" def _parse_color_true(desc: str) -> int | None: c = _parse_color_256(desc) if c is not None: (r, g, b) = _COLOR_VALUES_256[c] return (r << 16) + (g << 8) + b if not desc.startswith("#"): return None if len(desc) == 7: h = desc[1:] return int(h, 16) if len(desc) == 4: h = f"0x{desc[1]}0{desc[2]}0{desc[3]}" return int(h, 16) return None def _parse_color_256(desc: str) -> int | None: """ Return a color number for the description desc. 'h0'..'h255' -> 0..255 actual color number '#000'..'#fff' -> 16..231 color cube colors 'g0'..'g100' -> 16, 232..255, 231 grays and color cube black/white 'g#00'..'g#ff' -> 16, 232...255, 231 gray and color cube black/white Returns None if desc is invalid. >>> _parse_color_256('h142') 142 >>> _parse_color_256('#f00') 196 >>> _parse_color_256('g100') 231 >>> _parse_color_256('g#80') 244 """ if len(desc) > 4: # keep the length within reason before parsing return None try: if desc.startswith("h"): # high-color number num = int(desc[1:], 10) if num < 0 or num > 255: return None return num if desc.startswith("#") and len(desc) == 4: # color-cube coordinates rgb = int(desc[1:], 16) if rgb < 0: return None b, rgb = rgb % 16, rgb // 16 g, r = rgb % 16, rgb // 16 # find the closest rgb values r = _CUBE_256_LOOKUP_16[r] g = _CUBE_256_LOOKUP_16[g] b = _CUBE_256_LOOKUP_16[b] return _CUBE_START + (r * _CUBE_SIZE_256 + g) * _CUBE_SIZE_256 + b # Only remaining possibility is gray value if desc.startswith("g#"): # hex value 00..ff gray = int(desc[2:], 16) if gray < 0 or gray > 255: return None gray = _GRAY_256_LOOKUP[gray] elif desc.startswith("g"): # decimal value 0..100 gray = int(desc[1:], 10) if gray < 0 or gray > 100: return None gray = _GRAY_256_LOOKUP_101[gray] else: return None if gray == 0: return _CUBE_BLACK gray -= 1 if gray == _GRAY_SIZE_256: return _CUBE_WHITE_256 return _GRAY_START_256 + gray except ValueError: return None def _true_to_256(desc: str) -> str | None: if not (desc.startswith("#") and len(desc) == 7): return None c256 = _parse_color_256("#" + "".join(format(int(x, 16) // 16, "x") for x in (desc[1:3], desc[3:5], desc[5:7]))) return _color_desc_256(c256) def _parse_color_88(desc: str) -> int | None: """ Return a color number for the description desc. 'h0'..'h87' -> 0..87 actual color number '#000'..'#fff' -> 16..79 color cube colors 'g0'..'g100' -> 16, 80..87, 79 grays and color cube black/white 'g#00'..'g#ff' -> 16, 80...87, 79 gray and color cube black/white Returns None if desc is invalid. >>> _parse_color_88('h142') >>> _parse_color_88('h42') 42 >>> _parse_color_88('#f00') 64 >>> _parse_color_88('g100') 79 >>> _parse_color_88('g#80') 83 """ if len(desc) == 7: desc = desc[0:2] + desc[3] + desc[5] if len(desc) > 4: # keep the length within reason before parsing return None try: if desc.startswith("h"): # high-color number num = int(desc[1:], 10) if num < 0 or num > 87: return None return num if desc.startswith("#") and len(desc) == 4: # color-cube coordinates rgb = int(desc[1:], 16) if rgb < 0: return None b, rgb = rgb % 16, rgb // 16 g, r = rgb % 16, rgb // 16 # find the closest rgb values r = _CUBE_88_LOOKUP_16[r] g = _CUBE_88_LOOKUP_16[g] b = _CUBE_88_LOOKUP_16[b] return _CUBE_START + (r * _CUBE_SIZE_88 + g) * _CUBE_SIZE_88 + b # Only remaining possibility is gray value if desc.startswith("g#"): # hex value 00..ff gray = int(desc[2:], 16) if gray < 0 or gray > 255: return None gray = _GRAY_88_LOOKUP[gray] elif desc.startswith("g"): # decimal value 0..100 gray = int(desc[1:], 10) if gray < 0 or gray > 100: return None gray = _GRAY_88_LOOKUP_101[gray] else: return None if gray == 0: return _CUBE_BLACK gray -= 1 if gray == _GRAY_SIZE_88: return _CUBE_WHITE_88 return _GRAY_START_88 + gray except ValueError: return None class AttrSpecError(Exception): pass class AttrSpec: __slots__ = ("__value",) def __init__(self, fg: str, bg: str, colors: Literal[1, 16, 88, 256, 16777216] = 256) -> None: """ fg -- a string containing a comma-separated foreground color and settings Color values: 'default' (use the terminal's default foreground), 'black', 'dark red', 'dark green', 'brown', 'dark blue', 'dark magenta', 'dark cyan', 'light gray', 'dark gray', 'light red', 'light green', 'yellow', 'light blue', 'light magenta', 'light cyan', 'white' High-color example values: '#009' (0% red, 0% green, 60% red, like HTML colors) '#23facc' (RRGGBB hex color code) '#fcc' (100% red, 80% green, 80% blue) 'g40' (40% gray, decimal), 'g#cc' (80% gray, hex), '#000', 'g0', 'g#00' (black), '#fff', 'g100', 'g#ff' (white) 'h8' (color number 8), 'h255' (color number 255) Setting: 'bold', 'italics', 'underline', 'blink', 'standout', 'strikethrough' Some terminals use 'bold' for bright colors. Most terminals ignore the 'blink' setting. If the color is not given then 'default' will be assumed. bg -- a string containing the background color Color values: 'default' (use the terminal's default background), 'black', 'dark red', 'dark green', 'brown', 'dark blue', 'dark magenta', 'dark cyan', 'light gray' High-color exaples: see fg examples above An empty string will be treated the same as 'default'. colors -- the maximum colors available for the specification Valid values include: 1, 16, 88, 256, and 2**24. High-color values are only usable with 88, 256, or 2**24 colors. With 1 color only the foreground settings may be used. >>> AttrSpec('dark red', 'light gray', 16) AttrSpec('dark red', 'light gray') >>> AttrSpec('yellow, underline, bold', 'dark blue') AttrSpec('yellow,bold,underline', 'dark blue') >>> AttrSpec('#ddb', '#004', 256) # closest colors will be found AttrSpec('#dda', '#006') >>> AttrSpec('#ddb', '#004', 88) AttrSpec('#ccc', '#000', colors=88) """ if colors not in {1, 16, 88, 256, 2**24}: raise AttrSpecError(f"invalid number of colors ({colors:d}).") self.__value = 0 | _HIGH_88_COLOR * (colors == 88) | _HIGH_TRUE_COLOR * (colors == 2**24) self.__set_foreground(fg) self.__set_background(bg) if self.colors > colors: raise AttrSpecError( f"foreground/background ({fg!r}/{bg!r}) require more colors than have been specified ({colors:d})." ) def copy_modified( self, fg: str | None = None, bg: str | None = None, colors: Literal[1, 16, 88, 256, 16777216] | None = None, ) -> Self: if fg is None: foreground = self.foreground else: foreground = fg if bg is None: background = self.background else: background = bg if colors is None: new_colors = self.colors else: new_colors = colors return self.__class__(foreground, background, new_colors) def __hash__(self) -> int: """Instance is immutable and hashable.""" return hash((self.__class__, self.__value)) @property def _value(self) -> int: """Read-only value access.""" return self.__value @property def foreground_basic(self) -> bool: return self.__value & _FG_BASIC_COLOR != 0 @property def foreground_high(self) -> bool: return self.__value & _FG_HIGH_COLOR != 0 @property def foreground_true(self) -> bool: return self.__value & _FG_TRUE_COLOR != 0 @property def foreground_number(self) -> int: return self.__value & _FG_COLOR_MASK @property def background_basic(self) -> bool: return self.__value & _BG_BASIC_COLOR != 0 @property def background_high(self) -> bool: return self.__value & _BG_HIGH_COLOR != 0 @property def background_true(self) -> bool: return self.__value & _BG_TRUE_COLOR != 0 @property def background_number(self) -> int: return (self.__value & _BG_COLOR_MASK) >> _BG_SHIFT @property def italics(self) -> bool: return self.__value & _ITALICS != 0 @property def bold(self) -> bool: return self.__value & _BOLD != 0 @property def underline(self) -> bool: return self.__value & _UNDERLINE != 0 @property def blink(self) -> bool: return self.__value & _BLINK != 0 @property def standout(self) -> bool: return self.__value & _STANDOUT != 0 @property def strikethrough(self) -> bool: return self.__value & _STRIKETHROUGH != 0 @property def colors(self) -> int: """ Return the maximum colors required for this object. Returns 256, 88, 16 or 1. """ if self.__value & _HIGH_88_COLOR: return 88 if self.__value & (_BG_HIGH_COLOR | _FG_HIGH_COLOR): return 256 if self.__value & (_BG_TRUE_COLOR | _FG_TRUE_COLOR): return 2**24 if self.__value & (_BG_BASIC_COLOR | _FG_BASIC_COLOR): return 16 return 1 def _colors(self) -> int: warnings.warn( f"Method `{self.__class__.__name__}._colors` is deprecated, " f"please use property `{self.__class__.__name__}.colors`", DeprecationWarning, stacklevel=2, ) return self.colors def __repr__(self) -> str: """ Return an executable python representation of the AttrSpec object. """ args = f"{self.foreground!r}, {self.background!r}" if self.colors == 88: # 88-color mode is the only one that is handled differently args = f"{args}, colors=88" return f"{self.__class__.__name__}({args})" def _foreground_color(self) -> str: """Return only the color component of the foreground.""" if not (self.foreground_basic or self.foreground_high or self.foreground_true): return "default" if self.foreground_basic: return _BASIC_COLORS[self.foreground_number] if self.colors == 88: return _color_desc_88(self.foreground_number) if self.colors == 2**24: return _color_desc_true(self.foreground_number) return _color_desc_256(self.foreground_number) @property def foreground(self) -> str: return ( self._foreground_color() + ",bold" * self.bold + ",italics" * self.italics + ",standout" * self.standout + ",blink" * self.blink + ",underline" * self.underline + ",strikethrough" * self.strikethrough ) def __set_foreground(self, foreground: str) -> None: color = None flags = 0 # handle comma-separated foreground for part in foreground.split(","): part = part.strip() # noqa: PLW2901 if part in _ATTRIBUTES: # parse and store "settings"/attributes in flags if flags & _ATTRIBUTES[part]: raise AttrSpecError(f"Setting {part!r} specified more than once in foreground ({foreground!r})") flags |= _ATTRIBUTES[part] continue # past this point we must be specifying a color if part in {"", "default"}: scolor = 0 elif part in _BASIC_COLORS: scolor = _BASIC_COLORS.index(part) flags |= _FG_BASIC_COLOR elif self.__value & _HIGH_88_COLOR: scolor = _parse_color_88(part) flags |= _FG_HIGH_COLOR elif self.__value & _HIGH_TRUE_COLOR: scolor = _parse_color_true(part) flags |= _FG_TRUE_COLOR else: scolor = _parse_color_256(_true_to_256(part) or part) flags |= _FG_HIGH_COLOR # _parse_color_*() return None for unrecognised colors if scolor is None: raise AttrSpecError(f"Unrecognised color specification {part!r} in foreground ({foreground!r})") if color is not None: raise AttrSpecError(f"More than one color given for foreground ({foreground!r})") color = scolor if color is None: color = 0 self.__value = (self.__value & ~_FG_MASK) | color | flags def _foreground(self) -> str: warnings.warn( f"Method `{self.__class__.__name__}._foreground` is deprecated, " f"please use property `{self.__class__.__name__}.foreground`", DeprecationWarning, stacklevel=2, ) return self.foreground @property def background(self) -> str: """Return the background color.""" if not (self.background_basic or self.background_high or self.background_true): return "default" if self.background_basic: return _BASIC_COLORS[self.background_number] if self.__value & _HIGH_88_COLOR: return _color_desc_88(self.background_number) if self.colors == 2**24: return _color_desc_true(self.background_number) return _color_desc_256(self.background_number) def __set_background(self, background: str) -> None: flags = 0 if background in {"", "default"}: color = 0 elif background in _BASIC_COLORS: color = _BASIC_COLORS.index(background) flags |= _BG_BASIC_COLOR elif self.__value & _HIGH_88_COLOR: color = _parse_color_88(background) flags |= _BG_HIGH_COLOR elif self.__value & _HIGH_TRUE_COLOR: color = _parse_color_true(background) flags |= _BG_TRUE_COLOR else: color = _parse_color_256(_true_to_256(background) or background) flags |= _BG_HIGH_COLOR if color is None: raise AttrSpecError(f"Unrecognised color specification in background ({background!r})") self.__value = (self.__value & ~_BG_MASK) | (color << _BG_SHIFT) | flags def _background(self) -> str: warnings.warn( f"Method `{self.__class__.__name__}._background` is deprecated, " f"please use property `{self.__class__.__name__}.background`", DeprecationWarning, stacklevel=2, ) return self.background def get_rgb_values(self) -> tuple[int | None, int | None, int | None, int | None, int | None, int | None]: """ Return (fg_red, fg_green, fg_blue, bg_red, bg_green, bg_blue) color components. Each component is in the range 0-255. Values are taken from the XTerm defaults and may not exactly match the user's terminal. If the foreground or background is 'default' then all their compenents will be returned as None. >>> AttrSpec('yellow', '#ccf', colors=88).get_rgb_values() (255, 255, 0, 205, 205, 255) >>> AttrSpec('default', 'g92').get_rgb_values() (None, None, None, 238, 238, 238) """ if not (self.foreground_basic or self.foreground_high or self.foreground_true): vals = (None, None, None) elif self.colors == 88: if self.foreground_number >= 88: raise ValueError(f"Invalid AttrSpec _value: {self.foreground_number!r}") vals = _COLOR_VALUES_88[self.foreground_number] elif self.colors == 2**24: h = f"{self.foreground_number:06x}" vals = tuple(int(x, 16) for x in (h[0:2], h[2:4], h[4:6])) else: vals = _COLOR_VALUES_256[self.foreground_number] if not (self.background_basic or self.background_high or self.background_true): return (*vals, None, None, None) if self.colors == 88: if self.background_number >= 88: raise ValueError(f"Invalid AttrSpec _value: {self.background_number!r}") return vals + _COLOR_VALUES_88[self.background_number] if self.colors == 2**24: h = f"{self.background_number:06x}" return vals + tuple(int(x, 16) for x in (h[0:2], h[2:4], h[4:6])) return vals + _COLOR_VALUES_256[self.background_number] def __eq__(self, other: object) -> bool: return isinstance(other, AttrSpec) and self.__value == other._value def __ne__(self, other: object) -> bool: return not self == other class RealTerminal: def __init__(self) -> None: super().__init__() self._signal_keys_set = False self._old_signal_keys = None if IS_WINDOWS: def tty_signal_keys( self, intr: Literal["undefined"] | int | None = None, quit: Literal["undefined"] | int | None = None, # noqa: A002 # pylint: disable=redefined-builtin start: Literal["undefined"] | int | None = None, stop: Literal["undefined"] | int | None = None, susp: Literal["undefined"] | int | None = None, fileno: int | None = None, ): """ Read and/or set the tty's signal character settings. This function returns the current settings as a tuple. Use the string 'undefined' to unmap keys from their signals. The value None is used when no change is being made. Setting signal keys is done using the integer ascii code for the key, eg. 3 for CTRL+C. If this function is called after start() has been called then the original settings will be restored when stop() is called. """ return () else: def tty_signal_keys( self, intr: Literal["undefined"] | int | None = None, quit: Literal["undefined"] | int | None = None, # noqa: A002 # pylint: disable=redefined-builtin start: Literal["undefined"] | int | None = None, stop: Literal["undefined"] | int | None = None, susp: Literal["undefined"] | int | None = None, fileno: int | None = None, ): """ Read and/or set the tty's signal character settings. This function returns the current settings as a tuple. Use the string 'undefined' to unmap keys from their signals. The value None is used when no change is being made. Setting signal keys is done using the integer ascii code for the key, eg. 3 for CTRL+C. If this function is called after start() has been called then the original settings will be restored when stop() is called. """ import termios if fileno is None: fileno = sys.stdin.fileno() if not os.isatty(fileno): return None tattr = termios.tcgetattr(fileno) sattr = tattr[6] skeys = ( sattr[termios.VINTR], sattr[termios.VQUIT], sattr[termios.VSTART], sattr[termios.VSTOP], sattr[termios.VSUSP], ) if intr == "undefined": intr = 0 if quit == "undefined": quit = 0 # noqa: A001 if start == "undefined": start = 0 if stop == "undefined": stop = 0 if susp == "undefined": susp = 0 if intr is not None: tattr[6][termios.VINTR] = intr if quit is not None: tattr[6][termios.VQUIT] = quit if start is not None: tattr[6][termios.VSTART] = start if stop is not None: tattr[6][termios.VSTOP] = stop if susp is not None: tattr[6][termios.VSUSP] = susp if any(item is not None for item in (intr, quit, start, stop, susp)): termios.tcsetattr(fileno, termios.TCSADRAIN, tattr) self._signal_keys_set = True return skeys class ScreenError(Exception): pass class BaseMeta(signals.MetaSignals, abc.ABCMeta): """Base metaclass for abstra""" class BaseScreen(metaclass=BaseMeta): """ Base class for Screen classes (raw_display.Screen, .. etc) """ signals: typing.ClassVar[list[str]] = [UPDATE_PALETTE_ENTRY, INPUT_DESCRIPTORS_CHANGED] def __init__(self) -> None: super().__init__() self.logger = logging.getLogger(f"{self.__class__.__module__}.{self.__class__.__name__}") self._palette: dict[str | None, tuple[AttrSpec, AttrSpec, AttrSpec, AttrSpec, AttrSpec]] = {} self._started: bool = False @property def started(self) -> bool: return self._started def start(self, *args, **kwargs) -> StoppingContext: """Set up the screen. If the screen has already been started, does nothing. May be used as a context manager, in which case :meth:`stop` will automatically be called at the end of the block: with screen.start(): ... You shouldn't override this method in a subclass; instead, override :meth:`_start`. """ if not self._started: self._started = True self._start(*args, **kwargs) return StoppingContext(self) def _start(self) -> None: pass def stop(self) -> None: if self._started: self._stop() self._started = False def _stop(self) -> None: pass def run_wrapper(self, fn, *args, **kwargs): """Start the screen, call a function, then stop the screen. Extra arguments are passed to `start`. Deprecated in favor of calling `start` as a context manager. """ warnings.warn( "run_wrapper is deprecated in favor of calling `start` as a context manager.", DeprecationWarning, stacklevel=3, ) with self.start(*args, **kwargs): return fn() def set_mouse_tracking(self, enable: bool = True) -> None: pass @abc.abstractmethod def draw_screen(self, size: tuple[int, int], canvas: Canvas) -> None: pass def clear(self) -> None: """Clear the screen if possible. Force the screen to be completely repainted on the next call to draw_screen(). """ def get_cols_rows(self) -> tuple[int, int]: """Return the terminal dimensions (num columns, num rows). Default (fallback) is 80x24. """ return 80, 24 def register_palette( self, palette: Iterable[ tuple[str, str] | tuple[str, str, str] | tuple[str, str, str, str] | tuple[str, str, str, str, str, str] ], ) -> None: """Register a set of palette entries. palette -- a list of (name, like_other_name) or (name, foreground, background, mono, foreground_high, background_high) tuples The (name, like_other_name) format will copy the settings from the palette entry like_other_name, which must appear before this tuple in the list. The mono and foreground/background_high values are optional ie. the second tuple format may have 3, 4 or 6 values. See register_palette_entry() for a description of the tuple values. """ for item in palette: if len(item) in {3, 4, 6}: self.register_palette_entry(*item) continue if len(item) != 2: raise ScreenError(f"Invalid register_palette entry: {item!r}") name, like_name = item if like_name not in self._palette: raise ScreenError(f"palette entry '{like_name}' doesn't exist") self._palette[name] = self._palette[like_name] def register_palette_entry( self, name: str | None, foreground: str, background: str, mono: str | None = None, foreground_high: str | None = None, background_high: str | None = None, ) -> None: """Register a single palette entry. name -- new entry/attribute name foreground -- a string containing a comma-separated foreground color and settings Color values: 'default' (use the terminal's default foreground), 'black', 'dark red', 'dark green', 'brown', 'dark blue', 'dark magenta', 'dark cyan', 'light gray', 'dark gray', 'light red', 'light green', 'yellow', 'light blue', 'light magenta', 'light cyan', 'white' Settings: 'bold', 'underline', 'blink', 'standout', 'strikethrough' Some terminals use 'bold' for bright colors. Most terminals ignore the 'blink' setting. If the color is not given then 'default' will be assumed. background -- a string containing the background color Background color values: 'default' (use the terminal's default background), 'black', 'dark red', 'dark green', 'brown', 'dark blue', 'dark magenta', 'dark cyan', 'light gray' mono -- a comma-separated string containing monochrome terminal settings (see "Settings" above.) None = no terminal settings (same as 'default') foreground_high -- a string containing a comma-separated foreground color and settings, standard foreground colors (see "Color values" above) or high-colors may be used High-color example values: '#009' (0% red, 0% green, 60% red, like HTML colors) '#fcc' (100% red, 80% green, 80% blue) 'g40' (40% gray, decimal), 'g#cc' (80% gray, hex), '#000', 'g0', 'g#00' (black), '#fff', 'g100', 'g#ff' (white) 'h8' (color number 8), 'h255' (color number 255) None = use foreground parameter value background_high -- a string containing the background color, standard background colors (see "Background colors" above) or high-colors (see "High-color example values" above) may be used None = use background parameter value """ basic = AttrSpec(foreground, background, 16) if isinstance(mono, tuple): # old style of specifying mono attributes was to put them # in a tuple. convert to comma-separated string mono = ",".join(mono) if mono is None: mono = DEFAULT mono_spec = AttrSpec(mono, DEFAULT, 1) if foreground_high is None: foreground_high = foreground if background_high is None: background_high = background high_256 = AttrSpec(foreground_high, background_high, 256) high_true = AttrSpec(foreground_high, background_high, 2**24) # 'hX' where X > 15 are different in 88/256 color, use # basic colors for 88-color mode if high colors are specified # in this way (also avoids crash when X > 87) def large_h(desc: str) -> bool: if not desc.startswith("h"): return False if "," in desc: desc = desc.split(",", 1)[0] num = int(desc[1:], 10) return num > 15 if large_h(foreground_high) or large_h(background_high): high_88 = basic else: high_88 = AttrSpec(foreground_high, background_high, 88) signals.emit_signal(self, UPDATE_PALETTE_ENTRY, name, basic, mono_spec, high_88, high_256, high_true) self._palette[name] = (basic, mono_spec, high_88, high_256, high_true) def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() urwid-2.6.16/urwid/display/curses.py000066400000000000000000000543641470350774000174400ustar00rootroot00000000000000# Urwid curses output wrapper.. the horror.. # Copyright (C) 2004-2011 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Curses-based UI implementation """ from __future__ import annotations import curses import sys import typing from contextlib import suppress from urwid import util from . import escape from .common import UNPRINTABLE_TRANS_TABLE, AttrSpec, BaseScreen, RealTerminal if typing.TYPE_CHECKING: from typing_extensions import Literal from urwid import Canvas IS_WINDOWS = sys.platform == "win32" # curses.KEY_RESIZE (sometimes not defined) if IS_WINDOWS: KEY_MOUSE = 539 # under Windows key mouse is different KEY_RESIZE = 546 COLOR_CORRECTION: dict[int, int] = dict( enumerate( ( curses.COLOR_BLACK, curses.COLOR_RED, curses.COLOR_GREEN, curses.COLOR_YELLOW, curses.COLOR_BLUE, curses.COLOR_MAGENTA, curses.COLOR_CYAN, curses.COLOR_WHITE, ) ) ) def initscr(): import curses # noqa: I001 # pylint: disable=redefined-outer-name,reimported # special case for monkeypatch import _curses stdscr = _curses.initscr() for key, value in _curses.__dict__.items(): if key[:4] == "ACS_" or key in {"LINES", "COLS"}: setattr(curses, key, value) return stdscr curses.initscr = initscr else: KEY_MOUSE = 409 # curses.KEY_MOUSE KEY_RESIZE = 410 COLOR_CORRECTION = {} _curses_colours = { # pylint: disable=consider-using-namedtuple-or-dataclass # historic test/debug data "default": (-1, 0), "black": (curses.COLOR_BLACK, 0), "dark red": (curses.COLOR_RED, 0), "dark green": (curses.COLOR_GREEN, 0), "brown": (curses.COLOR_YELLOW, 0), "dark blue": (curses.COLOR_BLUE, 0), "dark magenta": (curses.COLOR_MAGENTA, 0), "dark cyan": (curses.COLOR_CYAN, 0), "light gray": (curses.COLOR_WHITE, 0), "dark gray": (curses.COLOR_BLACK, 1), "light red": (curses.COLOR_RED, 1), "light green": (curses.COLOR_GREEN, 1), "yellow": (curses.COLOR_YELLOW, 1), "light blue": (curses.COLOR_BLUE, 1), "light magenta": (curses.COLOR_MAGENTA, 1), "light cyan": (curses.COLOR_CYAN, 1), "white": (curses.COLOR_WHITE, 1), } class Screen(BaseScreen, RealTerminal): def __init__(self) -> None: super().__init__() self.curses_pairs = [(None, None)] # Can't be sure what pair 0 will default to self.palette = {} self.has_color = False self.s = None self.cursor_state = None self.prev_input_resize = 0 self.set_input_timeouts() self.last_bstate = 0 self._mouse_tracking_enabled = False self.register_palette_entry(None, "default", "default") def set_mouse_tracking(self, enable: bool = True) -> None: """ Enable mouse tracking. After calling this function get_input will include mouse click events along with keystrokes. """ enable = bool(enable) # noqa: FURB123,RUF100 if enable == self._mouse_tracking_enabled: return if enable: curses.mousemask( 0 | curses.BUTTON1_PRESSED | curses.BUTTON1_RELEASED | curses.BUTTON2_PRESSED | curses.BUTTON2_RELEASED | curses.BUTTON3_PRESSED | curses.BUTTON3_RELEASED | curses.BUTTON4_PRESSED | curses.BUTTON4_RELEASED | curses.BUTTON1_DOUBLE_CLICKED | curses.BUTTON1_TRIPLE_CLICKED | curses.BUTTON2_DOUBLE_CLICKED | curses.BUTTON2_TRIPLE_CLICKED | curses.BUTTON3_DOUBLE_CLICKED | curses.BUTTON3_TRIPLE_CLICKED | curses.BUTTON4_DOUBLE_CLICKED | curses.BUTTON4_TRIPLE_CLICKED | curses.BUTTON_SHIFT | curses.BUTTON_ALT | curses.BUTTON_CTRL ) else: raise NotImplementedError() self._mouse_tracking_enabled = enable def _start(self) -> None: """ Initialize the screen and input mode. """ self.s = curses.initscr() self.has_color = curses.has_colors() if self.has_color: curses.start_color() if curses.COLORS < 8: # not colourful enough self.has_color = False if self.has_color: try: curses.use_default_colors() self.has_default_colors = True except curses.error: self.has_default_colors = False self._setup_colour_pairs() curses.noecho() curses.meta(True) curses.halfdelay(10) # use set_input_timeouts to adjust self.s.keypad(False) if not self._signal_keys_set: self._old_signal_keys = self.tty_signal_keys() super()._start() if IS_WINDOWS: # halfdelay() seems unnecessary and causes everything to slow down a lot. curses.nocbreak() # exits halfdelay mode # keypad(1) is needed, or we get no special keys (cursor keys, etc.) self.s.keypad(True) def _stop(self) -> None: """ Restore the screen. """ curses.echo() self._curs_set(1) with suppress(curses.error): curses.endwin() # don't block original error with curses error if self._old_signal_keys: self.tty_signal_keys(*self._old_signal_keys) super()._stop() def _setup_colour_pairs(self) -> None: """ Initialize all 63 color pairs based on the term: bg * 8 + 7 - fg So to get a color, we just need to use that term and get the right color pair number. """ if not self.has_color: return if IS_WINDOWS: self.has_default_colors = False for fg in range(8): for bg in range(8): # leave out white on black if fg == curses.COLOR_WHITE and bg == curses.COLOR_BLACK: continue curses.init_pair(bg * 8 + 7 - fg, COLOR_CORRECTION.get(fg, fg), COLOR_CORRECTION.get(bg, bg)) def _curs_set(self, x: int): if self.cursor_state in {"fixed", x}: return try: curses.curs_set(x) self.cursor_state = x except curses.error: self.cursor_state = "fixed" def _clear(self) -> None: self.s.clear() self.s.refresh() def _getch(self, wait_tenths: int | None) -> int: if wait_tenths == 0: return self._getch_nodelay() if not IS_WINDOWS: if wait_tenths is None: curses.cbreak() else: curses.halfdelay(wait_tenths) self.s.nodelay(False) return self.s.getch() def _getch_nodelay(self) -> int: self.s.nodelay(True) if not IS_WINDOWS: while True: # this call fails sometimes, but seems to work when I try again with suppress(curses.error): curses.cbreak() break return self.s.getch() def set_input_timeouts( self, max_wait: float | None = None, complete_wait: float = 0.1, resize_wait: float = 0.1, ): """ Set the get_input timeout values. All values have a granularity of 0.1s, ie. any value between 0.15 and 0.05 will be treated as 0.1 and any value less than 0.05 will be treated as 0. The maximum timeout value for this module is 25.5 seconds. max_wait -- amount of time in seconds to wait for input when there is no input pending, wait forever if None complete_wait -- amount of time in seconds to wait when get_input detects an incomplete escape sequence at the end of the available input resize_wait -- amount of time in seconds to wait for more input after receiving two screen resize requests in a row to stop urwid from consuming 100% cpu during a gradual window resize operation """ def convert_to_tenths(s): if s is None: return None return int((s + 0.05) * 10) self.max_tenths = convert_to_tenths(max_wait) self.complete_tenths = convert_to_tenths(complete_wait) self.resize_tenths = convert_to_tenths(resize_wait) @typing.overload def get_input(self, raw_keys: Literal[False]) -> list[str]: ... @typing.overload def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]: ... def get_input(self, raw_keys: bool = False) -> list[str] | tuple[list[str], list[int]]: """Return pending input as a list. raw_keys -- return raw keycodes as well as translated versions This function will immediately return all the input since the last time it was called. If there is no input pending it will wait before returning an empty list. The wait time may be configured with the set_input_timeouts function. If raw_keys is False (default) this function will return a list of keys pressed. If raw_keys is True this function will return a ( keys pressed, raw keycodes ) tuple instead. Examples of keys returned: * ASCII printable characters: " ", "a", "0", "A", "-", "/" * ASCII control characters: "tab", "enter" * Escape sequences: "up", "page up", "home", "insert", "f1" * Key combinations: "shift f1", "meta a", "ctrl b" * Window events: "window resize" When a narrow encoding is not enabled: * "Extended ASCII" characters: "\\xa1", "\\xb2", "\\xfe" When a wide encoding is enabled: * Double-byte characters: "\\xa1\\xea", "\\xb2\\xd4" When utf8 encoding is enabled: * Unicode characters: u"\\u00a5", u'\\u253c" Examples of mouse events returned: * Mouse button press: ('mouse press', 1, 15, 13), ('meta mouse press', 2, 17, 23) * Mouse button release: ('mouse release', 0, 18, 13), ('ctrl mouse release', 0, 17, 23) """ if not self._started: raise RuntimeError keys, raw = self._get_input(self.max_tenths) # Avoid pegging CPU at 100% when slowly resizing, and work # around a bug with some braindead curses implementations that # return "no key" between "window resize" commands if keys == ["window resize"] and self.prev_input_resize: for _ in range(2): new_keys, new_raw = self._get_input(self.resize_tenths) raw += new_raw if new_keys and new_keys != ["window resize"]: if "window resize" in new_keys: keys = new_keys else: keys.extend(new_keys) break if keys == ["window resize"]: self.prev_input_resize = 2 elif self.prev_input_resize == 2 and not keys: self.prev_input_resize = 1 else: self.prev_input_resize = 0 if raw_keys: return keys, raw return keys def _get_input(self, wait_tenths: int | None) -> tuple[list[str], list[int]]: # this works around a strange curses bug with window resizing # not being reported correctly with repeated calls to this # function without a doupdate call in between curses.doupdate() key = self._getch(wait_tenths) resize = False raw = [] keys = [] while key >= 0: raw.append(key) if key == KEY_RESIZE: resize = True elif key == KEY_MOUSE: keys += self._encode_mouse_event() else: keys.append(key) key = self._getch_nodelay() processed = [] try: while keys: run, keys = escape.process_keyqueue(keys, True) processed += run except escape.MoreInputRequired: key = self._getch(self.complete_tenths) while key >= 0: raw.append(key) if key == KEY_RESIZE: resize = True elif key == KEY_MOUSE: keys += self._encode_mouse_event() else: keys.append(key) key = self._getch_nodelay() while keys: run, keys = escape.process_keyqueue(keys, False) processed += run if resize: processed.append("window resize") return processed, raw def _encode_mouse_event(self) -> list[int]: # convert to escape sequence last_state = next_state = self.last_bstate (_id, x, y, _z, bstate) = curses.getmouse() mod = 0 if bstate & curses.BUTTON_SHIFT: mod |= 4 if bstate & curses.BUTTON_ALT: mod |= 8 if bstate & curses.BUTTON_CTRL: mod |= 16 result = [] def append_button(b: int) -> None: b |= mod result.extend([27, ord("["), ord("M"), b + 32, x + 33, y + 33]) if bstate & curses.BUTTON1_PRESSED and last_state & 1 == 0: append_button(0) next_state |= 1 if bstate & curses.BUTTON2_PRESSED and last_state & 2 == 0: append_button(1) next_state |= 2 if bstate & curses.BUTTON3_PRESSED and last_state & 4 == 0: append_button(2) next_state |= 4 if bstate & curses.BUTTON4_PRESSED and last_state & 8 == 0: append_button(64) next_state |= 8 if bstate & curses.BUTTON1_RELEASED and last_state & 1: append_button(0 + escape.MOUSE_RELEASE_FLAG) next_state &= ~1 if bstate & curses.BUTTON2_RELEASED and last_state & 2: append_button(1 + escape.MOUSE_RELEASE_FLAG) next_state &= ~2 if bstate & curses.BUTTON3_RELEASED and last_state & 4: append_button(2 + escape.MOUSE_RELEASE_FLAG) next_state &= ~4 if bstate & curses.BUTTON4_RELEASED and last_state & 8: append_button(64 + escape.MOUSE_RELEASE_FLAG) next_state &= ~8 if bstate & curses.BUTTON1_DOUBLE_CLICKED: append_button(0 + escape.MOUSE_MULTIPLE_CLICK_FLAG) if bstate & curses.BUTTON2_DOUBLE_CLICKED: append_button(1 + escape.MOUSE_MULTIPLE_CLICK_FLAG) if bstate & curses.BUTTON3_DOUBLE_CLICKED: append_button(2 + escape.MOUSE_MULTIPLE_CLICK_FLAG) if bstate & curses.BUTTON4_DOUBLE_CLICKED: append_button(64 + escape.MOUSE_MULTIPLE_CLICK_FLAG) if bstate & curses.BUTTON1_TRIPLE_CLICKED: append_button(0 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2) if bstate & curses.BUTTON2_TRIPLE_CLICKED: append_button(1 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2) if bstate & curses.BUTTON3_TRIPLE_CLICKED: append_button(2 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2) if bstate & curses.BUTTON4_TRIPLE_CLICKED: append_button(64 + escape.MOUSE_MULTIPLE_CLICK_FLAG * 2) self.last_bstate = next_state return result def _dbg_instr(self): # messy input string (intended for debugging) curses.echo() self.s.nodelay(0) curses.halfdelay(100) string = self.s.getstr() curses.noecho() return string def _dbg_out(self, string) -> None: # messy output function (intended for debugging) self.s.clrtoeol() self.s.addstr(string) self.s.refresh() self._curs_set(1) def _dbg_query(self, question): # messy query (intended for debugging) self._dbg_out(question) return self._dbg_instr() def _dbg_refresh(self) -> None: self.s.refresh() def get_cols_rows(self) -> tuple[int, int]: """Return the terminal dimensions (num columns, num rows).""" rows, cols = self.s.getmaxyx() return cols, rows def _setattr(self, a): if a is None: self.s.attrset(0) return if not isinstance(a, AttrSpec): p = self._palette.get(a, (AttrSpec("default", "default"),)) a = p[0] if self.has_color: if a.foreground_basic: if a.foreground_number >= 8: fg = a.foreground_number - 8 else: fg = a.foreground_number else: fg = 7 if a.background_basic: bg = a.background_number else: bg = 0 attr = curses.color_pair(bg * 8 + 7 - fg) else: attr = 0 if a.bold: attr |= curses.A_BOLD if a.standout: attr |= curses.A_STANDOUT if a.underline: attr |= curses.A_UNDERLINE if a.blink: attr |= curses.A_BLINK self.s.attrset(attr) def draw_screen(self, size: tuple[int, int], canvas: Canvas) -> None: """Paint screen with rendered canvas.""" logger = self.logger.getChild("draw_screen") if not self._started: raise RuntimeError _cols, rows = size if canvas.rows() != rows: raise ValueError("canvas size and passed size don't match") logger.debug(f"Drawing screen with size {size!r}") y = -1 for row in canvas.content(): y += 1 try: self.s.move(y, 0) except curses.error: # terminal shrunk? # move failed so stop rendering. return first = True lasta = None for nr, (a, cs, seg) in enumerate(row): if cs != "U": seg = seg.translate(UNPRINTABLE_TRANS_TABLE) # noqa: PLW2901 if not isinstance(seg, bytes): raise TypeError(seg) if first or lasta != a: self._setattr(a) lasta = a try: if cs in {"0", "U"}: for segment in seg: self.s.addch(0x400000 + segment) else: if cs is not None: raise ValueError(f"cs not in ('0', 'U' ,'None'): {cs!r}") if not isinstance(seg, bytes): raise TypeError(seg) self.s.addstr(seg.decode(util.get_encoding())) except curses.error: # it's ok to get out of the # screen on the lower right if y != rows - 1 or nr != len(row) - 1: # perhaps screen size changed # quietly abort. return if canvas.cursor is not None: x, y = canvas.cursor self._curs_set(1) with suppress(curses.error): self.s.move(y, x) else: self._curs_set(0) self.s.move(0, 0) self.s.refresh() self.keep_cache_alive_link = canvas def clear(self) -> None: """ Force the screen to be completely repainted on the next call to draw_screen(). """ self.s.clear() class _test: def __init__(self): self.ui = Screen() self.l = sorted(_curses_colours) for c in self.l: self.ui.register_palette( [ (f"{c} on black", c, "black", "underline"), (f"{c} on dark blue", c, "dark blue", "bold"), (f"{c} on light gray", c, "light gray", "standout"), ] ) with self.ui.start(): self.run() def run(self) -> None: class FakeRender: pass r = FakeRender() text = [f" has_color = {self.ui.has_color!r}", ""] attr = [[], []] r.coords = {} r.cursor = None for c in self.l: t = "" a = [] for p in f"{c} on black", f"{c} on dark blue", f"{c} on light gray": a.append((p, 27)) t += (p + 27 * " ")[:27] text.append(t) attr.append(a) text += ["", "return values from get_input(): (q exits)", ""] attr += [[], [], []] cols, rows = self.ui.get_cols_rows() keys = None while keys != ["q"]: r.text = ([t.ljust(cols) for t in text] + [""] * rows)[:rows] r.attr = (attr + [[] for _ in range(rows)])[:rows] self.ui.draw_screen((cols, rows), r) keys, raw = self.ui.get_input(raw_keys=True) if "window resize" in keys: cols, rows = self.ui.get_cols_rows() if not keys: continue t = "" a = [] for k in keys: if isinstance(k, str): k = k.encode(util.get_encoding()) # noqa: PLW2901 t += f"'{k}' " a += [(None, 1), ("yellow on dark blue", len(k)), (None, 2)] text.append(f"{t}: {raw!r}") attr.append(a) text = text[-rows:] attr = attr[-rows:] if __name__ == "__main__": _test() urwid-2.6.16/urwid/display/escape.py000066400000000000000000000430251470350774000173640ustar00rootroot00000000000000# Urwid escape sequences common to curses_display and raw_display # Copyright (C) 2004-2011 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Terminal Escape Sequences for input and display """ from __future__ import annotations import re import sys import typing from collections.abc import MutableMapping, Sequence from urwid import str_util if typing.TYPE_CHECKING: from collections.abc import Iterable # NOTE: because of circular imports (urwid.util -> urwid.escape -> urwid.util) # from urwid.util import is_mouse_event -- will not work here import urwid.util # isort: skip # pylint: disable=wrong-import-position IS_WINDOWS = sys.platform == "win32" within_double_byte = str_util.within_double_byte SO = "\x0e" SI = "\x0f" IBMPC_ON = "\x1b[11m" IBMPC_OFF = "\x1b[10m" DEC_TAG = "0" DEC_SPECIAL_CHARS = "▮◆▒â‰âŒââŠÂ°Â±â¤â‹â”˜â”┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·" ALT_DEC_SPECIAL_CHARS = "_`abcdefghijklmnopqrstuvwxyz{|}~" DEC_SPECIAL_CHARMAP = {} if len(DEC_SPECIAL_CHARS) != len(ALT_DEC_SPECIAL_CHARS): raise RuntimeError(repr((DEC_SPECIAL_CHARS, ALT_DEC_SPECIAL_CHARS))) for c, alt in zip(DEC_SPECIAL_CHARS, ALT_DEC_SPECIAL_CHARS): DEC_SPECIAL_CHARMAP[ord(c)] = SO + alt + SI SAFE_ASCII_DEC_SPECIAL_RE = re.compile(f"^[ -~{DEC_SPECIAL_CHARS}]*$") DEC_SPECIAL_RE = re.compile(f"[{DEC_SPECIAL_CHARS}]") ################### # Input sequences ################### class MoreInputRequired(Exception): pass def escape_modifier(digit: str) -> str: mode = ord(digit) - ord("1") return "shift " * (mode & 1) + "meta " * ((mode & 2) // 2) + "ctrl " * ((mode & 4) // 4) input_sequences = [ ("[A", "up"), ("[B", "down"), ("[C", "right"), ("[D", "left"), ("[E", "5"), ("[F", "end"), ("[G", "5"), ("[H", "home"), ("[I", "focus in"), ("[O", "focus out"), ("[1~", "home"), ("[2~", "insert"), ("[3~", "delete"), ("[4~", "end"), ("[5~", "page up"), ("[6~", "page down"), ("[7~", "home"), ("[8~", "end"), ("[[A", "f1"), ("[[B", "f2"), ("[[C", "f3"), ("[[D", "f4"), ("[[E", "f5"), ("[11~", "f1"), ("[12~", "f2"), ("[13~", "f3"), ("[14~", "f4"), ("[15~", "f5"), ("[17~", "f6"), ("[18~", "f7"), ("[19~", "f8"), ("[20~", "f9"), ("[21~", "f10"), ("[23~", "f11"), ("[24~", "f12"), ("[25~", "f13"), ("[26~", "f14"), ("[28~", "f15"), ("[29~", "f16"), ("[31~", "f17"), ("[32~", "f18"), ("[33~", "f19"), ("[34~", "f20"), ("OA", "up"), ("OB", "down"), ("OC", "right"), ("OD", "left"), ("OH", "home"), ("OF", "end"), ("OP", "f1"), ("OQ", "f2"), ("OR", "f3"), ("OS", "f4"), ("Oo", "/"), ("Oj", "*"), ("Om", "-"), ("Ok", "+"), ("[Z", "shift tab"), ("On", "."), ("[200~", "begin paste"), ("[201~", "end paste"), *( (prefix + letter, modifier + key) for prefix, modifier in zip("O[", ("meta ", "shift ")) for letter, key in zip("abcd", ("up", "down", "right", "left")) ), *( (f"[{digit}{symbol}", modifier + key) for modifier, symbol in zip(("shift ", "meta "), "$^") for digit, key in zip("235678", ("insert", "delete", "page up", "page down", "home", "end")) ), *((f"O{ord('p') + n:c}", str(n)) for n in range(10)), *( # modified cursor keys + home, end, 5 -- [#X and [1;#X forms (prefix + digit + letter, escape_modifier(digit) + key) for prefix in ("[", "[1;") for digit in "12345678" for letter, key in zip("ABCDEFGH", ("up", "down", "right", "left", "5", "end", "5", "home")) ), *( # modified F1-F4 keys - O#X form and [1;#X form (prefix + digit + letter, escape_modifier(digit) + f"f{number}") for prefix in ("O", "[1;") for digit in "12345678" for number, letter in enumerate("PQRS", start=1) ), *( # modified F1-F13 keys -- [XX;#~ form (f"[{num!s};{digit}~", escape_modifier(digit) + key) for digit in "12345678" for num, key in zip( (3, 5, 6, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 31, 32, 33, 34), ( "delete", "page up", "page down", *(f"f{idx}" for idx in range(1, 21)), ), ) ), # mouse reporting (special handling done in KeyqueueTrie) ("[M", "mouse"), # mouse reporting for SGR 1006 ("[<", "sgrmouse"), # report status response ("[0n", "status ok"), ] class KeyqueueTrie: __slots__ = ("data",) def __init__(self, sequences: Iterable[tuple[str, str]]) -> None: self.data: dict[int, str | dict[int, str | dict[int, str]]] = {} for s, result in sequences: if isinstance(result, dict): raise TypeError(result) self.add(self.data, s, result) def add( self, root: MutableMapping[int, str | MutableMapping[int, str | MutableMapping[int, str]]], s: str, result: str, ) -> None: if not isinstance(root, MutableMapping) or not s: raise RuntimeError("trie conflict detected") if ord(s[0]) in root: self.add(root[ord(s[0])], s[1:], result) return if len(s) > 1: d = {} root[ord(s[0])] = d self.add(d, s[1:], result) return root[ord(s)] = result def get(self, keys, more_available: bool): result = self.get_recurse(self.data, keys, more_available) if not result: result = self.read_cursor_position(keys, more_available) return result def get_recurse( self, root: ( MutableMapping[int, str | MutableMapping[int, str | MutableMapping[int, str]]] | typing.Literal["mouse", "sgrmouse"] ), keys: Sequence[int], more_available: bool, ): if not isinstance(root, MutableMapping): if root == "mouse": return self.read_mouse_info(keys, more_available) if root == "sgrmouse": return self.read_sgrmouse_info(keys, more_available) return (root, keys) if not keys: # get more keys if more_available: raise MoreInputRequired() return None if keys[0] not in root: return None return self.get_recurse(root[keys[0]], keys[1:], more_available) def read_mouse_info(self, keys: Sequence[int], more_available: bool): if len(keys) < 3: if more_available: raise MoreInputRequired() return None b = keys[0] - 32 x, y = (keys[1] - 33) % 256, (keys[2] - 33) % 256 # supports 0-255 prefixes = [] if b & 4: prefixes.append("shift ") if b & 8: prefixes.append("meta ") if b & 16: prefixes.append("ctrl ") if (b & MOUSE_MULTIPLE_CLICK_MASK) >> 9 == 1: prefixes.append("double ") if (b & MOUSE_MULTIPLE_CLICK_MASK) >> 9 == 2: prefixes.append("triple ") prefix = "".join(prefixes) # 0->1, 1->2, 2->3, 64->4, 65->5 button = ((b & 64) // 64 * 3) + (b & 3) + 1 if b & 3 == 3: action = "release" button = 0 elif b & MOUSE_RELEASE_FLAG: action = "release" elif b & MOUSE_DRAG_FLAG: action = "drag" elif b & MOUSE_MULTIPLE_CLICK_MASK: action = "click" else: action = "press" return ((f"{prefix}mouse {action}", button, x, y), keys[3:]) def read_sgrmouse_info(self, keys: Sequence[int], more_available: bool): # Helpful links: # https://stackoverflow.com/questions/5966903/how-to-get-mousemove-and-mouseclick-in-bash # http://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf if not keys: if more_available: raise MoreInputRequired() return None value = "" pos_m = 0 found_m = False for k in keys: value += chr(k) if k in {ord("M"), ord("m")}: found_m = True break pos_m += 1 if not found_m: if more_available: raise MoreInputRequired() return None (b, x, y) = (int(val) for val in value[:-1].split(";")) action = value[-1] # Double and triple clicks are not supported. # They can be implemented by using a timer. # This timer can check if the last registered click is below a certain threshold. # This threshold is normally set in the operating system itself, # so setting one here will cause an inconsistent behaviour. prefixes = [] if b & 4: prefixes.append("shift ") if b & 8: prefixes.append("meta ") if b & 16: prefixes.append("ctrl ") prefix = "".join(prefixes) wheel_used: typing.Literal[0, 1] = (b & 64) >> 6 button = (wheel_used * 3) + (b & 3) + 1 x -= 1 y -= 1 if action == "M": if b & MOUSE_DRAG_FLAG: action = "drag" else: action = "press" elif action == "m": action = "release" else: raise ValueError(f"Unknown mouse action: {action!r}") return ((f"{prefix}mouse {action}", button, x, y), keys[pos_m + 1 :]) def read_cursor_position(self, keys, more_available: bool): """ Interpret cursor position information being sent by the user's terminal. Returned as ('cursor position', x, y) where (x, y) == (0, 0) is the top left of the screen. """ if not keys: if more_available: raise MoreInputRequired() return None if keys[0] != ord("["): return None # read y value y = 0 i = 1 for k in keys[i:]: i += 1 if k == ord(";"): if not y: return None break if k < ord("0") or k > ord("9"): return None if not y and k == ord("0"): return None y = y * 10 + k - ord("0") if not keys[i:]: if more_available: raise MoreInputRequired() return None # read x value x = 0 for k in keys[i:]: i += 1 if k == ord("R"): if not x: return None return (("cursor position", x - 1, y - 1), keys[i:]) if k < ord("0") or k > ord("9"): return None if not x and k == ord("0"): return None x = x * 10 + k - ord("0") if not keys[i:] and more_available: raise MoreInputRequired() return None # This is added to button value to signal mouse release by curses_display # and raw_display when we know which button was released. NON-STANDARD MOUSE_RELEASE_FLAG = 2048 # This 2-bit mask is used to check if the mouse release from curses or gpm # is a double or triple release. 00 means single click, 01 double, # 10 triple. NON-STANDARD MOUSE_MULTIPLE_CLICK_MASK = 1536 # This is added to button value at mouse release to differentiate between # single, double and triple press. Double release adds this times one, # triple release adds this times two. NON-STANDARD MOUSE_MULTIPLE_CLICK_FLAG = 512 # xterm adds this to the button value to signal a mouse drag event MOUSE_DRAG_FLAG = 32 ################################################# # Build the input trie from input_sequences list input_trie = KeyqueueTrie(input_sequences) ################################################# _keyconv = { -1: None, 8: "backspace", 9: "tab", 10: "enter", 13: "enter", 127: "backspace", # curses-only keycodes follow.. (XXX: are these used anymore?) 258: "down", 259: "up", 260: "left", 261: "right", 262: "home", 263: "backspace", 265: "f1", 266: "f2", 267: "f3", 268: "f4", 269: "f5", 270: "f6", 271: "f7", 272: "f8", 273: "f9", 274: "f10", 275: "f11", 276: "f12", 277: "shift f1", 278: "shift f2", 279: "shift f3", 280: "shift f4", 281: "shift f5", 282: "shift f6", 283: "shift f7", 284: "shift f8", 285: "shift f9", 286: "shift f10", 287: "shift f11", 288: "shift f12", 330: "delete", 331: "insert", 338: "page down", 339: "page up", 343: "enter", # on numpad 350: "5", # on numpad 360: "end", } if IS_WINDOWS: _keyconv[351] = "shift tab" _keyconv[358] = "end" def process_keyqueue(codes: Sequence[int], more_available: bool) -> tuple[list[str], Sequence[int]]: """ codes -- list of key codes more_available -- if True then raise MoreInputRequired when in the middle of a character sequence (escape/utf8/wide) and caller will attempt to send more key codes on the next call. returns (list of input, list of remaining key codes). """ code = codes[0] if 32 <= code <= 126: key = chr(code) return [key], codes[1:] if code in _keyconv: return [_keyconv[code]], codes[1:] if 0 < code < 27: return [f"ctrl {ord('a') + code - 1:c}"], codes[1:] if 27 < code < 32: return [f"ctrl {ord('A') + code - 1:c}"], codes[1:] em = str_util.get_byte_encoding() if ( em == "wide" and code < 256 and within_double_byte( code.to_bytes(1, "little"), 0, 0, ) ): if not codes[1:] and more_available: raise MoreInputRequired() if codes[1:] and codes[1] < 256: db = chr(code) + chr(codes[1]) if within_double_byte(db, 0, 1): return [db], codes[2:] if em == "utf8" and 127 < code < 256: if code & 0xE0 == 0xC0: # 2-byte form need_more = 1 elif code & 0xF0 == 0xE0: # 3-byte form need_more = 2 elif code & 0xF8 == 0xF0: # 4-byte form need_more = 3 else: return [f"<{code:d}>"], codes[1:] for i in range(1, need_more + 1): if len(codes) <= i: if more_available: raise MoreInputRequired() return [f"<{code:d}>"], codes[1:] k = codes[i] if k > 256 or k & 0xC0 != 0x80: return [f"<{code:d}>"], codes[1:] s = bytes(codes[: need_more + 1]) try: return [s.decode("utf-8")], codes[need_more + 1 :] except UnicodeDecodeError: return [f"<{code:d}>"], codes[1:] if 127 < code < 256: key = chr(code) return [key], codes[1:] if code != 27: return [f"<{code:d}>"], codes[1:] result = input_trie.get(codes[1:], more_available) if result is not None: result, remaining_codes = result return [result], remaining_codes if codes[1:]: # Meta keys -- ESC+Key form run, remaining_codes = process_keyqueue(codes[1:], more_available) if urwid.util.is_mouse_event(run[0]): return ["esc", *run], remaining_codes if run[0] == "esc" or run[0].find("meta ") >= 0: return ["esc", *run], remaining_codes return [f"meta {run[0]}"] + run[1:], remaining_codes return ["esc"], codes[1:] #################### # Output sequences #################### ESC = "\x1b" CURSOR_HOME = f"{ESC}[H" CURSOR_HOME_COL = "\r" APP_KEYPAD_MODE = f"{ESC}=" NUM_KEYPAD_MODE = f"{ESC}>" SWITCH_TO_ALTERNATE_BUFFER = f"{ESC}[?1049h" RESTORE_NORMAL_BUFFER = f"{ESC}[?1049l" ENABLE_BRACKETED_PASTE_MODE = f"{ESC}[?2004h" DISABLE_BRACKETED_PASTE_MODE = f"{ESC}[?2004l" ENABLE_FOCUS_REPORTING = f"{ESC}[?1004h" DISABLE_FOCUS_REPORTING = f"{ESC}[?1004l" # RESET_SCROLL_REGION = ESC+"[;r" # RESET = ESC+"c" REPORT_STATUS = f"{ESC}[5n" REPORT_CURSOR_POSITION = f"{ESC}[6n" INSERT_ON = f"{ESC}[4h" INSERT_OFF = f"{ESC}[4l" def set_cursor_position(x: int, y: int) -> str: if not isinstance(x, int): raise TypeError(x) if not isinstance(y, int): raise TypeError(y) return ESC + f"[{y + 1:d};{x + 1:d}H" def move_cursor_right(x: int) -> str: if x < 1: return "" return ESC + f"[{x:d}C" def move_cursor_up(x: int) -> str: if x < 1: return "" return ESC + f"[{x:d}A" def move_cursor_down(x: int) -> str: if x < 1: return "" return ESC + f"[{x:d}B" HIDE_CURSOR = f"{ESC}[?25l" SHOW_CURSOR = f"{ESC}[?25h" MOUSE_TRACKING_ON = f"{ESC}[?1000h{ESC}[?1002h{ESC}[?1006h" MOUSE_TRACKING_OFF = f"{ESC}[?1006l{ESC}[?1002l{ESC}[?1000l" DESIGNATE_G1_SPECIAL = f"{ESC})0" ERASE_IN_LINE_RIGHT = f"{ESC}[K" urwid-2.6.16/urwid/display/html_fragment.py000066400000000000000000000205431470350774000207530ustar00rootroot00000000000000# Urwid html fragment output wrapper for "screen shots" # Copyright (C) 2004-2007 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ HTML PRE-based UI implementation """ from __future__ import annotations import html import typing from urwid import str_util from urwid.event_loop import ExitMainLoop from urwid.util import get_encoding from .common import AttrSpec, BaseScreen if typing.TYPE_CHECKING: from typing_extensions import Literal from urwid import Canvas # replace control characters with ?'s _trans_table = "?" * 32 + "".join(chr(x) for x in range(32, 256)) _default_foreground = "black" _default_background = "light gray" class HtmlGeneratorSimulationError(Exception): pass class HtmlGenerator(BaseScreen): # class variables fragments: typing.ClassVar[list[str]] = [] sizes: typing.ClassVar[list[tuple[int, int]]] = [] keys: typing.ClassVar[list[list[str]]] = [] started = True def __init__(self) -> None: super().__init__() self.colors = 16 self.bright_is_bold = False # ignored self.has_underline = True # ignored self.register_palette_entry(None, _default_foreground, _default_background) def set_terminal_properties( self, colors: int | None = None, bright_is_bold: bool | None = None, has_underline: bool | None = None, ) -> None: if colors is None: colors = self.colors if bright_is_bold is None: bright_is_bold = self.bright_is_bold if has_underline is None: has_underline = self.has_underline self.colors = colors self.bright_is_bold = bright_is_bold self.has_underline = has_underline def set_input_timeouts(self, *args: typing.Any) -> None: pass def reset_default_terminal_palette(self, *args: typing.Any) -> None: pass def draw_screen(self, size: tuple[int, int], canvas: Canvas) -> None: """Create an html fragment from the render object. Append it to HtmlGenerator.fragments list. """ # collect output in l lines = [] _cols, rows = size if canvas.rows() != rows: raise ValueError(rows) if canvas.cursor is not None: cx, cy = canvas.cursor else: cx = cy = None for y, row in enumerate(canvas.content()): col = 0 for a, _cs, run in row: t_run = run.decode(get_encoding()).translate(_trans_table) if isinstance(a, AttrSpec): aspec = a else: aspec = self._palette[a][{1: 1, 16: 0, 88: 2, 256: 3}[self.colors]] if y == cy and col <= cx: run_width = str_util.calc_width(t_run, 0, len(t_run)) if col + run_width > cx: lines.append(html_span(t_run, aspec, cx - col)) else: lines.append(html_span(t_run, aspec)) col += run_width else: lines.append(html_span(t_run, aspec)) lines.append("\n") # add the fragment to the list self.fragments.append(f"
{''.join(lines)}
") def get_cols_rows(self): """Return the next screen size in HtmlGenerator.sizes.""" if not self.sizes: raise HtmlGeneratorSimulationError("Ran out of screen sizes to return!") return self.sizes.pop(0) @typing.overload def get_input(self, raw_keys: Literal[False]) -> list[str]: ... @typing.overload def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]: ... def get_input(self, raw_keys: bool = False) -> list[str] | tuple[list[str], list[int]]: """Return the next list of keypresses in HtmlGenerator.keys.""" if not self.keys: raise ExitMainLoop() if raw_keys: return (self.keys.pop(0), []) return self.keys.pop(0) _default_aspec = AttrSpec(_default_foreground, _default_background) (_d_fg_r, _d_fg_g, _d_fg_b, _d_bg_r, _d_bg_g, _d_bg_b) = _default_aspec.get_rgb_values() def html_span(s: str, aspec: AttrSpec, cursor: int = -1) -> str: fg_r, fg_g, fg_b, bg_r, bg_g, bg_b = aspec.get_rgb_values() # use real colours instead of default fg/bg if fg_r is None: fg_r, fg_g, fg_b = _d_fg_r, _d_fg_g, _d_fg_b if bg_r is None: bg_r, bg_g, bg_b = _d_bg_r, _d_bg_g, _d_bg_b html_fg = f"#{fg_r:02x}{fg_g:02x}{fg_b:02x}" html_bg = f"#{bg_r:02x}{bg_g:02x}{bg_b:02x}" if aspec.standout: html_fg, html_bg = html_bg, html_fg extra = ";text-decoration:underline" * aspec.underline + ";font-weight:bold" * aspec.bold def _span(fg: str, bg: str, string: str) -> str: if not s: return "" return f'{html.escape(string)}' if cursor >= 0: c_off, _ign = str_util.calc_text_pos(s, 0, len(s), cursor) c2_off = str_util.move_next_char(s, c_off, len(s)) return ( _span(html_fg, html_bg, s[:c_off]) + _span(html_bg, html_fg, s[c_off:c2_off]) + _span(html_fg, html_bg, s[c2_off:]) ) return _span(html_fg, html_bg, s) def screenshot_init(sizes: list[tuple[int, int]], keys: list[list[str]]) -> None: """ Replace curses_display.Screen and raw_display.Screen class with HtmlGenerator. Call this function before executing an application that uses curses_display.Screen to have that code use HtmlGenerator instead. sizes -- list of ( columns, rows ) tuples to be returned by each call to HtmlGenerator.get_cols_rows() keys -- list of lists of keys to be returned by each call to HtmlGenerator.get_input() Lists of keys may include "window resize" to force the application to call get_cols_rows and read a new screen size. For example, the following call will prepare an application to: 1. start in 80x25 with its first call to get_cols_rows() 2. take a screenshot when it calls draw_screen(..) 3. simulate 5 "down" keys from get_input() 4. take a screenshot when it calls draw_screen(..) 5. simulate keys "a", "b", "c" and a "window resize" 6. resize to 20x10 on its second call to get_cols_rows() 7. take a screenshot when it calls draw_screen(..) 8. simulate a "Q" keypress to quit the application screenshot_init( [ (80,25), (20,10) ], [ ["down"]*5, ["a","b","c","window resize"], ["Q"] ] ) """ for row, col in sizes: if not isinstance(row, int): raise TypeError(f"sizes must be list[tuple[int, int]], with values >0 : {row!r}") if row <= 0: raise ValueError(f"sizes must be list[tuple[int, int]], with values >0 : {row!r}") if not isinstance(col, int): raise TypeError(f"sizes must be list[tuple[int, int]], with values >0 : {col!r}") if col <= 0: raise ValueError(f"sizes must be list[tuple[int, int]], with values >0 : {col!r}") for line in keys: if not isinstance(line, list): raise TypeError(f"keys must be list[list[str]]: {line!r}") for k in line: if not isinstance(k, str): raise TypeError(f"keys must be list[list[str]]: {k!r}") from . import curses, raw curses.Screen = HtmlGenerator raw.Screen = HtmlGenerator HtmlGenerator.sizes = sizes HtmlGenerator.keys = keys def screenshot_collect() -> list[str]: """Return screenshots as a list of HTML fragments.""" fragments, HtmlGenerator.fragments = HtmlGenerator.fragments, [] return fragments urwid-2.6.16/urwid/display/lcd.py000066400000000000000000000423071470350774000166700ustar00rootroot00000000000000# Urwid LCD display module # Copyright (C) 2010 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import abc import time import typing from .common import BaseScreen if typing.TYPE_CHECKING: from collections.abc import Iterable, Sequence from typing_extensions import Literal from urwid import Canvas class LCDScreen(BaseScreen, abc.ABC): """Base class for LCD-based screens.""" DISPLAY_SIZE: tuple[int, int] def set_terminal_properties( self, colors: Literal[1, 16, 88, 256, 16777216] | None = None, bright_is_bold: bool | None = None, has_underline: bool | None = None, ) -> None: pass def set_input_timeouts(self, *args): pass def reset_default_terminal_palette(self, *args): pass def get_cols_rows(self): return self.DISPLAY_SIZE class CFLCDScreen(LCDScreen, abc.ABC): """ Common methods for Crystal Fonts LCD displays """ KEYS: typing.ClassVar[list[str | None]] = [ None, # no key with code 0 "up_press", "down_press", "left_press", "right_press", "enter_press", "exit_press", "up_release", "down_release", "left_release", "right_release", "enter_release", "exit_release", "ul_press", "ur_press", "ll_press", "lr_press", "ul_release", "ur_release", "ll_release", "lr_release", ] CMD_PING = 0 CMD_VERSION = 1 CMD_CLEAR = 6 CMD_CGRAM = 9 CMD_CURSOR_POSITION = 11 # data = [col, row] CMD_CURSOR_STYLE = 12 # data = [style (0-4)] CMD_LCD_CONTRAST = 13 # data = [contrast (0-255)] CMD_BACKLIGHT = 14 # data = [power (0-100)] CMD_LCD_DATA = 31 # data = [col, row] + text CMD_GPO = 34 # data = [pin(0-12), value(0-100)] # sent from device CMD_KEY_ACTIVITY = 0x80 CMD_ACK = 0x40 # in high two bits ie. & 0xc0 CURSOR_NONE = 0 CURSOR_BLINKING_BLOCK = 1 CURSOR_UNDERSCORE = 2 CURSOR_BLINKING_BLOCK_UNDERSCORE = 3 CURSOR_INVERTING_BLINKING_BLOCK = 4 MAX_PACKET_DATA_LENGTH = 22 colors = 1 has_underline = False def __init__(self, device_path: str, baud: int) -> None: """ device_path -- eg. '/dev/ttyUSB0' baud -- baud rate """ super().__init__() self.device_path = device_path from serial import Serial self._device = Serial(device_path, baud, timeout=0) self._unprocessed = bytearray() @classmethod def get_crc(cls, buf: Iterable[int]) -> bytes: # This seed makes the output of this shift based algorithm match # the table based algorithm. The center 16 bits of the 32-bit # "newCRC" are used for the CRC. The MSB of the lower byte is used # to see what bit was shifted out of the center 16 bit CRC # accumulator ("carry flag analog"); new_crc = 0x00F32100 for byte in buf: # Push this byte's bits through a software # implementation of a hardware shift & xor. for bit_count in range(8): # Shift the CRC accumulator new_crc >>= 1 # The new MSB of the CRC accumulator comes # from the LSB of the current data byte. if byte & (0x01 << bit_count): new_crc |= 0x00800000 # If the low bit of the current CRC accumulator was set # before the shift, then we need to XOR the accumulator # with the polynomial (center 16 bits of 0x00840800) if new_crc & 0x00000080: new_crc ^= 0x00840800 # All the data has been done. Do 16 more bits of 0 data. for _bit_count in range(16): # Shift the CRC accumulator new_crc >>= 1 # If the low bit of the current CRC accumulator was set # before the shift we need to XOR the accumulator with # 0x00840800. if new_crc & 0x00000080: new_crc ^= 0x00840800 # Return the center 16 bits, making this CRC match the one's # complement that is sent in the packet. return (((~new_crc) >> 8) & 0xFFFF).to_bytes(2, "little") def _send_packet(self, command: int, data: bytes) -> None: """ low-level packet sending. Following the protocol requires waiting for ack packet between sending each packet to the device. """ buf = bytearray([command, len(data)]) buf.extend(data) buf.extend(self.get_crc(buf)) self._device.write(buf) def _read_packet(self) -> tuple[int, bytearray] | None: """ low-level packet reading. returns (command/report code, data) or None This method stored data read and tries to resync when bad data is received. """ # pull in any new data available self._unprocessed += self._device.read() while True: try: command, data, unprocessed = self._parse_data(self._unprocessed) self._unprocessed = unprocessed except self.MoreDataRequired: # noqa: PERF203 return None except self.InvalidPacket: # throw out a byte and try to parse again self._unprocessed = self._unprocessed[1:] else: return command, data class InvalidPacket(Exception): pass class MoreDataRequired(Exception): pass @classmethod def _parse_data(cls, data: bytearray) -> tuple[int, bytearray, bytearray]: """ Try to read a packet from the start of data, returning (command/report code, packet_data, remaining_data) or raising InvalidPacket or MoreDataRequired """ if len(data) < 2: raise cls.MoreDataRequired command: int = data[0] packet_len: int = data[1] if packet_len > cls.MAX_PACKET_DATA_LENGTH: raise cls.InvalidPacket("length value too large") if len(data) < packet_len + 4: raise cls.MoreDataRequired data_end = 2 + packet_len crc = cls.get_crc(data[:data_end]) pcrc = data[data_end : data_end + 2] if crc != pcrc: raise cls.InvalidPacket("CRC doesn't match") return command, data[2:data_end], data[data_end + 2 :] class KeyRepeatSimulator: """ Provide simulated repeat key events when given press and release events. If two or more keys are pressed disable repeating until all keys are released. """ def __init__(self, repeat_delay: float, repeat_next: float) -> None: """ repeat_delay -- seconds to wait before starting to repeat keys repeat_next -- time between each repeated key """ self.repeat_delay = repeat_delay self.repeat_next = repeat_next self.pressed: dict[str, float] = {} self.multiple_pressed = False def press(self, key: str) -> None: if self.pressed: self.multiple_pressed = True self.pressed[key] = time.time() def release(self, key: str) -> None: if key not in self.pressed: return # ignore extra release events del self.pressed[key] if not self.pressed: self.multiple_pressed = False def next_event(self) -> tuple[float, str] | None: """ Return (remaining, key) where remaining is the number of seconds (float) until the key repeat event should be sent, or None if no events are pending. """ if len(self.pressed) != 1 or self.multiple_pressed: return None for key, val in self.pressed.items(): return max(0.0, val + self.repeat_delay - time.time()), key return None def sent_event(self) -> None: """ Cakk this method when you have sent a key repeat event so the timer will be reset for the next event """ if len(self.pressed) != 1: return # ignore event that shouldn't have been sent for key in self.pressed: self.pressed[key] = time.time() - self.repeat_delay + self.repeat_next return class CF635Screen(CFLCDScreen): """ Crystal Fontz 635 display 20x4 character display + cursor no foreground/background colors or settings supported see CGROM for list of close unicode matches to characters available 6 button input up, down, left, right, enter (check mark), exit (cross) """ DISPLAY_SIZE = (20, 4) # â‘  through â‘§ are programmable CGRAM (chars 0-7, repeated at 8-15) # double arrows (⇑⇓) appear as double arrowheads (chars 18, 19) # â‘´ resembles a bell # ⑵ resembles a filled-in "Y" # â‘¶ is the letters "Pt" together # partial blocks (▇▆▄▃â–) are actually shorter versions of (▉▋▌â–â–) # both groups are intended to draw horizontal bars with pixel # precision, use â–‡*[▆▄▃â–]? for a thin bar or â–‰*[â–‹â–Œâ–â–]? for a thick bar CGROM = ( "①②③④⑤⑥⑦⑧①②③④⑤⑥⑦⑧" "►◄⇑⇓«»↖↗↙↘▲▼↲^ˇ█" " !\"#¤%&'()*+,-./" "0123456789:;<=>?" "¡ABCDEFGHIJKLMNO" "PQRSTUVWXYZÄÖÑܧ" "¿abcdefghijklmno" "pqrstuvwxyzäöñüà" "â°Â¹Â²Â³â´âµâ¶â·â¸â¹Â½Â¼Â±â‰¥â‰¤Î¼" "♪♫⑴♥♦⑵⌜⌟“â€()αɛδ∞" "@£$¥èéùìòÇᴾØøʳÅå" "⌂¢ΦτλΩπΨΣθΞ♈ÆæßÉ" "ΓΛΠϒ_ÈÊêçğŞşİι~â—Š" "â–‡â–†â–„â–ƒâ–Æ’▉▋▌â–â–⑶◽▪↑→" "↓â†ÃÃÓÚÃáíóúýÔôŮů" r"ČĔŘŠŽÄĕřšž[\]{|}" ) cursor_style = CFLCDScreen.CURSOR_INVERTING_BLINKING_BLOCK def __init__( self, device_path: str, baud: int = 115200, repeat_delay: float = 0.5, repeat_next: float = 0.125, key_map: Iterable[str] = ("up", "down", "left", "right", "enter", "esc"), ): """ device_path -- eg. '/dev/ttyUSB0' baud -- baud rate repeat_delay -- seconds to wait before starting to repeat keys repeat_next -- time between each repeated key key_map -- the keys to send for this device's buttons """ super().__init__(device_path, baud) self.repeat_delay = repeat_delay self.repeat_next = repeat_next self.key_repeat = KeyRepeatSimulator(repeat_delay, repeat_next) self.key_map = tuple(key_map) self._last_command = None self._last_command_time = 0 self._command_queue: list[tuple[int, bytearray]] = [] self._screen_buf = None self._previous_canvas = None self._update_cursor = False def get_input_descriptors(self) -> list[int]: """ return the fd from our serial device so we get called on input and responses """ return [self._device.fd] def get_input_nonblocking(self) -> tuple[None, list[str], list[int]]: """ Return a (next_input_timeout, keys_pressed, raw_keycodes) tuple. The protocol for our device requires waiting for acks between each command, so this method responds to those as well as key press and release events. Key repeat events are simulated here as the device doesn't send any for us. raw_keycodes are the bytes of messages we received, which might not seem to have any correspondence to keys_pressed. """ data_input: list[str] = [] raw_data_input: list[int] = [] timeout = None packet = self._read_packet() while packet: command, data = packet if command == self.CMD_KEY_ACTIVITY and data: d0 = data[0] if 1 <= d0 <= 12: release = d0 > 6 keycode = d0 - (release * 6) - 1 key = self.key_map[keycode] if release: self.key_repeat.release(key) else: data_input.append(key) self.key_repeat.press(key) raw_data_input.append(d0) elif command & 0xC0 == 0x40 and command & 0x3F == self._last_command: # "ACK" self._send_next_command() packet = self._read_packet() next_repeat = self.key_repeat.next_event() if next_repeat: timeout, key = next_repeat if not timeout: data_input.append(key) self.key_repeat.sent_event() timeout = None return timeout, data_input, raw_data_input def _send_next_command(self) -> None: """ send out the next command in the queue """ if not self._command_queue: self._last_command = None return command, data = self._command_queue.pop(0) self._send_packet(command, data) self._last_command = command # record command for ACK self._last_command_time = time.time() def queue_command(self, command: int, data: bytearray) -> None: self._command_queue.append((command, data)) # not waiting? send away! if self._last_command is None: self._send_next_command() def draw_screen(self, size: tuple[int, int], canvas: Canvas) -> None: if size != self.DISPLAY_SIZE: raise ValueError(size) if self._screen_buf: osb = self._screen_buf else: osb = [] sb = [] for y, row in enumerate(canvas.content()): text = [run for _a, _cs, run in row] if not osb or osb[y] != text: data = bytearray([0, y]) for elem in text: data.extend(elem) self.queue_command(self.CMD_LCD_DATA, data) sb.append(text) if ( self._previous_canvas and self._previous_canvas.cursor == canvas.cursor and (not self._update_cursor or not canvas.cursor) ): pass elif canvas.cursor is None: self.queue_command(self.CMD_CURSOR_STYLE, bytearray([self.CURSOR_NONE])) else: x, y = canvas.cursor self.queue_command(self.CMD_CURSOR_POSITION, bytearray([x, y])) self.queue_command(self.CMD_CURSOR_STYLE, bytearray([self.cursor_style])) self._update_cursor = False self._screen_buf = sb self._previous_canvas = canvas def program_cgram(self, index: int, data: Sequence[int]) -> None: """ Program character data. Characters available as chr(0) through chr(7), and repeated as chr(8) through chr(15). index -- 0 to 7 index of character to program data -- list of 8, 6-bit integer values top to bottom with MSB on the left side of the character. """ if not 0 <= index <= 7: raise ValueError(index) if len(data) != 8: raise ValueError(data) self.queue_command(self.CMD_CGRAM, bytearray([index]) + bytearray(data)) def set_cursor_style(self, style: Literal[1, 2, 3, 4]) -> None: """ style -- CURSOR_BLINKING_BLOCK, CURSOR_UNDERSCORE, CURSOR_BLINKING_BLOCK_UNDERSCORE or CURSOR_INVERTING_BLINKING_BLOCK """ if not 1 <= style <= 4: raise ValueError(style) self.cursor_style = style self._update_cursor = True def set_backlight(self, value: int) -> None: """ Set backlight brightness value -- 0 to 100 """ if not 0 <= value <= 100: raise ValueError(value) self.queue_command(self.CMD_BACKLIGHT, bytearray([value])) def set_lcd_contrast(self, value: int) -> None: """ value -- 0 to 255 """ if not 0 <= value <= 255: raise ValueError(value) self.queue_command(self.CMD_LCD_CONTRAST, bytearray([value])) def set_led_pin(self, led: Literal[0, 1, 2, 3], rg: Literal[0, 1], value: int) -> None: """ led -- 0 to 3 rg -- 0 for red, 1 for green value -- 0 to 100 """ if not 0 <= led <= 3: raise ValueError(led) if rg not in {0, 1}: raise ValueError(rg) if not 0 <= value <= 100: raise ValueError(value) self.queue_command(self.CMD_GPO, bytearray([12 - 2 * led - rg, value])) urwid-2.6.16/urwid/display/raw.py000066400000000000000000000021451470350774000167130ustar00rootroot00000000000000# Urwid raw display module # Copyright (C) 2004-2009 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Direct terminal UI implementation """ from __future__ import annotations import sys __all__ = ("Screen",) IS_WINDOWS = sys.platform == "win32" if IS_WINDOWS: from ._win32_raw_display import Screen else: from ._posix_raw_display import Screen urwid-2.6.16/urwid/display/web.py000066400000000000000000000454621470350774000167100ustar00rootroot00000000000000# Urwid web (CGI/Asynchronous Javascript) display module # Copyright (C) 2004-2007 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ """ Urwid web application display module """ from __future__ import annotations import dataclasses import glob import html import os import pathlib import random import selectors import signal import socket import string import sys import tempfile import typing from contextlib import suppress from urwid.str_util import calc_text_pos, calc_width, move_next_char from urwid.util import StoppingContext, get_encoding from .common import BaseScreen if typing.TYPE_CHECKING: from typing_extensions import Literal from urwid import Canvas TEMP_DIR = tempfile.gettempdir() CURRENT_DIR = pathlib.Path(__file__).parent _js_code = CURRENT_DIR.joinpath("_web.js").read_text("utf-8") ALARM_DELAY = 60 POLL_CONNECT = 3 MAX_COLS = 200 MAX_ROWS = 100 MAX_READ = 4096 BUF_SZ = 16384 _code_colours = { "black": "0", "dark red": "1", "dark green": "2", "brown": "3", "dark blue": "4", "dark magenta": "5", "dark cyan": "6", "light gray": "7", "dark gray": "8", "light red": "9", "light green": "A", "yellow": "B", "light blue": "C", "light magenta": "D", "light cyan": "E", "white": "F", } # replace control characters with ?'s _trans_table = "?" * 32 + "".join([chr(x) for x in range(32, 256)]) _css_style = CURRENT_DIR.joinpath("_web.css").read_text("utf-8") # HTML Initial Page _html_page = [ """ Urwid Web Display - """, """

The quick brown fox jumps over the lazy dog.X
Y
Urwid Web Display - """, """ - Status: Set up



""",
]


class Screen(BaseScreen):
    def __init__(self) -> None:
        super().__init__()
        self.palette = {}
        self.has_color = True
        self._started = False

    @property
    def started(self) -> bool:
        return self._started

    def register_palette(self, palette) -> None:
        """Register a list of palette entries.

        palette -- list of (name, foreground, background) or
                   (name, same_as_other_name) palette entries.

        calls self.register_palette_entry for each item in l
        """

        for item in palette:
            if len(item) in {3, 4}:
                self.register_palette_entry(*item)
                continue
            if len(item) != 2:
                raise ValueError(f"Invalid register_palette usage: {item!r}")
            name, like_name = item
            if like_name not in self.palette:
                raise KeyError(f"palette entry '{like_name}' doesn't exist")
            self.palette[name] = self.palette[like_name]

    def register_palette_entry(
        self,
        name: str | None,
        foreground: str,
        background: str,
        mono: str | None = None,
        foreground_high: str | None = None,
        background_high: str | None = None,
    ) -> None:
        """Register a single palette entry.

        name -- new entry/attribute name
        foreground -- foreground colour
        background -- background colour
        mono -- monochrome terminal attribute

        See curses_display.register_palette_entry for more info.
        """
        if foreground == "default":
            foreground = "black"
        if background == "default":
            background = "light gray"
        self.palette[name] = (foreground, background, mono)

    def set_mouse_tracking(self, enable: bool = True) -> None:
        """Not yet implemented"""

    def tty_signal_keys(self, *args, **vargs):
        """Do nothing."""

    def start(self) -> StoppingContext:
        """
        This function reads the initial screen size, generates a
        unique id and handles cleanup when fn exits.

        web_display.set_preferences(..) must be called before calling
        this function for the preferences to take effect
        """
        if self._started:
            return StoppingContext(self)

        client_init = sys.stdin.read(50)
        if not client_init.startswith("window resize "):
            raise ValueError(client_init)
        _ignore1, _ignore2, x, y = client_init.split(" ", 3)
        x = int(x)
        y = int(y)
        self._set_screen_size(x, y)
        self.last_screen = {}
        self.last_screen_width = 0

        self.update_method = os.environ["HTTP_X_URWID_METHOD"]
        if self.update_method not in {"multipart", "polling"}:
            raise ValueError(self.update_method)

        if self.update_method == "polling" and not _prefs.allow_polling:
            sys.stdout.write("Status: 403 Forbidden\r\n\r\n")
            sys.exit(0)

        clients = glob.glob(os.path.join(_prefs.pipe_dir, "urwid*.in"))
        if len(clients) >= _prefs.max_clients:
            sys.stdout.write("Status: 503 Sever Busy\r\n\r\n")
            sys.exit(0)

        urwid_id = f"{random.randrange(10 ** 9):09d}{random.randrange(10 ** 9):09d}"  # noqa: S311
        self.pipe_name = os.path.join(_prefs.pipe_dir, f"urwid{urwid_id}")
        os.mkfifo(f"{self.pipe_name}.in", 0o600)
        signal.signal(signal.SIGTERM, self._cleanup_pipe)

        self.input_fd = os.open(f"{self.pipe_name}.in", os.O_NONBLOCK | os.O_RDONLY)
        self.input_tail = ""
        self.content_head = (
            "Content-type: "
            "multipart/x-mixed-replace;boundary=ZZ\r\n"
            "X-Urwid-ID: " + urwid_id + "\r\n"
            "\r\n\r\n"
            "--ZZ\r\n"
        )
        if self.update_method == "polling":
            self.content_head = f"Content-type: text/plain\r\nX-Urwid-ID: {urwid_id}\r\n\r\n\r\n"

        signal.signal(signal.SIGALRM, self._handle_alarm)
        signal.alarm(ALARM_DELAY)
        self._started = True

        return StoppingContext(self)

    def stop(self) -> None:
        """
        Restore settings and clean up.
        """
        if not self._started:
            return

        # XXX which exceptions does this actually raise? EnvironmentError?
        with suppress(Exception):
            self._close_connection()
        signal.signal(signal.SIGTERM, signal.SIG_DFL)
        self._cleanup_pipe()
        self._started = False

    def set_input_timeouts(self, *args: typing.Any) -> None:
        pass

    def _close_connection(self) -> None:
        if self.update_method == "polling child":
            self.server_socket.settimeout(0)
            sock, _addr = self.server_socket.accept()
            sock.sendall(b"Z")
            sock.close()

        if self.update_method == "multipart":
            sys.stdout.write("\r\nZ\r\n--ZZ--\r\n")
            sys.stdout.flush()

    def _cleanup_pipe(self, *args) -> None:
        if not self.pipe_name:
            return
        # XXX which exceptions does this actually raise? EnvironmentError?
        with suppress(Exception):
            os.remove(f"{self.pipe_name}.in")
            os.remove(f"{self.pipe_name}.update")

    def _set_screen_size(self, cols: int, rows: int) -> None:
        """Set the screen size (within max size)."""

        cols = min(cols, MAX_COLS)
        rows = min(rows, MAX_ROWS)
        self.screen_size = cols, rows

    def draw_screen(self, size: tuple[int, int], canvas: Canvas) -> None:
        """Send a screen update to the client."""

        (cols, rows) = size

        if cols != self.last_screen_width:
            self.last_screen = {}

        sendq = [self.content_head]

        if self.update_method == "polling":
            send = sendq.append
        elif self.update_method == "polling child":
            signal.alarm(0)
            try:
                s, _addr = self.server_socket.accept()
            except socket.timeout:
                sys.exit(0)
            send = s.sendall
        else:
            signal.alarm(0)
            send = sendq.append
            send("\r\n")
            self.content_head = ""

        if canvas.rows() != rows:
            raise ValueError(rows)

        if canvas.cursor is not None:
            cx, cy = canvas.cursor
        else:
            cx = cy = None

        new_screen = {}

        y = -1
        for row in canvas.content():
            y += 1
            l_row = tuple((attr_, line.decode(get_encoding())) for attr_, _, line in row)

            line = []

            sig = l_row
            if y == cy:
                sig = (*sig, cx)
            new_screen[sig] = [*new_screen.get(sig, []), y]
            old_line_numbers = self.last_screen.get(sig, None)
            if old_line_numbers is not None:
                if y in old_line_numbers:
                    old_line = y
                else:
                    old_line = old_line_numbers[0]
                send(f"<{old_line:d}\n")
                continue

            col = 0
            for a, run in l_row:
                t_run = run.translate(_trans_table)
                if a is None:
                    fg, bg, _mono = "black", "light gray", None
                else:
                    fg, bg, _mono = self.palette[a]
                if y == cy and col <= cx:
                    run_width = calc_width(t_run, 0, len(t_run))
                    if col + run_width > cx:
                        line.append(code_span(t_run, fg, bg, cx - col))
                    else:
                        line.append(code_span(t_run, fg, bg))
                    col += run_width
                else:
                    line.append(code_span(t_run, fg, bg))

            send(f"{''.join(line)}\n")
        self.last_screen = new_screen
        self.last_screen_width = cols

        if self.update_method == "polling":
            sys.stdout.write("".join(sendq))
            sys.stdout.flush()
            sys.stdout.close()
            self._fork_child()
        elif self.update_method == "polling child":
            s.close()
        else:  # update_method == "multipart"
            send("\r\n--ZZ\r\n")
            sys.stdout.write("".join(sendq))
            sys.stdout.flush()

        signal.alarm(ALARM_DELAY)

    def clear(self) -> None:
        """
        Force the screen to be completely repainted on the next
        call to draw_screen().

        (does nothing for web_display)
        """

    def _fork_child(self) -> None:
        """
        Fork a child to run CGI disconnected for polling update method.
        Force parent process to exit.
        """
        daemonize(f"{self.pipe_name}.err")
        self.input_fd = os.open(f"{self.pipe_name}.in", os.O_NONBLOCK | os.O_RDONLY)
        self.update_method = "polling child"
        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        s.bind(f"{self.pipe_name}.update")
        s.listen(1)
        s.settimeout(POLL_CONNECT)
        self.server_socket = s

    def _handle_alarm(self, sig, frame) -> None:
        if self.update_method not in {"multipart", "polling child"}:
            raise ValueError(self.update_method)
        if self.update_method == "polling child":
            # send empty update
            try:
                s, _addr = self.server_socket.accept()
                s.close()
            except socket.timeout:
                sys.exit(0)
        else:
            # send empty update
            sys.stdout.write("\r\n\r\n--ZZ\r\n")
            sys.stdout.flush()
        signal.alarm(ALARM_DELAY)

    def get_cols_rows(self) -> tuple[int, int]:
        """Return the screen size."""
        return self.screen_size

    @typing.overload
    def get_input(self, raw_keys: Literal[False]) -> list[str]: ...

    @typing.overload
    def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]: ...

    def get_input(self, raw_keys: bool = False) -> list[str] | tuple[list[str], list[int]]:
        """Return pending input as a list."""
        pending_input = []
        resized = False
        with selectors.DefaultSelector() as selector:
            selector.register(self.input_fd, selectors.EVENT_READ)

            iready = [event.fd for event, _ in selector.select(0.5)]

        if not iready:
            if raw_keys:
                return [], []
            return []

        keydata = os.read(self.input_fd, MAX_READ).decode(get_encoding())
        os.close(self.input_fd)
        self.input_fd = os.open(f"{self.pipe_name}.in", os.O_NONBLOCK | os.O_RDONLY)
        # sys.stderr.write( repr((keydata,self.input_tail))+"\n" )
        keys = keydata.split("\n")
        keys[0] = self.input_tail + keys[0]
        self.input_tail = keys[-1]

        for k in keys[:-1]:
            if k.startswith("window resize "):
                _ign1, _ign2, x, y = k.split(" ", 3)
                x = int(x)
                y = int(y)
                self._set_screen_size(x, y)
                resized = True
            else:
                pending_input.append(k)
        if resized:
            pending_input.append("window resize")

        if raw_keys:
            return pending_input, []
        return pending_input


def code_span(s, fg, bg, cursor=-1) -> str:
    code_fg = _code_colours[fg]
    code_bg = _code_colours[bg]

    if cursor >= 0:
        c_off, _ign = calc_text_pos(s, 0, len(s), cursor)
        c2_off = move_next_char(s, c_off, len(s))

        return (
            code_fg
            + code_bg
            + s[:c_off]
            + "\n"
            + code_bg
            + code_fg
            + s[c_off:c2_off]
            + "\n"
            + code_fg
            + code_bg
            + s[c2_off:]
            + "\n"
        )

    return f"{code_fg + code_bg + s}\n"


def is_web_request() -> bool:
    """
    Return True if this is a CGI web request.
    """
    return "REQUEST_METHOD" in os.environ


def handle_short_request() -> bool:
    """
    Handle short requests such as passing keystrokes to the application
    or sending the initial html page.  If returns True, then this
    function recognised and handled a short request, and the calling
    script should immediately exit.

    web_display.set_preferences(..) should be called before calling this
    function for the preferences to take effect
    """
    if not is_web_request():
        return False

    if os.environ["REQUEST_METHOD"] == "GET":
        # Initial request, send the HTML and javascript.
        sys.stdout.write("Content-type: text/html\r\n\r\n" + html.escape(_prefs.app_name).join(_html_page))
        return True

    if os.environ["REQUEST_METHOD"] != "POST":
        # Don't know what to do with head requests etc.
        return False

    if "HTTP_X_URWID_ID" not in os.environ:
        # If no urwid id, then the application should be started.
        return False

    urwid_id = os.environ["HTTP_X_URWID_ID"]
    if len(urwid_id) > 20:
        # invalid. handle by ignoring
        # assert 0, "urwid id too long!"
        sys.stdout.write("Status: 414 URI Too Long\r\n\r\n")
        return True
    for c in urwid_id:
        if c not in string.digits:
            # invald. handle by ignoring
            # assert 0, "invalid chars in id!"
            sys.stdout.write("Status: 403 Forbidden\r\n\r\n")
            return True

    if os.environ.get("HTTP_X_URWID_METHOD", None) == "polling":
        # this is a screen update request
        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            s.connect(os.path.join(_prefs.pipe_dir, f"urwid{urwid_id}.update"))
            data = f"Content-type: text/plain\r\n\r\n{s.recv(BUF_SZ)}"
            while data:
                sys.stdout.write(data)
                data = s.recv(BUF_SZ)
        except OSError:
            sys.stdout.write("Status: 404 Not Found\r\n\r\n")
            return True
        return True

    # this is a keyboard input request
    try:
        fd = os.open((os.path.join(_prefs.pipe_dir, f"urwid{urwid_id}.in")), os.O_WRONLY)
    except OSError:
        sys.stdout.write("Status: 404 Not Found\r\n\r\n")
        return True

    # FIXME: use the correct encoding based on the request
    keydata = sys.stdin.read(MAX_READ)
    os.write(fd, keydata.encode("ascii"))
    os.close(fd)
    sys.stdout.write("Content-type: text/plain\r\n\r\n")

    return True


@dataclasses.dataclass
class _Preferences:
    app_name: str = "Unnamed Application"
    pipe_dir: str = TEMP_DIR
    allow_polling: bool = True
    max_clients: int = 20


_prefs = _Preferences()


def set_preferences(
    app_name: str,
    pipe_dir: str = TEMP_DIR,
    allow_polling: bool = True,
    max_clients: int = 20,
) -> None:
    """
    Set web_display preferences.

    app_name -- application name to appear in html interface
    pipe_dir -- directory for input pipes, daemon update sockets
                and daemon error logs
    allow_polling -- allow creation of daemon processes for
                     browsers without multipart support
    max_clients -- maximum concurrent client connections. This
               pool is shared by all urwid applications
               using the same pipe_dir
    """
    _prefs.app_name = app_name
    _prefs.pipe_dir = pipe_dir
    _prefs.allow_polling = allow_polling
    _prefs.max_clients = max_clients


class ErrorLog:
    def __init__(self, errfile: str | pathlib.PurePath) -> None:
        self.errfile = errfile

    def write(self, err: str) -> None:
        with open(self.errfile, "a", encoding="utf-8") as f:
            f.write(err)


def daemonize(errfile: str) -> None:
    """
    Detach process and become a daemon.
    """
    pid = os.fork()
    if pid:
        os._exit(0)

    os.setsid()
    signal.signal(signal.SIGHUP, signal.SIG_IGN)
    os.umask(0)

    pid = os.fork()
    if pid:
        os._exit(0)

    os.chdir("/")
    for fd in range(0, 20):
        with suppress(OSError):
            os.close(fd)

    sys.stdin = open("/dev/null", encoding="utf-8")  # noqa: SIM115  # pylint: disable=consider-using-with
    sys.stdout = open("/dev/null", "w", encoding="utf-8")  # noqa: SIM115  # pylint: disable=consider-using-with
    sys.stderr = ErrorLog(errfile)
urwid-2.6.16/urwid/event_loop/000077500000000000000000000000001470350774000162535ustar00rootroot00000000000000urwid-2.6.16/urwid/event_loop/__init__.py000066400000000000000000000022571470350774000203720ustar00rootroot00000000000000"""Package with EventLoop implementations for urwid."""

from __future__ import annotations

import sys

from .abstract_loop import EventLoop, ExitMainLoop
from .asyncio_loop import AsyncioEventLoop
from .main_loop import MainLoop
from .select_loop import SelectEventLoop

__all__ = (
    "AsyncioEventLoop",
    "EventLoop",
    "ExitMainLoop",
    "MainLoop",
    "SelectEventLoop",
)

try:
    from .twisted_loop import TwistedEventLoop

    __all__ += ("TwistedEventLoop",)  # type: ignore[assignment]
except ImportError:
    pass

try:
    from .tornado_loop import TornadoEventLoop

    __all__ += ("TornadoEventLoop",)  # type: ignore[assignment]
except ImportError:
    pass

try:
    from .glib_loop import GLibEventLoop

    __all__ += ("GLibEventLoop",)  # type: ignore[assignment]
except ImportError:
    pass

try:
    from .trio_loop import TrioEventLoop

    __all__ += ("TrioEventLoop",)  # type: ignore[assignment]
except ImportError:
    pass

if sys.platform != "win32":
    # ZMQEventLoop cause interpreter crash on windows
    try:
        from .zmq_loop import ZMQEventLoop

        __all__ += ("ZMQEventLoop",)  # type: ignore[assignment]
    except ImportError:
        pass
urwid-2.6.16/urwid/event_loop/abstract_loop.py000066400000000000000000000125661470350774000214730ustar00rootroot00000000000000# Urwid main loop code
#    Copyright (C) 2004-2012  Ian Ward
#    Copyright (C) 2008 Walter Mundt
#    Copyright (C) 2009 Andrew Psaltis
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/

"""Abstract shared code for urwid EventLoop implementation."""

from __future__ import annotations

import abc
import logging
import signal
import typing

if typing.TYPE_CHECKING:
    import asyncio
    from collections.abc import Callable
    from concurrent.futures import Executor, Future
    from types import FrameType

    from typing_extensions import ParamSpec

    _T = typing.TypeVar("_T")
    _Spec = ParamSpec("_Spec")

__all__ = ("EventLoop", "ExitMainLoop")


class ExitMainLoop(Exception):
    """
    When this exception is raised within a main loop the main loop
    will exit cleanly.
    """


class EventLoop(abc.ABC):
    """
    Abstract class representing an event loop to be used by :class:`MainLoop`.
    """

    __slots__ = ("logger",)

    def __init__(self) -> None:
        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)

    def run_in_executor(
        self,
        executor: Executor,
        func: Callable[_Spec, _T],
        *args: _Spec.args,
        **kwargs: _Spec.kwargs,
    ) -> Future[_T] | asyncio.Future[_T]:
        """Run callable in executor if supported.

        :param executor: Executor to use for running the function
        :type executor: concurrent.futures.Executor
        :param func: function to call
        :type func: Callable
        :param args: arguments to function (positional only)
        :type args: object
        :param kwargs: keyword arguments to function (keyword only)
        :type kwargs: object
        :return: future object for the function call outcome.
                 (exact future type depends on the event loop type)
        :rtype: concurrent.futures.Future | asyncio.Future
        """
        raise NotImplementedError

    @abc.abstractmethod
    def alarm(self, seconds: float, callback: Callable[[], typing.Any]) -> typing.Any:
        """
        Call callback() a given time from now.  No parameters are
        passed to callback.

        This method has no default implementation.

        Returns a handle that may be passed to remove_alarm()

        seconds -- floating point time to wait before calling callback
        callback -- function to call from event loop
        """

    @abc.abstractmethod
    def enter_idle(self, callback):
        """
        Add a callback for entering idle.

        This method has no default implementation.

        Returns a handle that may be passed to remove_idle()
        """

    @abc.abstractmethod
    def remove_alarm(self, handle) -> bool:
        """
        Remove an alarm.

        This method has no default implementation.

        Returns True if the alarm exists, False otherwise
        """

    @abc.abstractmethod
    def remove_enter_idle(self, handle) -> bool:
        """
        Remove an idle callback.

        This method has no default implementation.

        Returns True if the handle was removed.
        """

    @abc.abstractmethod
    def remove_watch_file(self, handle) -> bool:
        """
        Remove an input file.

        This method has no default implementation.

        Returns True if the input file exists, False otherwise
        """

    @abc.abstractmethod
    def run(self) -> None:
        """
        Start the event loop.  Exit the loop when any callback raises
        an exception.  If ExitMainLoop is raised, exit cleanly.

        This method has no default implementation.
        """

    @abc.abstractmethod
    def watch_file(self, fd: int, callback: Callable[[], typing.Any]):
        """
        Call callback() when fd has some data to read.  No parameters
        are passed to callback.

        This method has no default implementation.

        Returns a handle that may be passed to remove_watch_file()

        fd -- file descriptor to watch for input
        callback -- function to call when input is available
        """

    def set_signal_handler(
        self,
        signum: int,
        handler: Callable[[int, FrameType | None], typing.Any] | int | signal.Handlers,
    ) -> Callable[[int, FrameType | None], typing.Any] | int | signal.Handlers | None:
        """
        Sets the signal handler for signal signum.

        The default implementation of :meth:`set_signal_handler`
        is simply a proxy function that calls :func:`signal.signal()`
        and returns the resulting value.

        signum -- signal number
        handler -- function (taking signum as its single argument),
        or `signal.SIG_IGN`, or `signal.SIG_DFL`
        """
        return signal.signal(signum, handler)
urwid-2.6.16/urwid/event_loop/asyncio_loop.py000066400000000000000000000204271470350774000213300ustar00rootroot00000000000000# Urwid main loop code
#    Copyright (C) 2004-2012  Ian Ward
#    Copyright (C) 2008 Walter Mundt
#    Copyright (C) 2009 Andrew Psaltis
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/

"""Asyncio based urwid EventLoop implementation."""

from __future__ import annotations

import asyncio
import functools
import logging
import sys
import typing

from .abstract_loop import EventLoop, ExitMainLoop

if typing.TYPE_CHECKING:
    from collections.abc import Callable
    from concurrent.futures import Executor

    from typing_extensions import ParamSpec

    _Spec = ParamSpec("_Spec")
    _T = typing.TypeVar("_T")

__all__ = ("AsyncioEventLoop",)
IS_WINDOWS = sys.platform == "win32"


class AsyncioEventLoop(EventLoop):
    """
    Event loop based on the standard library ``asyncio`` module.

    .. warning::
        Under Windows, AsyncioEventLoop globally enforces WindowsSelectorEventLoopPolicy
        as a side-effect of creating a class instance.
        Original event loop policy is restored in destructor method.

    .. note::
        If you make any changes to the urwid state outside of it
        handling input or responding to alarms (for example, from asyncio.Task
        running in background), and wish the screen to be
        redrawn, you must call :meth:`MainLoop.draw_screen` method of the
        main loop manually.

        A good way to do this:
            asyncio.get_event_loop().call_soon(main_loop.draw_screen)
    """

    def __init__(self, *, loop: asyncio.AbstractEventLoop | None = None, **kwargs) -> None:
        super().__init__()
        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
        if loop:
            self._loop: asyncio.AbstractEventLoop = loop
            self._event_loop_policy_altered: bool = False
            self._original_event_loop_policy: asyncio.AbstractEventLoopPolicy | None = None
        else:
            self._original_event_loop_policy = asyncio.get_event_loop_policy()
            if IS_WINDOWS and not isinstance(self._original_event_loop_policy, asyncio.WindowsSelectorEventLoopPolicy):
                self.logger.debug("Set WindowsSelectorEventLoopPolicy as asyncio event loop policy")
                asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
                self._event_loop_policy_altered = True
            else:
                self._event_loop_policy_altered = False

            self._loop = asyncio.get_event_loop()

        self._exc: BaseException | None = None

        self._idle_asyncio_handle: asyncio.TimerHandle | None = None
        self._idle_handle: int = 0
        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}

    def __del__(self) -> None:
        if self._event_loop_policy_altered:
            asyncio.set_event_loop_policy(self._original_event_loop_policy)  # Restore default event loop policy

    def _also_call_idle(self, callback: Callable[_Spec, _T]) -> Callable[_Spec, _T]:
        """
        Wrap the callback to also call _entering_idle.
        """

        @functools.wraps(callback)
        def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T:
            if not self._idle_asyncio_handle:
                self._idle_asyncio_handle = self._loop.call_later(0, self._entering_idle)
            return callback(*args, **kwargs)

        return wrapper

    def _entering_idle(self) -> None:
        """
        Call all the registered idle callbacks.
        """
        try:
            for callback in self._idle_callbacks.values():
                callback()
        finally:
            self._idle_asyncio_handle = None

    def run_in_executor(
        self,
        executor: Executor | None,
        func: Callable[_Spec, _T],
        *args: _Spec.args,
        **kwargs: _Spec.kwargs,
    ) -> asyncio.Future[_T]:
        """Run callable in executor.

        :param executor: Executor to use for running the function. Default asyncio executor is used if None.
        :type executor: concurrent.futures.Executor | None
        :param func: function to call
        :type func: Callable
        :param args: arguments to function (positional only)
        :type args: object
        :param kwargs: keyword arguments to function (keyword only)
        :type kwargs: object
        :return: future object for the function call outcome.
        :rtype: asyncio.Future
        """
        return self._loop.run_in_executor(executor, functools.partial(func, *args, **kwargs))

    def alarm(self, seconds: float, callback: Callable[[], typing.Any]) -> asyncio.TimerHandle:
        """
        Call callback() a given time from now.  No parameters are
        passed to callback.

        Returns a handle that may be passed to remove_alarm()

        seconds -- time in seconds to wait before calling callback
        callback -- function to call from event loop
        """
        return self._loop.call_later(seconds, self._also_call_idle(callback))

    def remove_alarm(self, handle) -> bool:
        """
        Remove an alarm.

        Returns True if the alarm exists, False otherwise
        """
        existed = not handle.cancelled()
        handle.cancel()
        return existed

    def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:
        """
        Call callback() when fd has some data to read.  No parameters
        are passed to callback.

        Returns a handle that may be passed to remove_watch_file()

        fd -- file descriptor to watch for input
        callback -- function to call when input is available
        """
        self._loop.add_reader(fd, self._also_call_idle(callback))
        return fd

    def remove_watch_file(self, handle: int) -> bool:
        """
        Remove an input file.

        Returns True if the input file exists, False otherwise
        """
        return self._loop.remove_reader(handle)

    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
        """
        Add a callback for entering idle.

        Returns a handle that may be passed to remove_enter_idle()
        """
        # XXX there's no such thing as "idle" in most event loops; this fakes
        # it by adding extra callback to the timer and file watch callbacks.
        self._idle_handle += 1
        self._idle_callbacks[self._idle_handle] = callback
        return self._idle_handle

    def remove_enter_idle(self, handle: int) -> bool:
        """
        Remove an idle callback.

        Returns True if the handle was removed.
        """
        try:
            del self._idle_callbacks[handle]
        except KeyError:
            return False
        return True

    def _exception_handler(self, loop: asyncio.AbstractEventLoop, context):
        exc = context.get("exception")
        if exc:
            loop.stop()

            if self._idle_asyncio_handle:
                # clean it up to prevent old callbacks
                # from messing things up if loop is restarted
                self._idle_asyncio_handle.cancel()
                self._idle_asyncio_handle = None

            if not isinstance(exc, ExitMainLoop):
                # Store the exc_info so we can re-raise after the loop stops
                self._exc = exc
        else:
            loop.default_exception_handler(context)

    def run(self) -> None:
        """Start the event loop.

        Exit the loop when any callback raises an exception.
        If ExitMainLoop is raised, exit cleanly.
        """
        self._loop.set_exception_handler(self._exception_handler)
        self._loop.run_forever()
        if self._exc:
            exc = self._exc
            self._exc = None
            raise exc.with_traceback(exc.__traceback__)
urwid-2.6.16/urwid/event_loop/glib_loop.py000066400000000000000000000222071470350774000205760ustar00rootroot00000000000000# Urwid main loop code
#    Copyright (C) 2004-2012  Ian Ward
#    Copyright (C) 2008 Walter Mundt
#    Copyright (C) 2009 Andrew Psaltis
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/

"""GLib based urwid EventLoop implementation.

PyGObject library is required.
"""

from __future__ import annotations

import functools
import logging
import signal
import typing

from gi.repository import GLib

from .abstract_loop import EventLoop, ExitMainLoop

if typing.TYPE_CHECKING:
    from collections.abc import Callable
    from concurrent.futures import Executor, Future
    from types import FrameType

    from typing_extensions import Literal, ParamSpec

    _Spec = ParamSpec("_Spec")
    _T = typing.TypeVar("_T")

__all__ = ("GLibEventLoop",)


def _ignore_handler(_sig: int, _frame: FrameType | None = None) -> None:
    return None


class GLibEventLoop(EventLoop):
    """
    Event loop based on GLib.MainLoop
    """

    def __init__(self) -> None:
        super().__init__()
        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
        self._alarms: list[int] = []
        self._watch_files: dict[int, int] = {}
        self._idle_handle: int = 0
        self._glib_idle_enabled = False  # have we called glib.idle_add?
        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}
        self._loop = GLib.MainLoop()
        self._exc: BaseException | None = None
        self._enable_glib_idle()
        self._signal_handlers: dict[int, int] = {}

    def run_in_executor(
        self,
        executor: Executor,
        func: Callable[_Spec, _T],
        *args: _Spec.args,
        **kwargs: _Spec.kwargs,
    ) -> Future[_T]:
        """Run callable in executor.

        :param executor: Executor to use for running the function
        :type executor: concurrent.futures.Executor
        :param func: function to call
        :type func: Callable
        :param args: positional arguments to function
        :type args: object
        :param kwargs: keyword arguments to function
        :type kwargs: object
        :return: future object for the function call outcome.
        :rtype: concurrent.futures.Future
        """
        return executor.submit(func, *args, **kwargs)

    def alarm(
        self,
        seconds: float,
        callback: Callable[[], typing.Any],
    ) -> tuple[int, Callable[[], typing.Any]]:
        """
        Call callback() a given time from now.  No parameters are
        passed to callback.

        Returns a handle that may be passed to remove_alarm()

        seconds -- floating point time to wait before calling callback
        callback -- function to call from event loop
        """

        @self.handle_exit
        def ret_false() -> Literal[False]:
            callback()
            self._enable_glib_idle()
            return False

        fd = GLib.timeout_add(int(seconds * 1000), ret_false)
        self._alarms.append(fd)
        return (fd, callback)

    def set_signal_handler(
        self,
        signum: int,
        handler: Callable[[int, FrameType | None], typing.Any] | int | signal.Handlers,
    ) -> None:
        """
        Sets the signal handler for signal signum.

        .. WARNING::
            Because this method uses the `GLib`-specific `unix_signal_add`
            function, its behaviour is different than `signal.signal().`

            If `signum` is not `SIGHUP`, `SIGINT`, `SIGTERM`, `SIGUSR1`,
            `SIGUSR2` or `SIGWINCH`, this method performs no actions and
            immediately returns None.

            Returns None in all cases (unlike :func:`signal.signal()`).
        ..

        signum -- signal number
        handler -- function (taking signum as its single argument),
        or `signal.SIG_IGN`, or `signal.SIG_DFL`
        """
        glib_signals = [
            signal.SIGHUP,
            signal.SIGINT,
            signal.SIGTERM,
            signal.SIGUSR1,
            signal.SIGUSR2,
        ]

        # GLib supports SIGWINCH as of version 2.54.
        if not GLib.check_version(2, 54, 0):
            glib_signals.append(signal.SIGWINCH)

        if signum not in glib_signals:
            # The GLib event loop supports only the signals listed above
            return

        if signum in self._signal_handlers:
            GLib.source_remove(self._signal_handlers.pop(signum))

        if handler == signal.Handlers.SIG_IGN:
            handler = _ignore_handler
        elif handler == signal.Handlers.SIG_DFL:
            return

        def final_handler(signal_number: int):
            # MyPy False-negative: signal.Handlers casted
            handler(signal_number, None)  # type: ignore[operator]
            return GLib.SOURCE_CONTINUE

        source = GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signum, final_handler, signum)
        self._signal_handlers[signum] = source

    def remove_alarm(self, handle) -> bool:
        """
        Remove an alarm.

        Returns True if the alarm exists, False otherwise
        """
        try:
            self._alarms.remove(handle[0])
            GLib.source_remove(handle[0])

        except ValueError:
            return False

        return True

    def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:
        """
        Call callback() when fd has some data to read.  No parameters
        are passed to callback.

        Returns a handle that may be passed to remove_watch_file()

        fd -- file descriptor to watch for input
        callback -- function to call when input is available
        """

        @self.handle_exit
        def io_callback(source, cb_condition) -> Literal[True]:
            callback()
            self._enable_glib_idle()
            return True

        self._watch_files[fd] = GLib.io_add_watch(fd, GLib.IO_IN, io_callback)
        return fd

    def remove_watch_file(self, handle: int) -> bool:
        """
        Remove an input file.

        Returns True if the input file exists, False otherwise
        """
        if handle in self._watch_files:
            GLib.source_remove(self._watch_files[handle])
            del self._watch_files[handle]
            return True
        return False

    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
        """
        Add a callback for entering idle.

        Returns a handle that may be passed to remove_enter_idle()
        """
        self._idle_handle += 1
        self._idle_callbacks[self._idle_handle] = callback
        return self._idle_handle

    def _enable_glib_idle(self) -> None:
        if self._glib_idle_enabled:
            return
        GLib.idle_add(self._glib_idle_callback)
        self._glib_idle_enabled = True

    def _glib_idle_callback(self):
        for callback in self._idle_callbacks.values():
            callback()
        self._glib_idle_enabled = False
        return False  # ask glib not to call again (or we would be called

    def remove_enter_idle(self, handle) -> bool:
        """
        Remove an idle callback.

        Returns True if the handle was removed.
        """
        try:
            del self._idle_callbacks[handle]
        except KeyError:
            return False
        return True

    def run(self) -> None:
        """
        Start the event loop.  Exit the loop when any callback raises
        an exception.  If ExitMainLoop is raised, exit cleanly.
        """
        try:
            self._loop.run()
        finally:
            if self._loop.is_running():
                self._loop.quit()
        if self._exc:
            # An exception caused us to exit, raise it now
            exc = self._exc
            self._exc = None
            raise exc.with_traceback(exc.__traceback__)

    def handle_exit(self, f: Callable[_Spec, _T]) -> Callable[_Spec, _T | Literal[False]]:
        """
        Decorator that cleanly exits the :class:`GLibEventLoop` if
        :exc:`ExitMainLoop` is thrown inside of the wrapped function. Store the
        exception info if some other exception occurs, it will be reraised after
        the loop quits.

        *f* -- function to be wrapped
        """

        @functools.wraps(f)
        def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T | Literal[False]:
            try:
                return f(*args, **kwargs)
            except ExitMainLoop:
                self._loop.quit()
            except BaseException as exc:
                self._exc = exc
                if self._loop.is_running():
                    self._loop.quit()
            return False

        return wrapper
urwid-2.6.16/urwid/event_loop/main_loop.py000066400000000000000000000626201470350774000206100ustar00rootroot00000000000000# Urwid main loop code
#    Copyright (C) 2004-2012  Ian Ward
#    Copyright (C) 2008 Walter Mundt
#    Copyright (C) 2009 Andrew Psaltis
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import heapq
import logging
import os
import sys
import time
import typing
import warnings
from contextlib import suppress

from urwid import display, signals
from urwid.command_map import Command, command_map
from urwid.display.common import INPUT_DESCRIPTORS_CHANGED
from urwid.util import StoppingContext, is_mouse_event
from urwid.widget import PopUpTarget

from .abstract_loop import ExitMainLoop
from .select_loop import SelectEventLoop

if typing.TYPE_CHECKING:
    from collections.abc import Callable, Iterable

    from typing_extensions import Self

    from urwid.display import BaseScreen
    from urwid.widget import Widget

    from .abstract_loop import EventLoop

    _T = typing.TypeVar("_T")


IS_WINDOWS = sys.platform == "win32"
PIPE_BUFFER_READ_SIZE = 4096  # can expect this much on Linux, so try for that

__all__ = ("CantUseExternalLoop", "MainLoop")


class CantUseExternalLoop(Exception):
    pass


class MainLoop:
    """
    This is the standard main loop implementation for a single interactive
    session.

    :param widget: the topmost widget used for painting the screen, stored as
                   :attr:`widget` and may be modified. Must be a box widget.
    :type widget: widget instance

    :param palette: initial palette for screen
    :type palette: iterable of palette entries

    :param screen: screen to use, default is a new :class:`raw_display.Screen`
                   instance; stored as :attr:`screen`
    :type screen: display module screen instance

    :param handle_mouse: ``True`` to ask :attr:`.screen` to process mouse events
    :type handle_mouse: bool

    :param input_filter: a function to filter input before sending it to
                   :attr:`.widget`, called from :meth:`.input_filter`
    :type input_filter: callable

    :param unhandled_input: a function called when input is not handled by
                            :attr:`.widget`, called from :meth:`.unhandled_input`
    :type unhandled_input: callable

    :param event_loop: if :attr:`.screen` supports external an event loop it may be
                       given here, default is a new :class:`SelectEventLoop` instance;
                       stored as :attr:`.event_loop`
    :type event_loop: event loop instance

    :param pop_ups: `True` to wrap :attr:`.widget` with a :class:`PopUpTarget`
                    instance to allow any widget to open a pop-up anywhere on the screen
    :type pop_ups: boolean


    .. attribute:: screen

        The screen object this main loop uses for screen updates and reading input

    .. attribute:: event_loop

        The event loop object this main loop uses for waiting on alarms and IO
    """

    def __init__(
        self,
        widget: Widget,
        palette: Iterable[
            tuple[str, str] | tuple[str, str, str] | tuple[str, str, str, str] | tuple[str, str, str, str, str, str]
        ] = (),
        screen: BaseScreen | None = None,
        handle_mouse: bool = True,
        input_filter: Callable[[list[str], list[int]], list[str]] | None = None,
        unhandled_input: Callable[[str | tuple[str, int, int, int]], bool | None] | None = None,
        event_loop: EventLoop | None = None,
        pop_ups: bool = False,
    ):
        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
        self._widget = widget
        self.handle_mouse = handle_mouse
        self._pop_ups = False  # only initialize placeholder
        self.pop_ups = pop_ups  # triggers property setting side-effect

        if not screen:
            screen = display.raw.Screen()

        if palette:
            screen.register_palette(palette)

        self.screen: BaseScreen = screen
        self.screen_size: tuple[int, int] | None = None

        self._unhandled_input = unhandled_input
        self._input_filter = input_filter

        if not hasattr(screen, "hook_event_loop") and event_loop is not None:
            raise NotImplementedError(f"screen object passed {screen!r} does not support external event loops")
        if event_loop is None:
            event_loop = SelectEventLoop()
        self.event_loop: EventLoop = event_loop

        if hasattr(self.screen, "signal_handler_setter"):
            # Tell the screen what function it must use to set
            # signal handlers
            self.screen.signal_handler_setter = self.event_loop.set_signal_handler

        self._watch_pipes: dict[int, tuple[Callable[[], typing.Any], int]] = {}

    @property
    def widget(self) -> Widget:
        """
        Property for the topmost widget used to draw the screen.
        This must be a box widget.
        """
        return self._widget

    @widget.setter
    def widget(self, widget: Widget) -> None:
        self._widget = widget
        if self.pop_ups:
            self._topmost_widget.original_widget = self._widget
        else:
            self._topmost_widget = self._widget

    def _set_widget(self, widget: Widget) -> None:
        warnings.warn(
            f"method `{self.__class__.__name__}._set_widget` is deprecated, "
            f"please use `{self.__class__.__name__}.widget` property",
            DeprecationWarning,
            stacklevel=2,
        )
        self.widget = widget

    @property
    def pop_ups(self) -> bool:
        return self._pop_ups

    @pop_ups.setter
    def pop_ups(self, pop_ups: bool) -> None:
        self._pop_ups = pop_ups
        if pop_ups:
            self._topmost_widget = PopUpTarget(self._widget)
        else:
            self._topmost_widget = self._widget

    def _set_pop_ups(self, pop_ups: bool) -> None:
        warnings.warn(
            f"method `{self.__class__.__name__}._set_pop_ups` is deprecated, "
            f"please use `{self.__class__.__name__}.pop_ups` property",
            DeprecationWarning,
            stacklevel=2,
        )
        self.pop_ups = pop_ups

    def set_alarm_in(self, sec: float, callback: Callable[[Self, _T], typing.Any], user_data: _T = None):
        """
        Schedule an alarm in *sec* seconds that will call *callback* from the
        within the :meth:`run` method.

        :param sec: seconds until alarm
        :type sec: float
        :param callback: function to call with two parameters: this main loop
                         object and *user_data*
        :type callback: callable
        :param user_data: optional user data to pass to the callback
        :type user_data: object
        """
        self.logger.debug(f"Setting alarm in {sec!r} seconds with callback {callback!r}")

        def cb() -> None:
            callback(self, user_data)

        return self.event_loop.alarm(sec, cb)

    def set_alarm_at(self, tm: float, callback: Callable[[Self, _T], typing.Any], user_data: _T = None):
        """
        Schedule an alarm at *tm* time that will call *callback* from the
        within the :meth:`run` function. Returns a handle that may be passed to
        :meth:`remove_alarm`.

        :param tm: time to call callback e.g. ``time.time() + 5``
        :type tm: float
        :param callback: function to call with two parameters: this main loop
                         object and *user_data*
        :type callback: callable
        :param user_data: optional user data to pass to the callback
        :type user_data: object
        """
        sec = tm - time.time()
        self.logger.debug(f"Setting alarm in {sec!r} seconds with callback {callback!r}")

        def cb() -> None:
            callback(self, user_data)

        return self.event_loop.alarm(sec, cb)

    def remove_alarm(self, handle) -> bool:
        """
        Remove an alarm. Return ``True`` if *handle* was found, ``False``
        otherwise.
        """
        return self.event_loop.remove_alarm(handle)

    if not IS_WINDOWS:

        def watch_pipe(self, callback: Callable[[bytes], bool | None]) -> int:
            """
            Create a pipe for use by a subprocess or thread to trigger a callback
            in the process/thread running the main loop.

            :param callback: function taking one parameter to call from within the process/thread running the main loop
            :type callback: callable

            This method returns a file descriptor attached to the write end of a pipe.
            The read end of the pipe is added to the list of files :attr:`event_loop` is watching.
            When data is written to the pipe the callback function will be called
            and passed a single value containing data read from the pipe.

            This method may be used any time you want to update widgets from another thread or subprocess.

            Data may be written to the returned file descriptor with ``os.write(fd, data)``.
            Ensure that data is less than 512 bytes (or 4K on Linux)
            so that the callback will be triggered just once with the complete value of data passed in.

            If the callback returns ``False`` then the watch will be removed from :attr:`event_loop`
            and the read end of the pipe will be closed.
            You are responsible for closing the write end of the pipe with ``os.close(fd)``.
            """
            import fcntl

            pipe_rd, pipe_wr = os.pipe()
            fcntl.fcntl(pipe_rd, fcntl.F_SETFL, os.O_NONBLOCK)
            watch_handle = None

            def cb() -> None:
                data = os.read(pipe_rd, PIPE_BUFFER_READ_SIZE)
                if callback(data) is False:
                    self.event_loop.remove_watch_file(watch_handle)
                    os.close(pipe_rd)

            watch_handle = self.event_loop.watch_file(pipe_rd, cb)
            self._watch_pipes[pipe_wr] = (watch_handle, pipe_rd)
            return pipe_wr

        def remove_watch_pipe(self, write_fd: int) -> bool:
            """
            Close the read end of the pipe and remove the watch created by :meth:`watch_pipe`.

            ..note:: You are responsible for closing the write end of the pipe.

            Returns ``True`` if the watch pipe exists, ``False`` otherwise
            """
            try:
                watch_handle, pipe_rd = self._watch_pipes.pop(write_fd)
            except KeyError:
                return False

            if not self.event_loop.remove_watch_file(watch_handle):
                return False
            os.close(pipe_rd)
            return True

    def watch_file(self, fd: int, callback: Callable[[], typing.Any]):
        """
        Call *callback* when *fd* has some data to read. No parameters are
        passed to callback.

        Returns a handle that may be passed to :meth:`remove_watch_file`.
        """
        self.logger.debug(f"Setting watch file descriptor {fd!r} with {callback!r}")
        return self.event_loop.watch_file(fd, callback)

    def remove_watch_file(self, handle):
        """
        Remove a watch file. Returns ``True`` if the watch file
        exists, ``False`` otherwise.
        """
        return self.event_loop.remove_watch_file(handle)

    def run(self) -> None:
        """
        Start the main loop handling input events and updating the screen. The
        loop will continue until an :exc:`ExitMainLoop` exception is raised.

        If you would prefer to manage the event loop yourself, don't use this
        method.  Instead, call :meth:`start` before starting the event loop,
        and :meth:`stop` once it's finished.
        """
        with suppress(ExitMainLoop):
            self._run()

    def _test_run(self):
        """
        >>> w = _refl("widget")   # _refl prints out function calls
        >>> w.render_rval = "fake canvas"  # *_rval is used for return values
        >>> scr = _refl("screen")
        >>> scr.get_input_descriptors_rval = [42]
        >>> scr.get_cols_rows_rval = (20, 10)
        >>> scr.started = True
        >>> scr._urwid_signals = {}
        >>> evl = _refl("event_loop")
        >>> evl.enter_idle_rval = 1
        >>> evl.watch_file_rval = 2
        >>> ml = MainLoop(w, [], scr, event_loop=evl)
        >>> ml.run()    # doctest:+ELLIPSIS
        screen.start()
        screen.set_mouse_tracking()
        screen.unhook_event_loop(...)
        screen.hook_event_loop(...)
        event_loop.enter_idle()
        event_loop.run()
        event_loop.remove_enter_idle(1)
        screen.unhook_event_loop(...)
        screen.stop()
        >>> ml.draw_screen()    # doctest:+ELLIPSIS
        screen.get_cols_rows()
        widget.render((20, 10), focus=True)
        screen.draw_screen((20, 10), 'fake canvas')
        """

    def start(self) -> StoppingContext:
        """
        Sets up the main loop, hooking into the event loop where necessary.
        Starts the :attr:`screen` if it hasn't already been started.

        If you want to control starting and stopping the event loop yourself,
        you should call this method before starting, and call `stop` once the
        loop has finished.  You may also use this method as a context manager,
        which will stop the loop automatically at the end of the block:

            with main_loop.start():
                ...

        Note that some event loop implementations don't handle exceptions
        specially if you manage the event loop yourself.  In particular, the
        Twisted and asyncio loops won't stop automatically when
        :exc:`ExitMainLoop` (or anything else) is raised.
        """

        self.logger.debug(f"Starting event loop {self.event_loop.__class__.__name__!r} to manage display.")

        self.screen.start()

        if self.handle_mouse:
            self.screen.set_mouse_tracking()

        if not hasattr(self.screen, "hook_event_loop"):
            raise CantUseExternalLoop(f"Screen {self.screen!r} doesn't support external event loops")

        with suppress(NameError):
            signals.connect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED, self._reset_input_descriptors)

        # watch our input descriptors
        self._reset_input_descriptors()
        self.idle_handle = self.event_loop.enter_idle(self.entering_idle)

        # the screen is redrawn automatically after input and alarms,
        # however, there can be none of those at the start,
        # so draw the initial screen here unconditionally
        self.event_loop.alarm(0, self.entering_idle)

        return StoppingContext(self)

    def stop(self) -> None:
        """
        Cleans up any hooks added to the event loop.  Only call this if you're
        managing the event loop yourself, after the loop stops.
        """

        self.event_loop.remove_enter_idle(self.idle_handle)
        del self.idle_handle
        signals.disconnect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED, self._reset_input_descriptors)
        self.screen.unhook_event_loop(self.event_loop)

        self.screen.stop()

    def _reset_input_descriptors(self) -> None:
        self.screen.unhook_event_loop(self.event_loop)
        self.screen.hook_event_loop(self.event_loop, self._update)

    def _run(self) -> None:
        try:
            self.start()
        except CantUseExternalLoop:
            try:
                self._run_screen_event_loop()
                return
            finally:
                self.screen.stop()

        try:
            self.event_loop.run()
        except:
            self.screen.stop()  # clean up screen control
            raise
        self.stop()

    def _update(self, keys: list[str], raw: list[int]) -> None:
        """
        >>> w = _refl("widget")
        >>> w.selectable_rval = True
        >>> w.mouse_event_rval = True
        >>> scr = _refl("screen")
        >>> scr.get_cols_rows_rval = (15, 5)
        >>> evl = _refl("event_loop")
        >>> ml = MainLoop(w, [], scr, event_loop=evl)
        >>> ml._input_timeout = "old timeout"
        >>> ml._update(['y'], [121])    # doctest:+ELLIPSIS
        screen.get_cols_rows()
        widget.selectable()
        widget.keypress((15, 5), 'y')
        >>> ml._update([("mouse press", 1, 5, 4)], [])
        widget.mouse_event((15, 5), 'mouse press', 1, 5, 4, focus=True)
        >>> ml._update([], [])
        """
        keys = self.input_filter(keys, raw)

        if keys:
            self.process_input(keys)
            if "window resize" in keys:
                self.screen_size = None

    def _run_screen_event_loop(self) -> None:
        """
        This method is used when the screen does not support using external event loops.

        The alarms stored in the SelectEventLoop in :attr:`event_loop` are modified by this method.
        """
        # pylint: disable=protected-access  # special case for alarms handling
        self.logger.debug(f"Starting screen {self.screen!r} event loop")

        next_alarm = None

        while True:
            self.draw_screen()

            if not next_alarm and self.event_loop._alarms:
                next_alarm = heapq.heappop(self.event_loop._alarms)

            keys: list[str] = []
            raw: list[int] = []
            while not keys:
                if next_alarm:
                    sec = max(0.0, next_alarm[0] - time.time())
                    self.screen.set_input_timeouts(sec)
                else:
                    self.screen.set_input_timeouts(None)
                keys, raw = self.screen.get_input(True)
                if not keys and next_alarm:
                    sec = next_alarm[0] - time.time()
                    if sec <= 0:
                        break

            keys = self.input_filter(keys, raw)

            if keys:
                self.process_input(keys)

            while next_alarm:
                sec = next_alarm[0] - time.time()
                if sec > 0:
                    break
                _tm, _tie_break, callback = next_alarm
                callback()

                if self.event_loop._alarms:
                    next_alarm = heapq.heappop(self.event_loop._alarms)
                else:
                    next_alarm = None

            if "window resize" in keys:
                self.screen_size = None

    def _test_run_screen_event_loop(self):
        """
        >>> w = _refl("widget")
        >>> scr = _refl("screen")
        >>> scr.get_cols_rows_rval = (10, 5)
        >>> scr.get_input_rval = [], []
        >>> ml = MainLoop(w, screen=scr)
        >>> def stop_now(loop, data):
        ...     raise ExitMainLoop()
        >>> handle = ml.set_alarm_in(0, stop_now)
        >>> try:
        ...     ml._run_screen_event_loop()
        ... except ExitMainLoop:
        ...     pass
        screen.get_cols_rows()
        widget.render((10, 5), focus=True)
        screen.draw_screen((10, 5), None)
        screen.set_input_timeouts(0.0)
        screen.get_input(True)
        """

    def process_input(self, keys: Iterable[str | tuple[str, int, int, int]]) -> bool:
        """
        This method will pass keyboard input and mouse events to :attr:`widget`.
        This method is called automatically from the :meth:`run` method when
        there is input, but may also be called to simulate input from the user.

        *keys* is a list of input returned from :attr:`screen`'s get_input()
        or get_input_nonblocking() methods.

        Returns ``True`` if any key was handled by a widget or the
        :meth:`unhandled_input` method.
        """
        self.logger.debug(f"Processing input: keys={keys!r}")
        if not self.screen_size:
            self.screen_size = self.screen.get_cols_rows()

        something_handled = False

        for key in keys:
            if key == "window resize":
                continue

            if isinstance(key, str):
                if self._topmost_widget.selectable():
                    handled_key = self._topmost_widget.keypress(self.screen_size, key)
                    if not handled_key:
                        something_handled = True
                        continue

                    key = handled_key  # noqa: PLW2901

            elif is_mouse_event(key):
                event, button, col, row = key
                if hasattr(self._topmost_widget, "mouse_event") and self._topmost_widget.mouse_event(
                    self.screen_size,
                    event,
                    button,
                    col,
                    row,
                    focus=True,
                ):
                    something_handled = True
                    continue

            else:
                raise TypeError(f"{key!r} is not str | tuple[str, int, int, int]")

            if key:
                if command_map[key] == Command.REDRAW_SCREEN:
                    self.screen.clear()
                    something_handled = True
                else:
                    something_handled |= bool(self.unhandled_input(key))
            else:
                something_handled = True

        return something_handled

    def _test_process_input(self):
        """
        >>> w = _refl("widget")
        >>> w.selectable_rval = True
        >>> scr = _refl("screen")
        >>> scr.get_cols_rows_rval = (10, 5)
        >>> ml = MainLoop(w, [], scr)
        >>> ml.process_input(['enter', ('mouse drag', 1, 14, 20)])
        screen.get_cols_rows()
        widget.selectable()
        widget.keypress((10, 5), 'enter')
        widget.mouse_event((10, 5), 'mouse drag', 1, 14, 20, focus=True)
        True
        """

    def input_filter(self, keys: list[str], raw: list[int]) -> list[str]:
        """
        This function is passed each all the input events and raw keystroke
        values. These values are passed to the *input_filter* function
        passed to the constructor. That function must return a list of keys to
        be passed to the widgets to handle. If no *input_filter* was
        defined this implementation will return all the input events.
        """
        if self._input_filter:
            return self._input_filter(keys, raw)
        return keys

    def unhandled_input(self, data: str | tuple[str, int, int, int]) -> bool | None:
        """
        This function is called with any input that was not handled by the
        widgets, and calls the *unhandled_input* function passed to the
        constructor. If no *unhandled_input* was defined then the input
        will be ignored.

        *input* is the keyboard or mouse input.

        The *unhandled_input* function should return ``True`` if it handled
        the input.
        """
        if self._unhandled_input:
            return self._unhandled_input(data)
        return False

    def entering_idle(self) -> None:
        """
        This method is called whenever the event loop is about to enter the
        idle state. :meth:`draw_screen` is called here to update the
        screen when anything has changed.
        """
        if self.screen.started:
            self.draw_screen()
        else:
            self.logger.debug(f"No redrawing screen: {self.screen!r} is not started.")

    def draw_screen(self) -> None:
        """
        Render the widgets and paint the screen. This method is called
        automatically from :meth:`entering_idle`.

        If you modify the widgets displayed outside of handling input or
        responding to an alarm you will need to call this method yourself
        to repaint the screen.
        """
        if not self.screen_size:
            self.screen_size = self.screen.get_cols_rows()
            self.logger.debug(f"Screen size recalculated: {self.screen_size!r}")

        canvas = self._topmost_widget.render(self.screen_size, focus=True)
        self.screen.draw_screen(self.screen_size, canvas)


def _refl(name: str, rval=None, loop_exit=False):
    """
    This function is used to test the main loop classes.

    >>> scr = _refl("screen")
    >>> scr.function("argument")
    screen.function('argument')
    >>> scr.callme(when="now")
    screen.callme(when='now')
    >>> scr.want_something_rval = 42
    >>> x = scr.want_something()
    screen.want_something()
    >>> x
    42

    """

    class Reflect:
        def __init__(self, name: str, rval=None):
            self._name = name
            self._rval = rval

        def __call__(self, *argl, **argd):
            args = ", ".join([repr(a) for a in argl])
            if args and argd:
                args = f"{args}, "
            args += ", ".join([f"{k}={v!r}" for k, v in argd.items()])
            print(f"{self._name}({args})")
            if loop_exit:
                raise ExitMainLoop()
            return self._rval

        def __getattr__(self, attr):
            if attr.endswith("_rval"):
                raise AttributeError()
            # print(self._name+"."+attr)
            if hasattr(self, f"{attr}_rval"):
                return Reflect(f"{self._name}.{attr}", getattr(self, f"{attr}_rval"))
            return Reflect(f"{self._name}.{attr}")

    return Reflect(name)


def _test():
    import doctest

    doctest.testmod()


if __name__ == "__main__":
    _test()
urwid-2.6.16/urwid/event_loop/select_loop.py000066400000000000000000000165561470350774000211520ustar00rootroot00000000000000# Urwid main loop code
#    Copyright (C) 2004-2012  Ian Ward
#    Copyright (C) 2008 Walter Mundt
#    Copyright (C) 2009 Andrew Psaltis
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/

"""Select based urwid EventLoop implementation."""

from __future__ import annotations

import contextlib
import heapq
import logging
import selectors
import time
import typing
from contextlib import suppress
from itertools import count

from .abstract_loop import EventLoop, ExitMainLoop

if typing.TYPE_CHECKING:
    from collections.abc import Callable, Iterator
    from concurrent.futures import Executor, Future

    from typing_extensions import Literal, ParamSpec

    _T = typing.TypeVar("_T")
    _Spec = ParamSpec("_Spec")

__all__ = ("SelectEventLoop",)


class SelectEventLoop(EventLoop):
    """
    Event loop based on :func:`selectors.DefaultSelector.select`
    """

    def __init__(self) -> None:
        super().__init__()
        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
        self._alarms: list[tuple[float, int, Callable[[], typing.Any]]] = []
        self._watch_files: dict[int, Callable[[], typing.Any]] = {}
        self._idle_handle: int = 0
        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}
        self._tie_break: Iterator[int] = count()
        self._did_something: bool = False

    def run_in_executor(
        self,
        executor: Executor,
        func: Callable[_Spec, _T],
        *args: _Spec.args,
        **kwargs: _Spec.kwargs,
    ) -> Future[_T]:
        """Run callable in executor.

        :param executor: Executor to use for running the function
        :type executor: concurrent.futures.Executor
        :param func: function to call
        :type func: Callable
        :param args: positional arguments to function
        :type args: object
        :param kwargs: keyword arguments to function
        :type kwargs: object
        :return: future object for the function call outcome.
        :rtype: concurrent.futures.Future
        """
        return executor.submit(func, *args, **kwargs)

    def alarm(
        self,
        seconds: float,
        callback: Callable[[], typing.Any],
    ) -> tuple[float, int, Callable[[], typing.Any]]:
        """
        Call callback() a given time from now.  No parameters are
        passed to callback.

        Returns a handle that may be passed to remove_alarm()

        seconds -- floating point time to wait before calling callback
        callback -- function to call from event loop
        """
        tm = time.time() + seconds
        handle = (tm, next(self._tie_break), callback)
        heapq.heappush(self._alarms, handle)
        return handle

    def remove_alarm(self, handle: tuple[float, int, Callable[[], typing.Any]]) -> bool:
        """
        Remove an alarm.

        Returns True if the alarm exists, False otherwise
        """
        try:
            self._alarms.remove(handle)
            heapq.heapify(self._alarms)

        except ValueError:
            return False

        return True

    def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:
        """
        Call callback() when fd has some data to read.  No parameters
        are passed to callback.

        Returns a handle that may be passed to remove_watch_file()

        fd -- file descriptor to watch for input
        callback -- function to call when input is available
        """
        self._watch_files[fd] = callback
        return fd

    def remove_watch_file(self, handle: int) -> bool:
        """
        Remove an input file.

        Returns True if the input file exists, False otherwise
        """
        if handle in self._watch_files:
            del self._watch_files[handle]
            return True
        return False

    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
        """
        Add a callback for entering idle.

        Returns a handle that may be passed to remove_idle()
        """
        self._idle_handle += 1
        self._idle_callbacks[self._idle_handle] = callback
        return self._idle_handle

    def remove_enter_idle(self, handle: int) -> bool:
        """
        Remove an idle callback.

        Returns True if the handle was removed.
        """
        try:
            del self._idle_callbacks[handle]
        except KeyError:
            return False
        return True

    def _entering_idle(self) -> None:
        """
        Call all the registered idle callbacks.
        """
        for callback in self._idle_callbacks.values():
            callback()

    def run(self) -> None:
        """
        Start the event loop.  Exit the loop when any callback raises
        an exception.  If ExitMainLoop is raised, exit cleanly.
        """
        with contextlib.suppress(ExitMainLoop):
            self._did_something = True
            while True:
                with suppress(InterruptedError):
                    self._loop()

    def _loop(self) -> None:
        """
        A single iteration of the event loop
        """
        tm: float | Literal["idle"] | None = None

        with selectors.DefaultSelector() as selector:
            for fd, callback in self._watch_files.items():
                selector.register(fd, selectors.EVENT_READ, callback)

            if self._alarms or self._did_something:
                timeout = 0.0

                if self._alarms:
                    timeout_ = self._alarms[0][0]
                    tm = timeout_
                    timeout = max(timeout, timeout_ - time.time())

                if self._did_something and (not self._alarms or (self._alarms and timeout > 0)):
                    timeout = 0.0
                    tm = "idle"

                self.logger.debug(f"Waiting for input: timeout={timeout!r}")
                ready = [event for event, _ in selector.select(timeout)]

            elif self._watch_files:
                self.logger.debug("Waiting for input: timeout")
                ready = [event for event, _ in selector.select()]
            else:
                ready = []

        if not ready:
            if tm == "idle":
                self.logger.debug("No input, entering IDLE")
                self._entering_idle()
                self._did_something = False
            elif tm is not None:
                # must have been a timeout
                tm, _tie_break, alarm_callback = heapq.heappop(self._alarms)
                self.logger.debug(f"No input in timeout, calling scheduled {alarm_callback!r}")
                alarm_callback()
                self._did_something = True

        self.logger.debug("Processing input")
        for record in ready:
            record.data()
            self._did_something = True
urwid-2.6.16/urwid/event_loop/tornado_loop.py000066400000000000000000000156741470350774000213410ustar00rootroot00000000000000# Urwid main loop code
#    Copyright (C) 2004-2012  Ian Ward
#    Copyright (C) 2008 Walter Mundt
#    Copyright (C) 2009 Andrew Psaltis
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/

"""Tornado IOLoop based urwid EventLoop implementation.

Tornado library is required.
"""

from __future__ import annotations

import asyncio
import functools
import logging
import typing
from contextlib import suppress

from tornado import ioloop

from .abstract_loop import EventLoop, ExitMainLoop

if typing.TYPE_CHECKING:
    from collections.abc import Callable
    from concurrent.futures import Executor

    from typing_extensions import Literal, ParamSpec

    _Spec = ParamSpec("_Spec")
    _T = typing.TypeVar("_T")

__all__ = ("TornadoEventLoop",)


class TornadoEventLoop(EventLoop):
    """This is an Urwid-specific event loop to plug into its MainLoop.
    It acts as an adaptor for Tornado's IOLoop which does all
    heavy lifting except idle-callbacks.
    """

    def __init__(self, loop: ioloop.IOLoop | None = None) -> None:
        super().__init__()
        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
        if loop:
            self._loop: ioloop.IOLoop = loop
        else:
            try:
                asyncio.get_running_loop()
            except RuntimeError:
                asyncio.set_event_loop(asyncio.new_event_loop())

            self._loop = ioloop.IOLoop.current()

        self._pending_alarms: dict[object, int] = {}
        self._watch_handles: dict[int, int] = {}  # { : }
        self._max_watch_handle: int = 0
        self._exc: BaseException | None = None

        self._idle_asyncio_handle: object | None = None
        self._idle_handle: int = 0
        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}

    def _also_call_idle(self, callback: Callable[_Spec, _T]) -> Callable[_Spec, _T]:
        """
        Wrap the callback to also call _entering_idle.
        """

        @functools.wraps(callback)
        def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T:
            if not self._idle_asyncio_handle:
                self._idle_asyncio_handle = self._loop.call_later(0, self._entering_idle)
            return callback(*args, **kwargs)

        return wrapper

    def _entering_idle(self) -> None:
        """
        Call all the registered idle callbacks.
        """
        try:
            for callback in self._idle_callbacks.values():
                callback()
        finally:
            self._idle_asyncio_handle = None

    def run_in_executor(
        self,
        executor: Executor,
        func: Callable[_Spec, _T],
        *args: _Spec.args,
        **kwargs: _Spec.kwargs,
    ) -> asyncio.Future[_T]:
        """Run callable in executor.

        :param executor: Executor to use for running the function
        :type executor: concurrent.futures.Executor
        :param func: function to call
        :type func: Callable
        :param args: arguments to function (positional only)
        :type args: object
        :param kwargs: keyword arguments to function (keyword only)
        :type kwargs: object
        :return: future object for the function call outcome.
        :rtype: asyncio.Future
        """
        return self._loop.run_in_executor(executor, functools.partial(func, *args, **kwargs))

    def alarm(self, seconds: float, callback: Callable[[], typing.Any]):
        @self._also_call_idle
        @functools.wraps(callback)
        def wrapped() -> None:
            with suppress(KeyError):
                del self._pending_alarms[handle]

            self.handle_exit(callback)()

        handle = self._loop.add_timeout(self._loop.time() + seconds, wrapped)
        self._pending_alarms[handle] = 1
        return handle

    def remove_alarm(self, handle: object) -> bool:
        self._loop.remove_timeout(handle)
        try:
            del self._pending_alarms[handle]
        except KeyError:
            return False

        return True

    def watch_file(self, fd: int, callback: Callable[[], _T]) -> int:
        @self._also_call_idle
        def handler(_fd: int, _events: int) -> None:
            self.handle_exit(callback)()

        self._loop.add_handler(fd, handler, ioloop.IOLoop.READ)
        self._max_watch_handle += 1
        handle = self._max_watch_handle
        self._watch_handles[handle] = fd
        return handle

    def remove_watch_file(self, handle: int) -> bool:
        fd = self._watch_handles.pop(handle, None)
        if fd is None:
            return False

        self._loop.remove_handler(fd)
        return True

    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
        """
        Add a callback for entering idle.

        Returns a handle that may be passed to remove_idle()
        """
        # XXX there's no such thing as "idle" in most event loops; this fakes
        # it by adding extra callback to the timer and file watch callbacks.
        self._idle_handle += 1
        self._idle_callbacks[self._idle_handle] = callback
        return self._idle_handle

    def remove_enter_idle(self, handle: int) -> bool:
        """
        Remove an idle callback.

        Returns True if the handle was removed.
        """
        try:
            del self._idle_callbacks[handle]
        except KeyError:
            return False
        return True

    def handle_exit(self, f: Callable[_Spec, _T]) -> Callable[_Spec, _T | Literal[False]]:
        @functools.wraps(f)
        def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T | Literal[False]:
            try:
                return f(*args, **kwargs)
            except ExitMainLoop:
                pass  # handled later
            except Exception as exc:
                self._exc = exc

            if self._idle_asyncio_handle:
                # clean it up to prevent old callbacks
                # from messing things up if loop is restarted
                self._loop.remove_timeout(self._idle_asyncio_handle)
                self._idle_asyncio_handle = None

            self._loop.stop()
            return False

        return wrapper

    def run(self) -> None:
        self._loop.start()
        if self._exc:
            exc, self._exc = self._exc, None
            raise exc.with_traceback(exc.__traceback__)
urwid-2.6.16/urwid/event_loop/trio_loop.py000066400000000000000000000245411470350774000206410ustar00rootroot00000000000000# Urwid main loop code using Python-3.5 features (Trio, Curio, etc)
#    Copyright (C) 2018 Toshio Kuratomi
#    Copyright (C) 2019 Tamas Nepusz
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/

"""Trio Runner based urwid EventLoop implementation.

Trio library is required.
"""

from __future__ import annotations

import logging
import sys
import typing

import trio

from .abstract_loop import EventLoop, ExitMainLoop

if sys.version_info < (3, 11):
    from exceptiongroup import BaseExceptionGroup  # pylint: disable=redefined-builtin  # backport

if typing.TYPE_CHECKING:
    import io
    from collections.abc import Awaitable, Callable, Hashable, Mapping

    from typing_extensions import Concatenate, ParamSpec

    _Spec = ParamSpec("_Spec")

__all__ = ("TrioEventLoop",)


class _TrioIdleCallbackInstrument(trio.abc.Instrument):
    """IDLE callbacks emulation helper."""

    __slots__ = ("idle_callbacks",)

    def __init__(self, idle_callbacks: Mapping[Hashable, Callable[[], typing.Any]]):
        self.idle_callbacks = idle_callbacks

    def before_io_wait(self, timeout: float) -> None:
        if timeout > 0:
            for idle_callback in self.idle_callbacks.values():
                idle_callback()


class TrioEventLoop(EventLoop):
    """
    Event loop based on the ``trio`` module.

    ``trio`` is an async library for Python 3.5 and later.
    """

    def __init__(self) -> None:
        """Constructor."""
        super().__init__()
        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)

        self._idle_handle = 0
        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}
        self._pending_tasks: list[tuple[Callable[_Spec, Awaitable], trio.CancelScope, _Spec.args]] = []

        self._nursery: trio.Nursery | None = None

        self._sleep = trio.sleep
        self._wait_readable = trio.lowlevel.wait_readable

    def alarm(
        self,
        seconds: float,
        callback: Callable[[], typing.Any],
    ) -> trio.CancelScope:
        """Calls `callback()` a given time from now.

        :param seconds: time in seconds to wait before calling the callback
        :type seconds: float
        :param callback: function to call from the event loop
        :type callback: Callable[[], typing.Any]
        :return: a handle that may be passed to `remove_alarm()`
        :rtype: trio.CancelScope

        No parameters are passed to the callback.
        """
        return self._start_task(self._alarm_task, seconds, callback)

    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
        """Calls `callback()` when the event loop enters the idle state.

        There is no such thing as being idle in a Trio event loop so we
        simulate it by repeatedly calling `callback()` with a short delay.
        """
        self._idle_handle += 1
        self._idle_callbacks[self._idle_handle] = callback
        return self._idle_handle

    def remove_alarm(self, handle: trio.CancelScope) -> bool:
        """Removes an alarm.

        Parameters:
            handle: the handle of the alarm to remove
        """
        return self._cancel_scope(handle)

    def remove_enter_idle(self, handle: int) -> bool:
        """Removes an idle callback.

        Parameters:
            handle: the handle of the idle callback to remove
        """
        try:
            del self._idle_callbacks[handle]
        except KeyError:
            return False
        return True

    def remove_watch_file(self, handle: trio.CancelScope) -> bool:
        """Removes a file descriptor being watched for input.

        Parameters:
            handle: the handle of the file descriptor callback to remove

        Returns:
            True if the file descriptor was watched, False otherwise
        """
        return self._cancel_scope(handle)

    def _cancel_scope(self, scope: trio.CancelScope) -> bool:
        """Cancels the given Trio cancellation scope.

        Returns:
            True if the scope was cancelled, False if it was cancelled already
            before invoking this function
        """
        existed = not scope.cancel_called
        scope.cancel()
        return existed

    def run(self) -> None:
        """Starts the event loop. Exits the loop when any callback raises an
        exception. If ExitMainLoop is raised, exits cleanly.
        """

        emulate_idle_callbacks = _TrioIdleCallbackInstrument(self._idle_callbacks)

        try:
            trio.run(self._main_task, instruments=[emulate_idle_callbacks])
        except BaseException as exc:
            self._handle_main_loop_exception(exc)

    async def run_async(self) -> None:
        """Starts the main loop and blocks asynchronously until the main loop exits.

        This allows one to embed an urwid app in a Trio app even if the Trio event loop is already running.
        Example::

            with trio.open_nursery() as nursery:
                event_loop = urwid.TrioEventLoop()

                # [...launch other async tasks in the nursery...]

                loop = urwid.MainLoop(widget, event_loop=event_loop)
                with loop.start():
                    await event_loop.run_async()

                nursery.cancel_scope.cancel()
        """

        emulate_idle_callbacks = _TrioIdleCallbackInstrument(self._idle_callbacks)

        try:
            trio.lowlevel.add_instrument(emulate_idle_callbacks)
            try:
                await self._main_task()
            finally:
                trio.lowlevel.remove_instrument(emulate_idle_callbacks)
        except BaseException as exc:
            self._handle_main_loop_exception(exc)

    def watch_file(
        self,
        fd: int | io.IOBase,
        callback: Callable[[], typing.Any],
    ) -> trio.CancelScope:
        """Calls `callback()` when the given file descriptor has some data
        to read. No parameters are passed to the callback.

        Parameters:
            fd: file descriptor to watch for input
            callback: function to call when some input is available

        Returns:
            a handle that may be passed to `remove_watch_file()`
        """
        return self._start_task(self._watch_task, fd, callback)

    async def _alarm_task(
        self,
        scope: trio.CancelScope,
        seconds: float,
        callback: Callable[[], typing.Any],
    ) -> None:
        """Asynchronous task that sleeps for a given number of seconds and then
        calls the given callback.

        Parameters:
            scope: the cancellation scope that can be used to cancel the task
            seconds: the number of seconds to wait
            callback: the callback to call
        """
        with scope:
            await self._sleep(seconds)
            callback()

    def _handle_main_loop_exception(self, exc: BaseException) -> None:
        """Handles exceptions raised from the main loop, catching ExitMainLoop
        instead of letting it propagate through.

        Note that since Trio may collect multiple exceptions from tasks into an ExceptionGroup,
        we cannot simply use a try..catch clause, we need a helper function like this.
        """
        self._idle_callbacks.clear()
        if isinstance(exc, BaseExceptionGroup) and len(exc.exceptions) == 1:
            exc = exc.exceptions[0]

        if isinstance(exc, ExitMainLoop):
            return

        raise exc.with_traceback(exc.__traceback__) from None

    async def _main_task(self) -> None:
        """Main Trio task that opens a nursery and then sleeps until the user
        exits the app by raising ExitMainLoop.
        """
        try:
            async with trio.open_nursery() as self._nursery:
                self._schedule_pending_tasks()
                await trio.sleep_forever()
        finally:
            self._nursery = None

    def _schedule_pending_tasks(self) -> None:
        """Schedules all pending asynchronous tasks that were created before
        the nursery to be executed on the nursery soon.
        """
        for task, scope, args in self._pending_tasks:
            self._nursery.start_soon(task, scope, *args)
        del self._pending_tasks[:]

    def _start_task(
        self,
        task: Callable[Concatenate[trio.CancelScope, _Spec], Awaitable],
        *args: _Spec.args,
    ) -> trio.CancelScope:
        """Starts an asynchronous task in the Trio nursery managed by the
        main loop. If the nursery has not started yet, store a reference to
        the task and the arguments so we can start the task when the nursery
        is open.

        Parameters:
            task: a Trio task to run

        Returns:
            a cancellation scope for the Trio task
        """
        scope = trio.CancelScope()
        if self._nursery:
            self._nursery.start_soon(task, scope, *args)
        else:
            self._pending_tasks.append((task, scope, args))
        return scope

    async def _watch_task(
        self,
        scope: trio.CancelScope,
        fd: int | io.IOBase,
        callback: Callable[[], typing.Any],
    ) -> None:
        """Asynchronous task that watches the given file descriptor and calls
        the given callback whenever the file descriptor becomes readable.

        Parameters:
            scope: the cancellation scope that can be used to cancel the task
            fd: the file descriptor to watch
            callback: the callback to call
        """
        with scope:
            # We check for the scope being cancelled before calling
            # wait_readable because if callback cancels the scope, fd might be
            # closed and calling wait_readable with a closed fd does not work.
            while not scope.cancel_called:
                await self._wait_readable(fd)
                callback()
urwid-2.6.16/urwid/event_loop/twisted_loop.py000066400000000000000000000220031470350774000213360ustar00rootroot00000000000000# Urwid main loop code
#    Copyright (C) 2004-2012  Ian Ward
#    Copyright (C) 2008 Walter Mundt
#    Copyright (C) 2009 Andrew Psaltis
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/

"""Twisted Reactor based urwid EventLoop implementation.

Twisted library is required.
"""

from __future__ import annotations

import functools
import logging
import sys
import typing

from twisted.internet.abstract import FileDescriptor
from twisted.internet.error import AlreadyCalled, AlreadyCancelled

from .abstract_loop import EventLoop, ExitMainLoop

if typing.TYPE_CHECKING:
    from collections.abc import Callable
    from concurrent.futures import Executor, Future

    from twisted.internet.base import DelayedCall, ReactorBase
    from typing_extensions import ParamSpec

    _Spec = ParamSpec("_Spec")
    _T = typing.TypeVar("_T")

__all__ = ("TwistedEventLoop",)


class _TwistedInputDescriptor(FileDescriptor):
    def __init__(self, reactor: ReactorBase, fd: int, cb: Callable[[], typing.Any]) -> None:
        self._fileno = fd
        self.cb = cb
        super().__init__(reactor)  # ReactorBase implement full API as required in interfaces

    def fileno(self) -> int:
        return self._fileno

    def doRead(self):
        return self.cb()

    def getHost(self):
        raise NotImplementedError("No network operation expected")

    def getPeer(self):
        raise NotImplementedError("No network operation expected")

    def writeSomeData(self, data: bytes) -> None:
        raise NotImplementedError("Reduced functionality: read-only")


class TwistedEventLoop(EventLoop):
    """
    Event loop based on Twisted_
    """

    _idle_emulation_delay = 1.0 / 256  # a short time (in seconds)

    def __init__(self, reactor: ReactorBase | None = None, manage_reactor: bool = True) -> None:
        """
        :param reactor: reactor to use
        :type reactor: :class:`twisted.internet.reactor`.
        :param: manage_reactor: `True` if you want this event loop to run
                                and stop the reactor.
        :type manage_reactor: boolean

        .. WARNING::
           Twisted's reactor doesn't like to be stopped and run again.  If you
           need to stop and run your :class:`MainLoop`, consider setting
           ``manage_reactor=False`` and take care of running/stopping the reactor
           at the beginning/ending of your program yourself.

           You can also forego using :class:`MainLoop`'s run() entirely, and
           instead call start() and stop() before and after starting the
           reactor.

        .. _Twisted: https://twisted.org/
        """
        super().__init__()
        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
        if reactor is None:
            import twisted.internet.reactor

            reactor = twisted.internet.reactor
        self.reactor: ReactorBase = reactor
        self._watch_files: dict[int, _TwistedInputDescriptor] = {}
        self._idle_handle: int = 0
        self._twisted_idle_enabled = False
        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}
        self._exc: BaseException | None = None
        self.manage_reactor = manage_reactor
        self._enable_twisted_idle()

    def run_in_executor(
        self,
        executor: Executor,
        func: Callable[..., _T],
        *args: object,
        **kwargs: object,
    ) -> Future[_T]:
        raise NotImplementedError(
            "Twisted implement it's own ThreadPool executor. Please use native API for call:\n"
            "'threads.deferToThread(Callable[..., Any], *args, **kwargs)'\n"
            "And use 'addCallback' api for callbacks:\n"
            "'threads.deferToThread(Callable[..., T], *args, **kwargs).addCallback(Callable[[T], None])'"
        )

    def alarm(self, seconds: float, callback: Callable[[], typing.Any]) -> DelayedCall:
        """
        Call callback() a given time from now.  No parameters are
        passed to callback.

        Returns a handle that may be passed to remove_alarm()

        seconds -- floating point time to wait before calling callback
        callback -- function to call from event loop
        """
        handle = self.reactor.callLater(seconds, self.handle_exit(callback))
        return handle

    def remove_alarm(self, handle: DelayedCall) -> bool:
        """
        Remove an alarm.

        Returns True if the alarm exists, False otherwise
        """
        try:
            handle.cancel()

        except (AlreadyCancelled, AlreadyCalled):
            return False

        return True

    def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:
        """
        Call callback() when fd has some data to read.  No parameters
        are passed to callback.

        Returns a handle that may be passed to remove_watch_file()

        fd -- file descriptor to watch for input
        callback -- function to call when input is available
        """
        ind = _TwistedInputDescriptor(self.reactor, fd, self.handle_exit(callback))
        self._watch_files[fd] = ind
        self.reactor.addReader(ind)
        return fd

    def remove_watch_file(self, handle: int) -> bool:
        """
        Remove an input file.

        Returns True if the input file exists, False otherwise
        """
        if handle in self._watch_files:
            self.reactor.removeReader(self._watch_files[handle])
            del self._watch_files[handle]
            return True
        return False

    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
        """
        Add a callback for entering idle.

        Returns a handle that may be passed to remove_enter_idle()
        """
        self._idle_handle += 1
        self._idle_callbacks[self._idle_handle] = callback
        return self._idle_handle

    def _enable_twisted_idle(self) -> None:
        """
        Twisted's reactors don't have an idle or enter-idle callback
        so the best we can do for now is to set a timer event in a very
        short time to approximate an enter-idle callback.

        .. WARNING::
           This will perform worse than the other event loops until we can find a
           fix or workaround
        """
        if self._twisted_idle_enabled:
            return
        self.reactor.callLater(
            self._idle_emulation_delay,
            self.handle_exit(self._twisted_idle_callback, enable_idle=False),
        )
        self._twisted_idle_enabled = True

    def _twisted_idle_callback(self) -> None:
        for callback in self._idle_callbacks.values():
            callback()
        self._twisted_idle_enabled = False

    def remove_enter_idle(self, handle: int) -> bool:
        """
        Remove an idle callback.

        Returns True if the handle was removed.
        """
        try:
            del self._idle_callbacks[handle]
        except KeyError:
            return False
        return True

    def run(self) -> None:
        """
        Start the event loop.  Exit the loop when any callback raises
        an exception.  If ExitMainLoop is raised, exit cleanly.
        """
        if not self.manage_reactor:
            return
        self.reactor.run()
        if self._exc:
            # An exception caused us to exit, raise it now
            exc = self._exc
            self._exc = None
            raise exc.with_traceback(exc.__traceback__)

    def handle_exit(self, f: Callable[_Spec, _T], enable_idle: bool = True) -> Callable[_Spec, _T | None]:
        """
        Decorator that cleanly exits the :class:`TwistedEventLoop` if
        :class:`ExitMainLoop` is thrown inside of the wrapped function. Store the
        exception info if some other exception occurs, it will be reraised after
        the loop quits.

        *f* -- function to be wrapped
        """

        @functools.wraps(f)
        def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T | None:
            rval = None
            try:
                rval = f(*args, **kwargs)
            except ExitMainLoop:
                if self.manage_reactor:
                    self.reactor.stop()
            except BaseException as exc:
                print(sys.exc_info())
                self._exc = exc
                if self.manage_reactor:
                    self.reactor.crash()
            if enable_idle:
                self._enable_twisted_idle()
            return rval

        return wrapper
urwid-2.6.16/urwid/event_loop/zmq_loop.py000066400000000000000000000216321470350774000204710ustar00rootroot00000000000000# Urwid main loop code using ZeroMQ queues
#    Copyright (C) 2019 Dave Jones
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/

"""ZeroMQ based urwid EventLoop implementation.

`ZeroMQ `_ library is required.
"""

from __future__ import annotations

import contextlib
import errno
import heapq
import logging
import os
import time
import typing
from itertools import count

import zmq

from .abstract_loop import EventLoop, ExitMainLoop

if typing.TYPE_CHECKING:
    import io
    from collections.abc import Callable
    from concurrent.futures import Executor, Future

    from typing_extensions import ParamSpec

    ZMQAlarmHandle = typing.TypeVar("ZMQAlarmHandle")
    _T = typing.TypeVar("_T")
    _Spec = ParamSpec("_Spec")


class ZMQEventLoop(EventLoop):
    """
    This class is an urwid event loop for `ZeroMQ`_ applications. It is very
    similar to :class:`SelectEventLoop`, supporting the usual :meth:`alarm`
    events and file watching (:meth:`watch_file`) capabilities, but also
    incorporates the ability to watch zmq queues for events
    (:meth:`watch_queue`).

    .. _ZeroMQ: https://zeromq.org/
    """

    _alarm_break = count()

    def __init__(self) -> None:
        super().__init__()
        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
        self._did_something = True
        self._alarms: list[tuple[float, int, Callable[[], typing.Any]]] = []
        self._poller = zmq.Poller()
        self._queue_callbacks: dict[int, Callable[[], typing.Any]] = {}
        self._idle_handle = 0
        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}

    def run_in_executor(
        self,
        executor: Executor,
        func: Callable[_Spec, _T],
        *args: _Spec.args,
        **kwargs: _Spec.kwargs,
    ) -> Future[_T]:
        """Run callable in executor.

        :param executor: Executor to use for running the function
        :type executor: concurrent.futures.Executor
        :param func: function to call
        :type func: Callable
        :param args: positional arguments to function
        :type args: object
        :param kwargs: keyword arguments to function
        :type kwargs: object
        :return: future object for the function call outcome.
        :rtype: concurrent.futures.Future
        """
        return executor.submit(func, *args, **kwargs)

    def alarm(self, seconds: float, callback: Callable[[], typing.Any]) -> ZMQAlarmHandle:
        """
        Call *callback* a given time from now. No parameters are passed to
        callback. Returns a handle that may be passed to :meth:`remove_alarm`.

        :param float seconds:
            floating point time to wait before calling callback.

        :param callback:
            function to call from event loop.
        """
        handle = (time.time() + seconds, next(self._alarm_break), callback)
        heapq.heappush(self._alarms, handle)
        return handle

    def remove_alarm(self, handle: ZMQAlarmHandle) -> bool:
        """
        Remove an alarm. Returns ``True`` if the alarm exists, ``False``
        otherwise.
        """
        try:
            self._alarms.remove(handle)
            heapq.heapify(self._alarms)

        except ValueError:
            return False

        return True

    def watch_queue(
        self,
        queue: zmq.Socket,
        callback: Callable[[], typing.Any],
        flags: int = zmq.POLLIN,
    ) -> zmq.Socket:
        """
        Call *callback* when zmq *queue* has something to read (when *flags* is
        set to ``POLLIN``, the default) or is available to write (when *flags*
        is set to ``POLLOUT``). No parameters are passed to the callback.
        Returns a handle that may be passed to :meth:`remove_watch_queue`.

        :param queue:
            The zmq queue to poll.

        :param callback:
            The function to call when the poll is successful.

        :param int flags:
            The condition to monitor on the queue (defaults to ``POLLIN``).
        """
        if queue in self._queue_callbacks:
            raise ValueError(f"already watching {queue!r}")
        self._poller.register(queue, flags)
        self._queue_callbacks[queue] = callback
        return queue

    def watch_file(
        self,
        fd: int | io.TextIOWrapper,
        callback: Callable[[], typing.Any],
        flags: int = zmq.POLLIN,
    ) -> io.TextIOWrapper:
        """
        Call *callback* when *fd* has some data to read. No parameters are
        passed to the callback. The *flags* are as for :meth:`watch_queue`.
        Returns a handle that may be passed to :meth:`remove_watch_file`.

        :param fd:
            The file-like object, or fileno to monitor.

        :param callback:
            The function to call when the file has data available.

        :param int flags:
            The condition to monitor on the file (defaults to ``POLLIN``).
        """
        if isinstance(fd, int):
            fd = os.fdopen(fd)
        self._poller.register(fd, flags)
        self._queue_callbacks[fd.fileno()] = callback
        return fd

    def remove_watch_queue(self, handle: zmq.Socket) -> bool:
        """
        Remove a queue from background polling. Returns ``True`` if the queue
        was being monitored, ``False`` otherwise.
        """
        try:
            try:
                self._poller.unregister(handle)
            finally:
                self._queue_callbacks.pop(handle, None)

        except KeyError:
            return False

        return True

    def remove_watch_file(self, handle: io.TextIOWrapper) -> bool:
        """
        Remove a file from background polling. Returns ``True`` if the file was
        being monitored, ``False`` otherwise.
        """
        try:
            try:
                self._poller.unregister(handle)
            finally:
                self._queue_callbacks.pop(handle.fileno(), None)

        except KeyError:
            return False

        return True

    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
        """
        Add a *callback* to be executed when the event loop detects it is idle.
        Returns a handle that may be passed to :meth:`remove_enter_idle`.
        """
        self._idle_handle += 1
        self._idle_callbacks[self._idle_handle] = callback
        return self._idle_handle

    def remove_enter_idle(self, handle: int) -> bool:
        """
        Remove an idle callback. Returns ``True`` if *handle* was removed,
        ``False`` otherwise.
        """
        try:
            del self._idle_callbacks[handle]
        except KeyError:
            return False

        return True

    def _entering_idle(self) -> None:
        for callback in list(self._idle_callbacks.values()):
            callback()

    def run(self) -> None:
        """
        Start the event loop. Exit the loop when any callback raises an
        exception. If :exc:`ExitMainLoop` is raised, exit cleanly.
        """
        with contextlib.suppress(ExitMainLoop):
            while True:
                try:
                    self._loop()
                except zmq.error.ZMQError as exc:  # noqa: PERF203
                    if exc.errno != errno.EINTR:
                        raise

    def _loop(self) -> None:
        """
        A single iteration of the event loop.
        """
        state = "wait"  # default state not expecting any action
        if self._alarms or self._did_something:
            timeout = 0
            if self._alarms:
                state = "alarm"
                timeout = max(0.0, self._alarms[0][0] - time.time())
            if self._did_something and (not self._alarms or (self._alarms and timeout > 0)):
                state = "idle"
                timeout = 0
            ready = dict(self._poller.poll(timeout * 1000))
        else:
            ready = dict(self._poller.poll())

        if not ready:
            if state == "idle":
                self._entering_idle()
                self._did_something = False
            elif state == "alarm":
                _due, _tie_break, callback = heapq.heappop(self._alarms)
                callback()
                self._did_something = True

        for queue in ready:
            self._queue_callbacks[queue]()
            self._did_something = True
urwid-2.6.16/urwid/font.py000066400000000000000000000764501470350774000154350ustar00rootroot00000000000000# Urwid BigText fonts
#    Copyright (C) 2004-2006  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import typing
import warnings
from pprint import pformat

from urwid.canvas import CanvasError, TextCanvas
from urwid.display.escape import SAFE_ASCII_DEC_SPECIAL_RE
from urwid.util import apply_target_encoding, str_util

if typing.TYPE_CHECKING:
    from collections.abc import Iterable, Iterator, Sequence

    from typing_extensions import Literal


def separate_glyphs(gdata: str, height: int) -> tuple[dict[str, tuple[int, list[str]]], bool]:
    """return (dictionary of glyphs, utf8 required)"""
    gl: list[str] = gdata.split("\n")[1:-1]

    if any("\t" in elem for elem in gl):
        raise ValueError(f"Incorrect glyphs data:\n{gdata!r}")

    if len(gl) != height + 1:
        raise ValueError(f"Incorrect glyphs height (expected: {height}):\n{gdata}")

    key_line: str = gl[0]

    character: str | None = None  # current character
    key_index = 0  # index into character key line
    end_col = 0  # column position at end of glyph
    start_col = 0  # column position at start of glyph
    jl: list[int] = [0] * height  # indexes into lines of gdata (gl)
    result: dict[str, tuple[int, list[str]]] = {}
    utf8_required = False
    while True:
        if character is None:
            if key_index >= len(key_line):
                break
            character = key_line[key_index]

        if key_index < len(key_line) and key_line[key_index] == character:
            end_col += str_util.get_char_width(character)
            key_index += 1
            continue

        out: list[str] = []
        y = 0
        fill = 0

        for k, line in enumerate(gl[1:]):
            j: int = jl[k]
            y = 0
            fill = 0
            while y < end_col - start_col:
                if j >= len(line):
                    fill = end_col - start_col - y
                    break
                y += str_util.get_char_width(line[j])
                j += 1
            if y + fill != end_col - start_col:
                raise ValueError(repr((y, fill, end_col)))

            segment = line[jl[k] : j]
            if not SAFE_ASCII_DEC_SPECIAL_RE.match(segment):
                utf8_required = True

            out.append(segment + " " * fill)
            jl[k] = j

        start_col = end_col
        result[character] = (y + fill, out)
        character = None
    return result, utf8_required


def add_font(name: str, cls: FontRegistry) -> None:
    warnings.warn(
        "`add_font` is deprecated, please set 'name' attribute to the font class,"
        " use metaclass keyword argument 'font_name'"
        " or use `Font.register()`",
        DeprecationWarning,
        stacklevel=2,
    )
    cls.register(name)


class FontRegistryWarning(UserWarning):
    """FontRegistry warning."""


class FontRegistry(type):
    """Font registry.

    Store all registered fonts, register during class creation if possible.
    """

    __slots__ = ()

    __registered: typing.ClassVar[dict[str, FontRegistry]] = {}

    def __iter__(cls) -> Iterator[str]:
        """Iterate over registered font names."""
        return iter(cls.__registered)

    def __getitem__(cls, item: str) -> FontRegistry | None:
        """Get font by name if registered."""
        return cls.__registered.get(item)

    def __class_getitem__(mcs, item: str) -> FontRegistry | None:
        """Get font by name if registered.

        This method is needed to get access to font from registry class.
        >>> from urwid.util import set_temporary_encoding
        >>> repr(FontRegistry["a"])
        'None'
        >>> font = FontRegistry["Thin 3x3"]()
        >>> font.height
        3
        >>> with set_temporary_encoding("utf-8"):
        ...     canvas: TextCanvas = font.render("+")
        >>> b'\\n'.join(canvas.text).decode('utf-8')
        '  \\n ┼\\n  '
        """
        return mcs.__registered.get(item)

    @property
    def registered(cls) -> Sequence[str]:
        """Registered font names in alphabetical order."""
        return tuple(sorted(cls.__registered))

    @classmethod
    def as_list(mcs) -> list[tuple[str, FontRegistry]]:
        """List of (font name, font class) tuples."""
        return list(mcs.__registered.items())

    def __new__(
        mcs: type[FontRegistry],
        name: str,
        bases: tuple[type, ...],
        namespace: dict[str, typing.Any],
        **kwds: typing.Any,
    ) -> FontRegistry:
        font_name: str = namespace.setdefault("name", kwds.get("font_name", ""))
        font_class = super().__new__(mcs, name, bases, namespace)
        if font_name:
            if font_name not in mcs.__registered:
                mcs.__registered[font_name] = font_class
            if mcs.__registered[font_name] != font_class:
                warnings.warn(
                    f"{font_name!r} is already registered, please override explicit if required or change name",
                    FontRegistryWarning,
                    stacklevel=2,
                )
        return font_class

    def register(cls, font_name: str) -> None:
        """Register font explicit.

        :param font_name: Font name to use in registration.
        """
        if not font_name:
            raise ValueError('"font_name" is not set.')
        cls.__registered[font_name] = cls


get_all_fonts = FontRegistry.as_list


class Font(metaclass=FontRegistry):
    """Font base class."""

    __slots__ = ("canvas", "char", "utf8_required")

    height: int  # pylint: disable=declare-non-slot
    data: Sequence[str]  # pylint: disable=declare-non-slot
    name: str  # pylint: disable=declare-non-slot

    def __init__(self) -> None:
        if not self.height:
            raise ValueError(f'"height" is invalid: {self.height!r}')
        if not self.data:
            raise ValueError(f'"data" is empty: {self.data!r}')

        self.char: dict[str, tuple[int, list[str]]] = {}
        self.canvas: dict[str, TextCanvas] = {}
        self.utf8_required = False
        if isinstance(self.data, str):
            self.add_glyphs(self._to_text(self.data))

        else:
            data: Iterable[str] = (self._to_text(block) for block in self.data)

            for gdata in data:
                self.add_glyphs(gdata)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}()"

    def __str__(self) -> str:
        """Font description."""
        return f"{self.__class__.__name__}():\n  {self.height!r}\n  {pformat(self.data, indent=4)}"

    @staticmethod
    def _to_text(
        obj: str | bytes,
        encoding: str = "utf-8",
        errors: Literal["strict", "ignore", "replace"] = "strict",
    ) -> str:
        if isinstance(obj, str):
            return obj

        if isinstance(obj, bytes):
            warnings.warn(
                "Bytes based fonts are deprecated, please switch to the text one",
                DeprecationWarning,
                stacklevel=3,
            )
            return obj.decode(encoding, errors)

        raise TypeError(f"{obj!r} is not str|bytes")

    def add_glyphs(self, gdata: str) -> None:
        d, utf8_required = separate_glyphs(gdata, self.height)
        self.char.update(d)
        self.utf8_required |= utf8_required

    def characters(self) -> str:
        return "".join(sorted(self.char))

    def char_width(self, character: str) -> int:
        if character in self.char:
            return self.char[character][0]
        return 0

    def char_data(self, character: str) -> list[str]:
        return self.char[character][1]

    def render(self, character: str) -> TextCanvas:
        if character in self.canvas:
            return self.canvas[character]
        width, line = self.char[character]
        byte_lines = []
        character_set_lines = []
        for d in line:
            t, cs = apply_target_encoding(d)
            byte_lines.append(t)
            character_set_lines.append(cs)

        try:
            canv = TextCanvas(byte_lines, None, character_set_lines, maxcol=width, check_width=False)
        except CanvasError as exc:
            raise CanvasError(f"Failed render of {character!r} from line {line!r}:\n{self}\n:{exc}").with_traceback(
                exc.__traceback__
            ) from exc

        self.canvas[character] = canv
        return canv


# safe_palette = u"┘â”┌└┼─├┤┴┬│"
# more_palette = u"â•║╒╓╔╕╖╗╘╙╚╛╜â•╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬○"
# block_palette = u"â–„#â–ˆ#â–€#â–Œ#â–#â––#â–—#â–˜#â–™#â–š#â–›#â–œ#â–#â–ž#â–Ÿ"


class Thin3x3Font(Font):
    name = "Thin 3x3"
    height = 3
    data = (
        """
000111222333444555666777888999  !
┌─┠┠┌─â”┌─┠ â”┌─ ┌─ ┌─â”┌─â”┌─┠ │
│ │ │ ┌─┘ ─┤└─┼└─â”├─┠ ┼├─┤└─┤  │
└─┘ ┴ └─ └─┘  ┴ ─┘└─┘  ┴└─┘ ─┘  .
""",
        r"""
"###$$$%%%'*++,--.///:;==???[[\\\]]^__`
" ┼┼┌┼â”O /'         /.. _┌─â”┌ \   â”^  `
  ┼┼└┼┠/  * ┼  ─  / ., _ ┌┘│  \  │
    └┼┘/ O    ,  ./       . └   \ ┘ ──
""",
    )


class Thin4x3Font(Font):
    name = "Thin 4x3"
    height = 3
    data = (
        *Thin3x3Font.data,
        """
0000111122223333444455556666777788889999  ####$$$$
┌──┠ ┠┌──â”┌──┠  â”┌── ┌── ┌──â”┌──â”┌──┠  ┼─┼┌┼┼â”
│  │  │ ┌──┘  ─┤└──┼└──â”├──┠  ┼├──┤└──┤   ┼─┼└┼┼â”
└──┘  ┴ └── └──┘   ┴ ──┘└──┘   ┴└──┘ ──┘      └┼┼┘
""",
    )


class Sextant3x3Font(Font):
    name = "Sextant 3x3"
    height = 3
    data = (
        """
   !!!###$$$%%%&&&'''((()))***+++,,,---...///
    ■🬞🬲🬲🬞ðŸ¬ðŸ¬‹ðŸ¬‰ðŸ¬„🬖🬦🬧  🬉 🬞🬅 ðŸ¬ðŸ¬¢ 🬞🬦🬞 🬦            🬖
    🬉 🬇🬛🬛🬞🬰🬗🬞🬅🬭🬦🬈🬖   🬉🬠 🬘 🬇🬨🬈ðŸ¬ðŸ¬¨ðŸ¬‚ 🬭 ðŸ¬ðŸ¬‚🬂 🬭 🬞🬅
    🬠 🬀🬀 🬠  🬂 🬂🬠   🬠🬠        🬅     🬂
""",
        """
000111222333444555666777888999
🬦🬂🬧🬞🬫 🬇🬂🬧ðŸ¬ðŸ¬‚🬧 🬞🬫â–🬂🬂🬞🬅🬀ðŸ¬ðŸ¬‚🬙🬦🬂🬧🬦🬂🬧
â–ðŸ¬â– ■🬞🬅🬀 🬂🬧🬇🬌🬫ðŸ¬ðŸ¬‚🬧â–🬂🬧 🬔 🬦🬂🬧 🬂🬙
 🬂🬀 🬠ðŸ¬ðŸ¬‚🬂ðŸ¬ðŸ¬‚🬀  ðŸ¬ðŸ¬ðŸ¬‚🬀 🬂🬀 🬀  🬂🬀 🬂
""",
        """
\"\"\"
 🬄🬄


""",
        """
:::;;;<<<===>>>???@@@
 🬭  🬭  🬖🬀   ðŸ¬ðŸ¬¢ 🬇🬂🬧🬦🬂🬧
 🬰  🬰 ðŸ¬ðŸ¬¢ 🬠🬰🬰 🬖🬀 🬇🬀â–🬉🬅
 🬂  🬅   🬀   🬠  🬠 🬂🬀
""",
        """
AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQ
🬞🬅🬢â–🬂🬧🬦🬂🬈â–🬂🬧â–🬂🬂â–🬂🬂🬦🬂🬈■■🬨🬀  â–â–🬞🬅■ â–🬢🬫â–🬢â–🬦🬂🬧â–🬂🬧🬦🬂🬧
â–🬋🬫â–🬂🬧■🬞■â–â–🬂 â–🬂 ■🬨â–🬂🬨 ■🬞 â–â–🬈ðŸ¬â–  â–ðŸ¬â–■🬨■â–â–🬂🬀â–🬇🬘
🬠ðŸ¬ðŸ¬ðŸ¬‚🬀 🬂🬀ðŸ¬ðŸ¬‚🬀ðŸ¬ðŸ¬‚🬂🬠  🬂🬂🬠🬠🬂🬀 🬂🬀🬠ðŸ¬ðŸ¬ðŸ¬‚🬂🬠ðŸ¬ðŸ¬ 🬠🬂🬀🬠  🬂ðŸ¬
""",
        """
RRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[]]]^^^___```
â–🬂🬧🬦🬂🬈ðŸ¬ðŸ¬¨ðŸ¬‚â– â–â– â–â– â–🬉ðŸ¬ðŸ¬˜â– â–ðŸ¬ðŸ¬‚🬙 🬕🬀 🬂▌🬞🬅🬢    🬈ðŸ¬
â–🬊ðŸ¬ðŸ¬žðŸ¬‚🬧 â– â– â–🬉ðŸ¬ðŸ¬˜â–🬖🬷🬞🬅🬢 🬧🬀🬞🬅  â–Œ   â–Œ
🬠🬠🬂🬀 🬠 🬂🬀 🬠🬠ðŸ¬ðŸ¬ 🬠🬠ðŸ¬ðŸ¬‚🬂 🬂🬀 🬂🬀   🬂🬂🬂
""",
        """
\\\\\\
🬇ðŸ¬
 ðŸ¬ðŸ¬¢

""",
    )


class Sextant2x2Font(Font):
    name = "Sextant 2x2"
    height = 2
    data = """
..,,%%00112233445566778899
    ðŸ¬ðŸ¬–â–🬨🬇▌ðŸ¬ðŸ¬—🬠🬸🬦â–â–🬒â–🬭ðŸ¬ðŸ¬™â–🬸â–🬸
🬇 🬇🬀ðŸ¬ðŸ¬‡ðŸ¬‰ðŸ¬ 🬄🬉🬋🬇ðŸ¬ðŸ¬ðŸ¬ŠðŸ¬‡ðŸ¬…🬉🬠🬄🬉🬠🬉
"""


class HalfBlock5x4Font(Font):
    name = "Half Block 5x4"
    height = 4
    data = (
        """
00000111112222233333444445555566666777778888899999  !!
▄▀▀▄  ▄█  ▄▀▀▄ ▄▀▀▄ ▄  █ █▀▀▀ ▄▀▀  ▀▀▀█ ▄▀▀▄ ▄▀▀▄   █
â–ˆ  â–ˆ   â–ˆ    â–„â–€   â–„â–€ █▄▄█ █▄▄  █▄▄    â–â–Œ ▀▄▄▀ ▀▄▄█   â–ˆ
â–ˆ  â–ˆ   â–ˆ  â–„â–€   â–„  â–ˆ    â–ˆ    â–ˆ â–ˆ  â–ˆ   â–ˆ  â–ˆ  â–ˆ    â–ˆ   â–€
 ▀▀   ▀▀▀ ▀▀▀▀  ▀▀     ▀ ▀▀▀   ▀▀    ▀   ▀▀   ▀▀    ▀
""",
        '''
"""######$$$$$$%%%%%&&&&&((()))******++++++,,,-----..////::;;;
â–ˆâ–â–Œ â–ˆ â–ˆ  ▄▀█▀▄ â–â–Œâ–â–Œ ▄▀▄   â–ˆ â–ˆ   â–„ â–„    â–„              â–â–Œ
   ▀█▀█▀ ▀▄█▄    â–ˆ  ▀▄▀  â–â–Œ â–â–Œ ▄▄█▄▄ ▄▄█▄▄    â–„â–„â–„â–„    â–ˆ  â–€  â–€
   ▀█▀█▀ â–„ â–ˆ â–ˆ  â–▌▄ â–ˆ ▀▄▌â–â–Œ â–â–Œ  ▄▀▄    â–ˆ             â–â–Œ  â–€ â–„â–€
    ▀ ▀   ▀▀▀   ▀ ▀  ▀▀   ▀ ▀              ▄▀      ▀ ▀
''',
        r"""
<<<<<=====>>>>>?????@@@@@@[[[[\\\\]]]]^^^^____```{{{{||}}}}~~~~''```
  â–„â–€      ▀▄   ▄▀▀▄ ▄▀▀▀▄ █▀▀ â–â–Œ  ▀▀█ ▄▀▄     ▀▄  â–„â–€ â–ˆ ▀▄   â–„  â–ˆ â–„â–€
â–„â–€   ▀▀▀▀   ▀▄   â–„â–€ â–ˆ █▀█ â–ˆ    â–ˆ    â–ˆ            â–„â–€  â–ˆ  ▀▄ â–â–▌▌
 ▀▄  ▀▀▀▀  â–„â–€    â–€  â–ˆ ▀▀▀ â–ˆ    â–â–Œ   â–ˆ             â–ˆ  â–ˆ  â–ˆ    â–€
   ▀      ▀      ▀   ▀▀▀  ▀▀▀   ▀ ▀▀▀     ▀▀▀▀     ▀ ▀ ▀
""",
        """
AAAAABBBBBCCCCCDDDDDEEEEEFFFFFGGGGGHHHHHIIJJJJJKKKKK
▄▀▀▄ █▀▀▄ ▄▀▀▄ █▀▀▄ █▀▀▀ █▀▀▀ ▄▀▀▄ █  █ █    █ █  █
█▄▄█ █▄▄▀ █    █  █ █▄▄  █▄▄  █    █▄▄█ █    █ █▄▀
█  █ █  █ █  ▄ █  █ █    █    █ ▀█ █  █ █ ▄  █ █ ▀▄
▀  ▀ ▀▀▀   ▀▀  ▀▀▀  ▀▀▀▀ ▀     ▀▀  ▀  ▀ ▀  ▀▀  ▀  ▀
""",
        """
LLLLLMMMMMMNNNNNOOOOOPPPPPQQQQQRRRRRSSSSSTTTTT
█    █▄ ▄█ ██ █ ▄▀▀▄ █▀▀▄ ▄▀▀▄ █▀▀▄ ▄▀▀▄ ▀▀█▀▀
â–ˆ    â–ˆ â–€ â–ˆ â–ˆâ–▌█ â–ˆ  â–ˆ █▄▄▀ â–ˆ  â–ˆ █▄▄▀ ▀▄▄    â–ˆ
█    █   █ █ ██ █  █ █    █ ▌█ █  █ ▄  █   █
▀▀▀▀ ▀   ▀ ▀  ▀  ▀▀  ▀     ▀▀▌ ▀  ▀  ▀▀    ▀
""",
        """
UUUUUVVVVVVWWWWWWXXXXXXYYYYYYZZZZZ
█  █ █   █ █   █ █   █ █   █ ▀▀▀█
â–ˆ  â–ˆ â–â–Œ â–â–Œ â–ˆ â–„ â–ˆ  ▀▄▀   ▀▄▀   â–„â–€
â–ˆ  â–ˆ  â–ˆ â–ˆ  â–▌█â–â–Œ â–„â–€ ▀▄   â–ˆ   â–ˆ
 ▀▀    ▀    ▀ ▀  ▀   ▀   ▀   ▀▀▀▀
""",
        """
aaaaabbbbbcccccdddddeeeeeffffggggghhhhhiijjjjkkkkk
     █            █       ▄▀▀     █    ▄   ▄ █
 ▀▀▄ █▀▀▄ ▄▀▀▄ ▄▀▀█ ▄▀▀▄ ▀█▀ ▄▀▀▄ █▀▀▄ ▄   ▄ █ ▄▀
▄▀▀█ █  █ █  ▄ █  █ █▀▀   █  ▀▄▄█ █  █ █   █ █▀▄
 ▀▀▀ ▀▀▀   ▀▀   ▀▀▀  ▀▀   ▀   ▄▄▀ ▀  ▀ ▀ ▄▄▀ ▀  ▀
""",
        """
llmmmmmmnnnnnooooopppppqqqqqrrrrssssstttt
â–ˆ                                     â–ˆ
█ █▀▄▀▄ █▀▀▄ ▄▀▀▄ █▀▀▄ ▄▀▀█ █▀▀ ▄▀▀▀ ▀█▀
█ █ █ █ █  █ █  █ █  █ █  █ █    ▀▀▄  █
▀ ▀   ▀ ▀  ▀  ▀▀  █▀▀   ▀▀█ ▀   ▀▀▀    ▀
""",
        """
uuuuuvvvvvwwwwwwxxxxxxyyyyyzzzzz

█  █ █  █ █ ▄ █ ▀▄ ▄▀ █  █ ▀▀█▀
â–ˆ  â–ˆ â–â–Œâ–â–Œ â–▌█â–â–Œ  ▄▀▄  ▀▄▄█ â–„â–€
 ▀▀   ▀▀   ▀ ▀  ▀   ▀  ▄▄▀ ▀▀▀▀
""",
    )


class HalfBlock6x5Font(Font):
    name = "Half Block 6x5"
    height = 5
    data = """
000000111111222222333333444444555555666666777777888888999999  ..::////
▄▀▀▀▄  ▄█   ▄▀▀▀▄ ▄▀▀▀▄ ▄  █  █▀▀▀▀ ▄▀▀▀  ▀▀▀▀█ ▄▀▀▀▄ ▄▀▀▀▄         █
â–ˆ   â–ˆ   â–ˆ       â–ˆ     â–ˆ â–ˆ  â–ˆ  â–ˆ     â–ˆ        â–â–Œ â–ˆ   â–ˆ â–ˆ   â–ˆ     â–€  â–â–Œ
█   █   █     ▄▀    ▀▀▄ ▀▀▀█▀ ▀▀▀▀▄ █▀▀▀▄    █  ▄▀▀▀▄  ▀▀▀█     ▄  █
â–ˆ   â–ˆ   â–ˆ   â–„â–€    â–„   â–ˆ    â–ˆ      â–ˆ â–ˆ   â–ˆ   â–â–Œ  â–ˆ   â–ˆ     â–ˆ       â–â–Œ
 ▀▀▀   ▀▀▀  ▀▀▀▀▀  ▀▀▀     ▀  ▀▀▀▀   ▀▀▀    ▀    ▀▀▀   ▀▀▀    ▀   ▀
"""


class HalfBlockHeavy6x5Font(Font):
    name = "Half Block Heavy 6x5"
    height = 5
    data = """
000000111111222222333333444444555555666666777777888888999999  ..::////
▄███▄  â–█▌  ▄███▄ ▄███▄    █▌ █████ ▄███▄ █████ ▄███▄ ▄███▄         █▌
█▌ â–â–ˆ  ▀█▌  â–€  â–â–ˆ â–€  â–â–ˆ █▌ █▌ █▌    █▌       █▌ █▌ â–â–ˆ █▌ â–â–ˆ     █▌ â–â–ˆ
█▌ â–â–ˆ   █▌    ▄█▀   ██▌ █████ ████▄ ████▄   â–â–ˆ  â–███▌ ▀████        █▌
█▌ â–â–ˆ   █▌  ▄█▀   â–„  â–â–ˆ    █▌    â–â–ˆ █▌ â–â–ˆ   █▌  █▌ â–â–ˆ    â–â–ˆ     █▌â–â–ˆ
▀███▀  ███▌ █████ ▀███▀    █▌ ████▀ ▀███▀  â–â–ˆ   ▀███▀ ▀███▀   █▌  █▌
"""


class Thin6x6Font(Font):
    name = "Thin 6x6"
    height = 6
    data = (
        """
000000111111222222333333444444555555666666777777888888999999''
┌───┠  ┠  ┌───┠┌───┠   ┠ ┌───  ┌───  ┌───┠┌───┠┌───┠│
│   │   │       │     │ ┌  │  │     │         │ │   │ │   │
│ / │   │   ┌───┘    ─┤ └──┼─ └───┠├───┠    ┼ ├───┤ └───┤
│   │   │   │         │    │      │ │   │     │ │   │     │
└───┘   ┴   └───  └───┘    ┴   ───┘ └───┘     ┴ └───┘  ───┘

""",
        r'''
!!   """######$$$$$$%%%%%%&&&&&&((()))******++++++
│    ││  ┌ ┌  ┌─┼─┠┌┠ /  ┌─┠  / \
│       ─┼─┼─ │ │   └┘ /   │ │  │   │  \ /    │
│        │ │  └─┼─┠  /   ┌─\┘  │   │ ──X── ──┼──
│       ─┼─┼─   │ │  / ┌┠│  \, │   │  / \    │
.        ┘ ┘  └─┼─┘ /  └┘ └───\  \ /

''',
        r"""
,,-----..//////::;;<<<<=====>>>>??????@@@@@@
             /                  ┌───┠┌───â”
            /  . .   / ──── \       │ │┌──┤
  ────     /        /        \    ┌─┘ ││  │
          /    . ,  \  ────  /    │   │└──┘
,      . /           \      /     .   └───┘

""",
        r"""
[[\\\\\\]]^^^____``{{||}}~~~~~~
┌ \     â” /\     \ ┌ │ â”
│  \    │          │ │ │ ┌─â”
│   \   │          ┤ │ ├   └─┘
│    \  │          │ │ │
└     \ ┘    ────  └ │ ┘

""",
        """
AAAAAABBBBBBCCCCCCDDDDDDEEEEEEFFFFFFGGGGGGHHHHHHIIJJJJJJ
┌───┠┬───┠┌───┠┬───┠┬───┠┬───┠┌───┠┬   ┬ ┬     ┬
│   │ │   │ │     │   │ │     │     │     │   │ │     │
├───┤ ├───┤ │     │   │ ├──   ├──   │ ──┬ ├───┤ │     │
│   │ │   │ │     │   │ │     │     │   │ │   │ │ ┬   │
┴   ┴ ┴───┘ └───┘ ┴───┘ ┴───┘ ┴     └───┘ ┴   ┴ ┴ └───┘

""",
        """
KKKKKKLLLLLLMMMMMMNNNNNNOOOOOOPPPPPPQQQQQQRRRRRRSSSSSS
┬   ┬ ┬     ┌─┬─┠┬─┠┬ ┌───┠┬───┠┌───┠┬───┠┌───â”
│ ┌─┘ │     │ │ │ │ │ │ │   │ │   │ │   │ │   │ │
├─┴┠ │     │ │ │ │ │ │ │   │ ├───┘ │   │ ├─┬─┘ └───â”
│  └┠│     │   │ │ │ │ │   │ │     │  â”│ │ └─┠    │
┴   ┴ ┴───┘ ┴   ┴ ┴ └─┴ └───┘ ┴     └──┼┘ ┴   ┴ └───┘
                                       â””
""",
        """
TTTTTTUUUUUUVVVVVVWWWWWWXXXXXXYYYYYYZZZZZZ
┌─┬─┠┬   ┬ ┬   ┬ ┬   ┬ ┬   ┬ ┬   ┬ ┌───â”
  │   │   │ │   │ │   │ └┠┌┘ │   │   ┌─┘
  │   │   │ │   │ │ │ │  ├─┤  └─┬─┘  ┌┘
  │   │   │ └┠┌┘ │ │ │ ┌┘ └┠  │   ┌┘
  ┴   └───┘  └─┘  └─┴─┘ ┴   ┴   ┴   └───┘

""",
        """
aaaaaabbbbbbccccccddddddeeeeeefffgggggghhhhhhiijjj
                              ┌─â”
      │               │       │        │     .  .
┌───┠├───┠┌───┠┌───┤ ┌───┠┼  ┌───┠├───┠┠ â”
┌───┤ │   │ │     │   │ ├───┘ │  │   │ │   │ │  │
└───┴ └───┘ └───┘ └───┘ └───┘ ┴  └───┤ ┴   ┴ ┴  │
                                 └───┘         ─┘
""",
        """
kkkkkkllmmmmmmnnnnnnooooooppppppqqqqqqrrrrrssssss

│     │
│ ┌─  │ ┬─┬─┠┬───┠┌───┠┌───┠┌───┠┬──┠┌───â”
├─┴┠ │ │ │ │ │   │ │   │ │   │ │   │ │    └───â”
┴  └─ └ ┴   ┴ ┴   ┴ └───┘ ├───┘ └───┤ ┴    └───┘
                          │         │
""",
        """
ttttuuuuuuvvvvvvwwwwwwxxxxxxyyyyyyzzzzzz

 │
─┼─ ┬   ┬ ┬   ┬ ┬   ┬ ─┠┌─ ┬   ┬ ────┬
 │  │   │ └┠┌┘ │ │ │  ├─┤  │   │ ┌───┘
 └─ └───┴  └─┘  └─┴─┘ ─┘ └─ └───┤ ┴────
                            └───┘
""",
    )


class HalfBlock7x7Font(Font):
    name = "Half Block 7x7"
    height = 7
    data = (
        """
0000000111111122222223333333444444455555556666666777777788888889999999'''
 ▄███▄   â–█▌   ▄███▄  ▄███▄     █▌ â–█████▌ ▄███▄ â–█████▌ ▄███▄  ▄███▄ â–â–ˆ
â–â–ˆ   █▌  ▀█▌  â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ  █▌ â–â–ˆ     â–â–ˆ         â–â–ˆ â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ
â–â–ˆ ■█▌   █▌       █▌   â–██ â–█████▌â–████▄ â–████▄     █▌  █████  ▀████▌
â–â–ˆ â–Œ █▌   █▌     ▄█▀      █▌    █▌      █▌â–â–ˆ   █▌   â–â–ˆ  â–â–ˆ   █▌     █▌
â–â–ˆ   █▌   █▌   ▄█▀   â–â–ˆ   █▌    █▌      █▌â–â–ˆ   █▌   █▌  â–â–ˆ   █▌     █▌
 ▀███▀   ███▌ â–█████▌ ▀███▀     █▌ â–████▀  ▀███▀   â–â–ˆ    ▀███▀  ▀███▀

""",
        '''
!!!   """""#######$$$$$$$%%%%%%%&&&&&&&(((())))*******++++++
â–â–ˆ    â–â–ˆ █▌ â–â–ˆ █▌    â–ˆ    â–„  █▌   ▄█▄    █▌â–â–ˆ   â–„â–„ â–„â–„
â–â–ˆ    â–â–ˆ █▌â–█████▌ ▄███▄ â–█▌â–â–ˆ   â–â–ˆ █▌  â–â–ˆ  █▌  ▀█▄█▀   â–â–ˆ
â–â–ˆ          â–â–ˆ █▌ â–█▄█▄▄  â–€ █▌    ███   █▌  â–â–ˆ â–█████▌ ████▌
â–â–ˆ         â–█████▌ ▀▀█▀█▌  â–â–ˆ â–„  ███▌▄  █▌  â–â–ˆ  ▄█▀█▄   â–â–ˆ
            â–â–ˆ █▌  ▀███▀   █▌â–█▌â–â–ˆ  █▌  â–â–ˆ  █▌  ▀▀ ▀▀
â–â–ˆ                   â–ˆ    â–â–ˆ  â–€  ▀██▀█▌  █▌â–â–ˆ

''',
        """
,,,------.../////:::;;;<<<<<<<======>>>>>>>???????@@@@@@@
               █▌          ▄█▌      â–█▄     ▄███▄  ▄███▄
              â–â–ˆ â–â–ˆ â–â–ˆ   ▄█▀  â–████▌  ▀█▄  â–â–ˆ   █▌â–â–ˆ ▄▄█▌
   â–████▌     █▌       â–██              ██▌    █▌ â–â–ˆâ–█▀█▌
             â–â–ˆ  â–â–ˆ â–â–ˆ   ▀█▄  â–████▌  ▄█▀     █▌  â–â–ˆâ–█▄█▌
             █▌     â–€      ▀█▌      â–█▀           â–â–ˆ ▀▀▀
â–â–ˆ       â–â–ˆ â–â–ˆ                                █▌   ▀███▀
â–€
""",
        r"""
[[[[\\\\\]]]]^^^^^^^_____```{{{{{|||}}}}}~~~~~~~```
â–██▌â–â–ˆ   â–██▌  â–█▌       â–â–ˆ    █▌â–â–ˆ â–â–ˆ           █▌
â–â–ˆ   █▌    █▌ â–â–ˆ █▌       █▌  █▌ â–â–ˆ  â–â–ˆ   â–„â–„    â–â–ˆ
â–â–ˆ   â–â–ˆ    █▌â–â–ˆ   █▌         ▄█▌ â–â–ˆ  â–█▄ â–▀▀█▄▄▌
â–â–ˆ    █▌   █▌                ▀█▌ â–â–ˆ  â–█▀     ▀▀
â–â–ˆ    â–â–ˆ   █▌                 █▌ â–â–ˆ  â–â–ˆ
â–██▌   █▌â–██▌       █████      █▌â–â–ˆ â–â–ˆ

""",
        """
AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIJJJJJJJ
 ▄███▄ â–████▄  ▄███▄ â–████▄ â–█████▌â–█████▌ ▄███▄ â–â–ˆ   █▌ ██▌     █▌
â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ     â–â–ˆ   █▌â–â–ˆ     â–â–ˆ     â–â–ˆ     â–â–ˆ   █▌ â–â–ˆ      █▌
â–█████▌â–█████ â–â–ˆ     â–â–ˆ   █▌â–████  â–████  â–â–ˆ     â–█████▌ â–â–ˆ      █▌
â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ     â–â–ˆ   █▌â–â–ˆ     â–â–ˆ     â–â–ˆ  ██▌â–â–ˆ   █▌ â–â–ˆ      █▌
â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ     â–â–ˆ   █▌â–â–ˆ     â–â–ˆ     â–â–ˆ   █▌â–â–ˆ   █▌ â–â–ˆ â–â–ˆ   █▌
â–â–ˆ   █▌â–████▀  ▀███▀ â–████▀ â–█████▌â–â–ˆ      ▀███▀ â–â–ˆ   █▌ ██▌ ▀███▀

""",
        """
KKKKKKKLLLLLLLMMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSS
â–â–ˆ   █▌â–â–ˆ      ▄█▌â–█▄ â–██  █▌ ▄███▄ â–████▄  ▄███▄ â–████▄  ▄███▄
â–â–ˆ  █▌ â–â–ˆ     â–â–ˆ â–â–Œ █▌â–██▌ █▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ
â–█▄█▌  â–â–ˆ     â–â–ˆ â–â–Œ █▌â–â–ˆâ–â–ˆ █▌â–â–ˆ   █▌â–████▀ â–â–ˆ   █▌â–█████  ▀███▄
â–█▀█▌  â–â–ˆ     â–â–ˆ    █▌â–â–ˆ █▌█▌â–â–ˆ   █▌â–â–ˆ     â–â–ˆ   █▌â–â–ˆ   █▌     █▌
â–â–ˆ  █▌ â–â–ˆ     â–â–ˆ    █▌â–â–ˆ â–██▌â–â–ˆ   █▌â–â–ˆ     â–â–ˆ █▌█▌â–â–ˆ   █▌     █▌
â–â–ˆ   █▌â–█████▌â–â–ˆ    █▌â–â–ˆ  ██▌ ▀███▀ â–â–ˆ      ▀███▀ â–â–ˆ   █▌ ▀███▀
                                               ▀▀
""",
        """
TTTTTTTUUUUUUUVVVVVVVWWWWWWWWXXXXXXXYYYYYYYZZZZZZZ
 █████▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ    █▌â–â–ˆ   █▌ █▌  █▌â–█████▌
   █▌  â–â–ˆ   █▌ █▌ â–â–ˆ â–â–ˆ    █▌ â–â–ˆ █▌  â–â–ˆ â–â–ˆ     █▌
   █▌  â–â–ˆ   █▌ â–â–ˆ █▌ â–â–ˆ    █▌  â–█▌    â–██     █▌
   █▌  â–â–ˆ   █▌  ███  â–â–ˆ â–â–Œ █▌  ███     █▌    █▌
   █▌  â–â–ˆ   █▌  â–█▌  â–â–ˆ â–â–Œ █▌ █▌ â–â–ˆ    █▌   █▌
   █▌   ▀███▀    â–ˆ    ▀█▌â–█▀ â–â–ˆ   █▌   █▌  â–█████▌

""",
        """
aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffggggggghhhhhhhiiijjjj
       â–â–ˆ                 █▌         ▄█▌       â–â–ˆ      █▌  █▌
       â–â–ˆ                 █▌        â–â–ˆ         â–â–ˆ
 ▄███▄ â–████▄  ▄███▄  ▄████▌ ▄███▄ â–███  ▄███▄ â–████▄ â–█▌ â–█▌
  ▄▄▄█▌â–â–ˆ   █▌â–â–ˆ     â–â–ˆ   █▌â–█▄▄▄█▌ â–â–ˆ  â–â–ˆ   █▌â–â–ˆ   █▌ █▌  █▌
â–█▀▀▀█▌â–â–ˆ   █▌â–â–ˆ     â–â–ˆ   █▌â–█▀▀▀   â–â–ˆ  â–█▄▄▄█▌â–â–ˆ   █▌ █▌  █▌
 ▀████▌â–████▀  ▀███▀  ▀████▌ ▀███▀  â–â–ˆ    ▀▀▀█▌â–â–ˆ   █▌ █▌  █▌
                                         ▀███▀           â–██
""",
        """
kkkkkkkllllmmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrsssssss
â–â–ˆ      ██
â–â–ˆ      â–â–ˆ
â–â–ˆ  ▄█▌ â–â–ˆ  ▄█▌â–█▄ â–████▄  ▄███▄ â–████▄  ▄████▌ ▄███▌ ▄███▄
â–█▄█▀   â–â–ˆ â–â–ˆ â–â–Œ █▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ    â–█▄▄▄
â–█▀▀█▄  â–â–ˆ â–â–ˆ â–â–Œ █▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ      ▀▀▀█▌
â–â–ˆ   █▌ â–█▌â–â–ˆ    █▌â–â–ˆ   █▌ ▀███▀ â–████▀  ▀████▌â–â–ˆ     ▀███▀
                                 â–â–ˆ          █▌
""",
        """
tttttuuuuuuuvvvvvvvwwwwwwwwxxxxxxxyyyyyyyzzzzzzz
  █▌
  █▌
 ███▌â–â–ˆ   █▌â–â–ˆ   █▌â–â–ˆ    █▌â–â–ˆ   █▌â–â–ˆ   █▌â–█████▌
  █▌ â–â–ˆ   █▌ █▌ â–â–ˆ â–â–ˆ    █▌ ▀█▄█▀ â–â–ˆ   █▌   ▄█▀
  █▌ â–â–ˆ   █▌  ███  â–â–ˆ â–â–Œ █▌ ▄█▀█▄ â–█▄▄▄█▌ ▄█▀
  █▌  ▀███▀   â–█▌   ▀█▌â–█▀ â–â–ˆ   █▌  ▀▀▀█▌â–█████▌
                                   ▀███▀
""",
    )


if __name__ == "__main__":
    all_ascii = frozenset(chr(x) for x in range(32, 127))
    print("Available Fonts:     (U) = UTF-8 required")
    print("----------------")
    for n, font_cls in get_all_fonts():
        f = font_cls()
        u = ""
        if f.utf8_required:
            u = "(U)"
        print(f"{n:<20} {u:>3} ", end=" ")
        chars = f.characters()
        font_chars = frozenset(chars)
        if font_chars == all_ascii:
            print("Full ASCII")
        elif font_chars & all_ascii == all_ascii:
            print(f"Full ASCII + {''.join(font_chars ^ all_ascii)!r}")
        else:
            print(f"Characters: {chars!r}")
urwid-2.6.16/urwid/graphics.py000066400000000000000000000046611470350774000162620ustar00rootroot00000000000000# Urwid graphics widgets
#    Copyright (C) 2004-2011  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

from urwid.display import AttrSpec
from urwid.widget import (
    BarGraph,
    BarGraphError,
    BarGraphMeta,
    BigText,
    GraphVScale,
    LineBox,
    ProgressBar,
    Sizing,
    Text,
    Widget,
    fixed_size,
    scale_bar_values,
)

__all__ = (
    "BarGraph",
    "BarGraphError",
    "BarGraphMeta",
    "BigText",
    "GraphVScale",
    "LineBox",
    "ProgressBar",
    "scale_bar_values",
)


class PythonLogo(Widget):
    _sizing = frozenset([Sizing.FIXED])

    def __init__(self) -> None:
        """
        Create canvas containing an ASCII version of the Python
        Logo and store it.
        """
        super().__init__()
        blu = AttrSpec("light blue", "default")
        yel = AttrSpec("yellow", "default")
        width = 17
        # fmt: off
        self._canvas = Text(
            [
                (blu, "     ______\n"),
                (blu, "   _|_o__  |"), (yel, "__\n"),
                (blu, "  |   _____|"), (yel, "  |\n"),
                (blu, "  |__|  "), (yel, "______|\n"),
                (yel, "     |____o_|"),
            ]
        ).render((width,))
        # fmt: on

    def pack(self, size=None, focus: bool = False):
        """
        Return the size from our pre-rendered canvas.
        """
        return self._canvas.cols(), self._canvas.rows()

    def render(self, size, focus: bool = False):
        """
        Return the pre-rendered canvas.
        """
        fixed_size(size)
        return self._canvas


def _test():
    import doctest

    doctest.testmod()


if __name__ == "__main__":
    _test()
urwid-2.6.16/urwid/numedit.py000066400000000000000000000320761470350774000161300ustar00rootroot00000000000000#
# Urwid basic widget classes
#    Copyright (C) 2004-2012  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import re
import warnings
from decimal import Decimal
from typing import TYPE_CHECKING

from urwid import Edit

if TYPE_CHECKING:
    from collections.abc import Container


class NumEdit(Edit):
    """NumEdit - edit numerical types

    based on the characters in 'allowed' different numerical types
    can be edited:
      + regular int: 0123456789
      + regular float: 0123456789.
      + regular oct: 01234567
      + regular hex: 0123456789abcdef
    """

    ALLOWED = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

    def __init__(
        self,
        allowed: Container[str],
        caption,
        default: str | bytes,
        trimLeadingZeros: bool | None = None,
        *,
        trim_leading_zeros: bool = True,
        allow_negative: bool = False,
    ):
        super().__init__(caption, default)
        self._allowed = allowed
        self._trim_leading_zeros = trim_leading_zeros
        self._allow_negative = allow_negative

        if trimLeadingZeros is not None:
            warnings.warn(
                "'trimLeadingZeros' argument is deprecated. Use 'trim_leading_zeros' keyword argument",
                DeprecationWarning,
                stacklevel=3,
            )
            self._trim_leading_zeros = trimLeadingZeros

    def valid_char(self, ch: str) -> bool:
        """
        Return true for allowed characters.
        """
        if len(ch) == 1:
            if ch.upper() in self._allowed:
                return True

            return self._allow_negative and ch == "-" and self.edit_pos == 0 and "-" not in self.edit_text
        return False

    def keypress(
        self,
        size: tuple[int],  # type: ignore[override]
        key: str,
    ) -> str | None:
        """
        Handle editing keystrokes.  Remove leading zeros.

        >>> e, size = NumEdit("0123456789", "", "5002"), (10,)
        >>> e.keypress(size, 'home')
        >>> e.keypress(size, 'delete')
        >>> assert e.edit_text == "002"
        >>> e.keypress(size, 'end')
        >>> assert e.edit_text == "2"
        >>> # binary only
        >>> e, size = NumEdit("01", "", ""), (10,)
        >>> assert e.edit_text == ""
        >>> e.keypress(size, '1')
        >>> e.keypress(size, '0')
        >>> e.keypress(size, '1')
        >>> assert e.edit_text == "101"
        >>> e, size = NumEdit("0123456789", "", "", allow_negative=True), (10,)
        >>> e.keypress(size, "-")
        >>> e.keypress(size, '1')
        >>> e.edit_text
        '-1'
        >>> e.keypress(size, 'home')
        >>> e.keypress(size, 'delete')
        >>> e.edit_text
        '1'
        >>> e.keypress(size, 'end')
        >>> e.keypress(size, "-")
        '-'
        >>> e.edit_text
        '1'
        """
        unhandled = super().keypress(size, key)

        if not unhandled and self._trim_leading_zeros:
            # trim leading zeros
            while self.edit_pos > 0 and self.edit_text[:1] == "0":
                self.set_edit_pos(self.edit_pos - 1)
                self.set_edit_text(self.edit_text[1:])

        return unhandled


class IntegerEdit(NumEdit):
    """Edit widget for integer values"""

    def __init__(
        self,
        caption="",
        default: int | str | Decimal | None = None,
        base: int = 10,
        *,
        allow_negative: bool = False,
    ) -> None:
        """
        caption -- caption markup
        default -- default edit value

        >>> IntegerEdit(u"", 42)
        
        >>> e, size = IntegerEdit(u"", "5002"), (10,)
        >>> e.keypress(size, 'home')
        >>> e.keypress(size, 'delete')
        >>> assert e.edit_text == "002"
        >>> e.keypress(size, 'end')
        >>> assert e.edit_text == "2"
        >>> e.keypress(size, '9')
        >>> e.keypress(size, '0')
        >>> assert e.edit_text == "290"
        >>> e, size = IntegerEdit("", ""), (10,)
        >>> assert e.value() is None
        >>> # binary
        >>> e, size = IntegerEdit(u"", "1010", base=2), (10,)
        >>> e.keypress(size, 'end')
        >>> e.keypress(size, '1')
        >>> assert e.edit_text == "10101"
        >>> assert e.value() == Decimal("21")
        >>> # HEX
        >>> e, size = IntegerEdit(u"", "10", base=16), (10,)
        >>> e.keypress(size, 'end')
        >>> e.keypress(size, 'F')
        >>> e.keypress(size, 'f')
        >>> assert e.edit_text == "10Ff"
        >>> assert e.keypress(size, 'G') == 'G'  # unhandled key
        >>> assert e.edit_text == "10Ff"
        >>> # keep leading 0's when not base 10
        >>> e, size = IntegerEdit(u"", "10FF", base=16), (10,)
        >>> assert e.edit_text == "10FF"
        >>> assert e.value() == Decimal("4351")
        >>> e.keypress(size, 'home')
        >>> e.keypress(size, 'delete')
        >>> e.keypress(size, '0')
        >>> assert e.edit_text == "00FF"
        >>> # test exception on incompatible value for base
        >>> e, size = IntegerEdit(u"", "10FG", base=16), (10,)
        Traceback (most recent call last):
            ...
        ValueError: invalid value: 10FG for base 16
        >>> # test exception on float init value
        >>> e, size = IntegerEdit(u"", 10.0), (10,)
        Traceback (most recent call last):
            ...
        ValueError: default: Only 'str', 'int', 'long' or Decimal input allowed
        >>> e, size = IntegerEdit(u"", Decimal("10.0")), (10,)
        Traceback (most recent call last):
            ...
        ValueError: not an 'integer Decimal' instance
        """
        self.base = base
        val = ""
        allowed_chars = self.ALLOWED[: self.base]
        if default is not None:
            if not isinstance(default, (int, str, Decimal)):
                raise ValueError("default: Only 'str', 'int' or Decimal input allowed")

            # convert to a long first, this will raise a ValueError
            # in case a float is passed or some other error
            if isinstance(default, str) and len(default):
                # check if it is a valid initial value
                validation_re = f"^[{allowed_chars}]+$"
                if not re.match(validation_re, str(default), re.IGNORECASE):
                    raise ValueError(f"invalid value: {default} for base {base}")

            elif isinstance(default, Decimal) and default.as_tuple()[2] != 0:
                # a Decimal instance with no fractional part
                raise ValueError("not an 'integer Decimal' instance")

            # convert possible int, long or Decimal to str
            val = str(default)

        super().__init__(
            allowed_chars,
            caption,
            val,
            trim_leading_zeros=(self.base == 10),
            allow_negative=allow_negative,
        )

    def value(self) -> Decimal | None:
        """
        Return the numeric value of self.edit_text.

        >>> e, size = IntegerEdit(), (10,)
        >>> e.keypress(size, '5')
        >>> e.keypress(size, '1')
        >>> assert e.value() == 51
        """
        if self.edit_text:
            return Decimal(int(self.edit_text, self.base))

        return None

    def __int__(self) -> int:
        """Enforced int value return.

        >>> e, size = IntegerEdit(allow_negative=True), (10,)
        >>> assert int(e) == 0
        >>> e.keypress(size, '-')
        >>> e.keypress(size, '4')
        >>> e.keypress(size, '2')
        >>> assert int(e) == -42
        """
        if self.edit_text:
            return int(self.edit_text, self.base)
        return 0


class FloatEdit(NumEdit):
    """Edit widget for float values."""

    def __init__(
        self,
        caption="",
        default: str | int | Decimal | None = None,
        preserveSignificance: bool | None = None,
        decimalSeparator: str | None = None,
        *,
        preserve_significance: bool = True,
        decimal_separator: str = ".",
        allow_negative: bool = False,
    ) -> None:
        """
        caption -- caption markup
        default -- default edit value
        preserve_significance -- return value has the same signif. as default
        decimal_separator -- use '.' as separator by default, optionally a ','

        >>> FloatEdit(u"",  "1.065434")
        
        >>> e, size = FloatEdit(u"", "1.065434"), (10,)
        >>> e.keypress(size, 'home')
        >>> e.keypress(size, 'delete')
        >>> assert e.edit_text == ".065434"
        >>> e.keypress(size, 'end')
        >>> e.keypress(size, 'backspace')
        >>> assert e.edit_text == ".06543"
        >>> e, size = FloatEdit(), (10,)
        >>> e.keypress(size, '5')
        >>> e.keypress(size, '1')
        >>> e.keypress(size, '.')
        >>> e.keypress(size, '5')
        >>> e.keypress(size, '1')
        >>> assert e.value() == Decimal("51.51"), e.value()
        >>> e, size = FloatEdit(decimal_separator=":"), (10,)
        Traceback (most recent call last):
            ...
        ValueError: invalid decimal separator: :
        >>> e, size = FloatEdit(decimal_separator=","), (10,)
        >>> e.keypress(size, '5')
        >>> e.keypress(size, '1')
        >>> e.keypress(size, ',')
        >>> e.keypress(size, '5')
        >>> e.keypress(size, '1')
        >>> assert e.edit_text == "51,51"
        >>> e, size = FloatEdit("", "3.1415", preserve_significance=True), (10,)
        >>> e.keypress(size, 'end')
        >>> e.keypress(size, 'backspace')
        >>> e.keypress(size, 'backspace')
        >>> assert e.edit_text == "3.14"
        >>> assert e.value() == Decimal("3.1400")
        >>> e.keypress(size, '1')
        >>> e.keypress(size, '5')
        >>> e.keypress(size, '9')
        >>> assert e.value() == Decimal("3.1416"), e.value()
        >>> e, size = FloatEdit("", ""), (10,)
        >>> assert e.value() is None
        >>> e, size = FloatEdit(u"", 10.0), (10,)
        Traceback (most recent call last):
            ...
        ValueError: default: Only 'str', 'int', 'long' or Decimal input allowed
        """
        self.significance = None
        self._decimal_separator = decimal_separator
        if decimalSeparator is not None:
            warnings.warn(
                "'decimalSeparator' argument is deprecated. Use 'decimal_separator' keyword argument",
                DeprecationWarning,
                stacklevel=3,
            )
            self._decimal_separator = decimalSeparator

        if self._decimal_separator not in {".", ","}:
            raise ValueError(f"invalid decimal separator: {self._decimal_separator}")

        if preserveSignificance is not None:
            warnings.warn(
                "'preserveSignificance' argument is deprecated. Use 'preserve_significance' keyword argument",
                DeprecationWarning,
                stacklevel=3,
            )
            preserve_significance = preserveSignificance

        val = ""
        if default is not None and default != "":  # noqa: PLC1901,RUF100
            if not isinstance(default, (int, str, Decimal)):
                raise ValueError("default: Only 'str', 'int' or Decimal input allowed")

            if isinstance(default, str) and default:
                # check if it is a float, raises a ValueError otherwise
                float(default)
                default = Decimal(default)

            if preserve_significance and isinstance(default, Decimal):
                self.significance = default

            val = str(default)

        super().__init__(self.ALLOWED[0:10] + self._decimal_separator, caption, val, allow_negative=allow_negative)

    def value(self) -> Decimal | None:
        """
        Return the numeric value of self.edit_text.
        """
        if self.edit_text:
            normalized = Decimal(self.edit_text.replace(self._decimal_separator, "."))
            if self.significance is not None:
                return normalized.quantize(self.significance)
            return normalized

        return None

    def __float__(self) -> float:
        """Enforced float value return.

        >>> e, size = FloatEdit(allow_negative=True), (10,)
        >>> assert float(e) == 0.
        >>> e.keypress(size, '-')
        >>> e.keypress(size, '4')
        >>> e.keypress(size, '.')
        >>> e.keypress(size, '2')
        >>> assert float(e) == -4.2
        """
        if self.edit_text:
            return float(self.edit_text.replace(self._decimal_separator, "."))
        return 0.0
urwid-2.6.16/urwid/signals.py000066400000000000000000000315661470350774000161260ustar00rootroot00000000000000# Urwid signal dispatching
#    Copyright (C) 2004-2012  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import itertools
import typing
import warnings
import weakref

if typing.TYPE_CHECKING:
    from collections.abc import Callable, Collection, Container, Hashable, Iterable


class MetaSignals(type):
    """
    register the list of signals in the class variable signals,
    including signals in superclasses.
    """

    def __init__(cls, name: str, bases: tuple[type, ...], d: dict[str, typing.Any]) -> None:
        signals = d.get("signals", [])
        for superclass in cls.__bases__:
            signals.extend(getattr(superclass, "signals", []))
        signals = list(dict.fromkeys(signals).keys())
        d["signals"] = signals
        register_signal(cls, signals)
        super().__init__(name, bases, d)


def setdefaultattr(obj, name, value):
    # like dict.setdefault() for object attributes
    if hasattr(obj, name):
        return getattr(obj, name)
    setattr(obj, name, value)
    return value


class Key:
    """
    Minimal class, whose only purpose is to produce objects with a
    unique hash
    """

    __slots__ = ()


class Signals:
    _signal_attr = "_urwid_signals"  # attribute to attach to signal senders

    def __init__(self) -> None:
        self._supported = {}

    def register(self, sig_cls, signals: Container[Hashable]) -> None:
        """
        :param sig_cls: the class of an object that will be sending signals
        :type sig_cls: class
        :param signals: a list of signals that may be sent, typically each
                        signal is represented by a string
        :type signals: signal names

        This function must be called for a class before connecting any
        signal callbacks or emitting any signals from that class' objects
        """
        self._supported[sig_cls] = signals

    def connect(
        self,
        obj,
        name: Hashable,
        callback: Callable[..., typing.Any],
        user_arg: typing.Any = None,
        *,
        weak_args: Iterable[typing.Any] = (),
        user_args: Iterable[typing.Any] = (),
    ) -> Key:
        """
        :param obj: the object sending a signal
        :type obj: object
        :param name: the signal to listen for, typically a string
        :type name: signal name
        :param callback: the function to call when that signal is sent
        :type callback: function
        :param user_arg: deprecated additional argument to callback (appended
                         after the arguments passed when the signal is
                         emitted). If None no arguments will be added.
                         Don't use this argument, use user_args instead.
        :param weak_args: additional arguments passed to the callback
                          (before any arguments passed when the signal
                          is emitted and before any user_args).

                          These arguments are stored as weak references
                          (but converted back into their original value
                          before passing them to callback) to prevent
                          any objects referenced (indirectly) from
                          weak_args from being kept alive just because
                          they are referenced by this signal handler.

                          Use this argument only as a keyword argument,
                          since user_arg might be removed in the future.
        :type weak_args: iterable
        :param user_args: additional arguments to pass to the callback,
                          (before any arguments passed when the signal
                          is emitted but after any weak_args).

                          Use this argument only as a keyword argument,
                          since user_arg might be removed in the future.
        :type user_args: iterable

        When a matching signal is sent, callback will be called. The
        arguments it receives will be the user_args passed at connect
        time (as individual arguments) followed by all the positional
        parameters sent with the signal.

        As an example of using weak_args, consider the following snippet:

        >>> import urwid
        >>> debug = urwid.Text('')
        >>> def handler(widget, newtext):
        ...    debug.set_text("Edit widget changed to %s" % newtext)
        >>> edit = urwid.Edit('')
        >>> key = urwid.connect_signal(edit, 'change', handler)

        If you now build some interface using "edit" and "debug", the
        "debug" widget will show whatever you type in the "edit" widget.
        However, if you remove all references to the "debug" widget, it
        will still be kept alive by the signal handler. This because the
        signal handler is a closure that (implicitly) references the
        "edit" widget. If you want to allow the "debug" widget to be
        garbage collected, you can create a "fake" or "weak" closure
        (it's not really a closure, since it doesn't reference any
        outside variables, so it's just a dynamic function):

        >>> debug = urwid.Text('')
        >>> def handler(weak_debug, widget, newtext):
        ...    weak_debug.set_text("Edit widget changed to %s" % newtext)
        >>> edit = urwid.Edit('')
        >>> key = urwid.connect_signal(edit, 'change', handler, weak_args=[debug])

        Here the weak_debug parameter in print_debug is the value passed
        in the weak_args list to connect_signal. Note that the
        weak_debug value passed is not a weak reference anymore, the
        signals code transparently dereferences the weakref parameter
        before passing it to print_debug.

        Returns a key associated by this signal handler, which can be
        used to disconnect the signal later on using
        urwid.disconnect_signal_by_key. Alternatively, the signal
        handler can also be disconnected by calling
        urwid.disconnect_signal, which doesn't need this key.
        """
        if user_arg is not None:
            warnings.warn(
                "Don't use user_arg argument, use user_args instead.",
                DeprecationWarning,
                stacklevel=2,
            )

        sig_cls = obj.__class__
        if name not in self._supported.get(sig_cls, ()):
            raise NameError(f"No such signal {name!r} for object {obj!r}")

        # Just generate an arbitrary (but unique) key
        key = Key()

        handlers = setdefaultattr(obj, self._signal_attr, {}).setdefault(name, [])

        # Remove the signal handler when any of the weakref'd arguments
        # are garbage collected. Note that this means that the handlers
        # dictionary can be modified _at any time_, so it should never
        # be iterated directly (e.g. iterate only over .keys() and
        # .items(), never over .iterkeys(), .iteritems() or the object
        # itself).
        # We let the callback keep a weakref to the object as well, to
        # prevent a circular reference between the handler and the
        # object (via the weakrefs, which keep strong references to
        # their callbacks) from existing.
        obj_weak = weakref.ref(obj)

        def weakref_callback(weakref):  # pylint: disable=redefined-outer-name  # bad, but not changing API
            o = obj_weak()
            if o:
                self.disconnect_by_key(o, name, key)

        user_args = self._prepare_user_args(weak_args, user_args, weakref_callback)
        handlers.append((key, callback, user_arg, user_args))

        return key

    def _prepare_user_args(
        self,
        weak_args: Iterable[typing.Any] = (),
        user_args: Iterable[typing.Any] = (),
        callback: Callable[..., typing.Any] | None = None,
    ) -> tuple[Collection[weakref.ReferenceType], Collection[typing.Any]]:
        # Turn weak_args into weakrefs and prepend them to user_args
        w_args = tuple(weakref.ref(w_arg, callback) for w_arg in weak_args)
        args = tuple(user_args) or ()
        return (w_args, args)

    def disconnect(
        self,
        obj,
        name: Hashable,
        callback: Callable[..., typing.Any],
        user_arg: typing.Any = None,
        *,
        weak_args: Iterable[typing.Any] = (),
        user_args: Iterable[typing.Any] = (),
    ) -> None:
        """
        :param obj: the object to disconnect the signal from
        :type obj: object
        :param name: the signal to disconnect, typically a string
        :type name: signal name
        :param callback: the callback function passed to connect_signal
        :type callback: function
        :param user_arg: the user_arg parameter passed to connect_signal
        :param weak_args: the weak_args parameter passed to connect_signal
        :param user_args: the weak_args parameter passed to connect_signal

        This function will remove a callback from the list connected
        to a signal with connect_signal(). The arguments passed should
        be exactly the same as those passed to connect_signal().

        If the callback is not connected or already disconnected, this
        function will simply do nothing.
        """
        signals = setdefaultattr(obj, self._signal_attr, {})
        if name not in signals:
            return None

        handlers = signals[name]

        # Do the same processing as in connect, so we can compare the
        # resulting tuple.
        user_args = self._prepare_user_args(weak_args, user_args)

        # Remove the given handler
        for h in handlers:
            if h[1:] == (callback, user_arg, user_args):
                return self.disconnect_by_key(obj, name, h[0])
        return None

    def disconnect_by_key(self, obj, name: Hashable, key: Key) -> None:
        """
        :param obj: the object to disconnect the signal from
        :type obj: object
        :param name: the signal to disconnect, typically a string
        :type name: signal name
        :param key: the key for this signal handler, as returned by
                    connect_signal().
        :type key: Key

        This function will remove a callback from the list connected
        to a signal with connect_signal(). The key passed should be the
        value returned by connect_signal().

        If the callback is not connected or already disconnected, this
        function will simply do nothing.
        """
        handlers = setdefaultattr(obj, self._signal_attr, {}).get(name, [])
        handlers[:] = [h for h in handlers if h[0] is not key]

    def emit(self, obj, name: Hashable, *args) -> bool:
        """
        :param obj: the object sending a signal
        :type obj: object
        :param name: the signal to send, typically a string
        :type name: signal name
        :param args: zero or more positional arguments to pass to the signal callback functions

        This function calls each of the callbacks connected to this signal
        with the args arguments as positional parameters.

        This function returns True if any of the callbacks returned True.
        """
        result = False
        handlers = getattr(obj, self._signal_attr, {}).get(name, [])
        for _key, callback, user_arg, (weak_args, user_args) in handlers:
            result |= self._call_callback(callback, user_arg, weak_args, user_args, args)
        return result

    def _call_callback(
        self,
        callback,
        user_arg: typing.Any,
        weak_args: Collection[weakref.ReferenceType],
        user_args: Collection[typing.Any],
        emit_args: Iterable[typing.Any],
    ) -> bool:
        args_to_pass = []
        for w_arg in weak_args:
            real_arg = w_arg()
            if real_arg is not None:
                args_to_pass.append(real_arg)
            else:
                # de-referenced
                return False

        # The deprecated user_arg argument was added to the end
        # instead of the beginning.
        args = itertools.chain(args_to_pass, user_args, emit_args, (user_arg,) if user_arg is not None else ())

        return bool(callback(*args))


_signals = Signals()
emit_signal = _signals.emit
register_signal = _signals.register
connect_signal = _signals.connect
disconnect_signal = _signals.disconnect
disconnect_signal_by_key = _signals.disconnect_by_key
urwid-2.6.16/urwid/split_repr.py000066400000000000000000000074731470350774000166510ustar00rootroot00000000000000# Urwid split_repr helper functions
#    Copyright (C) 2004-2011  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

from inspect import getfullargspec


def split_repr(self):
    """
    Return a helpful description of the object using
    self._repr_words() and self._repr_attrs() to add
    to the description.  This function may be used by
    adding code to your class like this:

    >>> class Foo(object):
    ...     __repr__ = split_repr
    ...     def _repr_words(self):
    ...         return ["words", "here"]
    ...     def _repr_attrs(self):
    ...         return {'attrs': "appear too"}
    >>> Foo()
    
    >>> class Bar(Foo):
    ...     def _repr_words(self):
    ...         return Foo._repr_words(self) + ["too"]
    ...     def _repr_attrs(self):
    ...         return dict(Foo._repr_attrs(self), barttr=42)
    >>> Bar()
    
    """
    alist = sorted((str(k), normalize_repr(v)) for k, v in self._repr_attrs().items())

    words = self._repr_words()
    if not words and not alist:
        # if we're just going to print the classname fall back
        # to the previous __repr__ implementation instead
        return super(self.__class__, self).__repr__()
    if words and alist:
        words.append("")
    return f"<{self.__class__.__name__} {' '.join(words) + ' '.join([f'{k}={v}' for k, v in alist])}>"


def normalize_repr(v):
    """
    Return dictionary repr sorted by keys, leave others unchanged

    >>> normalize_repr({1:2,3:4,5:6,7:8})
    '{1: 2, 3: 4, 5: 6, 7: 8}'
    >>> normalize_repr('foo')
    "'foo'"
    """
    if isinstance(v, dict):
        items = sorted((repr(k), repr(v)) for k, v in v.items())

        return f"{{{', '.join(f'{k}: {v}' for k, v in items)}}}"

    return repr(v)


def remove_defaults(d, fn):
    """
    Remove keys in d that are set to the default values from
    fn.  This method is used to unclutter the _repr_attrs()
    return value.

    d will be modified by this function.

    Returns d.

    >>> class Foo(object):
    ...     def __init__(self, a=1, b=2):
    ...         self.values = a, b
    ...     __repr__ = split_repr
    ...     def _repr_words(self):
    ...         return ["object"]
    ...     def _repr_attrs(self):
    ...         d = dict(a=self.values[0], b=self.values[1])
    ...         return remove_defaults(d, Foo.__init__)
    >>> Foo(42, 100)
    
    >>> Foo(10, 2)
    
    >>> Foo()
    
    """
    args, varargs, varkw, defaults, _, _, _ = getfullargspec(fn)

    # ignore *varargs and **kwargs
    if varkw:
        del args[-1]
    if varargs:
        del args[-1]

    # create a dictionary of args with default values
    ddict = dict(zip(args[len(args) - len(defaults) :], defaults))

    for k in list(d.keys()):
        if k in ddict and ddict[k] == d[k]:
            # remove values that match their defaults
            del d[k]

    return d


def _test():
    import doctest

    doctest.testmod()


if __name__ == "__main__":
    _test()
urwid-2.6.16/urwid/str_util.py000066400000000000000000000251341470350774000163250ustar00rootroot00000000000000# Urwid unicode character processing tables
#    Copyright (C) 2004-2011  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import re
import typing
import warnings

import wcwidth

if typing.TYPE_CHECKING:
    from typing_extensions import Literal

SAFE_ASCII_RE = re.compile("^[ -~]*$")
SAFE_ASCII_BYTES_RE = re.compile(b"^[ -~]*$")

_byte_encoding: Literal["utf8", "narrow", "wide"] = "narrow"


def get_char_width(char: str) -> Literal[0, 1, 2]:
    width = wcwidth.wcwidth(char)
    if width < 0:
        return 0
    return width


def get_width(o: int) -> Literal[0, 1, 2]:
    """Return the screen column width for unicode ordinal o."""
    return get_char_width(chr(o))


def decode_one(text: bytes | str, pos: int) -> tuple[int, int]:
    """
    Return (ordinal at pos, next position) for UTF-8 encoded text.
    """
    lt = len(text) - pos

    b2 = 0  # Fallback, not changing anything
    b3 = 0  # Fallback, not changing anything
    b4 = 0  # Fallback, not changing anything

    try:
        if isinstance(text, str):
            b1 = ord(text[pos])
            if lt > 1:
                b2 = ord(text[pos + 1])
            if lt > 2:
                b3 = ord(text[pos + 2])
            if lt > 3:
                b4 = ord(text[pos + 3])
        else:
            b1 = text[pos]
            if lt > 1:
                b2 = text[pos + 1]
            if lt > 2:
                b3 = text[pos + 2]
            if lt > 3:
                b4 = text[pos + 3]
    except Exception as e:
        raise ValueError(f"{e}: text={text!r}, pos={pos!r}, lt={lt!r}").with_traceback(e.__traceback__) from e

    if not b1 & 0x80:
        return b1, pos + 1
    error = ord("?"), pos + 1

    if lt < 2:
        return error
    if b1 & 0xE0 == 0xC0:
        if b2 & 0xC0 != 0x80:
            return error
        o = ((b1 & 0x1F) << 6) | (b2 & 0x3F)
        if o < 0x80:
            return error
        return o, pos + 2
    if lt < 3:
        return error
    if b1 & 0xF0 == 0xE0:
        if b2 & 0xC0 != 0x80:
            return error
        if b3 & 0xC0 != 0x80:
            return error
        o = ((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)
        if o < 0x800:
            return error
        return o, pos + 3
    if lt < 4:
        return error
    if b1 & 0xF8 == 0xF0:
        if b2 & 0xC0 != 0x80:
            return error
        if b3 & 0xC0 != 0x80:
            return error
        if b4 & 0xC0 != 0x80:
            return error
        o = ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F)
        if o < 0x10000:
            return error
        return o, pos + 4
    return error


def decode_one_uni(text: str, i: int) -> tuple[int, int]:
    """
    decode_one implementation for unicode strings
    """
    return ord(text[i]), i + 1


def decode_one_right(text: bytes, pos: int) -> tuple[int, int] | None:
    """
    Return (ordinal at pos, next position) for UTF-8 encoded text.
    pos is assumed to be on the trailing byte of a utf-8 sequence.
    """
    if not isinstance(text, bytes):
        raise TypeError(text)
    error = ord("?"), pos - 1
    p = pos
    while p >= 0:
        if text[p] & 0xC0 != 0x80:
            o, _next_pos = decode_one(text, p)
            return o, p - 1
        p -= 1
        if p == p - 4:
            return error
    return None


def set_byte_encoding(enc: Literal["utf8", "narrow", "wide"]) -> None:
    if enc not in {"utf8", "narrow", "wide"}:
        raise ValueError(enc)
    global _byte_encoding  # noqa: PLW0603  # pylint: disable=global-statement
    _byte_encoding = enc


def get_byte_encoding() -> Literal["utf8", "narrow", "wide"]:
    return _byte_encoding


def calc_string_text_pos(text: str, start_offs: int, end_offs: int, pref_col: int) -> tuple[int, int]:
    """
    Calculate the closest position to the screen column pref_col in text
    where start_offs is the offset into text assumed to be screen column 0
    and end_offs is the end of the range to search.

    :param text: string
    :param start_offs: starting text position
    :param end_offs: ending text position
    :param pref_col: target column
    :returns: (position, actual_col)

    ..note:: this method is a simplified version of `wcwidth.wcswidth` and ideally should be in wcwidth package.
    """
    if start_offs > end_offs:
        raise ValueError((start_offs, end_offs))

    cols = 0
    for idx in range(start_offs, end_offs):
        width = get_char_width(text[idx])
        if width + cols > pref_col:
            return idx, cols
        cols += width

    return end_offs, cols


def calc_text_pos(text: str | bytes, start_offs: int, end_offs: int, pref_col: int) -> tuple[int, int]:
    """
    Calculate the closest position to the screen column pref_col in text
    where start_offs is the offset into text assumed to be screen column 0
    and end_offs is the end of the range to search.

    text may be unicode or a byte string in the target _byte_encoding

    Returns (position, actual_col).
    """
    if start_offs > end_offs:
        raise ValueError((start_offs, end_offs))

    if isinstance(text, str):
        return calc_string_text_pos(text, start_offs, end_offs, pref_col)

    if not isinstance(text, bytes):
        raise TypeError(text)

    if _byte_encoding == "utf8":
        i = start_offs
        sc = 0
        while i < end_offs:
            o, n = decode_one(text, i)
            w = get_width(o)
            if w + sc > pref_col:
                return i, sc
            i = n
            sc += w
        return i, sc

    # "wide" and "narrow"
    i = start_offs + pref_col
    if i >= end_offs:
        return end_offs, end_offs - start_offs
    if _byte_encoding == "wide" and within_double_byte(text, start_offs, i) == 2:
        i -= 1
    return i, i - start_offs


def calc_width(text: str | bytes, start_offs: int, end_offs: int) -> int:
    """
    Return the screen column width of text between start_offs and end_offs.

    text may be unicode or a byte string in the target _byte_encoding

    Some characters are wide (take two columns) and others affect the
    previous character (take zero columns).  Use the widths table above
    to calculate the screen column width of text[start_offs:end_offs]
    """

    if start_offs > end_offs:
        raise ValueError((start_offs, end_offs))

    if isinstance(text, str):
        return sum(get_char_width(char) for char in text[start_offs:end_offs])

    if _byte_encoding == "utf8":
        try:
            return sum(get_char_width(char) for char in text[start_offs:end_offs].decode("utf-8"))
        except UnicodeDecodeError as exc:
            warnings.warn(
                "`calc_width` with text encoded to bytes can produce incorrect results"
                f"due to possible offset in the middle of character: {exc}",
                UnicodeWarning,
                stacklevel=2,
            )

        i = start_offs
        sc = 0
        while i < end_offs:
            o, i = decode_one(text, i)
            w = get_width(o)
            sc += w
        return sc
    # "wide", "narrow" or all printable ASCII, just return the character count
    return end_offs - start_offs


def is_wide_char(text: str | bytes, offs: int) -> bool:
    """
    Test if the character at offs within text is wide.

    text may be unicode or a byte string in the target _byte_encoding
    """
    if isinstance(text, str):
        return get_char_width(text[offs]) == 2
    if not isinstance(text, bytes):
        raise TypeError(text)
    if _byte_encoding == "utf8":
        o, _n = decode_one(text, offs)
        return get_width(o) == 2
    if _byte_encoding == "wide":
        return within_double_byte(text, offs, offs) == 1
    return False


def move_prev_char(text: str | bytes, start_offs: int, end_offs: int) -> int:
    """
    Return the position of the character before end_offs.
    """
    if start_offs >= end_offs:
        raise ValueError((start_offs, end_offs))
    if isinstance(text, str):
        return end_offs - 1
    if not isinstance(text, bytes):
        raise TypeError(text)
    if _byte_encoding == "utf8":
        o = end_offs - 1
        while text[o] & 0xC0 == 0x80:
            o -= 1
        return o
    if _byte_encoding == "wide" and within_double_byte(text, start_offs, end_offs - 1) == 2:
        return end_offs - 2
    return end_offs - 1


def move_next_char(text: str | bytes, start_offs: int, end_offs: int) -> int:
    """
    Return the position of the character after start_offs.
    """
    if start_offs >= end_offs:
        raise ValueError((start_offs, end_offs))
    if isinstance(text, str):
        return start_offs + 1
    if not isinstance(text, bytes):
        raise TypeError(text)
    if _byte_encoding == "utf8":
        o = start_offs + 1
        while o < end_offs and text[o] & 0xC0 == 0x80:
            o += 1
        return o
    if _byte_encoding == "wide" and within_double_byte(text, start_offs, start_offs) == 1:
        return start_offs + 2
    return start_offs + 1


def within_double_byte(text: bytes, line_start: int, pos: int) -> Literal[0, 1, 2]:
    """Return whether pos is within a double-byte encoded character.

    text -- byte string in question
    line_start -- offset of beginning of line (< pos)
    pos -- offset in question

    Return values:
    0 -- not within dbe char, or double_byte_encoding == False
    1 -- pos is on the 1st half of a dbe char
    2 -- pos is on the 2nd half of a dbe char
    """
    if not isinstance(text, bytes):
        raise TypeError(text)
    v = text[pos]

    if 0x40 <= v < 0x7F:
        # might be second half of big5, uhc or gbk encoding
        if pos == line_start:
            return 0

        if text[pos - 1] >= 0x81 and within_double_byte(text, line_start, pos - 1) == 1:
            return 2
        return 0

    if v < 0x80:
        return 0

    i = pos - 1
    while i >= line_start:
        if text[i] < 0x80:
            break
        i -= 1

    if (pos - i) & 1:
        return 1
    return 2
urwid-2.6.16/urwid/text_layout.py000066400000000000000000000544451470350774000170500ustar00rootroot00000000000000# Urwid Text Layout classes
#    Copyright (C) 2004-2011  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import functools
import typing

from urwid.str_util import calc_text_pos, calc_width, get_char_width, is_wide_char, move_next_char, move_prev_char
from urwid.util import calc_trim_text, get_encoding

if typing.TYPE_CHECKING:
    from typing_extensions import Literal

    from urwid.widget import Align, WrapMode


@functools.lru_cache(maxsize=4)
def get_ellipsis_string(encoding: str) -> str:
    """Get ellipsis character for given encoding."""
    try:
        return "…".encode(encoding).decode(encoding)
    except UnicodeEncodeError:
        return "..."


@functools.lru_cache(maxsize=4)
def _get_width(string) -> int:
    """Get ellipsis character width for given encoding."""
    return sum(get_char_width(char) for char in string)


class TextLayout:
    def supports_align_mode(self, align: Literal["left", "center", "right"] | Align) -> bool:
        """Return True if align is a supported align mode."""
        return True

    def supports_wrap_mode(self, wrap: Literal["any", "space", "clip", "ellipsis"] | WrapMode) -> bool:
        """Return True if wrap is a supported wrap mode."""
        return True

    def layout(
        self,
        text: str | bytes,
        width: int,
        align: Literal["left", "center", "right"] | Align,
        wrap: Literal["any", "space", "clip", "ellipsis"] | WrapMode,
    ) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]:
        """
        Return a layout structure for text.

        :param text: string in current encoding or unicode string
        :param width: number of screen columns available
        :param align: align mode for text
        :param wrap: wrap mode for text

        Layout structure is a list of line layouts, one per output line.
        Line layouts are lists than may contain the following tuples:

        * (column width of text segment, start offset, end offset)
        * (number of space characters to insert, offset or None)
        * (column width of insert text, offset, "insert text")

        The offset in the last two tuples is used to determine the
        attribute used for the inserted spaces or text respectively.
        The attribute used will be the same as the attribute at that
        text offset.  If the offset is None when inserting spaces
        then no attribute will be used.
        """
        raise NotImplementedError(
            "This function must be overridden by a real text layout class. (see StandardTextLayout)"
        )


class CanNotDisplayText(Exception):
    pass


class StandardTextLayout(TextLayout):
    def __init__(self) -> None:  # , tab_stops=(), tab_stop_every=8):
        pass
        # """
        # tab_stops -- list of screen column indexes for tab stops
        # tab_stop_every -- repeated interval for following tab stops
        # """
        # assert tab_stop_every is None or type(tab_stop_every)==int
        # if not tab_stops and tab_stop_every:
        #    self.tab_stops = (tab_stop_every,)
        # self.tab_stops = tab_stops
        # self.tab_stop_every = tab_stop_every

    def supports_align_mode(self, align: Literal["left", "center", "right"] | Align) -> bool:
        """Return True if align is 'left', 'center' or 'right'."""
        return align in {"left", "center", "right"}

    def supports_wrap_mode(self, wrap: Literal["any", "space", "clip", "ellipsis"] | WrapMode) -> bool:
        """Return True if wrap is 'any', 'space', 'clip' or 'ellipsis'."""
        return wrap in {"any", "space", "clip", "ellipsis"}

    def layout(
        self,
        text: str | bytes,
        width: int,
        align: Literal["left", "center", "right"] | Align,
        wrap: Literal["any", "space", "clip", "ellipsis"] | WrapMode,
    ) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]:
        """Return a layout structure for text."""
        try:
            segs = self.calculate_text_segments(text, width, wrap)
            return self.align_layout(text, width, segs, wrap, align)
        except CanNotDisplayText:
            return [[]]

    def pack(
        self,
        maxcol: int,
        layout: list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]],
    ) -> int:
        """Return a minimal maxcol value that would result in the same number of lines for layout.

        layout must be a layout structure returned by self.layout().
        """
        maxwidth = 0
        if not layout:
            raise ValueError(f"huh? empty layout?: {layout!r}")
        for lines in layout:
            lw = line_width(lines)
            if lw >= maxcol:
                return maxcol
            maxwidth = max(maxwidth, lw)
        return maxwidth

    def align_layout(
        self,
        text: str | bytes,
        width: int,
        segs: list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]],
        wrap: Literal["any", "space", "clip", "ellipsis"] | WrapMode,
        align: Literal["left", "center", "right"] | Align,
    ) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]:
        """Convert the layout segments to an aligned layout."""
        out = []
        for lines in segs:
            sc = line_width(lines)
            if sc == width or align == "left":
                out.append(lines)
                continue

            if align == "right":
                out.append([(width - sc, None), *lines])
                continue
            if align != "center":
                raise ValueError(align)
            pad_trim_left = (width - sc + 1) // 2
            out.append([(pad_trim_left, None), *lines] if pad_trim_left else lines)
        return out

    def _calculate_trimmed_segments(
        self,
        text: str | bytes,
        width: int,
        wrap: Literal["any", "space", "clip", "ellipsis"] | WrapMode,
    ) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]:
        """Calculate text segments for cases of a text trimmed (wrap is clip or ellipsis)."""
        segments = []

        nl: str | bytes = "\n" if isinstance(text, str) else b"\n"
        encoding = get_encoding()
        ellipsis_string = get_ellipsis_string(encoding)
        ellipsis_width = _get_width(ellipsis_string)
        while width - 1 < ellipsis_width and ellipsis_string:
            ellipsis_string = ellipsis_string[:-1]
            ellipsis_width = _get_width(ellipsis_string)

        ellipsis_char = ellipsis_string.encode(encoding)

        idx = 0

        while idx <= len(text):
            nl_pos = text.find(nl, idx)
            if nl_pos == -1:
                nl_pos = len(text)
            screen_columns = calc_width(text, idx, nl_pos)

            # trim line to max width if needed, add ellipsis if trimmed
            if wrap == "ellipsis" and screen_columns > width and ellipsis_width:
                trimmed = True

                start_off, end_off, pad_left, pad_right = calc_trim_text(text, idx, nl_pos, 0, width - ellipsis_width)
                # pad_left should be 0, because the start_col parameter was 0 (no trimming on the left)
                # similarly spos should not be changed from p
                if pad_left != 0:
                    raise ValueError(f"Invalid padding for start column==0: {pad_left!r}")
                if start_off != idx:
                    raise ValueError(f"Invalid start offset for  start column==0 and position={idx!r}: {start_off!r}")
                screen_columns = width - 1 - pad_right

            else:
                trimmed = False
                end_off = nl_pos
                pad_right = 0

            line = []
            if idx != end_off:
                line += [(screen_columns, idx, end_off)]
            if trimmed:
                line += [(ellipsis_width, end_off, ellipsis_char)]
            line += [(pad_right, end_off)]
            segments.append(line)
            idx = nl_pos + 1
        return segments

    def calculate_text_segments(
        self,
        text: str | bytes,
        width: int,
        wrap: Literal["any", "space", "clip", "ellipsis"] | WrapMode,
    ) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]:
        """
        Calculate the segments of text to display given width screen columns to display them.

        text - unicode text or byte string to display
        width - number of available screen columns
        wrap - wrapping mode used

        Returns a layout structure without an alignment applied.
        """
        if wrap in {"clip", "ellipsis"}:
            return self._calculate_trimmed_segments(text, width, wrap)

        nl, nl_o, sp_o = "\n", "\n", " "
        if isinstance(text, bytes):
            nl = b"\n"  # can only find bytes in python3 bytestrings
            nl_o = ord(nl_o)  # + an item of a bytestring is the ordinal value
            sp_o = ord(sp_o)
        segments = []
        idx = 0

        while idx <= len(text):
            # look for the next eligible line break
            nl_pos = text.find(nl, idx)
            if nl_pos == -1:
                nl_pos = len(text)

            screen_columns = calc_width(text, idx, nl_pos)
            if screen_columns == 0:
                # removed character hint
                segments.append([(0, nl_pos)])
                idx = nl_pos + 1
                continue

            if screen_columns <= width:
                # this segment fits
                segments.append([(screen_columns, idx, nl_pos), (0, nl_pos)])
                # removed character hint

                idx = nl_pos + 1
                continue

            pos, screen_columns = calc_text_pos(text, idx, nl_pos, width)
            if pos == idx:  # pathological width=1 double-byte case
                raise CanNotDisplayText("Wide character will not fit in 1-column width")

            if wrap == "any":
                segments.append([(screen_columns, idx, pos)])
                idx = pos
                continue

            if wrap != "space":
                raise ValueError(wrap)

            if text[pos] == sp_o:
                # perfect space wrap
                segments.append([(screen_columns, idx, pos), (0, pos)])
                # removed character hint

                idx = pos + 1
                continue

            if is_wide_char(text, pos):
                # perfect next wide
                segments.append([(screen_columns, idx, pos)])
                idx = pos
                continue

            prev = pos
            while prev > idx:
                prev = move_prev_char(text, idx, prev)
                if text[prev] == sp_o:
                    screen_columns = calc_width(text, idx, prev)
                    line = [(0, prev)]
                    if idx != prev:
                        line = [(screen_columns, idx, prev), *line]
                    segments.append(line)
                    idx = prev + 1
                    break

                if is_wide_char(text, prev):
                    # wrap after wide char
                    next_char = move_next_char(text, prev, pos)
                    screen_columns = calc_width(text, idx, next_char)
                    segments.append([(screen_columns, idx, next_char)])
                    idx = next_char
                    break

            else:
                # unwrap previous line space if possible to
                # fit more text (we're breaking a word anyway)
                if segments and (len(segments[-1]) == 2 or (len(segments[-1]) == 1 and len(segments[-1][0]) == 2)):
                    # look for the removed space above
                    if len(segments[-1]) == 1:
                        [(h_sc, h_off)] = segments[-1]
                        p_sc = 0
                        p_off = _p_end = h_off

                    else:
                        [(p_sc, p_off, _p_end), (h_sc, h_off)] = segments[-1]

                    if p_sc < width and h_sc == 0 and text[h_off] == sp_o:
                        # combine with the previous line
                        del segments[-1]
                        idx = p_off
                        pos, screen_columns = calc_text_pos(text, idx, nl_pos, width)
                        segments.append([(screen_columns, idx, pos)])
                        # check for trailing " " or "\n"
                        idx = pos
                        if idx < len(text) and (text[idx] in {sp_o, nl_o}):
                            # removed character hint
                            segments[-1].append((0, idx))
                            idx += 1
                        continue

                # force any char wrap
                segments.append([(screen_columns, idx, pos)])
                idx = pos
        return segments


######################################
# default layout object to use
default_layout = StandardTextLayout()
######################################


class LayoutSegment:
    def __init__(self, seg: tuple[int, int, int | bytes] | tuple[int, int | None]) -> None:
        """Create object from line layout segment structure"""

        if not isinstance(seg, tuple):
            raise TypeError(seg)
        if len(seg) not in {2, 3}:
            raise ValueError(seg)

        self.sc, self.offs = seg[:2]

        if not isinstance(self.sc, int):
            raise TypeError(self.sc)

        if len(seg) == 3:
            if not isinstance(self.offs, int):
                raise TypeError(self.offs)
            if self.sc <= 0:
                raise ValueError(seg)
            t = seg[2]
            if isinstance(t, bytes):
                self.text: bytes | None = t
                self.end = None
            else:
                if not isinstance(t, int):
                    raise TypeError(t)
                self.text = None
                self.end = t
        else:
            if len(seg) != 2:
                raise ValueError(seg)
            if self.offs is not None:
                if self.sc < 0:
                    raise ValueError(seg)
                if not isinstance(self.offs, int):
                    raise TypeError(self.offs)
            self.text = self.end = None

    def subseg(self, text: str | bytes, start: int, end: int) -> list[tuple[int, int] | tuple[int, int, int | bytes]]:
        """
        Return a "sub-segment" list containing segment structures
        that make up a portion of this segment.

        A list is returned to handle cases where wide characters
        need to be replaced with a space character at either edge
        so two or three segments will be returned.
        """
        start = max(start, 0)
        end = min(end, self.sc)

        if start >= end:
            return []  # completely gone
        if self.text:
            # use text stored in segment (self.text)
            spos, epos, pad_left, pad_right = calc_trim_text(self.text, 0, len(self.text), start, end)
            return [(end - start, self.offs, b"".ljust(pad_left) + self.text[spos:epos] + b"".ljust(pad_right))]
        if self.end:
            # use text passed as parameter (text)
            spos, epos, pad_left, pad_right = calc_trim_text(text, self.offs, self.end, start, end)
            lines = []
            if pad_left:
                lines.append((1, spos - 1))
            lines.append((end - start - pad_left - pad_right, spos, epos))
            if pad_right:
                lines.append((1, epos))
            return lines

        return [(end - start, self.offs)]


def line_width(segs: list[tuple[int, int, int | bytes] | tuple[int, int | None]]) -> int:
    """
    Return the screen column width of one line of a text layout structure.

    This function ignores any existing shift applied to the line,
    represented by an (amount, None) tuple at the start of the line.
    """
    sc = 0
    seglist = segs
    if segs and len(segs[0]) == 2 and segs[0][1] is None:
        seglist = segs[1:]
    for s in seglist:
        sc += s[0]
    return sc


def shift_line(
    segs: list[tuple[int, int, int | bytes] | tuple[int, int | None]],
    amount: int,
) -> list[tuple[int, int, int | bytes] | tuple[int, int | None]]:
    """
    Return a shifted line from a layout structure to the left or right.
    segs -- line of a layout structure
    amount -- screen columns to shift right (+ve) or left (-ve)
    """
    if not isinstance(amount, int):
        raise TypeError(amount)

    if segs and len(segs[0]) == 2 and segs[0][1] is None:
        # existing shift
        amount += segs[0][0]
        if amount:
            return [(amount, None)] + segs[1:]
        return segs[1:]

    if amount:
        return [(amount, None), *segs]
    return segs


def trim_line(
    segs: list[tuple[int, int, int | bytes] | tuple[int, int | None]],
    text: str | bytes,
    start: int,
    end: int,
) -> list[tuple[int, int, int | bytes] | tuple[int, int | None]]:
    """
    Return a trimmed line of a text layout structure.
    text -- text to which this layout structure applies
    start -- starting screen column
    end -- ending screen column
    """
    result = []
    x = 0
    for seg in segs:
        sc = seg[0]
        if start or sc < 0:
            if start >= sc:
                start -= sc
                x += sc
                continue
            s = LayoutSegment(seg)
            if x + sc >= end:
                # can all be done at once
                return s.subseg(text, start, end - x)
            result += s.subseg(text, start, sc)
            start = 0
            x += sc
            continue
        if x >= end:
            break
        if x + sc > end:
            s = LayoutSegment(seg)
            result += s.subseg(text, 0, end - x)
            break
        result.append(seg)
    return result


def calc_line_pos(
    text: str | bytes,
    line_layout,
    pref_col: Literal["left", "right", Align.LEFT, Align.RIGHT] | int,
):
    """
    Calculate the closest linear position to pref_col given a
    line layout structure.  Returns None if no position found.
    """
    closest_sc = None
    closest_pos = None
    current_sc = 0

    if pref_col == "left":
        for seg in line_layout:
            s = LayoutSegment(seg)
            if s.offs is not None:
                return s.offs
        return None
    if pref_col == "right":
        for seg in line_layout:
            s = LayoutSegment(seg)
            if s.offs is not None:
                closest_pos = s
        s = closest_pos
        if s is None:
            return None
        if s.end is None:
            return s.offs
        return calc_text_pos(text, s.offs, s.end, s.sc - 1)[0]

    for seg in line_layout:
        s = LayoutSegment(seg)
        if s.offs is not None:
            if s.end is not None:
                if current_sc <= pref_col < current_sc + s.sc:
                    # exact match within this segment
                    return calc_text_pos(text, s.offs, s.end, pref_col - current_sc)[0]
                if current_sc <= pref_col:
                    closest_sc = current_sc + s.sc - 1
                    closest_pos = s

            if closest_sc is None or (abs(pref_col - current_sc) < abs(pref_col - closest_sc)):
                # this screen column is closer
                closest_sc = current_sc
                closest_pos = s.offs
            if current_sc > closest_sc:
                # we're moving past
                break
        current_sc += s.sc

    if closest_pos is None or isinstance(closest_pos, int):
        return closest_pos

    # return the last positions in the segment "closest_pos"
    s = closest_pos
    return calc_text_pos(text, s.offs, s.end, s.sc - 1)[0]


def calc_pos(
    text: str | bytes,
    layout: list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]],
    pref_col: Literal["left", "right", Align.LEFT, Align.RIGHT] | int,
    row: int,
) -> int:
    """
    Calculate the closest linear position to pref_col and row given a
    layout structure.
    """

    if row < 0 or row >= len(layout):
        raise ValueError("calculate_pos: out of layout row range")

    pos = calc_line_pos(text, layout[row], pref_col)
    if pos is not None:
        return pos

    rows_above = list(range(row - 1, -1, -1))
    rows_below = list(range(row + 1, len(layout)))
    while rows_above and rows_below:
        if rows_above:
            r = rows_above.pop(0)
            pos = calc_line_pos(text, layout[r], pref_col)
            if pos is not None:
                return pos
        if rows_below:
            r = rows_below.pop(0)
            pos = calc_line_pos(text, layout[r], pref_col)
            if pos is not None:
                return pos
    return 0


def calc_coords(
    text: str | bytes,
    layout: list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]],
    pos: int,
    clamp: int = 1,
) -> tuple[int, int]:
    """
    Calculate the coordinates closest to position pos in text with layout.

    text -- raw string or unicode string
    layout -- layout structure applied to text
    pos -- integer position into text
    clamp -- ignored right now
    """
    closest: tuple[int, tuple[int, int]] | None = None
    y = 0
    for line_layout in layout:
        x = 0
        for seg in line_layout:
            s = LayoutSegment(seg)
            if s.offs is None:
                x += s.sc
                continue
            if s.offs == pos:
                return x, y
            if s.end is not None and s.offs <= pos < s.end:
                x += calc_width(text, s.offs, pos)
                return x, y
            distance = abs(s.offs - pos)
            if s.end is not None and s.end < pos:
                distance = pos - (s.end - 1)
            if closest is None or distance < closest[0]:  # pylint: disable=unsubscriptable-object
                closest = distance, (x, y)
            x += s.sc
        y += 1

    if closest:
        return closest[1]
    return 0, 0
urwid-2.6.16/urwid/util.py000066400000000000000000000365671470350774000154510ustar00rootroot00000000000000# Urwid utility functions
#    Copyright (C) 2004-2011  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import codecs
import contextlib
import sys
import typing
import warnings
from contextlib import suppress

from urwid import str_util

if typing.TYPE_CHECKING:
    from collections.abc import Generator, Iterable
    from types import TracebackType

    from typing_extensions import Literal, Protocol, Self

    class CanBeStopped(Protocol):
        def stop(self) -> None: ...


def __getattr__(name: str) -> typing.Any:
    if hasattr(str_util, name):
        warnings.warn(
            f"Do not import {name!r} from {__package__}.{__name__}, import it from 'urwid'.",
            DeprecationWarning,
            stacklevel=3,
        )
        return getattr(str_util, name)
    raise AttributeError(f"{name} is not defined in {__package__}.{__name__}")


def detect_encoding() -> str:
    # Windows is a special case:
    # CMD is Unicode and non-unicode at the same time:
    # Unicode display support depends on C API usage and partially limited by font settings.
    # By default, python is distributed in "Unicode" version, and since Python version 3.8 only Unicode.
    # In case of curses, "windows-curses" is distributed in unicode-only.
    # Since Windows 10 default console font is already unicode,
    # in older versions maybe need to set TTF font manually to support more symbols
    # (this does not affect unicode IO).
    if sys.platform == "win32" and sys.getdefaultencoding() == "utf-8":
        return "utf-8"
    # Try to determine if using a supported double-byte encoding
    import locale

    no_set_locale = locale.getpreferredencoding(False)

    if no_set_locale != "ascii":
        # ascii is fallback locale in case of detect failed

        return no_set_locale

    # Use actual `getpreferredencoding` with public API only
    old_loc = locale.setlocale(locale.LC_CTYPE)  # == getlocale, but not mangle data
    try:
        with suppress(locale.Error):
            locale.setlocale(locale.LC_CTYPE, "")
        # internally call private `_get_locale_encoding`
        return locale.getpreferredencoding(False)
    finally:
        with suppress(locale.Error):
            locale.setlocale(locale.LC_CTYPE, old_loc)


if "detected_encoding" not in locals():
    detected_encoding = detect_encoding()
else:
    raise RuntimeError("Encoding detection broken")

_target_encoding = "ascii"
_use_dec_special = True


def set_encoding(encoding: str) -> None:
    """
    Set the byte encoding to assume when processing strings and the
    encoding to use when converting unicode strings.
    """
    encoding = encoding.lower()

    global _target_encoding, _use_dec_special  # noqa: PLW0603  # noqa: PLW0603  # pylint: disable=global-statement

    if encoding in {"utf-8", "utf8", "utf"}:
        str_util.set_byte_encoding("utf8")

        _use_dec_special = False
    elif encoding in {
        "euc-jp",  # JISX 0208 only
        "euc-kr",
        "euc-cn",
        "euc-tw",  # CNS 11643 plain 1 only
        "gb2312",
        "gbk",
        "big5",
        "cn-gb",
        "uhc",
        # these shouldn't happen, should they?
        "eucjp",
        "euckr",
        "euccn",
        "euctw",
        "cncb",
    }:
        str_util.set_byte_encoding("wide")

        _use_dec_special = True
    else:
        str_util.set_byte_encoding("narrow")
        _use_dec_special = True

    # if encoding is valid for conversion from unicode, remember it
    _target_encoding = "ascii"
    with contextlib.suppress(LookupError):
        if encoding:
            "".encode(encoding)
            _target_encoding = encoding


def get_encoding() -> str:
    """Get target encoding."""
    return _target_encoding


@contextlib.contextmanager
def set_temporary_encoding(encoding_name: str) -> Generator[None]:
    """Internal helper for encoding specific validation in unittests/doctests.

    Not exported globally.
    """
    old_encoding = _target_encoding
    try:
        set_encoding(encoding_name)
        yield
    finally:
        set_encoding(old_encoding)


def get_encoding_mode() -> Literal["wide", "narrow", "utf8"]:
    """
    Get the mode Urwid is using when processing text strings.
    Returns 'narrow' for 8-bit encodings, 'wide' for CJK encodings
    or 'utf8' for UTF-8 encodings.
    """
    return str_util.get_byte_encoding()


def apply_target_encoding(s: str | bytes) -> tuple[bytes, list[tuple[Literal["U", "0"] | None, int]]]:
    """
    Return (encoded byte string, character set rle).
    """
    # Import locally to warranty no circular imports
    from urwid.display import escape

    if _use_dec_special and isinstance(s, str):
        # first convert drawing characters
        s = s.translate(escape.DEC_SPECIAL_CHARMAP)

    if isinstance(s, str):
        s = s.replace(escape.SI + escape.SO, "")  # remove redundant shifts
        s = codecs.encode(s, _target_encoding, "replace")

    if not isinstance(s, bytes):
        raise TypeError(s)
    SO = escape.SO.encode("ascii")
    SI = escape.SI.encode("ascii")

    sis = s.split(SO)

    sis0 = sis[0].replace(SI, b"")
    sout = []
    cout = []
    if sis0:
        sout.append(sis0)
        cout.append((None, len(sis0)))

    if len(sis) == 1:
        return sis0, cout

    for sn in sis[1:]:
        sl = sn.split(SI, 1)
        if len(sl) == 1:
            sin = sl[0]
            sout.append(sin)
            rle_append_modify(cout, (escape.DEC_TAG, len(sin)))
            continue

        sin, son = sl
        son = son.replace(SI, b"")
        if sin:
            sout.append(sin)
            rle_append_modify(cout, (escape.DEC_TAG, len(sin)))

        if son:
            sout.append(son)
            rle_append_modify(cout, (None, len(son)))

    outstr = b"".join(sout)
    return outstr, cout


######################################################################
# Try to set the encoding using the one detected by the locale module
set_encoding(detected_encoding)
######################################################################


def supports_unicode() -> bool:
    """
    Return True if python is able to convert non-ascii unicode strings
    to the current encoding.
    """
    return _target_encoding and _target_encoding != "ascii"


def calc_trim_text(
    text: str | bytes,
    start_offs: int,
    end_offs: int,
    start_col: int,
    end_col: int,
) -> tuple[int, int, int, int]:
    """
    Calculate the result of trimming text.
    start_offs -- offset into text to treat as screen column 0
    end_offs -- offset into text to treat as the end of the line
    start_col -- screen column to trim at the left
    end_col -- screen column to trim at the right

    Returns (start, end, pad_left, pad_right), where:
    start -- resulting start offset
    end -- resulting end offset
    pad_left -- 0 for no pad or 1 for one space to be added
    pad_right -- 0 for no pad or 1 for one space to be added
    """
    spos = start_offs
    pad_left = pad_right = 0
    if start_col > 0:
        spos, sc = str_util.calc_text_pos(text, spos, end_offs, start_col)
        if sc < start_col:
            pad_left = 1
            spos, sc = str_util.calc_text_pos(text, start_offs, end_offs, start_col + 1)
    run = end_col - start_col - pad_left
    pos, sc = str_util.calc_text_pos(text, spos, end_offs, run)
    if sc < run:
        pad_right = 1
    return (spos, pos, pad_left, pad_right)


def trim_text_attr_cs(text: bytes, attr, cs, start_col: int, end_col: int):
    """
    Return ( trimmed text, trimmed attr, trimmed cs ).
    """
    spos, epos, pad_left, pad_right = calc_trim_text(text, 0, len(text), start_col, end_col)
    attrtr = rle_subseg(attr, spos, epos)
    cstr = rle_subseg(cs, spos, epos)
    if pad_left:
        al = rle_get_at(attr, spos - 1)
        rle_prepend_modify(attrtr, (al, 1))
        rle_prepend_modify(cstr, (None, 1))
    if pad_right:
        al = rle_get_at(attr, epos)
        rle_append_modify(attrtr, (al, 1))
        rle_append_modify(cstr, (None, 1))

    return (b"".rjust(pad_left) + text[spos:epos] + b"".rjust(pad_right), attrtr, cstr)


def rle_get_at(rle, pos: int):
    """
    Return the attribute at offset pos.
    """
    x = 0
    if pos < 0:
        return None
    for a, run in rle:
        if x + run > pos:
            return a
        x += run
    return None


def rle_subseg(rle, start: int, end: int):
    """Return a sub segment of a rle list."""
    sub_segment = []
    x = 0
    for a, run in rle:
        if start:
            if start >= run:
                start -= run
                x += run
                continue
            x += start
            run -= start  # noqa: PLW2901
            start = 0
        if x >= end:
            break
        if x + run > end:
            run = end - x  # noqa: PLW2901
        x += run
        sub_segment.append((a, run))
    return sub_segment


def rle_len(rle: Iterable[tuple[typing.Any, int]]) -> int:
    """
    Return the number of characters covered by a run length
    encoded attribute list.
    """

    run = 0
    for v in rle:
        if not isinstance(v, tuple):
            raise TypeError(rle)
        _a, r = v
        run += r
    return run


def rle_prepend_modify(rle, a_r) -> None:
    """
    Append (a, r) (unpacked from *a_r*) to BEGINNING of rle.
    Merge with first run when possible

    MODIFIES rle parameter contents. Returns None.
    """
    a, r = a_r
    if not rle:
        rle[:] = [(a, r)]
    else:
        al, run = rle[0]
        if a == al:
            rle[0] = (a, run + r)
        else:
            rle[0:0] = [(a, r)]


def rle_append_modify(rle, a_r) -> None:
    """
    Append (a, r) (unpacked from *a_r*) to the rle list rle.
    Merge with last run when possible.

    MODIFIES rle parameter contents. Returns None.
    """
    a, r = a_r
    if not rle or rle[-1][0] != a:
        rle.append((a, r))
        return
    _la, lr = rle[-1]
    rle[-1] = (a, lr + r)


def rle_join_modify(rle, rle2) -> None:
    """
    Append attribute list rle2 to rle.
    Merge last run of rle with first run of rle2 when possible.

    MODIFIES attr parameter contents. Returns None.
    """
    if not rle2:
        return
    rle_append_modify(rle, rle2[0])
    rle += rle2[1:]


def rle_product(rle1, rle2):
    """
    Merge the runs of rle1 and rle2 like this:
    eg.
    rle1 = [ ("a", 10), ("b", 5) ]
    rle2 = [ ("Q", 5), ("P", 10) ]
    rle_product: [ (("a","Q"), 5), (("a","P"), 5), (("b","P"), 5) ]

    rle1 and rle2 are assumed to cover the same total run.
    """
    i1 = i2 = 1  # rle1, rle2 indexes
    if not rle1 or not rle2:
        return []
    a1, r1 = rle1[0]
    a2, r2 = rle2[0]

    result = []
    while r1 and r2:
        r = min(r1, r2)
        rle_append_modify(result, ((a1, a2), r))
        r1 -= r
        if r1 == 0 and i1 < len(rle1):
            a1, r1 = rle1[i1]
            i1 += 1
        r2 -= r
        if r2 == 0 and i2 < len(rle2):
            a2, r2 = rle2[i2]
            i2 += 1
    return result


def rle_factor(rle):
    """
    Inverse of rle_product.
    """
    rle1 = []
    rle2 = []
    for (a1, a2), r in rle:
        rle_append_modify(rle1, (a1, r))
        rle_append_modify(rle2, (a2, r))
    return rle1, rle2


class TagMarkupException(Exception):
    pass


def decompose_tagmarkup(tm):
    """Return (text string, attribute list) for tagmarkup passed."""

    tl, al = _tagmarkup_recurse(tm, None)
    # join as unicode or bytes based on type of first element
    if tl:
        text = tl[0][:0].join(tl)
    else:
        text = ""

    if al and al[-1][0] is None:
        del al[-1]

    return text, al


def _tagmarkup_recurse(tm, attr):
    """Return (text list, attribute list) for tagmarkup passed.

    tm -- tagmarkup
    attr -- current attribute or None"""

    if isinstance(tm, list):
        # for lists recurse to process each subelement
        rtl = []
        ral = []
        for element in tm:
            tl, al = _tagmarkup_recurse(element, attr)
            if ral:
                # merge attributes when possible
                last_attr, last_run = ral[-1]
                top_attr, top_run = al[0]
                if last_attr == top_attr:
                    ral[-1] = (top_attr, last_run + top_run)
                    del al[0]
            rtl += tl
            ral += al
        return rtl, ral

    if isinstance(tm, tuple):
        # tuples mark a new attribute boundary
        if len(tm) != 2:
            raise TagMarkupException(f"Tuples must be in the form (attribute, tagmarkup): {tm!r}")

        attr, element = tm
        return _tagmarkup_recurse(element, attr)

    if not isinstance(tm, (str, bytes)):
        raise TagMarkupException(f"Invalid markup element: {tm!r}")

    # text
    return [tm], [(attr, len(tm))]


def is_mouse_event(ev: tuple[str, int, int, int] | typing.Any) -> bool:
    return isinstance(ev, tuple) and len(ev) == 4 and "mouse" in ev[0]


def is_mouse_press(ev: str) -> bool:
    return "press" in ev


class MetaSuper(type):
    """adding .__super"""

    def __init__(cls, name: str, bases, d):
        super().__init__(name, bases, d)
        if hasattr(cls, f"_{name}__super"):
            raise AttributeError("Class has same name as one of its super classes")

        @property
        def _super(self):
            warnings.warn(
                f"`{name}.__super` was a deprecated feature for old python versions."
                f"Please use `super()` call instead.",
                DeprecationWarning,
                stacklevel=3,
            )
            return super(cls, self)

        setattr(cls, f"_{name}__super", _super)


def int_scale(val: int, val_range: int, out_range: int) -> int:
    """
    Scale val in the range [0, val_range-1] to an integer in the range
    [0, out_range-1].  This implementation uses the "round-half-up" rounding
    method.

    >>> "%x" % int_scale(0x7, 0x10, 0x10000)
    '7777'
    >>> "%x" % int_scale(0x5f, 0x100, 0x10)
    '6'
    >>> int_scale(2, 6, 101)
    40
    >>> int_scale(1, 3, 4)
    2
    """
    num = int(val * (out_range - 1) * 2 + (val_range - 1))
    dem = (val_range - 1) * 2
    # if num % dem == 0 then we are exactly half-way and have rounded up.
    return num // dem


class StoppingContext(typing.ContextManager["StoppingContext"]):
    """Context manager that calls ``stop`` on a given object on exit.  Used to
    make the ``start`` method on `MainLoop` and `BaseScreen` optionally act as
    context managers.
    """

    __slots__ = ("_wrapped",)

    def __init__(self, wrapped: CanBeStopped) -> None:
        self._wrapped = wrapped

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self._wrapped.stop()
urwid-2.6.16/urwid/vterm.py000066400000000000000000001624511470350774000156210ustar00rootroot00000000000000# Urwid terminal emulation widget
#    Copyright (C) 2010  aszlig
#    Copyright (C) 2011  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import atexit
import copy
import errno
import fcntl
import os
import pty
import selectors
import signal
import struct
import sys
import termios
import time
import traceback
import typing
import warnings
from collections import deque
from contextlib import suppress
from dataclasses import dataclass

from urwid import event_loop, util
from urwid.canvas import Canvas
from urwid.display import AttrSpec, RealTerminal
from urwid.display.escape import ALT_DEC_SPECIAL_CHARS, DEC_SPECIAL_CHARS
from urwid.widget import Sizing, Widget

from .display.common import _BASIC_COLORS, _color_desc_256, _color_desc_true

if typing.TYPE_CHECKING:
    from collections.abc import Callable, Iterable, Mapping, Sequence

    from typing_extensions import Literal

EOF = b""
ESC = chr(27)
ESC_B = b"\x1b"

KEY_TRANSLATIONS = {
    "enter": "\r",
    "backspace": chr(127),
    "tab": "\t",
    "esc": ESC,
    "up": f"{ESC}[A",
    "down": f"{ESC}[B",
    "right": f"{ESC}[C",
    "left": f"{ESC}[D",
    "home": f"{ESC}[1~",
    "insert": f"{ESC}[2~",
    "delete": f"{ESC}[3~",
    "end": f"{ESC}[4~",
    "page up": f"{ESC}[5~",
    "page down": f"{ESC}[6~",
    "begin paste": f"{ESC}[200~",
    "end paste": f"{ESC}[201~",
    "f1": f"{ESC}[[A",
    "f2": f"{ESC}[[B",
    "f3": f"{ESC}[[C",
    "f4": f"{ESC}[[D",
    "f5": f"{ESC}[[E",
    "f6": f"{ESC}[17~",
    "f7": f"{ESC}[18~",
    "f8": f"{ESC}[19~",
    "f9": f"{ESC}[20~",
    "f10": f"{ESC}[21~",
    "f11": f"{ESC}[23~",
    "f12": f"{ESC}[24~",
}

KEY_TRANSLATIONS_DECCKM = {
    "up": f"{ESC}OA",
    "down": f"{ESC}OB",
    "right": f"{ESC}OC",
    "left": f"{ESC}OD",
    "f1": f"{ESC}OP",
    "f2": f"{ESC}OQ",
    "f3": f"{ESC}OR",
    "f4": f"{ESC}OS",
    "f5": f"{ESC}[15~",
}


class CSIAlias(typing.NamedTuple):
    alias_mark: str  # can not have constructor with default first and non-default second arg
    alias: bytes


class CSICommand(typing.NamedTuple):
    num_args: int
    default: int
    callback: Callable[[TermCanvas, list[int], bool], typing.Any]  # return value ignored


CSI_COMMANDS: dict[bytes, CSIAlias | CSICommand] = {
    # possible values:
    #     None -> ignore sequence
    #     (, , callback)
    #     ('alias', )
    #
    # while callback is executed as:
    #     callback(, arguments, has_question_mark)
    b"@": CSICommand(1, 1, lambda s, number, q: s.insert_chars(chars=number[0])),
    b"A": CSICommand(1, 1, lambda s, rows, q: s.move_cursor(0, -rows[0], relative=True)),
    b"B": CSICommand(1, 1, lambda s, rows, q: s.move_cursor(0, rows[0], relative=True)),
    b"C": CSICommand(1, 1, lambda s, cols, q: s.move_cursor(cols[0], 0, relative=True)),
    b"D": CSICommand(1, 1, lambda s, cols, q: s.move_cursor(-cols[0], 0, relative=True)),
    b"E": CSICommand(1, 1, lambda s, rows, q: s.move_cursor(0, rows[0], relative_y=True)),
    b"F": CSICommand(1, 1, lambda s, rows, q: s.move_cursor(0, -rows[0], relative_y=True)),
    b"G": CSICommand(1, 1, lambda s, col, q: s.move_cursor(col[0] - 1, 0, relative_y=True)),
    b"H": CSICommand(2, 1, lambda s, x_y, q: s.move_cursor(x_y[1] - 1, x_y[0] - 1)),
    b"J": CSICommand(1, 0, lambda s, mode, q: s.csi_erase_display(mode[0])),
    b"K": CSICommand(1, 0, lambda s, mode, q: s.csi_erase_line(mode[0])),
    b"L": CSICommand(1, 1, lambda s, number, q: s.insert_lines(lines=number[0])),
    b"M": CSICommand(1, 1, lambda s, number, q: s.remove_lines(lines=number[0])),
    b"P": CSICommand(1, 1, lambda s, number, q: s.remove_chars(chars=number[0])),
    b"X": CSICommand(
        1,
        1,
        lambda s, number, q: s.erase(s.term_cursor, (s.term_cursor[0] + number[0] - 1, s.term_cursor[1])),
    ),
    b"a": CSIAlias("alias", b"C"),
    b"c": CSICommand(0, 0, lambda s, none, q: s.csi_get_device_attributes(q)),
    b"d": CSICommand(1, 1, lambda s, row, q: s.move_cursor(0, row[0] - 1, relative_x=True)),
    b"e": CSIAlias("alias", b"B"),
    b"f": CSIAlias("alias", b"H"),
    b"g": CSICommand(1, 0, lambda s, mode, q: s.csi_clear_tabstop(mode[0])),
    b"h": CSICommand(1, 0, lambda s, modes, q: s.csi_set_modes(modes, q)),
    b"l": CSICommand(1, 0, lambda s, modes, q: s.csi_set_modes(modes, q, reset=True)),
    b"m": CSICommand(1, 0, lambda s, attrs, q: s.csi_set_attr(attrs)),
    b"n": CSICommand(1, 0, lambda s, mode, q: s.csi_status_report(mode[0])),
    b"q": CSICommand(1, 0, lambda s, mode, q: s.csi_set_keyboard_leds(mode[0])),
    b"r": CSICommand(2, 0, lambda s, t_b, q: s.csi_set_scroll(t_b[0], t_b[1])),
    b"s": CSICommand(0, 0, lambda s, none, q: s.save_cursor()),
    b"u": CSICommand(0, 0, lambda s, none, q: s.restore_cursor()),
    b"`": CSIAlias("alias", b"G"),
}

CHARSET_DEFAULT: Literal[1] = 1  # type annotated exclusively for buggy IDE
CHARSET_UTF8: Literal[2] = 2


@dataclass(eq=True, order=False)
class TermModes:
    # ECMA-48
    display_ctrl: bool = False
    insert: bool = False
    lfnl: bool = False

    # DEC private modes
    keys_decckm: bool = False
    reverse_video: bool = False
    constrain_scrolling: bool = False
    autowrap: bool = True
    visible_cursor: bool = True
    bracketed_paste: bool = False

    # charset stuff
    main_charset: Literal[1, 2] = CHARSET_DEFAULT

    def reset(self) -> None:
        # ECMA-48
        self.display_ctrl = False
        self.insert = False
        self.lfnl = False

        # DEC private modes
        self.keys_decckm = False
        self.reverse_video = False
        self.constrain_scrolling = False
        self.autowrap = True
        self.visible_cursor = True

        # charset stuff
        self.main_charset = CHARSET_DEFAULT


class TermCharset:
    __slots__ = ("_g", "_sgr_mapping", "active", "current")

    MAPPING: typing.ClassVar[dict[str, str | None]] = {
        "default": None,
        "vt100": "0",
        "ibmpc": "U",
        "user": None,
    }

    def __init__(self) -> None:
        self._g = [
            "default",
            "vt100",
        ]

        self._sgr_mapping = False

        # prepare defaults
        self.active = 0
        self.current: str | None = None

        self.activate(0)

    def define(self, g: int, charset: str) -> None:
        """
        Redefine G'g' with new mapping.
        """
        self._g[g] = charset
        self.activate(g=self.active)

    def activate(self, g: int) -> None:
        """
        Activate the given charset slot.
        """
        self.active = g
        self.current = self.MAPPING.get(self._g[g], None)

    def set_sgr_ibmpc(self) -> None:
        """
        Set graphics rendition mapping to IBM PC CP437.
        """
        self._sgr_mapping = True

    def reset_sgr_ibmpc(self) -> None:
        """
        Reset graphics rendition mapping to IBM PC CP437.
        """
        self._sgr_mapping = False
        self.activate(g=self.active)

    def apply_mapping(self, char: bytes) -> bytes:
        if self._sgr_mapping or self._g[self.active] == "ibmpc":
            dec_pos = DEC_SPECIAL_CHARS.find(char.decode("cp437"))
            if dec_pos >= 0:
                self.current = "0"
                return ALT_DEC_SPECIAL_CHARS[dec_pos].encode("cp437")

            self.current = "U"
            return char

        return char


class TermScroller(list):
    """
    List subclass that handles the terminal scrollback buffer,
    truncating it as necessary.
    """

    SCROLLBACK_LINES = 10000

    def __init__(self, iterable: Iterable[typing.Any]) -> None:
        warnings.warn(
            "`TermScroller` is deprecated. Please use `collections.deque` with non-zero `maxlen` instead.",
            DeprecationWarning,
            stacklevel=3,
        )
        super().__init__(iterable)

    def trunc(self) -> None:
        if len(self) >= self.SCROLLBACK_LINES:
            self.pop(0)

    def append(self, obj) -> None:
        self.trunc()
        super().append(obj)

    def insert(self, idx: typing.SupportsIndex, obj) -> None:
        self.trunc()
        super().insert(idx, obj)

    def extend(self, seq) -> None:
        self.trunc()
        super().extend(seq)


class TermCanvas(Canvas):
    cacheable = False

    def __init__(self, width: int, height: int, widget: Terminal) -> None:
        super().__init__()

        self.width, self.height = width, height
        self.widget = widget
        self.modes: TermModes = widget.term_modes
        self.has_focus = False

        self.scrollback_buffer: deque[list[tuple[AttrSpec | None, str | None, bytes]]] = deque(maxlen=10000)
        self.scrolling_up = 0

        self.utf8_eat_bytes: int | None = None
        self.utf8_buffer = bytearray()
        self.escbuf = b""

        self.coords["cursor"] = (0, 0, None)

        self.term_cursor: tuple[int, int] = (0, 0)  # do not allow to shoot in the leg at `set_term_cursor`

        self.within_escape = False
        self.parsestate = 0

        self.attrspec: AttrSpec | None = None

        self.charset = TermCharset()

        self.saved_cursor: tuple[int, int] | None = None
        self.saved_attrs: tuple[AttrSpec | None, TermCharset] | None = None

        self.is_rotten_cursor = False

        self.scrollregion_start = 0
        self.scrollregion_end = self.height - 1

        self.tabstops: list[int] = []
        self.term: list[list[tuple[AttrSpec | None, str | None, bytes]]] = []

        self.reset()

    def set_term_cursor(self, x: int | None = None, y: int | None = None) -> None:
        """
        Set terminal cursor to x/y and update canvas cursor. If one or both axes
        are omitted, use the values of the current position.
        """
        if x is None:
            x = self.term_cursor[0]
        if y is None:
            y = self.term_cursor[1]

        self.term_cursor = self.constrain_coords(x, y)

        if self.has_focus and self.modes.visible_cursor and self.scrolling_up < self.height - y:
            self.cursor = (x, y + self.scrolling_up)
        else:
            self.cursor = None

    def reset_scroll(self) -> None:
        """
        Reset scrolling region to full terminal size.
        """
        self.scrollregion_start = 0
        self.scrollregion_end = self.height - 1

    def scroll_buffer(self, up: bool = True, reset: bool = False, lines: int | None = None) -> None:
        """
        Scroll the scrolling buffer up (up=True) or down (up=False) the given
        amount of lines or half the screen height.

        If just 'reset' is True, set the scrollbuffer view to the current
        terminal content.
        """
        if reset:
            self.scrolling_up = 0
            self.set_term_cursor()
            return

        if lines is None:
            lines = self.height // 2

        if not up:
            lines = -lines

        maxscroll = len(self.scrollback_buffer)
        self.scrolling_up += lines

        if self.scrolling_up > maxscroll:
            self.scrolling_up = maxscroll
        elif self.scrolling_up < 0:
            self.scrolling_up = 0

        self.set_term_cursor()

    def reset(self) -> None:
        """
        Reset the terminal.
        """
        self.escbuf = b""
        self.within_escape = False
        self.parsestate = 0

        self.attrspec = None
        self.charset = TermCharset()

        self.saved_cursor = None
        self.saved_attrs = None

        self.is_rotten_cursor = False

        self.reset_scroll()

        self.init_tabstops()

        # terminal modes
        self.modes.reset()

        # initialize self.term
        self.clear()

    def init_tabstops(self, extend: bool = False) -> None:
        tablen, mod = divmod(self.width, 8)
        if mod > 0:
            tablen += 1

        if extend:
            while len(self.tabstops) < tablen:
                self.tabstops.append(1 << 0)
        else:
            self.tabstops = [1 << 0] * tablen

    def set_tabstop(self, x: int | None = None, remove: bool = False, clear: bool = False) -> None:
        if clear:
            for tab in range(len(self.tabstops)):
                self.tabstops[tab] = 0
            return

        if x is None:
            x = self.term_cursor[0]

        div, mod = divmod(x, 8)
        if remove:
            self.tabstops[div] &= ~(1 << mod)
        else:
            self.tabstops[div] |= 1 << mod

    def is_tabstop(self, x: int | None = None) -> bool:
        if x is None:
            x = self.term_cursor[0]

        div, mod = divmod(x, 8)
        return (self.tabstops[div] & (1 << mod)) > 0

    def empty_line(self, char: bytes = b" ") -> list[tuple[AttrSpec | None, str | None, bytes]]:
        return [self.empty_char(char)] * self.width

    def empty_char(self, char: bytes = b" ") -> tuple[AttrSpec | None, str | None, bytes]:
        return (self.attrspec, self.charset.current, char)

    def addstr(self, data: Iterable[int]) -> None:
        if self.width <= 0 or self.height <= 0:
            # not displayable, do nothing!
            return

        for byte in data:
            self.addbyte(byte)

    def resize(self, width: int, height: int) -> None:
        """
        Resize the terminal to the given width and height.
        """
        x, y = self.term_cursor

        if width > self.width:
            # grow
            for y in range(self.height):
                self.term[y] += [self.empty_char()] * (width - self.width)
        elif width < self.width:
            # shrink
            for y in range(self.height):
                self.term[y] = self.term[y][:width]

        self.width = width

        if height > self.height:
            # grow
            for _y in range(self.height, height):
                try:
                    last_line = self.scrollback_buffer.pop()
                except IndexError:
                    # nothing in scrollback buffer, append an empty line
                    self.term.append(self.empty_line())
                    self.scrollregion_end += 1
                    continue

                # adjust x axis of scrollback buffer to the current width
                padding = self.width - len(last_line)
                if padding > 0:
                    last_line += [self.empty_char()] * padding
                else:
                    last_line = last_line[: self.width]

                self.term.insert(0, last_line)
        elif height < self.height:
            # shrink
            for _y in range(height, self.height):
                self.scrollback_buffer.append(self.term.pop(0))

        self.height = height

        self.reset_scroll()

        x, y = self.constrain_coords(x, y)
        self.set_term_cursor(x, y)

        # extend tabs
        self.init_tabstops(extend=True)

    def set_g01(self, char: bytes, mod: bytes) -> None:
        """
        Set G0 or G1 according to 'char' and modifier 'mod'.
        """
        if self.modes.main_charset != CHARSET_DEFAULT:
            return

        if mod == b"(":
            g = 0
        else:
            g = 1

        if char == b"0":
            cset = "vt100"
        elif char == b"U":
            cset = "ibmpc"
        elif char == b"K":
            cset = "user"
        else:
            cset = "default"

        self.charset.define(g, cset)

    def parse_csi(self, char: bytes) -> None:
        """
        Parse ECMA-48 CSI (Control Sequence Introducer) sequences.
        """
        qmark = self.escbuf.startswith(b"?")

        escbuf = []
        for arg in self.escbuf[1 if qmark else 0 :].split(b";"):
            try:
                num = int(arg)
            except ValueError:
                num = None

            escbuf.append(num)

        cmd_ = CSI_COMMANDS[char]
        if cmd_ is not None:
            if isinstance(cmd_, CSIAlias):
                csi_cmd: CSICommand = CSI_COMMANDS[cmd_.alias]  # type: ignore[assignment]
            elif isinstance(cmd_, CSICommand):
                csi_cmd = cmd_
            elif cmd_[0] == "alias":  # fallback, hard deprecated
                csi_cmd = CSI_COMMANDS[CSIAlias(*cmd_).alias]
            else:
                csi_cmd = CSICommand(*cmd_)  # fallback, hard deprecated

            number_of_args, default_value, cmd = csi_cmd
            while len(escbuf) < number_of_args:
                escbuf.append(default_value)
            for i in range(len(escbuf)):
                if escbuf[i] is None or escbuf[i] == 0:
                    escbuf[i] = default_value

            with suppress(ValueError):
                cmd(self, escbuf, qmark)
                # ignore commands that don't match the
                # unpacked tuples in CSI_COMMANDS.

    def parse_noncsi(self, char: bytes, mod: bytes = b"") -> None:
        """
        Parse escape sequences which are not CSI.
        """
        if mod == b"#" and char == b"8":
            self.decaln()
        elif mod == b"%":  # select main character set
            if char == b"@":
                self.modes.main_charset = CHARSET_DEFAULT
            elif char in b"G8":
                # 8 is obsolete and only for backwards compatibility
                self.modes.main_charset = CHARSET_UTF8
        elif mod in {b"(", b")"}:  # define G0/G1
            self.set_g01(char, mod)
        elif char == b"M":  # reverse line feed
            self.linefeed(reverse=True)
        elif char == b"D":  # line feed
            self.linefeed()
        elif char == b"c":  # reset terminal
            self.reset()
        elif char == b"E":  # newline
            self.newline()
        elif char == b"H":  # set tabstop
            self.set_tabstop()
        elif char == b"Z":  # DECID
            self.widget.respond(f"{ESC}[?6c")
        elif char == b"7":  # save current state
            self.save_cursor(with_attrs=True)
        elif char == b"8":  # restore current state
            self.restore_cursor(with_attrs=True)

    def parse_osc(self, buf: bytes) -> None:
        """
        Parse operating system command.
        """
        if buf.startswith((b";", b"0;", b"2;")):
            # set window title
            self.widget.set_title(buf.decode().partition(";")[2])

    def parse_escape(self, char: bytes) -> None:
        if self.parsestate == 1:
            # within CSI
            if char in CSI_COMMANDS:
                self.parse_csi(char)
                self.parsestate = 0
            elif char in b"0123456789;" or (not self.escbuf and char == b"?"):
                self.escbuf += char
                return
        elif self.parsestate == 0 and char == b"]":
            # start of OSC
            self.escbuf = b""
            self.parsestate = 2
            return
        elif self.parsestate == 2 and char == b"\a":
            # end of OSC
            self.parse_osc(self.escbuf.lstrip(b"0"))
        elif self.parsestate == 2 and self.escbuf[-1:] + char == f"{ESC}\\".encode("iso8859-1"):
            # end of OSC
            self.parse_osc(self.escbuf[:-1].lstrip(b"0"))
        elif self.parsestate == 2 and self.escbuf.startswith(b"P") and len(self.escbuf) == 8:
            # set palette (ESC]Pnrrggbb)
            pass
        elif self.parsestate == 2 and not self.escbuf and char == b"R":
            # reset palette
            pass
        elif self.parsestate == 2:
            self.escbuf += char
            return
        elif self.parsestate == 0 and char == b"[":
            # start of CSI
            self.escbuf = b""
            self.parsestate = 1
            return
        elif self.parsestate == 0 and char in {b"%", b"#", b"(", b")"}:
            # non-CSI sequence
            self.escbuf = char
            self.parsestate = 3
            return
        elif self.parsestate == 3:
            self.parse_noncsi(char, self.escbuf)
        elif char in {b"c", b"D", b"E", b"H", b"M", b"Z", b"7", b"8", b">", b"="}:
            self.parse_noncsi(char)

        self.leave_escape()

    def leave_escape(self) -> None:
        self.within_escape = False
        self.parsestate = 0
        self.escbuf = b""

    def get_utf8_len(self, bytenum: int) -> int:
        """
        Process startbyte and return the number of bytes following it to get a
        valid UTF-8 multibyte sequence.

        bytenum -- an integer ordinal
        """
        length = 0

        while bytenum & 0x40:
            bytenum <<= 1
            length += 1

        return length

    def addbyte(self, byte: int) -> None:
        """
        Parse main charset and add the processed byte(s) to the terminal state
        machine.

        byte -- an integer ordinal
        """
        if self.modes.main_charset == CHARSET_UTF8 or util.get_encoding() == "utf8":
            if byte >= 0xC0:
                # start multibyte sequence
                self.utf8_eat_bytes = self.get_utf8_len(byte)
                self.utf8_buffer = bytearray([byte])
                return
            if 0x80 <= byte < 0xC0 and self.utf8_eat_bytes is not None:
                if self.utf8_eat_bytes > 1:
                    # continue multibyte sequence
                    self.utf8_eat_bytes -= 1
                    self.utf8_buffer.append(byte)
                    return

                # end multibyte sequence
                self.utf8_eat_bytes = None
                sequence = (self.utf8_buffer + bytes([byte])).decode("utf-8", "ignore")
                if not sequence:
                    # invalid multibyte sequence, stop processing
                    return
                char = sequence.encode(util.get_encoding(), "replace")
            else:
                self.utf8_eat_bytes = None
                char = bytes([byte])
        else:
            char = bytes([byte])

        self.process_char(char)

    def process_char(self, char: int | bytes) -> None:
        """
        Process a single character (single- and multi-byte).

        char -- a byte string
        """
        x, y = self.term_cursor

        if isinstance(char, int):
            char = char.to_bytes(1, "little")

        dc = self.modes.display_ctrl

        if char == ESC_B and self.parsestate != 2:  # escape
            self.within_escape = True
        elif not dc and char == b"\r":  # carriage return CR
            self.carriage_return()
        elif not dc and char == b"\x0f":  # activate G0
            self.charset.activate(0)
        elif not dc and char == b"\x0e":  # activate G1
            self.charset.activate(1)
        elif not dc and char in b"\n\v\f":  # line feed LF/VT/FF
            self.linefeed()
            if self.modes.lfnl:
                self.carriage_return()
        elif not dc and char == b"\t":  # char tab
            self.tab()
        elif not dc and char == b"\b":  # backspace BS
            if x > 0:
                self.set_term_cursor(x - 1, y)
        elif not dc and char == b"\a" and self.parsestate != 2:  # BEL
            # we need to check if we're in parsestate 2, as an OSC can be
            # terminated by the BEL character!
            self.widget.beep()
        elif not dc and char in b"\x18\x1a":  # CAN/SUB
            self.leave_escape()
        elif not dc and char in b"\x00\x7f":  # NUL/DEL
            pass  # this is ignored
        elif self.within_escape:
            self.parse_escape(char)
        elif not dc and char == b"\x9b":  # CSI (equivalent to "ESC [")
            self.within_escape = True
            self.escbuf = b""
            self.parsestate = 1
        else:
            self.push_cursor(char)

    def set_char(self, char: bytes, x: int | None = None, y: int | None = None) -> None:
        """
        Set character of either the current cursor position
        or a position given by 'x' and/or 'y' to 'char'.
        """
        if x is None:
            x = self.term_cursor[0]
        if y is None:
            y = self.term_cursor[1]

        x, y = self.constrain_coords(x, y)
        self.term[y][x] = (self.attrspec, self.charset.current, char)

    def constrain_coords(self, x: int, y: int, ignore_scrolling: bool = False) -> tuple[int, int]:
        """
        Checks if x/y are within the terminal and returns the corrected version.
        If 'ignore_scrolling' is set, constrain within the full size of the
        screen and not within scrolling region.
        """
        if x >= self.width:
            x = self.width - 1
        elif x < 0:
            x = 0

        if self.modes.constrain_scrolling and not ignore_scrolling:
            if y > self.scrollregion_end:
                y = self.scrollregion_end
            elif y < self.scrollregion_start:
                y = self.scrollregion_start
        else:  # noqa: PLR5501  # pylint: disable=else-if-used  # readability
            if y >= self.height:
                y = self.height - 1
            elif y < 0:
                y = 0

        return x, y

    def linefeed(self, reverse: bool = False) -> None:
        """
        Move the cursor down (or up if reverse is True) one line but don't reset
        horizontal position.
        """
        x, y = self.term_cursor

        if reverse:
            if y <= 0 < self.scrollregion_start:
                pass
            elif y == self.scrollregion_start:
                self.scroll(reverse=True)
            else:
                y -= 1
        else:  # noqa: PLR5501  # pylint: disable=else-if-used  # readability
            if y >= self.height - 1 > self.scrollregion_end:
                pass
            elif y == self.scrollregion_end:
                self.scroll()
            else:
                y += 1

        self.set_term_cursor(x, y)

    def carriage_return(self) -> None:
        self.set_term_cursor(0, self.term_cursor[1])

    def newline(self) -> None:
        """
        Do a carriage return followed by a line feed.
        """
        self.carriage_return()
        self.linefeed()

    def move_cursor(
        self,
        x: int,
        y: int,
        relative_x: bool = False,
        relative_y: bool = False,
        relative: bool = False,
    ) -> None:
        """
        Move cursor to position x/y while constraining terminal sizes.
        If 'relative' is True, x/y is relative to the current cursor
        position. 'relative_x' and 'relative_y' is the same but just with
        the corresponding axis.
        """
        if relative:
            relative_y = relative_x = True

        if relative_x:
            x += self.term_cursor[0]

        if relative_y:
            y += self.term_cursor[1]
        elif self.modes.constrain_scrolling:
            y += self.scrollregion_start

        self.set_term_cursor(x, y)

    def push_char(self, char: bytes | None, x: int, y: int) -> None:
        """
        Push one character to current position and advance cursor to x/y.
        """
        if char is not None:
            char = self.charset.apply_mapping(char)
            if self.modes.insert:
                self.insert_chars(char=char)
            else:
                self.set_char(char)

        self.set_term_cursor(x, y)

    def push_cursor(self, char: bytes | None = None) -> None:
        """
        Move cursor one character forward wrapping lines as needed.
        If 'char' is given, put the character into the former position.
        """
        x, y = self.term_cursor

        if self.modes.autowrap:
            if x + 1 >= self.width and not self.is_rotten_cursor:
                # "rotten cursor" - this is when the cursor gets to the rightmost
                # position of the screen, the cursor position remains the same but
                # one last set_char() is allowed for that piece of sh^H^H"border".
                self.is_rotten_cursor = True
                self.push_char(char, x, y)
            else:
                x += 1

                if x >= self.width and self.is_rotten_cursor:
                    if y >= self.scrollregion_end:
                        self.scroll()
                    else:
                        y += 1

                    x = 1

                    self.set_term_cursor(0, y)

                self.push_char(char, x, y)

                self.is_rotten_cursor = False
        else:
            if x + 1 < self.width:
                x += 1

            self.is_rotten_cursor = False
            self.push_char(char, x, y)

    def save_cursor(self, with_attrs: bool = False) -> None:
        self.saved_cursor = tuple(self.term_cursor)
        if with_attrs:
            self.saved_attrs = (copy.copy(self.attrspec), copy.copy(self.charset))

    def restore_cursor(self, with_attrs: bool = False) -> None:
        if self.saved_cursor is None:
            return

        x, y = self.saved_cursor
        self.set_term_cursor(x, y)

        if with_attrs and self.saved_attrs is not None:
            self.attrspec, self.charset = (copy.copy(self.saved_attrs[0]), copy.copy(self.saved_attrs[1]))

    def tab(self, tabstop: int = 8) -> None:
        """
        Moves cursor to the next 'tabstop' filling everything in between
        with spaces.
        """
        x, y = self.term_cursor

        while x < self.width - 1:
            self.set_char(b" ")
            x += 1

            if self.is_tabstop(x):
                break

        self.is_rotten_cursor = False
        self.set_term_cursor(x, y)

    def scroll(self, reverse: bool = False) -> None:
        """
        Append a new line at the bottom and put the topmost line into the
        scrollback buffer.

        If reverse is True, do exactly the opposite, but don't save into
        scrollback buffer.
        """
        if reverse:
            self.term.pop(self.scrollregion_end)
            self.term.insert(self.scrollregion_start, self.empty_line())
        else:
            killed = self.term.pop(self.scrollregion_start)
            self.scrollback_buffer.append(killed)
            self.term.insert(self.scrollregion_end, self.empty_line())

    def decaln(self) -> None:
        """
        DEC screen alignment test: Fill screen with E's.
        """
        for row in range(self.height):
            self.term[row] = self.empty_line(b"E")

    def blank_line(self, row: int) -> None:
        """
        Blank a single line at the specified row, without modifying other lines.
        """
        self.term[row] = self.empty_line()

    def insert_chars(
        self,
        position: tuple[int, int] | None = None,
        chars: int = 1,
        char: bytes | None = None,
    ) -> None:
        """
        Insert 'chars' number of either empty characters - or those specified by
        'char' - before 'position' (or the current position if not specified)
        pushing subsequent characters of the line to the right without wrapping.
        """
        if position is None:
            position = self.term_cursor

        if chars == 0:
            chars = 1

        if char is None:
            char_spec = self.empty_char()
        else:
            char_spec = (self.attrspec, self.charset.current, char)

        x, y = position

        while chars > 0:
            self.term[y].insert(x, char_spec)
            self.term[y].pop()
            chars -= 1

    def remove_chars(self, position: tuple[int, int] | None = None, chars: int = 1) -> None:
        """
        Remove 'chars' number of empty characters from 'position' (or the current
        position if not specified) pulling subsequent characters of the line to
        the left without joining any subsequent lines.
        """
        if position is None:
            position = self.term_cursor

        if chars == 0:
            chars = 1

        x, y = position

        while chars > 0:
            self.term[y].pop(x)
            self.term[y].append(self.empty_char())
            chars -= 1

    def insert_lines(self, row: int | None = None, lines: int = 1) -> None:
        """
        Insert 'lines' of empty lines after the specified row, pushing all
        subsequent lines to the bottom. If no 'row' is specified, the current
        row is used.
        """
        if row is None:
            row = self.term_cursor[1]
        else:
            row = self.scrollregion_start

        if lines == 0:
            lines = 1

        while lines > 0:
            self.term.insert(row, self.empty_line())
            self.term.pop(self.scrollregion_end)
            lines -= 1

    def remove_lines(self, row: int | None = None, lines: int = 1) -> None:
        """
        Remove 'lines' number of lines at the specified row, pulling all
        subsequent lines to the top. If no 'row' is specified, the current row
        is used.
        """
        if row is None:
            row = self.term_cursor[1]
        else:
            row = self.scrollregion_start

        if lines == 0:
            lines = 1

        while lines > 0:
            self.term.pop(row)
            self.term.insert(self.scrollregion_end, self.empty_line())
            lines -= 1

    def erase(
        self,
        start: tuple[int, int] | tuple[int, int, bool],
        end: tuple[int, int] | tuple[int, int, bool],
    ) -> None:
        """
        Erase a region of the terminal. The 'start' tuple (x, y) defines the
        starting position of the erase, while end (x, y) the last position.

        For example if the terminal size is 4x3, start=(1, 1) and end=(1, 2)
        would erase the following region:

        ....
        .XXX
        XX..
        """
        sx, sy = self.constrain_coords(*start)
        ex, ey = self.constrain_coords(*end)

        # within a single row
        if sy == ey:
            for x in range(sx, ex + 1):
                self.term[sy][x] = self.empty_char()
            return

        # spans multiple rows
        y = sy
        while y <= ey:
            if y == sy:
                for x in range(sx, self.width):
                    self.term[y][x] = self.empty_char()
            elif y == ey:
                for x in range(ex + 1):
                    self.term[y][x] = self.empty_char()
            else:
                self.blank_line(y)

            y += 1

    def sgi_to_attrspec(
        self,
        attrs: Sequence[int],
        fg: int,
        bg: int,
        attributes: set[str],
        prev_colors: int,
    ) -> AttrSpec | None:
        """
        Parse SGI sequence and return an AttrSpec representing the sequence
        including all earlier sequences specified as 'fg', 'bg' and
        'attributes'.
        """

        idx = 0
        colors = prev_colors

        while idx < len(attrs):
            attr = attrs[idx]
            if 30 <= attr <= 37:
                fg = attr - 30
                colors = max(16, colors)
            elif 40 <= attr <= 47:
                bg = attr - 40
                colors = max(16, colors)
            # AIXTERM bright color spec
            # https://en.wikipedia.org/wiki/ANSI_escape_code
            elif 90 <= attr <= 97:
                fg = attr - 90 + 8
                colors = max(16, colors)
            elif 100 <= attr <= 107:
                bg = attr - 100 + 8
                colors = max(16, colors)
            elif attr in {38, 48}:
                if idx + 2 < len(attrs) and attrs[idx + 1] == 5:
                    # 8 bit color specification
                    color = attrs[idx + 2]
                    colors = max(256, colors)
                    if attr == 38:
                        fg = color
                    else:
                        bg = color
                    idx += 2
                elif idx + 4 < len(attrs) and attrs[idx + 1] == 2:
                    # 24 bit color specification
                    color = (attrs[idx + 2] << 16) + (attrs[idx + 3] << 8) + attrs[idx + 4]
                    colors = 2**24
                    if attr == 38:
                        fg = color
                    else:
                        bg = color
                    idx += 4
            elif attr == 39:
                # set default foreground color
                fg = None
            elif attr == 49:
                # set default background color
                bg = None
            elif attr == 10:
                self.charset.reset_sgr_ibmpc()
                self.modes.display_ctrl = False
            elif attr in {11, 12}:
                self.charset.set_sgr_ibmpc()
                self.modes.display_ctrl = True

            # set attributes
            elif attr == 1:
                attributes.add("bold")
            elif attr == 4:
                attributes.add("underline")
            elif attr == 5:
                attributes.add("blink")
            elif attr == 7:
                attributes.add("standout")

            # unset attributes
            elif attr == 24:
                attributes.discard("underline")
            elif attr == 25:
                attributes.discard("blink")
            elif attr == 27:
                attributes.discard("standout")
            elif attr == 0:
                # clear all attributes
                fg = bg = None
                attributes.clear()

            idx += 1

        if "bold" in attributes and colors == 16 and fg is not None and fg < 8:
            fg += 8

        def _defaulter(color: int | None, colors: int) -> str:
            if color is None:
                return "default"
            # Note: we can't detect 88 color mode
            if color > 255 or colors == 2**24:
                return _color_desc_true(color)
            if color > 15 or colors == 256:
                return _color_desc_256(color)
            return _BASIC_COLORS[color]

        decoded_fg = _defaulter(fg, colors)
        decoded_bg = _defaulter(bg, colors)

        if attributes:
            decoded_fg = ",".join((decoded_fg, *list(attributes)))

        if decoded_fg == decoded_bg == "default":
            return None

        if colors:
            return AttrSpec(decoded_fg, decoded_bg, colors=colors)

        return AttrSpec(decoded_fg, decoded_bg)

    def csi_set_attr(self, attrs: Sequence[int]) -> None:
        """
        Set graphics rendition.
        """
        if attrs[-1] == 0:
            self.attrspec = None

        attributes = set()
        if self.attrspec is None:
            fg = bg = None
        else:
            # set default values from previous attrspec
            if "default" in self.attrspec.foreground:
                fg = None
            else:
                fg = self.attrspec.foreground_number
                if fg >= 8 and self.attrspec.colors == 16:
                    fg -= 8

            if "default" in self.attrspec.background:
                bg = None
            else:
                bg = self.attrspec.background_number
                if bg >= 8 and self.attrspec.colors == 16:
                    bg -= 8

            for attr in ("bold", "underline", "blink", "standout"):
                if not getattr(self.attrspec, attr):
                    continue

                attributes.add(attr)

        attrspec = self.sgi_to_attrspec(attrs, fg, bg, attributes, self.attrspec.colors if self.attrspec else 1)

        if self.modes.reverse_video:
            self.attrspec = self.reverse_attrspec(attrspec)
        else:
            self.attrspec = attrspec

    def reverse_attrspec(self, attrspec: AttrSpec | None, undo: bool = False) -> AttrSpec:
        """
        Put standout mode to the 'attrspec' given and remove it if 'undo' is
        True.
        """
        if attrspec is None:
            attrspec = AttrSpec("default", "default")
        attrs = [fg.strip() for fg in attrspec.foreground.split(",")]
        if "standout" in attrs and undo:
            attrs.remove("standout")
            attrspec = attrspec.copy_modified(fg=",".join(attrs))
        elif "standout" not in attrs and not undo:
            attrs.append("standout")
            attrspec = attrspec.copy_modified(fg=",".join(attrs))
        return attrspec

    def reverse_video(self, undo: bool = False) -> None:
        """
        Reverse video/scanmode (DECSCNM) by swapping fg and bg colors.
        """
        for y in range(self.height):
            for x in range(self.width):
                char = self.term[y][x]
                attrs = self.reverse_attrspec(char[0], undo=undo)
                self.term[y][x] = (attrs,) + char[1:]

    def set_mode(
        self,
        mode: Literal[1, 3, 4, 5, 6, 7, 20, 25, 2004],
        flag: bool,
        qmark: bool,
        reset: bool,
    ) -> None:
        """
        Helper method for csi_set_modes: set single mode.
        """
        if qmark:
            # DEC private mode
            if mode == 1:
                # cursor keys send an ESC O prefix, rather than ESC [
                self.modes.keys_decckm = flag
            elif mode == 3:
                # deccolm just clears the screen
                self.clear()
            elif mode == 5:
                if self.modes.reverse_video != flag:
                    self.reverse_video(undo=not flag)
                self.modes.reverse_video = flag
            elif mode == 6:
                self.modes.constrain_scrolling = flag
                self.set_term_cursor(0, 0)
            elif mode == 7:
                self.modes.autowrap = flag
            elif mode == 25:
                self.modes.visible_cursor = flag
                self.set_term_cursor()
            elif mode == 2004:
                self.modes.bracketed_paste = flag
        else:  # noqa: PLR5501  # pylint: disable=else-if-used  # readability
            # ECMA-48
            if mode == 3:
                self.modes.display_ctrl = flag
            elif mode == 4:
                self.modes.insert = flag
            elif mode == 20:
                self.modes.lfnl = flag

    def csi_set_modes(self, modes: Iterable[int], qmark: bool, reset: bool = False) -> None:
        """
        Set (DECSET/ECMA-48) or reset modes (DECRST/ECMA-48) if reset is True.
        """
        flag = not reset

        for mode in modes:
            self.set_mode(mode, flag, qmark, reset)

    def csi_set_scroll(self, top: int = 0, bottom: int = 0) -> None:
        """
        Set scrolling region, 'top' is the line number of first line in the
        scrolling region. 'bottom' is the line number of bottom line. If both
        are set to 0, the whole screen will be used (default).
        """
        if not top:
            top = 1
        if not bottom:
            bottom = self.height

        if top < bottom <= self.height:
            self.scrollregion_start = self.constrain_coords(0, top - 1, ignore_scrolling=True)[1]
            self.scrollregion_end = self.constrain_coords(0, bottom - 1, ignore_scrolling=True)[1]

            self.set_term_cursor(0, 0)

    def csi_clear_tabstop(self, mode: Literal[0, 3] = 0):
        """
        Clear tabstop at current position or if 'mode' is 3, delete all
        tabstops.
        """
        if mode == 0:
            self.set_tabstop(remove=True)
        elif mode == 3:
            self.set_tabstop(clear=True)

    def csi_get_device_attributes(self, qmark: bool) -> None:
        """
        Report device attributes (what are you?). In our case, we'll report
        ourself as a VT102 terminal.
        """
        if not qmark:
            self.widget.respond(f"{ESC}[?6c")

    def csi_status_report(self, mode: Literal[5, 6]) -> None:
        """
        Report various information about the terminal status.
        Information is queried by 'mode', where possible values are:
            5 -> device status report
            6 -> cursor position report
        """
        if mode == 5:
            # terminal OK
            self.widget.respond(f"{ESC}[0n")
        elif mode == 6:
            x, y = self.term_cursor
            self.widget.respond(ESC + f"[{y + 1:d};{x + 1:d}R")

    def csi_erase_line(self, mode: Literal[0, 1, 2]) -> None:
        """
        Erase current line, modes are:
            0 -> erase from cursor to end of line.
            1 -> erase from start of line to cursor.
            2 -> erase whole line.
        """
        x, y = self.term_cursor

        if mode == 0:
            self.erase(self.term_cursor, (self.width - 1, y))
        elif mode == 1:
            self.erase((0, y), (x, y))
        elif mode == 2:
            self.blank_line(y)

    def csi_erase_display(self, mode: Literal[0, 1, 2]) -> None:
        """
        Erase display, modes are:
            0 -> erase from cursor to end of display.
            1 -> erase from start to cursor.
            2 -> erase the whole display.
        """
        if mode == 0:
            self.erase(self.term_cursor, (self.width - 1, self.height - 1))
        if mode == 1:
            self.erase((0, 0), (self.term_cursor[0] - 1, self.term_cursor[1]))
        elif mode == 2:
            self.clear(cursor=self.term_cursor)

    def csi_set_keyboard_leds(self, mode: Literal[0, 1, 2, 3] = 0) -> None:
        """
        Set keyboard LEDs, modes are:
            0 -> clear all LEDs
            1 -> set scroll lock LED
            2 -> set num lock LED
            3 -> set caps lock LED

        This currently just emits a signal, so it can be processed by another
        widget or the main application.
        """
        states = {
            0: "clear",
            1: "scroll_lock",
            2: "num_lock",
            3: "caps_lock",
        }

        if mode in states:
            self.widget.leds(states[mode])

    def clear(self, cursor: tuple[int, int] | None = None) -> None:
        """
        Clears the whole terminal screen and resets the cursor position
        to (0, 0) or to the coordinates given by 'cursor'.
        """
        self.term = [self.empty_line() for _ in range(self.height)]

        if cursor is None:
            self.set_term_cursor(0, 0)
        else:
            self.set_term_cursor(*cursor)

    def cols(self) -> int:
        return self.width

    def rows(self) -> int:
        return self.height

    def content(
        self,
        trim_left: int = 0,
        trim_top: int = 0,
        cols: int | None = None,
        rows: int | None = None,
        attr=None,
    ) -> Iterable[list[tuple[object, Literal["0", "U"] | None, bytes]]]:
        if self.scrolling_up == 0:
            yield from self.term
        else:
            buf = self.scrollback_buffer + self.term
            yield from buf[-(self.height + self.scrolling_up) : -self.scrolling_up]

    def content_delta(self, other: Canvas):
        if other is self:
            return [self.cols()] * self.rows()
        return self.content()


class Terminal(Widget):
    _selectable = True
    _sizing = frozenset([Sizing.BOX])

    signals: typing.ClassVar[list[str]] = ["closed", "beep", "leds", "title", "resize"]

    def __init__(
        self,
        command: Sequence[str | bytes] | Callable[[], typing.Any] | None,
        env: Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
        main_loop: event_loop.EventLoop | None = None,
        escape_sequence: str | None = None,
        encoding: str = "utf-8",
    ):
        """
        A terminal emulator within a widget.

        ``command`` is the command to execute inside the terminal,
        provided as a list of the command followed by its arguments.
        If 'command' is None, the command is the current user's shell.
        You can also provide a callable instead of a command, which will be executed in the subprocess.

        ``env`` can be used to pass custom environment variables. If omitted,
        os.environ is used.

        ``main_loop`` should be provided, because the canvas state machine needs
        to act on input from the PTY master device. This object must have
        watch_file and remove_watch_file methods.

        ``escape_sequence`` is the urwid key symbol which should be used to break
        out of the terminal widget. If it's not specified, ``ctrl a`` is used.

        ``encoding`` specifies the encoding that is being used when local
        keypresses in Unicode are encoded into raw bytes. UTF-8 is used by default.
        Set this to the encoding of your terminal if you need to transmit
        characters to the spawned process in non-UTF8 encoding.
        Applies to Python 3.x only.

        .. note::

            If you notice your Terminal instance is not printing unicode glyphs
            correctly, make sure the global encoding for urwid is set to
            ``utf8`` with ``urwid.set_encoding("utf8")``. See
            :ref:`text-encodings` for more details.
        """
        super().__init__()

        self.escape_sequence: str = escape_sequence or "ctrl a"

        self.env = dict(env or os.environ)

        self.command = command or [self.env.get("SHELL", "/bin/sh")]

        self.encoding = encoding

        self.keygrab = False
        self.last_key: str | None = None

        self.response_buffer: list[str] = []

        self.term_modes = TermModes()

        if main_loop is not None:
            self.main_loop = main_loop
        else:
            self.main_loop = event_loop.SelectEventLoop()

        self.master: int | None = None
        self.pid: int | None = None

        self.width: int | None = None
        self.height: int | None = None
        self.term: TermCanvas | None = None
        self.has_focus = False
        self.terminated = False

    def get_cursor_coords(self, size: tuple[int, int]) -> tuple[int, int] | None:
        """Return the cursor coordinates for this terminal"""
        if self.term is None:
            return None

        # temporarily set width/height to figure out the new cursor position
        # given the provided width/height
        orig_width, orig_height = self.term.width, self.term.height

        self.term.width = size[0]
        self.term.height = size[1]

        x, y = self.term.constrain_coords(
            self.term.term_cursor[0],
            self.term.term_cursor[1],
        )

        self.term.width, self.term.height = orig_width, orig_height

        return (x, y)

    def spawn(self) -> None:
        env = self.env
        env["TERM"] = "linux"

        self.pid, self.master = pty.fork()

        if self.pid == 0:
            if callable(self.command):
                try:
                    # noinspection PyBroadException
                    try:
                        self.command()
                    except BaseException:  # special case
                        sys.stderr.write(traceback.format_exc())
                        sys.stderr.flush()
                finally:
                    os._exit(0)
            else:
                os.execvpe(self.command[0], self.command, env)  # noqa: S606

        if self.main_loop is None:
            fcntl.fcntl(self.master, fcntl.F_SETFL, os.O_NONBLOCK)

        atexit.register(self.terminate)

    def terminate(self) -> None:
        if self.terminated:
            return

        self.terminated = True
        self.remove_watch()
        self.change_focus(False)

        if self.pid > 0:
            self.set_termsize(0, 0)
            for sig in (signal.SIGHUP, signal.SIGCONT, signal.SIGINT, signal.SIGTERM, signal.SIGKILL):
                try:
                    os.kill(self.pid, sig)
                    pid, _status = os.waitpid(self.pid, os.WNOHANG)
                except OSError:
                    break

                if pid == 0:
                    break
                time.sleep(0.1)
            with suppress(OSError):
                os.waitpid(self.pid, 0)

            os.close(self.master)

    def beep(self) -> None:
        self._emit("beep")

    def leds(self, which: Literal["clear", "scroll_lock", "num_lock", "caps_lock"]) -> None:
        self._emit("leds", which)

    def respond(self, string: str) -> None:
        """
        Respond to the underlying application with 'string'.
        """
        self.response_buffer.append(string)

    def flush_responses(self) -> None:
        for string in self.response_buffer:
            os.write(self.master, string.encode("ascii"))
        self.response_buffer = []

    def set_termsize(self, width: int, height: int) -> None:
        winsize = struct.pack("HHHH", height, width, 0, 0)
        fcntl.ioctl(self.master, termios.TIOCSWINSZ, winsize)

    def touch_term(self, width: int, height: int) -> None:
        process_opened = False

        if self.pid is None:
            self.spawn()
            process_opened = True

        if self.width == width and self.height == height:
            return

        self.set_termsize(width, height)

        if not self.term:
            self.term = TermCanvas(width, height, self)
        else:
            self.term.resize(width, height)

        self.width = width
        self.height = height

        if process_opened:
            self.add_watch()

        self._emit("resize", (width, height))

    def set_title(self, title) -> None:
        self._emit("title", title)

    def change_focus(self, has_focus) -> None:
        """
        Ignore SIGINT if this widget has focus.
        """
        if self.terminated:
            return

        self.has_focus = has_focus

        if self.term is not None:
            self.term.has_focus = has_focus
            self.term.set_term_cursor()

        if has_focus:
            self.old_tios = RealTerminal().tty_signal_keys()
            RealTerminal().tty_signal_keys(*(["undefined"] * 5))
        elif hasattr(self, "old_tios"):
            RealTerminal().tty_signal_keys(*self.old_tios)

    def render(self, size: tuple[int, int], focus: bool = False) -> TermCanvas:
        if not self.terminated:
            self.change_focus(focus)

            width, height = size
            self.touch_term(width, height)

            if self.main_loop is None:
                self.feed()

        return self.term

    def add_watch(self) -> None:
        if self.main_loop is None:
            return
        self.main_loop.watch_file(self.master, self.feed)

    def remove_watch(self) -> None:
        if self.main_loop is None:
            return
        self.main_loop.remove_watch_file(self.master)

    def wait_and_feed(self, timeout: float = 1.0) -> None:
        with selectors.DefaultSelector() as selector:
            selector.register(self.master, selectors.EVENT_READ)

            selector.select(timeout)

        self.feed()

    def feed(self) -> None:
        data = EOF

        try:
            data = os.read(self.master, 4096)
        except OSError as e:
            if e.errno == errno.EIO:  # EIO, child terminated
                data = EOF
            elif e.errno == errno.EWOULDBLOCK:  # empty buffer
                return
            else:
                raise

        if data == EOF:
            self.terminate()
            self._emit("closed")
            return

        self.term.addstr(data)

        self.flush_responses()

    def keypress(self, size: tuple[int, int], key: str) -> str | None:
        if self.terminated:
            return key

        if key in {"begin paste", "end paste"}:
            if self.term_modes.bracketed_paste:
                pass  # passthrough bracketed paste sequences
            else:  # swallow bracketed paste sequences
                self.last_key = key
                return None

        if key == "window resize":
            width, height = size
            self.touch_term(width, height)
            return None

        if self.last_key == key == self.escape_sequence:
            # escape sequence pressed twice...
            self.last_key = key
            self.keygrab = True
            # ... so pass it to the terminal
        elif self.keygrab:
            if self.escape_sequence == key:
                # stop grabbing the terminal
                self.keygrab = False
                self.last_key = key
                return None
        else:
            if key == "page up":
                self.term.scroll_buffer()
                self.last_key = key
                self._invalidate()
                return None

            if key == "page down":
                self.term.scroll_buffer(up=False)
                self.last_key = key
                self._invalidate()
                return None

            if self.last_key == self.escape_sequence and key != self.escape_sequence:
                # hand down keypress directly after ungrab.
                self.last_key = key
                return key

            if self.escape_sequence == key:
                # start grabbing the terminal
                self.keygrab = True
                self.last_key = key
                return None

            if self._command_map[key] is None or key == "enter":
                # printable character or escape sequence means:
                # lock in terminal...
                self.keygrab = True
                # ... and do key processing
            else:
                # hand down keypress
                self.last_key = key
                return key

        self.last_key = key

        self.term.scroll_buffer(reset=True)

        if key.startswith("ctrl "):
            if key[-1].islower():
                key = chr(ord(key[-1]) - ord("a") + 1)
            else:
                key = chr(ord(key[-1]) - ord("A") + 1)
        else:  # noqa: PLR5501  # pylint: disable=else-if-used  # readability
            if self.term_modes.keys_decckm and key in KEY_TRANSLATIONS_DECCKM:
                key = KEY_TRANSLATIONS_DECCKM[key]
            else:
                key = KEY_TRANSLATIONS.get(key, key)

        # ENTER transmits both a carriage return and linefeed in LF/NL mode.
        if self.term_modes.lfnl and key == "\r":
            key += "\n"

        os.write(self.master, key.encode(self.encoding, "ignore"))

        return None
urwid-2.6.16/urwid/widget/000077500000000000000000000000001470350774000153645ustar00rootroot00000000000000urwid-2.6.16/urwid/widget/__init__.py000066400000000000000000000111131470350774000174720ustar00rootroot00000000000000from __future__ import annotations

from .attr_map import AttrMap, AttrMapError
from .attr_wrap import AttrWrap
from .bar_graph import BarGraph, BarGraphError, BarGraphMeta, GraphVScale, scale_bar_values
from .big_text import BigText
from .box_adapter import BoxAdapter, BoxAdapterError
from .columns import Columns, ColumnsError, ColumnsWarning
from .constants import (
    RELATIVE_100,
    Align,
    Sizing,
    VAlign,
    WHSettings,
    WrapMode,
    normalize_align,
    normalize_height,
    normalize_valign,
    normalize_width,
    simplify_align,
    simplify_height,
    simplify_valign,
    simplify_width,
)
from .container import WidgetContainerListContentsMixin, WidgetContainerMixin
from .divider import Divider
from .edit import Edit, EditError, IntEdit
from .filler import Filler, FillerError, calculate_top_bottom_filler
from .frame import Frame, FrameError
from .grid_flow import GridFlow, GridFlowError, GridFlowWarning
from .line_box import LineBox
from .listbox import ListBox, ListBoxError, ListWalker, ListWalkerError, SimpleFocusListWalker, SimpleListWalker
from .monitored_list import MonitoredFocusList, MonitoredList
from .overlay import Overlay, OverlayError, OverlayWarning
from .padding import Padding, PaddingError, PaddingWarning, calculate_left_right_padding
from .pile import Pile, PileError, PileWarning
from .popup import PopUpLauncher, PopUpTarget
from .progress_bar import ProgressBar
from .scrollable import Scrollable, ScrollableError, ScrollBar
from .solid_fill import SolidFill
from .text import Text, TextError
from .treetools import ParentNode, TreeListBox, TreeNode, TreeWalker, TreeWidget, TreeWidgetError
from .widget import (
    BoxWidget,
    FixedWidget,
    FlowWidget,
    Widget,
    WidgetError,
    WidgetMeta,
    WidgetWarning,
    WidgetWrap,
    WidgetWrapError,
    delegate_to_widget_mixin,
    fixed_size,
    nocache_widget_render,
    nocache_widget_render_instance,
)
from .widget_decoration import WidgetDecoration, WidgetDisable, WidgetPlaceholder
from .wimp import Button, CheckBox, CheckBoxError, RadioButton, SelectableIcon

__all__ = (
    "ANY",
    "BOTTOM",
    "BOX",
    "CENTER",
    "CLIP",
    "ELLIPSIS",
    "FIXED",
    "FLOW",
    "GIVEN",
    "LEFT",
    "MIDDLE",
    "PACK",
    "RELATIVE",
    "RELATIVE_100",
    "RIGHT",
    "SPACE",
    "TOP",
    "WEIGHT",
    "Align",
    "AttrMap",
    "AttrMapError",
    "AttrWrap",
    "BarGraph",
    "BarGraphError",
    "BarGraphMeta",
    "BigText",
    "BoxAdapter",
    "BoxAdapterError",
    "BoxWidget",
    "Button",
    "CheckBox",
    "CheckBoxError",
    "Columns",
    "ColumnsError",
    "ColumnsWarning",
    "Divider",
    "Edit",
    "EditError",
    "Filler",
    "FillerError",
    "FixedWidget",
    "FlowWidget",
    "Frame",
    "FrameError",
    "GraphVScale",
    "GridFlow",
    "GridFlowError",
    "GridFlowWarning",
    "IntEdit",
    "LineBox",
    "ListBox",
    "ListBoxError",
    "ListWalker",
    "ListWalkerError",
    "MonitoredFocusList",
    "MonitoredList",
    "Overlay",
    "OverlayError",
    "OverlayWarning",
    "Padding",
    "PaddingError",
    "PaddingWarning",
    "ParentNode",
    "Pile",
    "PileError",
    "PileWarning",
    "PopUpLauncher",
    "PopUpTarget",
    "ProgressBar",
    "RadioButton",
    "ScrollBar",
    "Scrollable",
    "ScrollableError",
    "SelectableIcon",
    "SimpleFocusListWalker",
    "SimpleListWalker",
    "Sizing",
    "SolidFill",
    "Text",
    "TextError",
    "TreeListBox",
    "TreeNode",
    "TreeWalker",
    "TreeWidget",
    "TreeWidgetError",
    "VAlign",
    "WHSettings",
    "Widget",
    "WidgetContainerListContentsMixin",
    "WidgetContainerMixin",
    "WidgetDecoration",
    "WidgetDisable",
    "WidgetError",
    "WidgetMeta",
    "WidgetPlaceholder",
    "WidgetWarning",
    "WidgetWrap",
    "WidgetWrapError",
    "WrapMode",
    "calculate_left_right_padding",
    "calculate_top_bottom_filler",
    "delegate_to_widget_mixin",
    "fixed_size",
    "nocache_widget_render",
    "nocache_widget_render_instance",
    "normalize_align",
    "normalize_height",
    "normalize_valign",
    "normalize_width",
    "scale_bar_values",
    "simplify_align",
    "simplify_height",
    "simplify_valign",
    "simplify_width",
)

# Backward compatibility
FLOW = Sizing.FLOW
BOX = Sizing.BOX
FIXED = Sizing.FIXED

LEFT = Align.LEFT
RIGHT = Align.RIGHT
CENTER = Align.CENTER

TOP = VAlign.TOP
MIDDLE = VAlign.MIDDLE
BOTTOM = VAlign.BOTTOM

SPACE = WrapMode.SPACE
ANY = WrapMode.ANY
CLIP = WrapMode.CLIP
ELLIPSIS = WrapMode.ELLIPSIS

PACK = WHSettings.PACK
GIVEN = WHSettings.GIVEN
RELATIVE = WHSettings.RELATIVE
WEIGHT = WHSettings.WEIGHT
urwid-2.6.16/urwid/widget/attr_map.py000066400000000000000000000140751470350774000175540ustar00rootroot00000000000000from __future__ import annotations

import typing
from collections.abc import Hashable, Mapping

from urwid.canvas import CompositeCanvas

from .widget import WidgetError, delegate_to_widget_mixin
from .widget_decoration import WidgetDecoration

WrappedWidget = typing.TypeVar("WrappedWidget")


class AttrMapError(WidgetError):
    pass


class AttrMap(delegate_to_widget_mixin("_original_widget"), WidgetDecoration[WrappedWidget]):
    """
    AttrMap is a decoration that maps one set of attributes to another.
    This object will pass all function calls and variable references to the
    wrapped widget.
    """

    def __init__(
        self,
        w: WrappedWidget,
        attr_map: Hashable | Mapping[Hashable | None, Hashable] | None,
        focus_map: Hashable | Mapping[Hashable | None, Hashable] | None = None,
    ) -> None:
        """
        :param w: widget to wrap (stored as self.original_widget)
        :type w: widget

        :param attr_map: attribute to apply to *w*, or dict of old display
            attribute: new display attribute mappings
        :type attr_map: display attribute or dict

        :param focus_map: attribute to apply when in focus or dict of
            old display attribute: new display attribute mappings;
            if ``None`` use *attr*
        :type focus_map: display attribute or dict

        >>> from urwid import Divider, Edit, Text
        >>> AttrMap(Divider(u"!"), 'bright')
         attr_map={None: 'bright'}>
        >>> AttrMap(Edit(), 'notfocus', 'focus').attr_map
        {None: 'notfocus'}
        >>> AttrMap(Edit(), 'notfocus', 'focus').focus_map
        {None: 'focus'}
        >>> size = (5,)
        >>> am = AttrMap(Text(u"hi"), 'greeting', 'fgreet')
        >>> next(am.render(size, focus=False).content()) # ... = b in Python 3
        [('greeting', None, ...'hi   ')]
        >>> next(am.render(size, focus=True).content())
        [('fgreet', None, ...'hi   ')]
        >>> am2 = AttrMap(Text(('word', u"hi")), {'word':'greeting', None:'bg'})
        >>> am2
         attr_map={'word': 'greeting', None: 'bg'}>
        >>> next(am2.render(size).content())
        [('greeting', None, ...'hi'), ('bg', None, ...'   ')]
        """
        super().__init__(w)

        if isinstance(attr_map, Mapping):
            self.attr_map = dict(attr_map)
        else:
            self.attr_map = {None: attr_map}

        if isinstance(focus_map, Mapping):
            self.focus_map = dict(focus_map)
        elif focus_map is None:
            self.focus_map = focus_map
        else:
            self.focus_map = {None: focus_map}

    def _repr_attrs(self) -> dict[str, typing.Any]:
        # only include the focus_attr when it takes effect (not None)
        d = {**super()._repr_attrs(), "attr_map": self._attr_map}
        if self._focus_map is not None:
            d["focus_map"] = self._focus_map
        return d

    def get_attr_map(self) -> dict[Hashable | None, Hashable]:
        # make a copy so ours is not accidentally modified
        # FIXME: a dictionary that detects modifications would be better
        return dict(self._attr_map)

    def set_attr_map(self, attr_map: dict[Hashable | None, Hashable] | None) -> None:
        """
        Set the attribute mapping dictionary {from_attr: to_attr, ...}

        Note this function does not accept a single attribute the way the
        constructor does.  You must specify {None: attribute} instead.

        >>> from urwid import Text
        >>> w = AttrMap(Text(u"hi"), None)
        >>> w.set_attr_map({'a':'b'})
        >>> w
         attr_map={'a': 'b'}>
        """
        for from_attr, to_attr in attr_map.items():
            if not isinstance(from_attr, Hashable) or not isinstance(to_attr, Hashable):
                raise AttrMapError(
                    f"{from_attr!r}:{to_attr!r} attribute mapping is invalid. Attributes must be hashable"
                )

        self._attr_map = attr_map
        self._invalidate()

    attr_map = property(get_attr_map, set_attr_map)

    def get_focus_map(self) -> dict[Hashable | None, Hashable] | None:
        # make a copy so ours is not accidentally modified
        # FIXME: a dictionary that detects modifications would be better
        if self._focus_map:
            return dict(self._focus_map)
        return None

    def set_focus_map(self, focus_map: dict[Hashable | None, Hashable] | None) -> None:
        """
        Set the focus attribute mapping dictionary
        {from_attr: to_attr, ...}

        If None this widget will use the attr mapping instead (no change
        when in focus).

        Note this function does not accept a single attribute the way the
        constructor does.  You must specify {None: attribute} instead.

        >>> from urwid import Text
        >>> w = AttrMap(Text(u"hi"), {})
        >>> w.set_focus_map({'a':'b'})
        >>> w
         attr_map={} focus_map={'a': 'b'}>
        >>> w.set_focus_map(None)
        >>> w
         attr_map={}>
        """
        if focus_map is not None:
            for from_attr, to_attr in focus_map.items():
                if not isinstance(from_attr, Hashable) or not isinstance(to_attr, Hashable):
                    raise AttrMapError(
                        f"{from_attr!r}:{to_attr!r} attribute mapping is invalid. Attributes must be hashable"
                    )
        self._focus_map = focus_map
        self._invalidate()

    focus_map = property(get_focus_map, set_focus_map)

    def render(self, size, focus: bool = False) -> CompositeCanvas:
        """
        Render wrapped widget and apply attribute. Return canvas.
        """
        attr_map = self._attr_map
        if focus and self._focus_map is not None:
            attr_map = self._focus_map
        canv = self._original_widget.render(size, focus=focus)
        canv = CompositeCanvas(canv)
        canv.fill_attr_apply(attr_map)
        return canv
urwid-2.6.16/urwid/widget/attr_wrap.py000066400000000000000000000113361470350774000177450ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings

from .attr_map import AttrMap

if typing.TYPE_CHECKING:
    from collections.abc import Hashable

    from .constants import Sizing
    from .widget import Widget


class AttrWrap(AttrMap):
    def __init__(self, w: Widget, attr, focus_attr=None):
        """
        w -- widget to wrap (stored as self.original_widget)
        attr -- attribute to apply to w
        focus_attr -- attribute to apply when in focus, if None use attr

        This widget is a special case of the new AttrMap widget, and it
        will pass all function calls and variable references to the wrapped
        widget.  This class is maintained for backwards compatibility only,
        new code should use AttrMap instead.

        >>> from urwid import Divider, Edit, Text
        >>> AttrWrap(Divider(u"!"), 'bright')
         attr='bright'>
        >>> AttrWrap(Edit(), 'notfocus', 'focus')
         attr='notfocus' focus_attr='focus'>
        >>> size = (5,)
        >>> aw = AttrWrap(Text(u"hi"), 'greeting', 'fgreet')
        >>> next(aw.render(size, focus=False).content())
        [('greeting', None, ...'hi   ')]
        >>> next(aw.render(size, focus=True).content())
        [('fgreet', None, ...'hi   ')]
        """
        warnings.warn(
            "AttrWrap is maintained for backwards compatibility only, new code should use AttrMap instead.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        super().__init__(w, attr, focus_attr)

    def _repr_attrs(self) -> dict[str, typing.Any]:
        # only include the focus_attr when it takes effect (not None)
        d = {**super()._repr_attrs(), "attr": self.attr}
        del d["attr_map"]
        if "focus_map" in d:
            del d["focus_map"]
        if self.focus_attr is not None:
            d["focus_attr"] = self.focus_attr
        return d

    @property
    def w(self) -> Widget:
        """backwards compatibility, widget used to be stored as w"""
        warnings.warn(
            "backwards compatibility, widget used to be stored as original_widget",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.original_widget

    @w.setter
    def w(self, new_widget: Widget) -> None:
        warnings.warn(
            "backwards compatibility, widget used to be stored as original_widget",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        self.original_widget = new_widget

    def get_w(self):
        warnings.warn(
            "backwards compatibility, widget used to be stored as original_widget",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.original_widget

    def set_w(self, new_widget: Widget) -> None:
        warnings.warn(
            "backwards compatibility, widget used to be stored as original_widget",
            DeprecationWarning,
            stacklevel=2,
        )
        self.original_widget = new_widget

    def get_attr(self) -> Hashable:
        return self.attr_map[None]

    def set_attr(self, attr: Hashable) -> None:
        """
        Set the attribute to apply to the wrapped widget

        >> w = AttrWrap(Divider("-"), None)
        >> w.set_attr('new_attr')
        >> w
         attr='new_attr'>
        """
        self.set_attr_map({None: attr})

    attr = property(get_attr, set_attr)

    def get_focus_attr(self) -> Hashable | None:
        focus_map = self.focus_map
        if focus_map:
            return focus_map[None]
        return None

    def set_focus_attr(self, focus_attr: Hashable) -> None:
        """
        Set the attribute to apply to the wapped widget when it is in
        focus

        If None this widget will use the attr instead (no change when in
        focus).

        >> w = AttrWrap(Divider("-"), 'old')
        >> w.set_focus_attr('new_attr')
        >> w
         attr='old' focus_attr='new_attr'>
        >> w.set_focus_attr(None)
        >> w
         attr='old'>
        """
        self.set_focus_map({None: focus_attr})

    focus_attr = property(get_focus_attr, set_focus_attr)

    def __getattr__(self, name: str):
        """
        Call getattr on wrapped widget.  This has been the longstanding
        behaviour of AttrWrap, but is discouraged.  New code should be
        using AttrMap and .base_widget or .original_widget instead.
        """
        return getattr(self._original_widget, name)

    def sizing(self) -> frozenset[Sizing]:
        return self._original_widget.sizing()
urwid-2.6.16/urwid/widget/bar_graph.py000066400000000000000000000521411470350774000176660ustar00rootroot00000000000000from __future__ import annotations

import typing

from urwid.canvas import CanvasCombine, CompositeCanvas, SolidCanvas
from urwid.util import get_encoding_mode

from .constants import BAR_SYMBOLS, Sizing
from .text import Text
from .widget import Widget, WidgetError, WidgetMeta, nocache_widget_render, nocache_widget_render_instance

if typing.TYPE_CHECKING:
    from typing_extensions import Literal


class BarGraphMeta(WidgetMeta):
    """
    Detect subclass get_data() method and dynamic change to
    get_data() method and disable caching in these cases.

    This is for backwards compatibility only, new programs
    should use set_data() instead of overriding get_data().
    """

    def __init__(cls, name, bases, d):
        # pylint: disable=protected-access

        super().__init__(name, bases, d)

        if "get_data" in d:
            cls.render = nocache_widget_render(cls)
            cls._get_data = cls.get_data
        cls.get_data = property(lambda self: self._get_data, nocache_bargraph_get_data)


def nocache_bargraph_get_data(self, get_data_fn):
    """
    Disable caching on this bargraph because get_data_fn needs
    to be polled to get the latest data.
    """
    self.render = nocache_widget_render_instance(self)
    self._get_data = get_data_fn  # pylint: disable=protected-access


class BarGraphError(WidgetError):
    pass


class BarGraph(Widget, metaclass=BarGraphMeta):
    _sizing = frozenset([Sizing.BOX])

    ignore_focus = True

    eighths = BAR_SYMBOLS.VERTICAL[:8]  # Full height is done by style
    hlines = "_⎺⎻─⎼⎽"

    def __init__(self, attlist, hatt=None, satt=None) -> None:
        """
        Create a bar graph with the passed display characteristics.
        see set_segment_attributes for a description of the parameters.
        """
        super().__init__()
        self.set_segment_attributes(attlist, hatt, satt)
        self.set_data([], 1, None)
        self.set_bar_width(None)

    def set_segment_attributes(self, attlist, hatt=None, satt=None):
        """
        :param attlist: list containing display attribute or
                        (display attribute, character) tuple for background,
                        first segment, and optionally following segments.
                        ie. len(attlist) == num segments+1
                        character defaults to ' ' if not specified.
        :param hatt: list containing attributes for horizontal lines. First
                     element is for lines on background, second is for lines
                     on first segment, third is for lines on second segment
                     etc.
        :param satt: dictionary containing attributes for smoothed
                     transitions of bars in UTF-8 display mode. The values
                     are in the form:

                       (fg,bg) : attr

                     fg and bg are integers where 0 is the graph background,
                     1 is the first segment, 2 is the second, ...
                     fg > bg in all values.  attr is an attribute with a
                     foreground corresponding to fg and a background
                     corresponding to bg.

        If satt is not None and the bar graph is being displayed in
        a terminal using the UTF-8 encoding then the character cell
        that is shared between the segments specified will be smoothed
        with using the UTF-8 vertical eighth characters.

        eg: set_segment_attributes( ['no', ('unsure',"?"), 'yes'] )
        will use the attribute 'no' for the background (the area from
        the top of the graph to the top of the bar), question marks
        with the attribute 'unsure' will be used for the topmost
        segment of the bar, and the attribute 'yes' will be used for
        the bottom segment of the bar.
        """
        self.attr = []
        self.char = []
        if len(attlist) < 2:
            raise BarGraphError(f"attlist must include at least background and seg1: {attlist!r}")
        if len(attlist) < 2:
            raise BarGraphError("must at least specify bg and fg!")
        for a in attlist:
            if not isinstance(a, tuple):
                self.attr.append(a)
                self.char.append(" ")
            else:
                attr, ch = a
                self.attr.append(attr)
                self.char.append(ch)

        self.hatt = []
        if hatt is None:
            hatt = [self.attr[0]]
        elif not isinstance(hatt, list):
            hatt = [hatt]
        self.hatt = hatt

        if satt is None:
            satt = {}
        for i in satt.items():
            try:
                (fg, bg), attr = i
            except ValueError as exc:
                raise BarGraphError(f"satt not in (fg,bg:attr) form: {i!r}").with_traceback(exc.__traceback__) from exc
            if not isinstance(fg, int) or fg >= len(attlist):
                raise BarGraphError(f"fg not valid integer: {fg!r}")
            if not isinstance(bg, int) or bg >= len(attlist):
                raise BarGraphError(f"bg not valid integer: {fg!r}")
            if fg <= bg:
                raise BarGraphError(f"fg ({fg}) not > bg ({bg})")
        self.satt = satt

    def set_data(self, bardata, top: float, hlines=None) -> None:
        """
        Store bar data, bargraph top and horizontal line positions.

        bardata -- a list of bar values.
        top -- maximum value for segments within bardata
        hlines -- None or a bar value marking horizontal line positions

        bar values are [ segment1, segment2, ... ] lists where top is
        the maximal value corresponding to the top of the bar graph and
        segment1, segment2, ... are the values for the top of each
        segment of this bar.  Simple bar graphs will only have one
        segment in each bar value.

        Eg: if top is 100 and there is a bar value of [ 80, 30 ] then
        the top of this bar will be at 80% of full height of the graph
        and it will have a second segment that starts at 30%.
        """
        if hlines is not None:
            hlines = sorted(hlines[:], reverse=True)  # shallow copy

        self.data = bardata, top, hlines
        self._invalidate()

    def _get_data(self, size: tuple[int, int]):
        """
        Return (bardata, top, hlines)

        This function is called by render to retrieve the data for
        the graph. It may be overloaded to create a dynamic bar graph.

        This implementation will truncate the bardata list returned
        if not all bars will fit within maxcol.
        """
        (maxcol, maxrow) = size
        bardata, top, hlines = self.data
        widths = self.calculate_bar_widths((maxcol, maxrow), bardata)

        if len(bardata) > len(widths):
            return bardata[: len(widths)], top, hlines

        return bardata, top, hlines

    def set_bar_width(self, width: int | None):
        """
        Set a preferred bar width for calculate_bar_widths to use.

        width -- width of bar or None for automatic width adjustment
        """
        if width is not None and width <= 0:
            raise ValueError(width)
        self.bar_width = width
        self._invalidate()

    def calculate_bar_widths(self, size: tuple[int, int], bardata):
        """
        Return a list of bar widths, one for each bar in data.

        If self.bar_width is None this implementation will stretch
        the bars across the available space specified by maxcol.
        """
        (maxcol, _maxrow) = size

        if self.bar_width is not None:
            return [self.bar_width] * min(len(bardata), maxcol // self.bar_width)

        if len(bardata) >= maxcol:
            return [1] * maxcol

        widths = []
        grow = maxcol
        remain = len(bardata)
        for _row in bardata:
            w = int(float(grow) / remain + 0.5)
            widths.append(w)
            grow -= w
            remain -= 1
        return widths

    def selectable(self) -> Literal[False]:
        """
        Return False.
        """
        return False

    def use_smoothed(self) -> bool:
        return self.satt and get_encoding_mode() == "utf8"

    def calculate_display(self, size: tuple[int, int]):
        """
        Calculate display data.
        """
        (maxcol, maxrow) = size
        bardata, top, hlines = self.get_data((maxcol, maxrow))  # pylint: disable=no-member  # metaclass defined
        widths = self.calculate_bar_widths((maxcol, maxrow), bardata)

        if self.use_smoothed():
            disp = calculate_bargraph_display(bardata, top, widths, maxrow * 8)
            disp = self.smooth_display(disp)

        else:
            disp = calculate_bargraph_display(bardata, top, widths, maxrow)

        if hlines:
            disp = self.hlines_display(disp, top, hlines, maxrow)

        return disp

    def hlines_display(self, disp, top: int, hlines, maxrow: int):
        """
        Add hlines to display structure represented as bar_type tuple
        values:
        (bg, 0-5)
        bg is the segment that has the hline on it
        0-5 is the hline graphic to use where 0 is a regular underscore
        and 1-5 are the UTF-8 horizontal scan line characters.
        """
        if self.use_smoothed():
            shiftr = 0
            r = [
                (0.2, 1),
                (0.4, 2),
                (0.6, 3),
                (0.8, 4),
                (1.0, 5),
            ]
        else:
            shiftr = 0.5
            r = [
                (1.0, 0),
            ]

        # reverse the hlines to match screen ordering
        rhl = []
        for h in hlines:
            rh = float(top - h) * maxrow / top - shiftr
            if rh < 0:
                continue
            rhl.append(rh)

        # build a list of rows that will have hlines
        hrows = []
        last_i = -1
        for rh in rhl:
            i = int(rh)
            if i == last_i:
                continue
            f = rh - i
            for spl, chnum in r:
                if f < spl:
                    hrows.append((i, chnum))
                    break
            last_i = i

        # fill hlines into disp data
        def fill_row(row, chnum):
            rout = []
            for bar_type, width in row:
                if isinstance(bar_type, int) and len(self.hatt) > bar_type:
                    rout.append(((bar_type, chnum), width))
                    continue
                rout.append((bar_type, width))
            return rout

        o = []
        k = 0
        rnum = 0
        for y_count, row in disp:
            if k >= len(hrows):
                o.append((y_count, row))
                continue
            end_block = rnum + y_count
            while k < len(hrows) and hrows[k][0] < end_block:
                i, chnum = hrows[k]
                if i - rnum > 0:
                    o.append((i - rnum, row))
                o.append((1, fill_row(row, chnum)))
                rnum = i + 1
                k += 1
            if rnum < end_block:
                o.append((end_block - rnum, row))
                rnum = end_block

        # assert 0, o
        return o

    def smooth_display(self, disp):
        """
        smooth (col, row*8) display into (col, row) display using
        UTF vertical eighth characters represented as bar_type
        tuple values:
        ( fg, bg, 1-7 )
        where fg is the lower segment, bg is the upper segment and
        1-7 is the vertical eighth character to use.
        """
        o = []
        r = 0  # row remainder

        def seg_combine(a, b):
            (bt1, w1), (bt2, w2) = a, b
            if (bt1, w1) == (bt2, w2):
                return (bt1, w1), None, None
            wmin = min(w1, w2)
            l1 = l2 = None
            if w1 > w2:
                l1 = (bt1, w1 - w2)
            elif w2 > w1:
                l2 = (bt2, w2 - w1)
            if isinstance(bt1, tuple):
                return (bt1, wmin), l1, l2
            if (bt2, bt1) not in self.satt:
                if r < 4:
                    return (bt2, wmin), l1, l2
                return (bt1, wmin), l1, l2
            return ((bt2, bt1, 8 - r), wmin), l1, l2

        def row_combine_last(count: int, row):
            o_count, o_row = o[-1]
            row = row[:]  # shallow copy, so we don't destroy orig.
            o_row = o_row[:]
            widget_list = []
            while row:
                (bt, w), l1, l2 = seg_combine(o_row.pop(0), row.pop(0))
                if widget_list and widget_list[-1][0] == bt:
                    widget_list[-1] = (bt, widget_list[-1][1] + w)
                else:
                    widget_list.append((bt, w))
                if l1:
                    o_row = [l1, *o_row]
                if l2:
                    row = [l2, *row]

            if o_row:
                raise BarGraphError(o_row)

            o[-1] = (o_count + count, widget_list)

        # regroup into actual rows (8 disp rows == 1 actual row)
        for y_count, row in disp:
            if r:
                count = min(8 - r, y_count)
                row_combine_last(count, row)
                y_count -= count  # noqa: PLW2901
                r += count
                r %= 8
                if not y_count:
                    continue
            if r != 0:
                raise BarGraphError
            # copy whole blocks
            if y_count > 7:
                o.append((y_count // 8 * 8, row))
                y_count %= 8  # noqa: PLW2901
                if not y_count:
                    continue
            o.append((y_count, row))
            r = y_count
        return [(y // 8, row) for (y, row) in o]

    def render(self, size: tuple[int, int], focus: bool = False) -> CompositeCanvas:
        """
        Render BarGraph.
        """
        (maxcol, maxrow) = size
        disp = self.calculate_display((maxcol, maxrow))

        combinelist = []
        for y_count, row in disp:
            widget_list = []
            for bar_type, width in row:
                if isinstance(bar_type, tuple):
                    if len(bar_type) == 3:
                        # vertical eighths
                        fg, bg, k = bar_type
                        a = self.satt[fg, bg]
                        t = self.eighths[k] * width
                    else:
                        # horizontal lines
                        bg, k = bar_type
                        a = self.hatt[bg]
                        t = self.hlines[k] * width
                else:
                    a = self.attr[bar_type]
                    t = self.char[bar_type] * width
                widget_list.append((a, t))
            c = Text(widget_list).render((maxcol,))
            if c.rows() != 1:
                raise BarGraphError("Invalid characters in BarGraph!")
            combinelist += [(c, None, False)] * y_count

        canv = CanvasCombine(combinelist)
        return canv


def calculate_bargraph_display(bardata, top: float, bar_widths: list[int], maxrow: int):
    """
    Calculate a rendering of the bar graph described by data, bar_widths
    and height.

    bardata -- bar information with same structure as BarGraph.data
    top -- maximal value for bardata segments
    bar_widths -- list of integer column widths for each bar
    maxrow -- rows for display of bargraph

    Returns a structure as follows:
      [ ( y_count, [ ( bar_type, width), ... ] ), ... ]

    The outer tuples represent a set of identical rows. y_count is
    the number of rows in this set, the list contains the data to be
    displayed in the row repeated through the set.

    The inner tuple describes a run of width characters of bar_type.
    bar_type is an integer starting from 0 for the background, 1 for
    the 1st segment, 2 for the 2nd segment etc..

    This function should complete in approximately O(n+m) time, where
    n is the number of bars displayed and m is the number of rows.
    """

    if len(bardata) != len(bar_widths):
        raise BarGraphError

    maxcol = sum(bar_widths)

    # build intermediate data structure
    rows = [None] * maxrow

    def add_segment(seg_num: int, col: int, row: int, width: int, rows=rows) -> None:
        if rows[row]:
            last_seg, last_col, last_end = rows[row][-1]
            if last_end > col:
                if last_col >= col:
                    del rows[row][-1]
                else:
                    rows[row][-1] = (last_seg, last_col, col)
            elif last_seg == seg_num and last_end == col:
                rows[row][-1] = (last_seg, last_col, last_end + width)
                return
        elif rows[row] is None:
            rows[row] = []
        rows[row].append((seg_num, col, col + width))

    col = 0
    barnum = 0
    for bar in bardata:
        width = bar_widths[barnum]
        if width < 1:
            continue
        # loop through in reverse order
        tallest = maxrow
        segments = scale_bar_values(bar, top, maxrow)
        for k in range(len(bar) - 1, -1, -1):
            s = segments[k]

            if s >= maxrow:
                continue
            s = max(s, 0)
            if s < tallest:
                # add only properly-overlapped bars
                tallest = s
                add_segment(k + 1, col, s, width)
        col += width
        barnum += 1

    # print(repr(rows))
    # build rowsets data structure
    rowsets = []
    y_count = 0
    last = [(0, maxcol)]

    for r in rows:
        if r is None:
            y_count += 1
            continue
        if y_count:
            rowsets.append((y_count, last))
            y_count = 0

        i = 0  # index into "last"
        la, ln = last[i]  # last attribute, last run length
        c = 0  # current column
        o = []  # output list to be added to rowsets
        for seg_num, start, end in r:
            while start > c + ln:
                o.append((la, ln))
                i += 1
                c += ln
                la, ln = last[i]

            if la == seg_num:
                # same attribute, can combine
                o.append((la, end - c))
            else:
                if start - c > 0:
                    o.append((la, start - c))
                o.append((seg_num, end - start))

            if end == maxcol:
                i = len(last)
                break

            # skip past old segments covered by new one
            while end >= c + ln:
                i += 1
                c += ln
                la, ln = last[i]

            if la != seg_num:
                ln = c + ln - end
                c = end
                continue

            # same attribute, can extend
            oa, on = o[-1]
            on += c + ln - end
            o[-1] = oa, on

            i += 1
            c += ln
            if c == maxcol:
                break
            if i >= len(last):
                raise ValueError(repr((on, maxcol)))
            la, ln = last[i]

        if i < len(last):
            o += [(la, ln)] + last[i + 1 :]
        last = o
        y_count += 1

    if y_count:
        rowsets.append((y_count, last))

    return rowsets


class GraphVScale(Widget):
    _sizing = frozenset([Sizing.BOX])

    def __init__(self, labels, top: float) -> None:
        """
        GraphVScale( [(label1 position, label1 markup),...], top )
        label position -- 0 < position < top for the y position
        label markup -- text markup for this label
        top -- top y position

        This widget is a vertical scale for the BarGraph widget that
        can correspond to the BarGraph's horizontal lines
        """
        super().__init__()
        self.set_scale(labels, top)

    def set_scale(self, labels, top: float) -> None:
        """
        set_scale( [(label1 position, label1 markup),...], top )
        label position -- 0 < position < top for the y position
        label markup -- text markup for this label
        top -- top y position
        """

        labels = sorted(labels[:], reverse=True)  # shallow copy

        self.pos = []
        self.txt = []
        for y, markup in labels:
            self.pos.append(y)
            self.txt.append(Text(markup))
        self.top = top

    def selectable(self) -> Literal[False]:
        """
        Return False.
        """
        return False

    def render(
        self,
        size: tuple[int, int],
        focus: bool = False,
    ) -> SolidCanvas | CompositeCanvas:
        """
        Render GraphVScale.
        """
        (maxcol, maxrow) = size
        pl = scale_bar_values(self.pos, self.top, maxrow)

        combinelist = []
        rows = 0
        for p, t in zip(pl, self.txt):
            p -= 1  # noqa: PLW2901
            if p >= maxrow:
                break
            if p < rows:
                continue
            c = t.render((maxcol,))
            if p > rows:
                run = p - rows
                c = CompositeCanvas(c)
                c.pad_trim_top_bottom(run, 0)
            rows += c.rows()
            combinelist.append((c, None, False))
        if not combinelist:
            return SolidCanvas(" ", size[0], size[1])

        canvas = CanvasCombine(combinelist)
        if maxrow - rows:
            canvas.pad_trim_top_bottom(0, maxrow - rows)
        return canvas


def scale_bar_values(bar, top: float, maxrow: int) -> list[int]:
    """
    Return a list of bar values aliased to integer values of maxrow.
    """
    return [maxrow - int(float(v) * maxrow / top + 0.5) for v in bar]
urwid-2.6.16/urwid/widget/big_text.py000066400000000000000000000045311470350774000175460ustar00rootroot00000000000000from __future__ import annotations

import typing

from urwid.canvas import CanvasJoin, CompositeCanvas, TextCanvas
from urwid.util import decompose_tagmarkup

from .constants import Sizing
from .widget import Widget, fixed_size

if typing.TYPE_CHECKING:
    from collections.abc import Hashable

    from urwid import Font


class BigText(Widget):
    _sizing = frozenset([Sizing.FIXED])

    def __init__(self, markup, font: Font) -> None:
        """
        markup -- same as Text widget markup
        font -- instance of a Font class
        """
        super().__init__()
        self.text: str = ""
        self.attrib = []
        self.font: Font = font
        self.set_font(font)
        self.set_text(markup)

    def set_text(self, markup):
        self.text, self.attrib = decompose_tagmarkup(markup)
        self._invalidate()

    def get_text(self):
        """
        Returns (text, attributes).
        """
        return self.text, self.attrib

    def set_font(self, font: Font) -> None:
        self.font = font
        self._invalidate()

    def pack(
        self,
        size: tuple[()] | None = (),  # type: ignore[override]
        focus: bool = False,
    ) -> tuple[int, int]:
        rows = self.font.height
        cols = 0
        for c in self.text:
            cols += self.font.char_width(c)
        return cols, rows

    def render(
        self,
        size: tuple[()],  # type: ignore[override]
        focus: bool = False,
    ) -> CompositeCanvas:
        fixed_size(size)  # complain if parameter is wrong
        a: Hashable | None = None
        ai = ak = 0
        o = []
        rows = self.font.height
        attrib = [*self.attrib, (None, len(self.text))]
        for ch in self.text:
            if not ak:
                a, ak = attrib[ai]
                ai += 1
            ak -= 1
            width = self.font.char_width(ch)
            if not width:
                # ignore invalid characters
                continue
            c: TextCanvas | CompositeCanvas = self.font.render(ch)
            if a is not None:
                c = CompositeCanvas(c)
                c.fill_attr(a)
            o.append((c, None, False, width))
        if o:
            canv = CanvasJoin(o)
        else:
            canv = CompositeCanvas(TextCanvas([b""] * rows, maxcol=0, check_width=False))
        canv.set_depends([])
        return canv
urwid-2.6.16/urwid/widget/box_adapter.py000066400000000000000000000102641470350774000202310ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings

from urwid.canvas import CompositeCanvas

from .constants import Sizing
from .widget_decoration import WidgetDecoration, WidgetError

WrappedWidget = typing.TypeVar("WrappedWidget")


class BoxAdapterError(WidgetError):
    pass


class BoxAdapter(WidgetDecoration[WrappedWidget]):
    """
    Adapter for using a box widget where a flow widget would usually go
    """

    no_cache: typing.ClassVar[list[str]] = ["rows"]

    def __init__(self, box_widget: WrappedWidget, height: int) -> None:
        """
        Create a flow widget that contains a box widget

        :param box_widget: box widget to wrap
        :type box_widget: Widget
        :param height: number of rows for box widget
        :type height: int

        >>> from urwid import SolidFill
        >>> BoxAdapter(SolidFill(u"x"), 5) # 5-rows of x's
         height=5>
        """
        if hasattr(box_widget, "sizing") and Sizing.BOX not in box_widget.sizing():
            raise BoxAdapterError(f"{box_widget!r} is not a box widget")
        super().__init__(box_widget)

        self.height = height

    def _repr_attrs(self) -> dict[str, typing.Any]:
        return {**super()._repr_attrs(), "height": self.height}

    # originally stored as box_widget, keep for compatibility
    @property
    def box_widget(self) -> WrappedWidget:
        warnings.warn(
            "original stored as original_widget, keep for compatibility",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.original_widget

    @box_widget.setter
    def box_widget(self, widget: WrappedWidget) -> None:
        warnings.warn(
            "original stored as original_widget, keep for compatibility",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        self.original_widget = widget

    def sizing(self) -> frozenset[Sizing]:
        return frozenset((Sizing.FLOW,))

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        """
        Return the predetermined height (behave like a flow widget)

        >>> from urwid import SolidFill
        >>> BoxAdapter(SolidFill(u"x"), 5).rows((20,))
        5
        """
        return self.height

    # The next few functions simply tack-on our height and pass through
    # to self._original_widget
    def get_cursor_coords(self, size: tuple[int]) -> int | None:
        (maxcol,) = size
        if not hasattr(self._original_widget, "get_cursor_coords"):
            return None
        return self._original_widget.get_cursor_coords((maxcol, self.height))

    def get_pref_col(self, size: tuple[int]) -> int | None:
        (maxcol,) = size
        if not hasattr(self._original_widget, "get_pref_col"):
            return None
        return self._original_widget.get_pref_col((maxcol, self.height))

    def keypress(
        self,
        size: tuple[int],  # type: ignore[override]
        key: str,
    ) -> str | None:
        (maxcol,) = size
        return self._original_widget.keypress((maxcol, self.height), key)

    def move_cursor_to_coords(self, size: tuple[int], col: int, row: int):
        (maxcol,) = size
        if not hasattr(self._original_widget, "move_cursor_to_coords"):
            return True
        return self._original_widget.move_cursor_to_coords((maxcol, self.height), col, row)

    def mouse_event(
        self,
        size: tuple[int],  # type: ignore[override]
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        (maxcol,) = size
        if not hasattr(self._original_widget, "mouse_event"):
            return False
        return self._original_widget.mouse_event((maxcol, self.height), event, button, col, row, focus)

    def render(
        self,
        size: tuple[int],  # type: ignore[override]
        focus: bool = False,
    ) -> CompositeCanvas:
        (maxcol,) = size
        canv = CompositeCanvas(self._original_widget.render((maxcol, self.height), focus))
        return canv

    def __getattr__(self, name: str):
        """
        Pass calls to box widget.
        """
        return getattr(self.original_widget, name)
urwid-2.6.16/urwid/widget/columns.py000066400000000000000000001324641470350774000174300ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings
from itertools import chain, repeat

import urwid
from urwid.canvas import Canvas, CanvasJoin, CompositeCanvas, SolidCanvas
from urwid.command_map import Command
from urwid.split_repr import remove_defaults
from urwid.util import is_mouse_press

from .constants import Align, Sizing, WHSettings
from .container import WidgetContainerListContentsMixin, WidgetContainerMixin, _ContainerElementSizingFlag
from .monitored_list import MonitoredFocusList, MonitoredList
from .widget import Widget, WidgetError, WidgetWarning

if typing.TYPE_CHECKING:
    from collections.abc import Iterable, Iterator, Sequence

    from typing_extensions import Literal


class ColumnsError(WidgetError):
    """Columns related errors."""


class ColumnsWarning(WidgetWarning):
    """Columns related warnings."""


class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
    """
    Widgets arranged horizontally in columns from left to right
    """

    def sizing(self) -> frozenset[Sizing]:
        """Sizing supported by widget.

        :return: Calculated widget sizing
        :rtype: frozenset[Sizing]

        Due to the nature of container with mutable contents, this method cannot be cached.

        Rules:
        * WEIGHT BOX -> BOX
        * GIVEN BOX -> Can be included in FIXED and FLOW depends on the other columns
        * PACK BOX -> Unsupported
        * BOX-only widget without `box_columns` disallow FLOW render

        * WEIGHT FLOW -> FLOW
        * GIVEN FLOW -> FIXED (known width and widget knows its height) + FLOW (historic)
        * PACK FLOW -> FLOW (widget fit in provided size)

        * WEIGHT FIXED -> Need also FLOW or/and BOX to properly render due to width calculation
        * GIVEN FIXED -> Unsupported
        * PACK FIXED -> FIXED (widget knows its size)

        Backward compatibility rules:
        * GIVEN BOX -> Allow BOX

        BOX can be only if ALL widgets support BOX.
        FIXED can be only if no BOX without "box_columns" flag and no strict FLOW.

        >>> from urwid import BigText, Edit, SolidFill, Text, Thin3x3Font
        >>> font = Thin3x3Font()

        # BOX-only widget
        >>> Columns((SolidFill("#"),))
        

        # BOX-only widget with "get height from max"
        >>> Columns((SolidFill("#"),), box_columns=(0,))
        

        # FLOW-only
        >>> Columns((Edit(),))
        

        # FLOW allowed by "box_columns"
        >>> Columns((Edit(), SolidFill("#")), box_columns=(1,))
        

        # FLOW/FIXED
        >>> Columns((Text("T"),))
        

        # GIVEN BOX only -> BOX only
        >>> Columns(((5, SolidFill("#")),), box_columns=(0,))
        

        # No FLOW - BOX only
        >>> Columns(((5, SolidFill("#")), SolidFill("*")), box_columns=(0, 1))
        

        # FIXED only -> FIXED (and FLOW via drop/expand)
        >>> Columns(((WHSettings.PACK, BigText("1", font)),))
        

        # Invalid sizing combination -> use fallback settings (and produce warning)
        >>> Columns(((WHSettings.PACK, SolidFill("#")),))
        

        # Special case: empty columns widget sizing is impossible to calculate
        >>> Columns(())
        
        """
        if not self.contents:
            return frozenset((urwid.BOX, urwid.FLOW))

        strict_box = False
        has_flow = False

        block_fixed = False
        has_fixed = False
        supported: set[Sizing] = set()

        box_flow_fixed = (
            _ContainerElementSizingFlag.BOX | _ContainerElementSizingFlag.FLOW | _ContainerElementSizingFlag.FIXED
        )
        flow_fixed = _ContainerElementSizingFlag.FLOW | _ContainerElementSizingFlag.FIXED
        given_box = _ContainerElementSizingFlag.BOX | _ContainerElementSizingFlag.WH_GIVEN

        flags: set[_ContainerElementSizingFlag] = set()

        for idx, (widget, (size_kind, _size_weight, is_box)) in enumerate(self.contents):
            w_sizing = widget.sizing()

            flag = _ContainerElementSizingFlag.NONE

            if size_kind == WHSettings.WEIGHT:
                flag |= _ContainerElementSizingFlag.WH_WEIGHT
                if Sizing.BOX in w_sizing:
                    flag |= _ContainerElementSizingFlag.BOX
                if Sizing.FLOW in w_sizing:
                    flag |= _ContainerElementSizingFlag.FLOW
                if Sizing.FIXED in w_sizing and w_sizing & {Sizing.BOX, Sizing.FLOW}:
                    flag |= _ContainerElementSizingFlag.FIXED

            elif size_kind == WHSettings.GIVEN:
                flag |= _ContainerElementSizingFlag.WH_GIVEN
                if Sizing.BOX in w_sizing:
                    flag |= _ContainerElementSizingFlag.BOX
                if Sizing.FLOW in w_sizing:
                    flag |= _ContainerElementSizingFlag.FIXED
                    flag |= _ContainerElementSizingFlag.FLOW

            else:
                flag |= _ContainerElementSizingFlag.WH_PACK
                if Sizing.FIXED in w_sizing:
                    flag |= _ContainerElementSizingFlag.FIXED
                if Sizing.FLOW in w_sizing:
                    flag |= _ContainerElementSizingFlag.FLOW

            if not flag & box_flow_fixed:
                warnings.warn(
                    f"Sizing combination of widget {widget} (position={idx}) not supported: "
                    f"{size_kind.name} box={is_box}",
                    ColumnsWarning,
                    stacklevel=3,
                )
                return frozenset((Sizing.BOX, Sizing.FLOW))

            flags.add(flag)

            if flag & _ContainerElementSizingFlag.BOX and not (is_box or flag & flow_fixed):
                strict_box = True

            if flag & _ContainerElementSizingFlag.FLOW:
                has_flow = True
            if flag & _ContainerElementSizingFlag.FIXED:
                has_fixed = True
            elif flag & given_box != given_box:
                block_fixed = True

        if all(flag & _ContainerElementSizingFlag.BOX for flag in flags):
            # Only if ALL widgets can be rendered as BOX, widget can be rendered as BOX.
            # Hacky "BOX" render for FLOW-only is still present,
            # due to incorrected implementation can be used by downstream
            supported.add(Sizing.BOX)

        if not strict_box:
            if has_flow:
                supported.add(Sizing.FLOW)

            if has_fixed and not block_fixed:
                supported.add(Sizing.FLOW)
                supported.add(Sizing.FIXED)

        if not supported:
            warnings.warn(
                f"Columns widget contents flags not allow to determine supported render kind:\n"
                f"{', '.join(sorted(flag.log_string for flag in flags))}\n"
                f"Using fallback hardcoded BOX|FLOW sizing kind.",
                ColumnsWarning,
                stacklevel=3,
            )
            return frozenset((Sizing.BOX, Sizing.FLOW))

        return frozenset(supported)

    def __init__(
        self,
        widget_list: Iterable[
            Widget
            | tuple[Literal["pack", WHSettings.PACK] | int, Widget]
            | tuple[Literal["given", WHSettings.GIVEN], int, Widget]
            | tuple[Literal["weight", WHSettings.WEIGHT], int | float, Widget]
        ],
        dividechars: int = 0,
        focus_column: int | Widget | None = None,
        min_width: int = 1,
        box_columns: Iterable[int] | None = None,
    ):
        """
        :param widget_list: iterable of flow or box widgets
        :param dividechars: number of blank characters between columns
        :param focus_column: index into widget_list of column in focus or focused widget instance,
            if ``None`` the first selectable widget will be chosen.
        :param min_width: minimum width for each column which is not
            calling widget.pack() in *widget_list*.
        :param box_columns: a list of column indexes containing box widgets
            whose height is set to the maximum of the rows
            required by columns not listed in *box_columns*.

        *widget_list* may also contain tuples such as:

        (*given_width*, *widget*)
            make this column *given_width* screen columns wide, where *given_width* is an int
        (``'pack'``, *widget*)
            call :meth:`pack() ` to calculate the width of this column
        (``'weight'``, *weight*, *widget*)
            give this column a relative *weight* (number) to calculate its width from th screen columns remaining

        Widgets not in a tuple are the same as (``'weight'``, ``1``, *widget*)

        If the Columns widget is treated as a box widget then all children
        are treated as box widgets, and *box_columns* is ignored.

        If the Columns widget is treated as a flow widget then the rows
        are calculated as the largest rows() returned from all columns
        except the ones listed in *box_columns*.  The box widgets in
        *box_columns* will be displayed with this calculated number of rows,
        filling the full height.
        """
        self._selectable = False
        self._cache_column_widths: list[int] = []
        super().__init__()
        self._contents: MonitoredFocusList[
            tuple[
                Widget,
                tuple[Literal[WHSettings.PACK], None, bool]
                | tuple[Literal[WHSettings.GIVEN], int, bool]
                | tuple[Literal[WHSettings.WEIGHT], int | float, bool],
            ],
        ] = MonitoredFocusList()
        self._contents.set_modified_callback(self._contents_modified)
        self._contents.set_focus_changed_callback(lambda f: self._invalidate())
        self._contents.set_validate_contents_modified(self._validate_contents_modified)

        box_columns = set(box_columns or ())

        for i, original in enumerate(widget_list):
            w = original
            if not isinstance(w, tuple):
                self.contents.append((w, (WHSettings.WEIGHT, 1, i in box_columns)))
            elif w[0] in {Sizing.FLOW, WHSettings.PACK}:  # 'pack' used to be called 'flow'
                _ignored, w = w
                self.contents.append((w, (WHSettings.PACK, None, i in box_columns)))
            elif len(w) == 2 or w[0] in {Sizing.FIXED, WHSettings.GIVEN}:  # backwards compatibility: FIXED -> GIVEN
                width, w = w[-2:]
                self.contents.append((w, (WHSettings.GIVEN, width, i in box_columns)))
            elif w[0] == WHSettings.WEIGHT:
                _ignored, width, w = w
                self.contents.append((w, (WHSettings.WEIGHT, width, i in box_columns)))
            else:
                raise ColumnsError(f"initial widget list item invalid: {original!r}")
            if focus_column == w or (focus_column is None and w.selectable()):
                focus_column = i

            if not isinstance(w, Widget):
                warnings.warn(f"{w!r} is not a Widget", ColumnsWarning, stacklevel=3)

        self.dividechars = dividechars

        if self.contents and focus_column is not None:
            self.focus_position = focus_column
        self.pref_col = None
        self.min_width = min_width
        self._cache_maxcol = None

    def _repr_words(self) -> list[str]:
        if len(self.contents) > 1:
            contents_string = f"({len(self.contents)} items)"
        elif self.contents:
            contents_string = "(1 item)"
        else:
            contents_string = "()"
        return [*super()._repr_words(), contents_string]

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = {
            **super()._repr_attrs(),
            "dividechars": self.dividechars,
            "focus_column": self.focus_position if len(self._contents) > 1 else None,
            "min_width": self.min_width,
        }
        return remove_defaults(attrs, Columns.__init__)

    def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
        widget_list: list[
            Widget
            | tuple[Literal[WHSettings.PACK] | int, Widget]
            | tuple[Literal[WHSettings.WEIGHT], int | float, Widget]
        ] = []
        box_columns: list[int] = []
        for idx, (w_instance, (sizing, amount, is_box)) in enumerate(self._contents):
            if sizing == WHSettings.GIVEN:
                widget_list.append((amount, w_instance))
            elif sizing == WHSettings.PACK:
                widget_list.append((WHSettings.PACK, w_instance))
            elif amount == 1:
                widget_list.append(w_instance)
            else:
                widget_list.append((WHSettings.WEIGHT, amount, w_instance))

            if is_box:
                box_columns.append(idx)

        yield "widget_list", widget_list
        yield "dividechars", self.dividechars
        yield "focus_column", self.focus_position if self._contents else None
        yield "min_width", self.min_width
        yield "box_columns", box_columns

    def __len__(self) -> int:
        return len(self._contents)

    def _contents_modified(self) -> None:
        """
        Recalculate whether this widget should be selectable whenever the
        contents has been changed.
        """
        self._selectable = any(w.selectable() for w, o in self.contents)
        self._invalidate()

    def _validate_contents_modified(self, slc, new_items) -> None:
        invalid_items: list[tuple[Widget, tuple[typing.Any, typing.Any, typing.Any]]] = []
        try:
            for item in new_items:
                _w, (t, n, b) = item
                if any(
                    (
                        t not in {WHSettings.PACK, WHSettings.GIVEN, WHSettings.WEIGHT},
                        (n is not None and (not isinstance(n, (int, float)) or n < 0)),
                        not isinstance(b, bool),
                    )
                ):
                    invalid_items.append(item)
        except (TypeError, ValueError) as exc:
            raise ColumnsError(f"added content invalid {exc}").with_traceback(exc.__traceback__) from exc

        if invalid_items:
            raise ColumnsError(f"added content invalid: {invalid_items!r}")

    @property
    def widget_list(self) -> MonitoredList:
        """
        A list of the widgets in this Columns

        .. note:: only for backwards compatibility. You should use the new
            standard container property :attr:`contents`.
        """
        warnings.warn(
            "only for backwards compatibility. You should use the new standard container `contents`",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        ml = MonitoredList(w for w, t in self.contents)

        def user_modified():
            self.widget_list = ml

        ml.set_modified_callback(user_modified)
        return ml

    @widget_list.setter
    def widget_list(self, widgets):
        warnings.warn(
            "only for backwards compatibility. You should use the new standard container `contents`",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        focus_position = self.focus_position
        self.contents = [
            # need to grow contents list if widgets is longer
            (new, options)
            for (new, (w, options)) in zip(
                widgets,
                chain(self.contents, repeat((None, (WHSettings.WEIGHT, 1, False)))),
            )
        ]
        if focus_position < len(widgets):
            self.focus_position = focus_position

    @property
    def column_types(self) -> MonitoredList:
        """
        A list of the old partial options values for widgets in this Pile,
        for backwards compatibility only.  You should use the new standard
        container property .contents to modify Pile contents.
        """
        warnings.warn(
            "for backwards compatibility only."
            "You should use the new standard container property .contents to modify Pile contents.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        ml = MonitoredList(
            # return the old column type names
            ({WHSettings.GIVEN: Sizing.FIXED, WHSettings.PACK: Sizing.FLOW}.get(t, t), n)
            for w, (t, n, b) in self.contents
        )

        def user_modified():
            self.column_types = ml

        ml.set_modified_callback(user_modified)
        return ml

    @column_types.setter
    def column_types(self, column_types):
        warnings.warn(
            "for backwards compatibility only."
            "You should use the new standard container property .contents to modify Pile contents.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        focus_position = self.focus_position
        self.contents = [
            (w, ({Sizing.FIXED: WHSettings.GIVEN, Sizing.FLOW: WHSettings.PACK}.get(new_t, new_t), new_n, b))
            for ((new_t, new_n), (w, (t, n, b))) in zip(column_types, self.contents)
        ]
        if focus_position < len(column_types):
            self.focus_position = focus_position

    @property
    def box_columns(self) -> MonitoredList:
        """
        A list of the indexes of the columns that are to be treated as
        box widgets when the Columns is treated as a flow widget.

        .. note:: only for backwards compatibility. You should use the new
            standard container property :attr:`contents`.
        """
        warnings.warn(
            "only for backwards compatibility.You should use the new standard container property `contents`",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        ml = MonitoredList(i for i, (w, (t, n, b)) in enumerate(self.contents) if b)

        def user_modified():
            self.box_columns = ml

        ml.set_modified_callback(user_modified)
        return ml

    @box_columns.setter
    def box_columns(self, box_columns):
        warnings.warn(
            "only for backwards compatibility.You should use the new standard container property `contents`",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        box_columns = set(box_columns)
        self.contents = [(w, (t, n, i in box_columns)) for (i, (w, (t, n, b))) in enumerate(self.contents)]

    @property
    def has_flow_type(self) -> bool:
        """
        .. deprecated:: 1.0 Read values from :attr:`contents` instead.
        """
        warnings.warn(
            ".has_flow_type is deprecated, read values from .contents instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        return WHSettings.PACK in self.column_types

    @has_flow_type.setter
    def has_flow_type(self, value):
        warnings.warn(
            ".has_flow_type is deprecated, read values from .contents instead.",
            DeprecationWarning,
            stacklevel=2,
        )

    @property
    def contents(
        self,
    ) -> MonitoredFocusList[
        tuple[
            Widget,
            tuple[Literal[WHSettings.PACK], None, bool]
            | tuple[Literal[WHSettings.GIVEN], int, bool]
            | tuple[Literal[WHSettings.WEIGHT], int | float, bool],
        ],
    ]:
        """
        The contents of this Columns as a list of `(widget, options)` tuples.
        This list may be modified like a normal list and the Columns
        widget will update automatically.

        .. seealso:: Create new options tuples with the :meth:`options` method
        """
        return self._contents

    @contents.setter
    def contents(
        self,
        c: Sequence[
            Widget,
            tuple[Literal[WHSettings.PACK], None, bool]
            | tuple[Literal[WHSettings.GIVEN], int, bool]
            | tuple[Literal[WHSettings.WEIGHT], int | float, bool],
        ],
    ) -> None:
        self._contents[:] = c

    @staticmethod
    def options(
        width_type: Literal[
            "pack", "given", "weight", WHSettings.PACK, WHSettings.GIVEN, WHSettings.WEIGHT
        ] = WHSettings.WEIGHT,
        width_amount: int | float | None = 1,  # noqa: PYI041  # provide explicit for IDEs
        box_widget: bool = False,
    ) -> (
        tuple[Literal[WHSettings.PACK], None, bool]
        | tuple[Literal[WHSettings.GIVEN], int, bool]
        | tuple[Literal[WHSettings.WEIGHT], int | float, bool]
    ):
        """
        Return a new options tuple for use in a Pile's .contents list.

        This sets an entry's width type: one of the following:

        ``'pack'``
            Call the widget's :meth:`Widget.pack` method to determine how wide
            this column should be. *width_amount* is ignored.
        ``'given'``
            Make column exactly width_amount screen-columns wide.
        ``'weight'``
            Allocate the remaining space to this column by using
            *width_amount* as a weight value.

        :param width_type: ``'pack'``, ``'given'`` or ``'weight'``
        :param width_amount: ``None`` for ``'pack'``, a number of screen columns
            for ``'given'`` or a weight value (number) for ``'weight'``
        :param box_widget: set to `True` if this widget is to be treated as a box
            widget when the Columns widget itself is treated as a flow widget.
        :type box_widget: bool
        """
        if width_type == WHSettings.PACK:
            return (WHSettings.PACK, None, box_widget)
        if width_type in {WHSettings.GIVEN, WHSettings.WEIGHT} and width_amount is not None:
            return (WHSettings(width_type), width_amount, box_widget)
        raise ColumnsError(f"invalid combination: width_type={width_type!r}, width_amount={width_amount!r}")

    def _invalidate(self) -> None:
        self._cache_maxcol = None
        super()._invalidate()

    def set_focus_column(self, num: int) -> None:
        """
        Set the column in focus by its index in :attr:`widget_list`.

        :param num: index of focus-to-be entry
        :type num: int

        .. note:: only for backwards compatibility. You may also use the new
            standard container property :attr:`focus_position` to set the focus.
        """
        warnings.warn(
            "only for backwards compatibility.You may also use the new standard container property `focus_position`",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        self.focus_position = num

    def get_focus_column(self) -> int:
        """
        Return the focus column index.

        .. note:: only for backwards compatibility. You may also use the new
            standard container property :attr:`focus_position` to get the focus.
        """
        warnings.warn(
            "only for backwards compatibility.You may also use the new standard container property `focus_position`",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.focus_position

    def set_focus(self, item: Widget | int) -> None:
        """
        Set the item in focus

        .. note:: only for backwards compatibility. You may also use the new
            standard container property :attr:`focus_position` to get the focus.

        :param item: widget or integer index"""
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property `focus_position` to get the focus.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        if isinstance(item, int):
            self.focus_position = item
            return
        for i, (w, _options) in enumerate(self.contents):
            if item == w:
                self.focus_position = i
                return
        raise ValueError(f"Widget not found in Columns contents: {item!r}")

    @property
    def focus(self) -> Widget | None:
        """
        the child widget in focus or None when Columns is empty

        Return the widget in focus, for backwards compatibility.  You may
        also use the new standard container property .focus to get the
        child widget in focus.
        """
        if not self.contents:
            return None
        return self.contents[self.focus_position][0]

    def _get_focus(self) -> Widget:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus` is deprecated, "
            f"please use `{self.__class__.__name__}.focus` property",
            DeprecationWarning,
            stacklevel=3,
        )
        if not self.contents:
            return None
        return self.contents[self.focus_position][0]

    def get_focus(self):
        """
        Return the widget in focus, for backwards compatibility.

        .. note:: only for backwards compatibility. You may also use the new
            standard container property :attr:`focus` to get the focus.
        """
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property `focus` to get the focus.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        if not self.contents:
            return None
        return self.contents[self.focus_position][0]

    @property
    def focus_position(self) -> int | None:
        """
        index of child widget in focus.
        Raises :exc:`IndexError` if read when Columns is empty, or when set to an invalid index.
        """
        if not self.contents:
            raise IndexError("No focus_position, Columns is empty")
        return self.contents.focus

    @focus_position.setter
    def focus_position(self, position: int) -> None:
        """
        Set the widget in focus.

        position -- index of child widget to be made focus
        """
        try:
            if position < 0 or position >= len(self.contents):
                raise IndexError(f"No Columns child widget at position {position}")
        except TypeError as exc:
            raise IndexError(f"No Columns child widget at position {position}").with_traceback(
                exc.__traceback__
            ) from exc
        self.contents.focus = position

    def _get_focus_position(self) -> int | None:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        if not self.contents:
            raise IndexError("No focus_position, Columns is empty")
        return self.contents.focus

    def _set_focus_position(self, position: int) -> None:
        """
        Set the widget in focus.

        position -- index of child widget to be made focus
        """
        warnings.warn(
            f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        try:
            if position < 0 or position >= len(self.contents):
                raise IndexError(f"No Columns child widget at position {position}")
        except TypeError as exc:
            raise IndexError(f"No Columns child widget at position {position}").with_traceback(
                exc.__traceback__
            ) from exc
        self.contents.focus = position

    @property
    def focus_col(self):
        """
        A property for reading and setting the index of the column in
        focus.

        .. note:: only for backwards compatibility. You may also use the new
            standard container property :attr:`focus_position` to get the focus.
        """
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property `focus_position` to get the focus.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.focus_position

    @focus_col.setter
    def focus_col(self, new_position) -> None:
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property `focus_position` to get the focus.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        self.focus_position = new_position

    def column_widths(self, size: tuple[int] | tuple[int, int], focus: bool = False) -> list[int]:
        """
        Return a list of column widths.

        0 values in the list means hide the corresponding column completely
        """
        maxcol = size[0]
        # FIXME: get rid of this check and recalculate only when a 'pack' widget has been modified.
        if maxcol == self._cache_maxcol and not any(t == WHSettings.PACK for w, (t, n, b) in self.contents):
            return self._cache_column_widths

        widths = []

        weighted = []
        shared = maxcol + self.dividechars

        for i, (w, (t, width, b)) in enumerate(self.contents):
            if t == WHSettings.GIVEN:
                static_w = width
            elif t == WHSettings.PACK:
                if isinstance(w, Widget):
                    w_sizing = w.sizing()
                else:
                    warnings.warn(f"{w!r} is not a Widget", ColumnsWarning, stacklevel=3)
                    w_sizing = frozenset((urwid.BOX, urwid.FLOW))

                if w_sizing & frozenset((Sizing.FIXED, Sizing.FLOW)):
                    candidate_size = 0

                    if Sizing.FIXED in w_sizing:
                        candidate_size = w.pack((), focus and i == self.focus_position)[0]

                    if Sizing.FLOW in w_sizing and (not candidate_size or candidate_size > maxcol):
                        # FIXME: should be able to pack with a different maxcol value
                        candidate_size = w.pack((maxcol,), focus and i == self.focus_position)[0]

                    static_w = candidate_size

                else:
                    warnings.warn(
                        f"Unusual widget {w} sizing for {t} (box={b}). "
                        f"Assuming wrong sizing and using {Sizing.FLOW.upper()} for width calculation",
                        ColumnsWarning,
                        stacklevel=3,
                    )
                    static_w = w.pack((maxcol,), focus and i == self.focus_position)[0]

            else:
                static_w = self.min_width

            if shared < static_w + self.dividechars and i > self.focus_position:
                break

            widths.append(static_w)
            shared -= static_w + self.dividechars
            if t not in {WHSettings.GIVEN, WHSettings.PACK}:
                weighted.append((width, i))

        # drop columns on the left until we fit
        for i, width_ in enumerate(widths):
            if shared >= 0:
                break
            shared += width_ + self.dividechars
            widths[i] = 0
            if weighted and weighted[0][1] == i:
                del weighted[0]

        if shared:
            # divide up the remaining space between weighted cols
            wtotal = sum(weight for weight, i in weighted)
            grow = shared + len(weighted) * self.min_width
            for weight, i in sorted(weighted):
                width = max(int(grow * weight / wtotal + 0.5), self.min_width)

                widths[i] = width
                grow -= width
                wtotal -= weight

        self._cache_maxcol = maxcol
        self._cache_column_widths = widths
        return widths

    def _get_fixed_column_sizes(
        self,
        focus: bool = False,
    ) -> tuple[Sequence[int], Sequence[int], Sequence[tuple[int] | tuple[()]]]:
        """Get column widths, heights and render size parameters"""
        widths: dict[int, int] = {}
        heights: dict[int, int] = {}
        w_h_args: dict[int, tuple[int, int] | tuple[int] | tuple[()]] = {}
        box: list[int] = []
        weighted: dict[int, list[tuple[Widget, int, bool, bool]]] = {}
        weights: list[int] = []
        weight_max_sizes: dict[int, int] = {}

        for i, (widget, (size_kind, size_weight, is_box)) in enumerate(self.contents):
            w_sizing = widget.sizing()
            focused = focus and i == self.focus_position

            if size_kind == WHSettings.GIVEN:
                widths[i] = size_weight
                if is_box:
                    box.append(i)
                elif Sizing.FLOW in w_sizing:
                    heights[i] = widget.rows((size_weight,), focused)
                    w_h_args[i] = (size_weight,)
                else:
                    raise ColumnsError(f"Unsupported combination of {size_kind} box={is_box!r} for {widget}")

            elif size_kind == WHSettings.PACK and Sizing.FIXED in w_sizing and not is_box:
                width, height = widget.pack((), focused)
                widths[i] = width
                heights[i] = height
                w_h_args[i] = ()

            elif size_weight <= 0:
                widths[i] = 0
                heights[i] = 1
                if is_box:
                    box.append(i)
                else:
                    w_h_args[i] = (0,)

            elif Sizing.FLOW in w_sizing or is_box:
                if Sizing.FIXED in w_sizing:
                    width, height = widget.pack((), focused)
                else:
                    width = self.min_width

                weighted.setdefault(size_weight, []).append((widget, i, is_box, focused))
                weights.append(size_weight)
                weight_max_sizes.setdefault(size_weight, width)
                weight_max_sizes[size_weight] = max(weight_max_sizes[size_weight], width)
            else:
                raise ColumnsError(f"Unsupported combination of {size_kind} box={is_box!r} for {widget}")

        if weight_max_sizes:
            max_weighted_coefficient = max(width / weight for weight, width in weight_max_sizes.items())

            for weight in weight_max_sizes:
                width = max(int(max_weighted_coefficient * weight + 0.5), self.min_width)
                for widget, i, is_box, focused in weighted[weight]:
                    widths[i] = width

                    if not is_box:
                        heights[i] = widget.rows((width,), focused)
                        w_h_args[i] = (width,)
                    else:
                        box.append(i)

        if not heights:
            raise ColumnsError(f"No height information for pack {self!r} as FIXED")

        max_height = max(heights.values())
        for idx in box:
            heights[idx] = max_height
            w_h_args[idx] = (widths[idx], max_height)

        return (
            tuple(widths[idx] for idx in range(len(widths))),
            tuple(heights[idx] for idx in range(len(heights))),
            tuple(w_h_args[idx] for idx in range(len(w_h_args))),
        )

    def get_column_sizes(
        self,
        size: tuple[int, int] | tuple[int] | tuple[()],
        focus: bool = False,
    ) -> tuple[Sequence[int], Sequence[int], Sequence[tuple[int, int] | tuple[int] | tuple[()]]]:
        """Get column widths, heights and render size parameters"""
        if not size:
            return self._get_fixed_column_sizes(focus=focus)

        widths = tuple(self.column_widths(size=size, focus=focus))
        heights: dict[int, int] = {}
        w_h_args: dict[int, tuple[int, int] | tuple[int] | tuple[()]] = {}
        box: list[int] = []
        box_need_height: list[int] = []

        for i, (width, (widget, (size_kind, _size_weight, is_box))) in enumerate(zip(widths, self.contents)):
            if isinstance(widget, Widget):
                w_sizing = widget.sizing()
            else:
                warnings.warn(f"{widget!r} is not Widget.", ColumnsWarning, stacklevel=3)
                # This branch should be fully deleted later.
                w_sizing = frozenset((Sizing.FLOW, Sizing.BOX))

            if len(size) == 2 and Sizing.BOX in w_sizing:
                heights[i] = size[1]
                w_h_args[i] = (width, size[1])

            elif is_box:
                box.append(i)

            elif Sizing.FLOW in w_sizing:
                if width > 0:
                    heights[i] = widget.rows((width,), focus and i == self.focus_position)
                else:
                    heights[i] = 0
                w_h_args[i] = (width,)

            elif size_kind == WHSettings.PACK:
                if width > 0:
                    heights[i] = widget.pack((), focus and i == self.focus_position)[1]
                else:
                    heights[i] = 0
                w_h_args[i] = ()

            else:
                box_need_height.append(i)

        if len(size) == 1:
            if heights:
                max_height = max(heights.values())
                if box_need_height:
                    warnings.warn(
                        f"Widgets in columns {box_need_height} "
                        f"({[self.contents[i][0] for i in box_need_height]}) "
                        f'are BOX widgets not marked "box_columns" while FLOW render is requested (size={size!r})',
                        ColumnsWarning,
                        stacklevel=3,
                    )
            else:
                max_height = 1
        else:
            max_height = size[1]

        for idx in (*box, *box_need_height):
            heights[idx] = max_height
            w_h_args[idx] = (widths[idx], max_height)

        return (
            widths,
            tuple(heights[idx] for idx in range(len(heights))),
            tuple(w_h_args[idx] for idx in range(len(w_h_args))),
        )

    def pack(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int] = (),
        focus: bool = False,
    ) -> tuple[int, int]:
        """Get packed sized for widget."""
        if size:
            return super().pack(size, focus)
        widths, heights, _ = self.get_column_sizes(size, focus)
        return (sum(widths) + self.dividechars * max(len(widths) - 1, 0), max(heights))

    def render(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        focus: bool = False,
    ) -> SolidCanvas | CompositeCanvas:
        """
        Render columns and return canvas.

        :param size: see :meth:`Widget.render` for details
        :param focus: ``True`` if this widget is in focus
        :type focus: bool
        """
        widths, _, size_args = self.get_column_sizes(size, focus)

        data: list[tuple[Canvas, int, bool, int]] = []
        for i, (width, w_size, (w, _)) in enumerate(zip(widths, size_args, self.contents)):
            # if the widget has a width of 0, hide it
            if width <= 0:
                continue

            if i < len(widths) - 1:
                width += self.dividechars  # noqa: PLW2901
            data.append(
                (
                    w.render(w_size, focus=focus and self.focus_position == i),
                    i,
                    self.focus_position == i,
                    width,
                )
            )

        if not data:
            if size:
                return SolidCanvas(" ", size[0], (size[1:] + (1,))[0])
            raise ColumnsError("No data to render")

        canvas = CanvasJoin(data)
        if size and canvas.cols() < size[0]:
            canvas.pad_trim_left_right(0, size[0] - canvas.cols())
        return canvas

    def get_cursor_coords(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> tuple[int, int] | None:
        """Return the cursor coordinates from the focus widget."""
        w, _ = self.contents[self.focus_position]

        if not w.selectable():
            return None
        if not hasattr(w, "get_cursor_coords"):
            return None

        widths, _, size_args = self.get_column_sizes(size, focus=True)
        if len(widths) <= self.focus_position:
            return None

        coords = w.get_cursor_coords(size_args[self.focus_position])
        if coords is None:
            return None

        x, y = coords
        x += sum(self.dividechars + wc for wc in widths[: self.focus_position] if wc > 0)
        return x, y

    def move_cursor_to_coords(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        col: int | Literal["left", "right"],
        row: int,
    ) -> bool:
        """
        Choose a selectable column to focus based on the coords.

        see :meth:`Widget.move_cursor_coords` for details
        """
        try:
            widths, _, size_args = self.get_column_sizes(size, focus=True)
        except Exception as exc:
            raise ValueError(self.contents, size, col, row) from exc

        best = None
        x = 0
        for i, (width, (w, _options)) in enumerate(zip(widths, self.contents)):
            end = x + width
            if w.selectable():
                if col != Align.RIGHT and (col == Align.LEFT or x > col) and best is None:
                    # no other choice
                    best = i, x, end, w
                    break
                if col != Align.RIGHT and x > col and col - best[2] < x - col:
                    # choose one on left
                    break
                best = i, x, end, w
                if col != Align.RIGHT and col < end:
                    # choose this one
                    break
            x = end + self.dividechars

        if best is None:
            return False
        i, x, end, w = best
        if hasattr(w, "move_cursor_to_coords"):
            if isinstance(col, int):
                move_x = min(max(0, col - x), end - x - 1)
            else:
                move_x = col
            rval = w.move_cursor_to_coords(size_args[i], move_x, row)
            if rval is False:
                return False

        self.focus_position = i
        self.pref_col = col
        return True

    def mouse_event(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        """
        Send event to appropriate column.
        May change focus on button 1 press.
        """
        widths, _, size_args = self.get_column_sizes(size, focus=focus)

        x = 0
        for i, (width, w_size, (w, _)) in enumerate(zip(widths, size_args, self.contents)):
            if col < x:
                return False
            w = self.contents[i][0]  # noqa: PLW2901
            end = x + width

            if col >= end:
                x = end + self.dividechars
                continue

            focus = focus and self.focus_position == i
            if is_mouse_press(event) and button == 1 and w.selectable():
                self.focus_position = i

            if not hasattr(w, "mouse_event"):
                warnings.warn(
                    f"{w.__class__.__module__}.{w.__class__.__name__} is not subclass of Widget",
                    DeprecationWarning,
                    stacklevel=2,
                )
                return False

            return w.mouse_event(w_size, event, button, col - x, row, focus)
        return False

    def get_pref_col(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> int:
        """Return the pref col from the column in focus."""
        widths, _, size_args = self.get_column_sizes(size, focus=True)

        w, _ = self.contents[self.focus_position]
        if len(widths) <= self.focus_position:
            return 0
        col = None
        cwidth = widths[self.focus_position]

        if hasattr(w, "get_pref_col"):
            col = w.get_pref_col(size_args[self.focus_position])
            if isinstance(col, int):
                col += self.focus_position * self.dividechars
                col += sum(widths[: self.focus_position])
        if col is None:
            col = self.pref_col

        if col is None and w.selectable():
            col = cwidth // 2
            col += self.focus_position * self.dividechars
            col += sum(widths[: self.focus_position])
        return col

    def rows(self, size: tuple[int] | tuple[int, int], focus: bool = False) -> int:
        """
        Return the number of rows required by the columns.
        This only makes sense if :attr:`widget_list` contains flow widgets.

        see :meth:`Widget.rows` for details
        """
        _, heights, _ = self.get_column_sizes(size, focus)
        if heights:
            return max(1, *heights)
        return 1

    def keypress(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        key: str,
    ) -> str | None:
        """
        Pass keypress to the focus column.

        :param size: Widget size correct for the supported sizing
        :type size: tuple[()] | tuple[int] | tuple[int, int]
        :param key: a single keystroke value
        :type key: str
        """
        if self.focus_position is None:
            return key

        widths, _, size_args = self.get_column_sizes(size, focus=True)
        if self.focus_position >= len(widths):
            return key

        i = self.focus_position
        w, _ = self.contents[i]
        if self._command_map[key] not in {Command.UP, Command.DOWN, Command.PAGE_UP, Command.PAGE_DOWN}:
            self.pref_col = None
        if w.selectable():
            key = w.keypress(size_args[i], key)

        if self._command_map[key] not in {Command.LEFT, Command.RIGHT}:
            return key

        if self._command_map[key] == Command.LEFT:
            candidates = list(range(i - 1, -1, -1))  # count backwards to 0
        else:  # key == 'right'
            candidates = list(range(i + 1, len(self.contents)))

        for j in candidates:
            if not self.contents[j][0].selectable():
                continue

            self.focus_position = j
            return None
        return key
urwid-2.6.16/urwid/widget/constants.py000066400000000000000000000357211470350774000177620ustar00rootroot00000000000000from __future__ import annotations

import dataclasses
import enum
import typing

if typing.TYPE_CHECKING:
    from typing_extensions import Literal


# define some names for these constants to avoid misspellings in the source
# and to document the constant strings we are using


class Sizing(str, enum.Enum):
    """Widget sizing methods."""

    FLOW = "flow"
    BOX = "box"
    FIXED = "fixed"


class Align(str, enum.Enum):
    """Text alignment modes"""

    LEFT = "left"
    RIGHT = "right"
    CENTER = "center"


class VAlign(str, enum.Enum):
    """Filler alignment"""

    TOP = "top"
    MIDDLE = "middle"
    BOTTOM = "bottom"


class WrapMode(str, enum.Enum):
    """Text wrapping modes"""

    SPACE = "space"
    ANY = "any"
    CLIP = "clip"
    ELLIPSIS = "ellipsis"


class WHSettings(str, enum.Enum):
    """Width and Height settings"""

    PACK = "pack"
    GIVEN = "given"
    RELATIVE = "relative"
    WEIGHT = "weight"
    CLIP = "clip"  # Used as "given" for widgets with fixed width (with clipping part of it)
    FLOW = "flow"  # Used as pack for flow widgets


RELATIVE_100 = (WHSettings.RELATIVE, 100)


@typing.overload
def normalize_align(
    align: Literal["left", "center", "right"] | Align,
    err: type[BaseException],
) -> tuple[Align, None]: ...


@typing.overload
def normalize_align(
    align: tuple[Literal["relative", WHSettings.RELATIVE], int],
    err: type[BaseException],
) -> tuple[Literal[WHSettings.RELATIVE], int]: ...


def normalize_align(
    align: Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int],
    err: type[BaseException],
) -> tuple[Align, None] | tuple[Literal[WHSettings.RELATIVE], int]:
    """
    Split align into (align_type, align_amount).  Raise exception err
    if align doesn't match a valid alignment.
    """
    if align in {Align.LEFT, Align.CENTER, Align.RIGHT}:
        return (Align(align), None)

    if isinstance(align, tuple) and len(align) == 2 and align[0] == WHSettings.RELATIVE:
        _align_type, align_amount = align
        return (WHSettings.RELATIVE, align_amount)

    raise err(
        f"align value {align!r} is not one of 'left', 'center', 'right', ('relative', percentage 0=left 100=right)"
    )


@typing.overload
def simplify_align(
    align_type: Literal["relative", WHSettings.RELATIVE],
    align_amount: int,
) -> tuple[Literal[WHSettings.RELATIVE], int]: ...


@typing.overload
def simplify_align(
    align_type: Literal["relative", WHSettings.RELATIVE],
    align_amount: None,
) -> typing.NoReturn: ...


@typing.overload
def simplify_align(
    align_type: Literal["left", "center", "right"] | Align,
    align_amount: int | None,
) -> Align: ...


def simplify_align(
    align_type: Literal["left", "center", "right", "relative", WHSettings.RELATIVE] | Align,
    align_amount: int | None,
) -> Align | tuple[Literal[WHSettings.RELATIVE], int]:
    """
    Recombine (align_type, align_amount) into an align value.
    Inverse of normalize_align.
    """
    if align_type == WHSettings.RELATIVE:
        if not isinstance(align_amount, int):
            raise TypeError(align_amount)

        return (WHSettings.RELATIVE, align_amount)
    return Align(align_type)


@typing.overload
def normalize_valign(
    valign: Literal["top", "middle", "bottom"] | VAlign,
    err: type[BaseException],
) -> tuple[VAlign, None]: ...


@typing.overload
def normalize_valign(
    valign: tuple[Literal["relative", WHSettings.RELATIVE], int],
    err: type[BaseException],
) -> tuple[Literal[WHSettings.RELATIVE], int]: ...


def normalize_valign(
    valign: Literal["top", "middle", "bottom"] | VAlign | tuple[Literal["relative", WHSettings.RELATIVE], int],
    err: type[BaseException],
) -> tuple[VAlign, None] | tuple[Literal[WHSettings.RELATIVE], int]:
    """
    Split align into (valign_type, valign_amount).  Raise exception err
    if align doesn't match a valid alignment.
    """
    if valign in {VAlign.TOP, VAlign.MIDDLE, VAlign.BOTTOM}:
        return (VAlign(valign), None)

    if isinstance(valign, tuple) and len(valign) == 2 and valign[0] == WHSettings.RELATIVE:
        _valign_type, valign_amount = valign
        return (WHSettings.RELATIVE, valign_amount)

    raise err(
        f"valign value {valign!r} is not one of 'top', 'middle', 'bottom', ('relative', percentage 0=left 100=right)"
    )


@typing.overload
def simplify_valign(
    valign_type: Literal["top", "middle", "bottom"] | VAlign,
    valign_amount: int | None,
) -> VAlign: ...


@typing.overload
def simplify_valign(
    valign_type: Literal["relative", WHSettings.RELATIVE],
    valign_amount: int,
) -> tuple[Literal[WHSettings.RELATIVE], int]: ...


@typing.overload
def simplify_valign(
    valign_type: Literal["relative", WHSettings.RELATIVE],
    valign_amount: None,
) -> typing.NoReturn: ...


def simplify_valign(
    valign_type: Literal["top", "middle", "bottom", "relative", WHSettings.RELATIVE] | VAlign,
    valign_amount: int | None,
) -> VAlign | tuple[Literal[WHSettings.RELATIVE], int]:
    """
    Recombine (valign_type, valign_amount) into an valign value.
    Inverse of normalize_valign.
    """
    if valign_type == WHSettings.RELATIVE:
        if not isinstance(valign_amount, int):
            raise TypeError(valign_amount)
        return (WHSettings.RELATIVE, valign_amount)
    return VAlign(valign_type)


@typing.overload
def normalize_width(
    width: Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK],
    err: type[BaseException],
) -> tuple[Literal[WHSettings.CLIP, WHSettings.PACK], None]: ...


@typing.overload
def normalize_width(
    width: int,
    err: type[BaseException],
) -> tuple[Literal[WHSettings.GIVEN], int]: ...


@typing.overload
def normalize_width(
    width: tuple[Literal["relative", WHSettings.RELATIVE], int],
    err: type[BaseException],
) -> tuple[Literal[WHSettings.RELATIVE], int]: ...


@typing.overload
def normalize_width(
    width: tuple[Literal["weight", WHSettings.WEIGHT], int | float],
    err: type[BaseException],
) -> tuple[Literal[WHSettings.WEIGHT], int | float]: ...


def normalize_width(
    width: (
        Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK]
        | int
        | tuple[Literal["relative", WHSettings.RELATIVE], int]
        | tuple[Literal["weight", WHSettings.WEIGHT], int | float]
    ),
    err: type[BaseException],
) -> (
    tuple[Literal[WHSettings.CLIP, WHSettings.PACK], None]
    | tuple[Literal[WHSettings.GIVEN, WHSettings.RELATIVE], int]
    | tuple[Literal[WHSettings.WEIGHT], int | float]
):
    """
    Split width into (width_type, width_amount).  Raise exception err
    if width doesn't match a valid alignment.
    """
    if width in {WHSettings.CLIP, WHSettings.PACK}:
        return (WHSettings(width), None)

    if isinstance(width, int):
        return (WHSettings.GIVEN, width)

    if isinstance(width, tuple) and len(width) == 2 and width[0] in {WHSettings.RELATIVE, WHSettings.WEIGHT}:
        width_type, width_amount = width
        return (WHSettings(width_type), width_amount)

    raise err(
        f"width value {width!r} is not one of"
        f"fixed number of columns, 'pack', ('relative', percentage of total width), 'clip'"
    )


@typing.overload
def simplify_width(
    width_type: Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK],
    width_amount: int | None,
) -> Literal[WHSettings.CLIP, WHSettings.PACK]: ...


@typing.overload
def simplify_width(
    width_type: Literal["given", WHSettings.GIVEN],
    width_amount: int,
) -> int: ...


@typing.overload
def simplify_width(
    width_type: Literal["relative", WHSettings.RELATIVE],
    width_amount: int,
) -> tuple[Literal[WHSettings.RELATIVE], int]: ...


@typing.overload
def simplify_width(
    width_type: Literal["weight", WHSettings.WEIGHT],
    width_amount: int | float,  # noqa: PYI041  # provide explicit for IDEs
) -> tuple[Literal[WHSettings.WEIGHT], int | float]: ...


@typing.overload
def simplify_width(
    width_type: Literal["given", "relative", "weight", WHSettings.GIVEN, WHSettings.RELATIVE, WHSettings.WEIGHT],
    width_amount: None,
) -> typing.NoReturn: ...


def simplify_width(
    width_type: Literal["clip", "pack", "given", "relative", "weight"] | WHSettings,
    width_amount: int | float | None,  # noqa: PYI041  # provide explicit for IDEs
) -> (
    Literal[WHSettings.CLIP, WHSettings.PACK]
    | int
    | tuple[Literal[WHSettings.RELATIVE], int]
    | tuple[Literal[WHSettings.WEIGHT], int | float]
):
    """
    Recombine (width_type, width_amount) into an width value.
    Inverse of normalize_width.
    """
    if width_type in {WHSettings.CLIP, WHSettings.PACK}:
        return WHSettings(width_type)

    if not isinstance(width_amount, int):
        raise TypeError(width_amount)

    if width_type == WHSettings.GIVEN:
        return width_amount

    return (WHSettings(width_type), width_amount)


@typing.overload
def normalize_height(
    height: int,
    err: type[BaseException],
) -> tuple[Literal[WHSettings.GIVEN], int]: ...


@typing.overload
def normalize_height(
    height: Literal["flow", "pack", Sizing.FLOW, WHSettings.PACK],
    err: type[BaseException],
) -> tuple[Literal[Sizing.FLOW, WHSettings.PACK], None]: ...


@typing.overload
def normalize_height(
    height: tuple[Literal["relative", WHSettings.RELATIVE], int],
    err: type[BaseException],
) -> tuple[Literal[WHSettings.RELATIVE], int]: ...


@typing.overload
def normalize_height(
    height: tuple[Literal["weight", WHSettings.WEIGHT], int | float],
    err: type[BaseException],
) -> tuple[Literal[WHSettings.WEIGHT], int | float]: ...


def normalize_height(
    height: (
        int
        | Literal["flow", "pack", Sizing.FLOW, WHSettings.PACK]
        | tuple[Literal["relative", WHSettings.RELATIVE], int]
        | tuple[Literal["weight", WHSettings.WEIGHT], int | float]
    ),
    err: type[BaseException],
) -> (
    tuple[Literal[Sizing.FLOW, WHSettings.PACK], None]
    | tuple[Literal[WHSettings.RELATIVE, WHSettings.GIVEN], int]
    | tuple[Literal[WHSettings.WEIGHT], int | float]
):
    """
    Split height into (height_type, height_amount).  Raise exception err
    if height isn't valid.
    """
    if height == Sizing.FLOW:
        return (Sizing.FLOW, None)

    if height == WHSettings.PACK:
        return (WHSettings.PACK, None)

    if isinstance(height, tuple) and len(height) == 2 and height[0] in {WHSettings.RELATIVE, WHSettings.WEIGHT}:
        return (WHSettings(height[0]), height[1])

    if isinstance(height, int):
        return (WHSettings.GIVEN, height)

    raise err(
        f"height value {height!r} is not one of "
        f"fixed number of columns, 'pack', ('relative', percentage of total height)"
    )


@typing.overload
def simplify_height(
    height_type: Literal["flow", "pack", WHSettings.FLOW, WHSettings.PACK],
    height_amount: int | None,
) -> Literal[WHSettings.FLOW, WHSettings.PACK]: ...


@typing.overload
def simplify_height(
    height_type: Literal["given", WHSettings.GIVEN],
    height_amount: int,
) -> int: ...


@typing.overload
def simplify_height(
    height_type: Literal["relative", WHSettings.RELATIVE],
    height_amount: int | None,
) -> tuple[Literal[WHSettings.RELATIVE], int]: ...


@typing.overload
def simplify_height(
    height_type: Literal["weight", WHSettings.WEIGHT],
    height_amount: int | float | None,  # noqa: PYI041  # provide explicit for IDEs
) -> tuple[Literal[WHSettings.WEIGHT], int | float]: ...


@typing.overload
def simplify_height(
    height_type: Literal["relative", "given", "weight", WHSettings.RELATIVE, WHSettings.GIVEN, WHSettings.WEIGHT],
    height_amount: None,
) -> typing.NoReturn: ...


def simplify_height(
    height_type: Literal[
        "flow",
        "pack",
        "relative",
        "given",
        "weight",
        WHSettings.FLOW,
        WHSettings.PACK,
        WHSettings.RELATIVE,
        WHSettings.GIVEN,
        WHSettings.WEIGHT,
    ],
    height_amount: int | float | None,  # noqa: PYI041  # provide explicit for IDEs
) -> (
    int
    | Literal[WHSettings.FLOW, WHSettings.PACK]
    | tuple[Literal[WHSettings.RELATIVE], int]
    | tuple[Literal[WHSettings.WEIGHT], int | float]
):
    """
    Recombine (height_type, height_amount) into a height value.
    Inverse of normalize_height.
    """
    if height_type in {WHSettings.FLOW, WHSettings.PACK}:
        return WHSettings(height_type)

    if not isinstance(height_amount, int):
        raise TypeError(height_amount)

    if height_type == WHSettings.GIVEN:
        return height_amount

    return (WHSettings(height_type), height_amount)


@dataclasses.dataclass(frozen=True)
class _BoxSymbols:
    """Box symbols for drawing."""

    HORIZONTAL: str
    VERTICAL: str
    TOP_LEFT: str
    TOP_RIGHT: str
    BOTTOM_LEFT: str
    BOTTOM_RIGHT: str
    # Joints for tables making
    LEFT_T: str
    RIGHT_T: str
    TOP_T: str
    BOTTOM_T: str
    CROSS: str


@dataclasses.dataclass(frozen=True)
class _BoxSymbolsWithDashes(_BoxSymbols):
    """Box symbols for drawing.

    Extra dashes symbols.
    """

    HORIZONTAL_4_DASHES: str
    HORIZONTAL_3_DASHES: str
    HORIZONTAL_2_DASHES: str
    VERTICAL_2_DASH: str
    VERTICAL_3_DASH: str
    VERTICAL_4_DASH: str


@dataclasses.dataclass(frozen=True)
class _LightBoxSymbols(_BoxSymbolsWithDashes):
    """Box symbols for drawing.

    The Thin version includes extra symbols.
    Symbols are ordered as in Unicode except dashes.
    """

    TOP_LEFT_ROUNDED: str
    TOP_RIGHT_ROUNDED: str
    BOTTOM_LEFT_ROUNDED: str
    BOTTOM_RIGHT_ROUNDED: str


class _BoxSymbolsCollection(typing.NamedTuple):
    """Standard Unicode box symbols for basic tables drawing.

    .. note::
        Transitions are not included: depends on line types, different kinds of transitions are available.
        Please check Unicode table for transitions symbols if required.
    """

    # fmt: off

    LIGHT: _LightBoxSymbols = _LightBoxSymbols(
        "─", "│", "┌", "â”", "â””", "┘", "├", "┤", "┬", "â”´", "┼", "┈", "┄", "╌", "╎", "┆", "┊", "â•­", "â•®", "â•°", "╯"
    )
    HEAVY: _BoxSymbolsWithDashes = _BoxSymbolsWithDashes(
        "â”", "┃", "â”", "┓", "â”—", "â”›", "┣", "┫", "┳", "â”»", "â•‹", "┉", "â”…", "â•", "â•", "┇", "┋"
    )
    DOUBLE: _BoxSymbols = _BoxSymbols(
        "â•", "â•‘", "â•”", "â•—", "╚", "â•", "â• ", "â•£", "╦", "â•©", "╬"
    )


BOX_SYMBOLS = _BoxSymbolsCollection()


class BAR_SYMBOLS(str, enum.Enum):
    """Standard Unicode bar symbols excluding empty space.

    Start from space (0), then 1/8 till full block (1/1).
    Typically used only 8 from this symbol collection depends on use-case:
    * empty - 7/8 and styles for BG different on both sides (like standard `ProgressBar` and `BarGraph`)
    * 1/8 - full block and single style for BG on the right side
    """

    # fmt: off

    HORISONTAL = " â–â–Žâ–▌▋▊▉█"
    VERTICAL =   " â–▂▃▄▅▆▇█"


class _SHADE_SYMBOLS(typing.NamedTuple):
    """Standard shade symbols excluding empty space."""

    # fmt: off

    FULL_BLOCK: str =   "â–ˆ"
    DARK_SHADE: str =   "â–“"
    MEDIUM_SHADE: str = "â–’"
    LITE_SHADE: str =   "â–‘"


SHADE_SYMBOLS = _SHADE_SYMBOLS()
urwid-2.6.16/urwid/widget/container.py000066400000000000000000000160111470350774000177170ustar00rootroot00000000000000from __future__ import annotations

import abc
import enum
import typing
import warnings

from .constants import Sizing, WHSettings

if typing.TYPE_CHECKING:
    from collections.abc import Iterable, Iterator

    from .widget import Widget


class _ContainerElementSizingFlag(enum.IntFlag):
    NONE = 0
    BOX = enum.auto()
    FLOW = enum.auto()
    FIXED = enum.auto()
    WH_WEIGHT = enum.auto()
    WH_PACK = enum.auto()
    WH_GIVEN = enum.auto()

    @property
    def reverse_flag(self) -> tuple[frozenset[Sizing], WHSettings | None]:
        """Get flag in public API format."""
        sizing: set[Sizing] = set()

        if self & self.BOX:
            sizing.add(Sizing.BOX)
        if self & self.FLOW:
            sizing.add(Sizing.FLOW)
        if self & self.FIXED:
            sizing.add(Sizing.FIXED)

        if self & self.WH_WEIGHT:
            return frozenset(sizing), WHSettings.WEIGHT
        if self & self.WH_PACK:
            return frozenset(sizing), WHSettings.PACK
        if self & self.WH_GIVEN:
            return frozenset(sizing), WHSettings.GIVEN
        return frozenset(sizing), None

    @property
    def log_string(self) -> str:
        """Get desctiprion in public API format."""
        sizing, render = self.reverse_flag
        render_string = f" {render.upper()}" if render else ""
        return "|".join(sorted(mode.upper() for mode in sizing)) + render_string


class WidgetContainerMixin:
    """
    Mixin class for widget containers implementing common container methods
    """

    def __getitem__(self, position) -> Widget:
        """
        Container short-cut for self.contents[position][0].base_widget
        which means "give me the child widget at position without any
        widget decorations".

        This allows for concise traversal of nested container widgets
        such as:

            my_widget[position0][position1][position2] ...
        """
        return self.contents[position][0].base_widget

    def get_focus_path(self) -> list[int | str]:
        """
        Return the .focus_position values starting from this container
        and proceeding along each child widget until reaching a leaf
        (non-container) widget.
        """
        out = []
        w = self
        while True:
            try:
                p = w.focus_position
            except IndexError:
                return out
            out.append(p)
            w = w.focus.base_widget

    def set_focus_path(self, positions: Iterable[int | str]) -> None:
        """
        Set the .focus_position property starting from this container
        widget and proceeding along newly focused child widgets.  Any
        failed assignment due do incompatible position types or invalid
        positions will raise an IndexError.

        This method may be used to restore a particular widget to the
        focus by passing in the value returned from an earlier call to
        get_focus_path().

        positions -- sequence of positions
        """
        w: Widget = self
        for p in positions:
            if p != w.focus_position:
                w.focus_position = p  # modifies w.focus
            w = w.focus.base_widget  # type: ignore[assignment]

    def get_focus_widgets(self) -> list[Widget]:
        """
        Return the .focus values starting from this container
        and proceeding along each child widget until reaching a leaf
        (non-container) widget.

        Note that the list does not contain the topmost container widget
        (i.e., on which this method is called), but does include the
        lowest leaf widget.
        """
        out = []
        w = self
        while True:
            w = w.base_widget.focus
            if w is None:
                return out
            out.append(w)

    @property
    @abc.abstractmethod
    def focus(self) -> Widget:
        """
        Read-only property returning the child widget in focus for
        container widgets.  This default implementation
        always returns ``None``, indicating that this widget has no children.
        """

    def _get_focus(self) -> Widget:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus` is deprecated, "
            f"please use `{self.__class__.__name__}.focus` property",
            DeprecationWarning,
            stacklevel=3,
        )
        return self.focus


class WidgetContainerListContentsMixin:
    """
    Mixin class for widget containers whose positions are indexes into
    a list available as self.contents.
    """

    def __iter__(self) -> Iterator[int]:
        """
        Return an iterable of positions for this container from first
        to last.
        """
        return iter(range(len(self.contents)))

    def __reversed__(self) -> Iterator[int]:
        """
        Return an iterable of positions for this container from last
        to first.
        """
        return iter(range(len(self.contents) - 1, -1, -1))

    def __len__(self) -> int:
        return len(self.contents)

    @property
    @abc.abstractmethod
    def contents(self) -> list[tuple[Widget, typing.Any]]:
        """The contents of container as a list of (widget, options)"""

    @contents.setter
    def contents(self, new_contents: list[tuple[Widget, typing.Any]]) -> None:
        """The contents of container as a list of (widget, options)"""

    def _get_contents(self) -> list[tuple[Widget, typing.Any]]:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_contents` is deprecated, "
            f"please use `{self.__class__.__name__}.contents` property",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.contents

    def _set_contents(self, c: list[tuple[Widget, typing.Any]]) -> None:
        warnings.warn(
            f"method `{self.__class__.__name__}._set_contents` is deprecated, "
            f"please use `{self.__class__.__name__}.contents` property",
            DeprecationWarning,
            stacklevel=2,
        )
        self.contents = c

    @property
    @abc.abstractmethod
    def focus_position(self) -> int | None:
        """
        index of child widget in focus.
        """

    @focus_position.setter
    def focus_position(self, position: int) -> None:
        """
        index of child widget in focus.
        """

    def _get_focus_position(self) -> int | None:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        return self.focus_position

    def _set_focus_position(self, position: int) -> None:
        """
        Set the widget in focus.

        position -- index of child widget to be made focus
        """
        warnings.warn(
            f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        self.focus_position = position
urwid-2.6.16/urwid/widget/divider.py000066400000000000000000000062631470350774000173730ustar00rootroot00000000000000from __future__ import annotations

import enum
import typing

from urwid.canvas import CompositeCanvas, SolidCanvas

from .constants import BOX_SYMBOLS, SHADE_SYMBOLS, Sizing
from .widget import Widget


class DividerSymbols(str, enum.Enum):
    """Common symbols for divider widgets."""

    # Lines
    LIGHT_HL = BOX_SYMBOLS.LIGHT.HORIZONTAL
    LIGHT_4_DASHES = BOX_SYMBOLS.LIGHT.HORIZONTAL_4_DASHES
    LIGHT_3_DASHES = BOX_SYMBOLS.LIGHT.HORIZONTAL_3_DASHES
    LIGHT_2_DASHES = BOX_SYMBOLS.LIGHT.HORIZONTAL_2_DASHES
    HEAVY_HL = BOX_SYMBOLS.HEAVY.HORIZONTAL
    HEAVY_4_DASHES = BOX_SYMBOLS.HEAVY.HORIZONTAL_4_DASHES
    HEAVY_3_DASHES = BOX_SYMBOLS.HEAVY.HORIZONTAL_3_DASHES
    HEAVY_2_DASHES = BOX_SYMBOLS.HEAVY.HORIZONTAL_2_DASHES
    DOUBLE_HL = BOX_SYMBOLS.DOUBLE.HORIZONTAL

    # Full block
    FULL_BLOCK = SHADE_SYMBOLS.FULL_BLOCK
    DARK_SHADE = SHADE_SYMBOLS.DARK_SHADE
    MEDIUM_SHADE = SHADE_SYMBOLS.MEDIUM_SHADE
    LITE_SHADE = SHADE_SYMBOLS.LITE_SHADE


class Divider(Widget):
    """
    Horizontal divider widget
    """

    Symbols = DividerSymbols

    _sizing = frozenset([Sizing.FLOW])

    ignore_focus = True

    def __init__(
        self,
        div_char: str | bytes = " ",
        top: int = 0,
        bottom: int = 0,
    ) -> None:
        """
        :param div_char: character to repeat across line
        :type div_char: bytes or unicode

        :param top: number of blank lines above
        :type top: int

        :param bottom: number of blank lines below
        :type bottom: int

        >>> Divider()
        
        >>> Divider(u'-')
        
        >>> Divider(u'x', 1, 2)
        
        """
        super().__init__()
        self.div_char = div_char
        self.top = top
        self.bottom = bottom

    def _repr_words(self) -> list[str]:
        return super()._repr_words() + [repr(self.div_char)] * (self.div_char != " ")

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = dict(super()._repr_attrs())
        if self.top:
            attrs["top"] = self.top
        if self.bottom:
            attrs["bottom"] = self.bottom
        return attrs

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        """
        Return the number of lines that will be rendered.

        >>> Divider().rows((10,))
        1
        >>> Divider(u'x', 1, 2).rows((10,))
        4
        """
        (_maxcol,) = size
        return self.top + 1 + self.bottom

    def render(
        self,
        size: tuple[int],  # type: ignore[override]
        focus: bool = False,
    ) -> CompositeCanvas:
        """
        Render the divider as a canvas and return it.

        >>> Divider().render((10,)).text # ... = b in Python 3
        [...'          ']
        >>> Divider(u'-', top=1).render((10,)).text
        [...'          ', ...'----------']
        >>> Divider(u'x', bottom=2).render((5,)).text
        [...'xxxxx', ...'     ', ...'     ']
        """
        (maxcol,) = size
        canv = CompositeCanvas(SolidCanvas(self.div_char, maxcol, 1))
        if self.top or self.bottom:
            canv.pad_trim_top_bottom(self.top, self.bottom)
        return canv
urwid-2.6.16/urwid/widget/edit.py000066400000000000000000000562641470350774000167000ustar00rootroot00000000000000from __future__ import annotations

import string
import typing

from urwid import text_layout
from urwid.canvas import CompositeCanvas
from urwid.command_map import Command
from urwid.split_repr import remove_defaults
from urwid.str_util import is_wide_char, move_next_char, move_prev_char
from urwid.util import decompose_tagmarkup

from .constants import Align, Sizing, WrapMode
from .text import Text, TextError

if typing.TYPE_CHECKING:
    from collections.abc import Hashable

    from typing_extensions import Literal

    from urwid.canvas import TextCanvas


class EditError(TextError):
    pass


class Edit(Text):
    """
    Text editing widget implements cursor movement, text insertion and
    deletion.  A caption may prefix the editing area.  Uses text class
    for text layout.

    Users of this class may listen for ``"change"`` or ``"postchange"``
    events.  See :func:``connect_signal``.

    * ``"change"`` is sent just before the value of edit_text changes.
      It receives the new text as an argument.  Note that ``"change"`` cannot
      change the text in question as edit_text changes the text afterwards.
    * ``"postchange"`` is sent after the value of edit_text changes.
      It receives the old value of the text as an argument and thus is
      appropriate for changing the text.  It is possible for a ``"postchange"``
      event handler to get into a loop of changing the text and then being
      called when the event is re-emitted.  It is up to the event
      handler to guard against this case (for instance, by not changing the
      text if it is signaled for text that it has already changed once).
    """

    _sizing = frozenset([Sizing.FLOW])
    _selectable = True
    ignore_focus = False
    # (this variable is picked up by the MetaSignals metaclass)
    signals: typing.ClassVar[list[str]] = ["change", "postchange"]

    def valid_char(self, ch: str) -> bool:
        """
        Filter for text that may be entered into this widget by the user

        :param ch: character to be inserted
        :type ch: str

        This implementation returns True for all printable characters.
        """
        return is_wide_char(ch, 0) or (len(ch) == 1 and ord(ch) >= 32)

    def __init__(
        self,
        caption: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]] = "",
        edit_text: str = "",
        multiline: bool = False,
        align: Literal["left", "center", "right"] | Align = Align.LEFT,
        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = WrapMode.SPACE,
        allow_tab: bool = False,
        edit_pos: int | None = None,
        layout: text_layout.TextLayout = None,
        mask: str | None = None,
    ) -> None:
        """
        :param caption: markup for caption preceding edit_text, see
                        :class:`Text` for description of text markup.
        :type caption: text markup
        :param edit_text: initial text for editing, type (bytes or unicode)
                          must match the text in the caption
        :type edit_text: bytes or unicode
        :param multiline: True: 'enter' inserts newline  False: return it
        :type multiline: bool
        :param align: typically 'left', 'center' or 'right'
        :type align: text alignment mode
        :param wrap: typically 'space', 'any' or 'clip'
        :type wrap: text wrapping mode
        :param allow_tab: True: 'tab' inserts 1-8 spaces  False: return it
        :type allow_tab: bool
        :param edit_pos: initial position for cursor, None:end of edit_text
        :type edit_pos: int
        :param layout: defaults to a shared :class:`StandardTextLayout` instance
        :type layout: text layout instance
        :param mask: hide text entered with this character, None:disable mask
        :type mask: bytes or unicode

        >>> Edit()
        
        >>> Edit(u"Y/n? ", u"yes")
        
        >>> Edit(u"Name ", u"Smith", edit_pos=1)
        
        >>> Edit(u"", u"3.14", align='right')
        
        """

        super().__init__("", align, wrap, layout)
        self.multiline = multiline
        self.allow_tab = allow_tab
        self._edit_pos = 0
        self._caption, self._attrib = decompose_tagmarkup(caption)
        self._edit_text = ""
        self.highlight: tuple[int, int] | None = None
        self.set_edit_text(edit_text)
        if edit_pos is None:
            edit_pos = len(edit_text)
        self.set_edit_pos(edit_pos)
        self.set_mask(mask)
        self._shift_view_to_cursor = False

    def _repr_words(self) -> list[str]:
        return (
            super()._repr_words()[:-1]
            + [repr(self._edit_text)]
            + [f"caption={self._caption!r}"] * bool(self._caption)
            + ["multiline"] * (self.multiline is True)
        )

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = {**super()._repr_attrs(), "edit_pos": self._edit_pos}
        return remove_defaults(attrs, Edit.__init__)

    def get_text(self) -> tuple[str | bytes, list[tuple[Hashable, int]]]:
        """
        Returns ``(text, display attributes)``. See :meth:`Text.get_text`
        for details.

        Text returned includes the caption and edit_text, possibly masked.

        >>> Edit(u"What? ","oh, nothing.").get_text()
        ('What? oh, nothing.', [])
        >>> Edit(('bright',u"user@host:~$ "),"ls").get_text()
        ('user@host:~$ ls', [('bright', 13)])
        >>> Edit(u"password:", u"seekrit", mask=u"*").get_text()
        ('password:*******', [])
        """

        if self._mask is None:
            return self._caption + self._edit_text, self._attrib

        return self._caption + (self._mask * len(self._edit_text)), self._attrib

    def set_text(self, markup: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]) -> None:
        """
        Not supported by Edit widget.

        >>> Edit().set_text("test")
        Traceback (most recent call last):
        EditError: set_text() not supported.  Use set_caption() or set_edit_text() instead.
        """
        # FIXME: this smells. reimplement Edit as a WidgetWrap subclass to
        # clean this up

        # hack to let Text.__init__() work
        if not hasattr(self, "_text") and markup == "":  # noqa: PLC1901,RUF100
            self._text = None
            return

        raise EditError("set_text() not supported.  Use set_caption() or set_edit_text() instead.")

    def get_pref_col(self, size: tuple[int]) -> int:
        """
        Return the preferred column for the cursor, or the
        current cursor x value.  May also return ``'left'`` or ``'right'``
        to indicate the leftmost or rightmost column available.

        This method is used internally and by other widgets when
        moving the cursor up or down between widgets so that the
        column selected is one that the user would expect.

        >>> size = (10,)
        >>> Edit().get_pref_col(size)
        0
        >>> e = Edit(u"", u"word")
        >>> e.get_pref_col(size)
        4
        >>> e.keypress(size, 'left')
        >>> e.get_pref_col(size)
        3
        >>> e.keypress(size, 'end')
        >>> e.get_pref_col(size)
        
        >>> e = Edit(u"", u"2\\nwords")
        >>> e.keypress(size, 'left')
        >>> e.keypress(size, 'up')
        >>> e.get_pref_col(size)
        4
        >>> e.keypress(size, 'left')
        >>> e.get_pref_col(size)
        0
        """
        (maxcol,) = size
        pref_col, then_maxcol = self.pref_col_maxcol
        if then_maxcol != maxcol:
            return self.get_cursor_coords((maxcol,))[0]

        return pref_col

    def set_caption(self, caption: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]) -> None:
        """
        Set the caption markup for this widget.

        :param caption: markup for caption preceding edit_text, see
                        :meth:`Text.__init__` for description of text markup.

        >>> e = Edit("")
        >>> e.set_caption("cap1")
        >>> print(e.caption)
        cap1
        >>> e.set_caption(('bold', "cap2"))
        >>> print(e.caption)
        cap2
        >>> e.attrib
        [('bold', 4)]
        >>> e.caption = "cap3"  # not supported because caption stores text but set_caption() takes markup
        Traceback (most recent call last):
        AttributeError: can't set attribute
        """
        self._caption, self._attrib = decompose_tagmarkup(caption)
        self._invalidate()

    @property
    def caption(self) -> str:
        """
        Read-only property returning the caption for this widget.
        """
        return self._caption

    def set_edit_pos(self, pos: int) -> None:
        """
        Set the cursor position with a self.edit_text offset.
        Clips pos to [0, len(edit_text)].

        :param pos: cursor position
        :type pos: int

        >>> e = Edit(u"", u"word")
        >>> e.edit_pos
        4
        >>> e.set_edit_pos(2)
        >>> e.edit_pos
        2
        >>> e.edit_pos = -1  # Urwid 0.9.9 or later
        >>> e.edit_pos
        0
        >>> e.edit_pos = 20
        >>> e.edit_pos
        4
        """
        pos = min(max(pos, 0), len(self._edit_text))
        self.highlight = None
        self.pref_col_maxcol = None, None
        self._edit_pos = pos
        self._invalidate()

    edit_pos = property(
        lambda self: self._edit_pos,
        set_edit_pos,
        doc="""
        Property controlling the edit position for this widget.
        """,
    )

    def set_mask(self, mask: str | None) -> None:
        """
        Set the character for masking text away.

        :param mask: hide text entered with this character, None:disable mask
        :type mask: bytes or unicode
        """

        self._mask = mask
        self._invalidate()

    def set_edit_text(self, text: str) -> None:
        """
        Set the edit text for this widget.

        :param text: text for editing, type (bytes or unicode)
                     must match the text in the caption
        :type text: bytes or unicode

        >>> e = Edit()
        >>> e.set_edit_text(u"yes")
        >>> print(e.edit_text)
        yes
        >>> e
        
        >>> e.edit_text = u"no"  # Urwid 0.9.9 or later
        >>> print(e.edit_text)
        no
        """
        text = self._normalize_to_caption(text)
        self.highlight = None
        self._emit("change", text)
        old_text = self._edit_text
        self._edit_text = text
        self.edit_pos = min(self.edit_pos, len(text))

        self._emit("postchange", old_text)
        self._invalidate()

    def get_edit_text(self) -> str:
        """
        Return the edit text for this widget.

        >>> e = Edit(u"What? ", u"oh, nothing.")
        >>> print(e.get_edit_text())
        oh, nothing.
        >>> print(e.edit_text)
        oh, nothing.
        """
        return self._edit_text

    edit_text = property(
        get_edit_text,
        set_edit_text,
        doc="""
        Property controlling the edit text for this widget.
        """,
    )

    def insert_text(self, text: str) -> None:
        """
        Insert text at the cursor position and update cursor.
        This method is used by the keypress() method when inserting
        one or more characters into edit_text.

        :param text: text for inserting, type (bytes or unicode)
                     must match the text in the caption
        :type text: bytes or unicode

        >>> e = Edit(u"", u"42")
        >>> e.insert_text(u".5")
        >>> e
        
        >>> e.set_edit_pos(2)
        >>> e.insert_text(u"a")
        >>> print(e.edit_text)
        42a.5
        """
        text = self._normalize_to_caption(text)
        result_text, result_pos = self.insert_text_result(text)
        self.set_edit_text(result_text)
        self.set_edit_pos(result_pos)
        self.highlight = None

    def _normalize_to_caption(self, text: str | bytes) -> str | bytes:
        """Return text converted to the same type as self.caption (bytes or unicode)"""
        tu = isinstance(text, str)
        cu = isinstance(self._caption, str)
        if tu == cu:
            return text
        if tu:
            return text.encode("ascii")  # follow python2's implicit conversion
        return text.decode("ascii")

    def insert_text_result(self, text: str) -> tuple[str | bytes, int]:
        """
        Return result of insert_text(text) without actually performing the
        insertion.  Handy for pre-validation.

        :param text: text for inserting, type (bytes or unicode)
                     must match the text in the caption
        :type text: bytes or unicode
        """

        # if there's highlighted text, it'll get replaced by the new text
        text = self._normalize_to_caption(text)
        if self.highlight:
            start, stop = self.highlight  # pylint: disable=unpacking-non-sequence  # already checked
            btext, etext = self.edit_text[:start], self.edit_text[stop:]
            result_text = btext + etext
            result_pos = start
        else:
            result_text = self.edit_text
            result_pos = self.edit_pos

        try:
            result_text = result_text[:result_pos] + text + result_text[result_pos:]
        except (IndexError, TypeError) as exc:
            raise ValueError(repr((self.edit_text, result_text, text))).with_traceback(exc.__traceback__) from exc

        result_pos += len(text)
        return (result_text, result_pos)

    def keypress(
        self,
        size: tuple[int],  # type: ignore[override]
        key: str,
    ) -> str | None:
        """
        Handle editing keystrokes, return others.

        >>> e, size = Edit(), (20,)
        >>> e.keypress(size, 'x')
        >>> e.keypress(size, 'left')
        >>> e.keypress(size, '1')
        >>> print(e.edit_text)
        1x
        >>> e.keypress(size, 'backspace')
        >>> e.keypress(size, 'end')
        >>> e.keypress(size, '2')
        >>> print(e.edit_text)
        x2
        >>> e.keypress(size, 'shift f1')
        'shift f1'
        """
        pos = self.edit_pos
        if self.valid_char(key):
            if isinstance(key, str) and not isinstance(self._caption, str):
                key = key.encode("utf-8")
            self.insert_text(key)
            return None

        if key == "tab" and self.allow_tab:
            key = " " * (8 - (self.edit_pos % 8))
            self.insert_text(key)
            return None

        if key == "enter" and self.multiline:
            key = "\n"
            self.insert_text(key)
            return None

        if self._command_map[key] == Command.LEFT:
            if pos == 0:
                return key
            pos = move_prev_char(self.edit_text, 0, pos)
            self.set_edit_pos(pos)
            return None

        if self._command_map[key] == Command.RIGHT:
            if pos >= len(self.edit_text):
                return key
            pos = move_next_char(self.edit_text, pos, len(self.edit_text))
            self.set_edit_pos(pos)
            return None

        if self._command_map[key] in {Command.UP, Command.DOWN}:
            self.highlight = None

            _x, y = self.get_cursor_coords(size)
            pref_col = self.get_pref_col(size)
            if pref_col is None:
                raise ValueError(pref_col)

            # if pref_col is None:
            #    pref_col = x

            if self._command_map[key] == Command.UP:
                y -= 1
            else:
                y += 1

            if not self.move_cursor_to_coords(size, pref_col, y):
                return key
            return None

        if key == "backspace":
            self.pref_col_maxcol = None, None
            if not self._delete_highlighted():
                if pos == 0:
                    return key
                pos = move_prev_char(self.edit_text, 0, pos)
                self.set_edit_text(self.edit_text[:pos] + self.edit_text[self.edit_pos :])
                self.set_edit_pos(pos)
                return None
            return None

        if key == "delete":
            self.pref_col_maxcol = None, None
            if not self._delete_highlighted():
                if pos >= len(self.edit_text):
                    return key
                pos = move_next_char(self.edit_text, pos, len(self.edit_text))
                self.set_edit_text(self.edit_text[: self.edit_pos] + self.edit_text[pos:])
                return None
            return None

        if self._command_map[key] in {Command.MAX_LEFT, Command.MAX_RIGHT}:
            self.highlight = None
            self.pref_col_maxcol = None, None

            _x, y = self.get_cursor_coords(size)

            if self._command_map[key] == Command.MAX_LEFT:
                self.move_cursor_to_coords(size, Align.LEFT, y)
            else:
                self.move_cursor_to_coords(size, Align.RIGHT, y)
            return None

        # key wasn't handled
        return key

    def move_cursor_to_coords(
        self,
        size: tuple[int],
        x: int | Literal[Align.LEFT, Align.RIGHT],
        y: int,
    ) -> bool:
        """
        Set the cursor position with (x,y) coordinates.
        Returns True if move succeeded, False otherwise.

        >>> size = (10,)
        >>> e = Edit("","edit\\ntext")
        >>> e.move_cursor_to_coords(size, 5, 0)
        True
        >>> e.edit_pos
        4
        >>> e.move_cursor_to_coords(size, 5, 3)
        False
        >>> e.move_cursor_to_coords(size, 0, 1)
        True
        >>> e.edit_pos
        5
        """
        (maxcol,) = size
        trans = self.get_line_translation(maxcol)
        _top_x, top_y = self.position_coords(maxcol, 0)
        if y < top_y or y >= len(trans):
            return False

        pos = text_layout.calc_pos(self.get_text()[0], trans, x, y)
        e_pos = min(max(pos - len(self.caption), 0), len(self.edit_text))
        self.edit_pos = e_pos
        self.pref_col_maxcol = x, maxcol
        self._invalidate()
        return True

    def mouse_event(
        self,
        size: tuple[int],  # type: ignore[override]
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        """
        Move the cursor to the location clicked for button 1.

        >>> size = (20,)
        >>> e = Edit("","words here")
        >>> e.mouse_event(size, 'mouse press', 1, 2, 0, True)
        True
        >>> e.edit_pos
        2
        """
        if button == 1:
            return self.move_cursor_to_coords(size, col, row)
        return False

    def _delete_highlighted(self) -> bool:
        """
        Delete all highlighted text and update cursor position, if any
        text is highlighted.
        """
        if not self.highlight:
            return False
        start, stop = self.highlight  # pylint: disable=unpacking-non-sequence  # already checked
        btext, etext = self.edit_text[:start], self.edit_text[stop:]
        self.set_edit_text(btext + etext)
        self.edit_pos = start
        self.highlight = None
        return True

    def render(
        self,
        size: tuple[int],  # type: ignore[override]
        focus: bool = False,
    ) -> TextCanvas | CompositeCanvas:
        """
        Render edit widget and return canvas.  Include cursor when in
        focus.

        >>> edit = Edit("? ","yes")
        >>> c = edit.render((10,), focus=True)
        >>> c.text
        [b'? yes     ']
        >>> c.cursor
        (5, 0)
        """
        self._shift_view_to_cursor = bool(focus)  # noqa: FURB123,RUF100

        canv: TextCanvas | CompositeCanvas = super().render(size, focus)
        if focus:
            canv = CompositeCanvas(canv)
            canv.cursor = self.get_cursor_coords(size)

        # .. will need to FIXME if I want highlight to work again
        # if self.highlight:
        #    hstart, hstop = self.highlight_coords()
        #    d.coords['highlight'] = [ hstart, hstop ]
        return canv

    def get_line_translation(
        self,
        maxcol: int,
        ta: tuple[str | bytes, list[tuple[Hashable, int]]] | None = None,
    ) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]:
        trans = super().get_line_translation(maxcol, ta)
        if not self._shift_view_to_cursor:
            return trans

        text, _ignore = self.get_text()
        x, y = text_layout.calc_coords(text, trans, self.edit_pos + len(self.caption))
        if x < 0:
            return [
                *trans[:y],
                *[text_layout.shift_line(trans[y], -x)],
                *trans[y + 1 :],
            ]

        if x >= maxcol:
            return [
                *trans[:y],
                *[text_layout.shift_line(trans[y], -(x - maxcol + 1))],
                *trans[y + 1 :],
            ]

        return trans

    def get_cursor_coords(self, size: tuple[int]) -> tuple[int, int]:
        """
        Return the (*x*, *y*) coordinates of cursor within widget.

        >>> Edit("? ","yes").get_cursor_coords((10,))
        (5, 0)
        """
        (maxcol,) = size

        self._shift_view_to_cursor = True
        return self.position_coords(maxcol, self.edit_pos)

    def position_coords(self, maxcol: int, pos: int) -> tuple[int, int]:
        """
        Return (*x*, *y*) coordinates for an offset into self.edit_text.
        """

        p = pos + len(self.caption)
        trans = self.get_line_translation(maxcol)
        x, y = text_layout.calc_coords(self.get_text()[0], trans, p)
        return x, y


class IntEdit(Edit):
    """Edit widget for integer values"""

    def valid_char(self, ch: str) -> bool:
        """
        Return true for decimal digits.
        """
        return len(ch) == 1 and ch in string.digits

    def __init__(self, caption="", default: int | str | None = None) -> None:
        """
        caption -- caption markup
        default -- default edit value

        >>> IntEdit(u"", 42)
        
        """
        if default is not None:
            val = str(default)
        else:
            val = ""
        super().__init__(caption, val)

    def keypress(
        self,
        size: tuple[int],  # type: ignore[override]
        key: str,
    ) -> str | None:
        """
        Handle editing keystrokes.  Remove leading zeros.

        >>> e, size = IntEdit(u"", 5002), (10,)
        >>> e.keypress(size, 'home')
        >>> e.keypress(size, 'delete')
        >>> print(e.edit_text)
        002
        >>> e.keypress(size, 'end')
        >>> print(e.edit_text)
        2
        """
        unhandled = super().keypress(size, key)

        if not unhandled:
            # trim leading zeros
            while self.edit_pos > 0 and self.edit_text[:1] == "0":
                self.set_edit_pos(self.edit_pos - 1)
                self.set_edit_text(self.edit_text[1:])

        return unhandled

    def value(self) -> int:
        """
        Return the numeric value of self.edit_text.

        >>> e, size = IntEdit(), (10,)
        >>> e.keypress(size, '5')
        >>> e.keypress(size, '1')
        >>> e.value() == 51
        True
        """
        if self.edit_text:
            return int(self.edit_text)

        return 0
urwid-2.6.16/urwid/widget/filler.py000066400000000000000000000345111470350774000172170ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings

from urwid.canvas import CompositeCanvas
from urwid.split_repr import remove_defaults
from urwid.util import int_scale

from .constants import (
    RELATIVE_100,
    Sizing,
    VAlign,
    WHSettings,
    normalize_height,
    normalize_valign,
    simplify_height,
    simplify_valign,
)
from .widget_decoration import WidgetDecoration, WidgetError

if typing.TYPE_CHECKING:
    from typing_extensions import Literal

WrappedWidget = typing.TypeVar("WrappedWidget")


class FillerError(WidgetError):
    pass


class Filler(WidgetDecoration[WrappedWidget]):
    def __init__(
        self,
        body: WrappedWidget,
        valign: (
            Literal["top", "middle", "bottom"] | VAlign | tuple[Literal["relative", WHSettings.RELATIVE], int]
        ) = VAlign.MIDDLE,
        height: (
            int | Literal["pack", WHSettings.PACK] | tuple[Literal["relative", WHSettings.RELATIVE], int] | None
        ) = WHSettings.PACK,
        min_height: int | None = None,
        top: int = 0,
        bottom: int = 0,
    ) -> None:
        """
        :param body: a flow widget or box widget to be filled around (stored as self.original_widget)
        :type body: Widget

        :param valign: one of:
            ``'top'``, ``'middle'``, ``'bottom'``,
            (``'relative'``, *percentage* 0=top 100=bottom)

        :param height: one of:

            ``'pack'``
              if body is a flow widget

            *given height*
              integer number of rows for self.original_widget

            (``'relative'``, *percentage of total height*)
              make height depend on container's height

        :param min_height: one of:

            ``None``
              if no minimum or if body is a flow widget

            *minimum height*
              integer number of rows for the widget when height not fixed

        :param top: a fixed number of rows to fill at the top
        :type top: int
        :param bottom: a fixed number of rows to fill at the bottom
        :type bottom: int

        If body is a flow widget, then height must be ``'pack'`` and *min_height* will be ignored.
        Sizing of the filler will be BOX/FLOW in this case.

        If height is integer, *min_height* will be ignored and sizing of filler will be BOX/FLOW.

        Filler widgets will try to satisfy height argument first by reducing the valign amount when necessary.
        If height still cannot be satisfied it will also be reduced.
        """
        super().__init__(body)

        # convert old parameters to the new top/bottom values
        if isinstance(height, tuple):
            if height[0] == "fixed top":
                if not isinstance(valign, tuple) or valign[0] != "fixed bottom":
                    raise FillerError("fixed top height may only be used with fixed bottom valign")
                top = height[1]
                height = RELATIVE_100
            elif height[0] == "fixed bottom":
                if not isinstance(valign, tuple) or valign[0] != "fixed top":
                    raise FillerError("fixed bottom height may only be used with fixed top valign")
                bottom = height[1]
                height = RELATIVE_100

        if isinstance(valign, tuple):
            if valign[0] == "fixed top":
                top = valign[1]
                normalized_valign = VAlign.TOP
            elif valign[0] == "fixed bottom":
                bottom = valign[1]
                normalized_valign = VAlign.BOTTOM
            else:
                normalized_valign = valign

        elif not isinstance(valign, (VAlign, str)):
            raise FillerError(f"invalid valign: {valign!r}")

        else:
            normalized_valign = VAlign(valign)

        # convert old flow mode parameter height=None to height='flow'
        if height is None or height == Sizing.FLOW:
            height = WHSettings.PACK

        self.top = top
        self.bottom = bottom
        self.valign_type, self.valign_amount = normalize_valign(normalized_valign, FillerError)
        self.height_type, self.height_amount = normalize_height(height, FillerError)

        if self.height_type not in {WHSettings.GIVEN, WHSettings.PACK}:
            self.min_height = min_height
        else:
            self.min_height = None

    def sizing(self) -> frozenset[Sizing]:
        """Widget sizing.

        Sizing BOX is always supported.
        Sizing FLOW is supported if: FLOW widget (a height type is PACK) or BOX widget with height GIVEN
        """
        sizing: set[Sizing] = {Sizing.BOX}
        if self.height_type in {WHSettings.PACK, WHSettings.GIVEN}:
            sizing.add(Sizing.FLOW)
        return frozenset(sizing)

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        """Flow pack support if FLOW sizing supported."""
        if self.height_type == WHSettings.PACK:
            return self.original_widget.rows(size, focus) + self.top + self.bottom
        if self.height_type == WHSettings.GIVEN:
            return self.height_amount + self.top + self.bottom
        raise FillerError("Method 'rows' not supported for BOX widgets")  # pragma: no cover

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = {
            **super()._repr_attrs(),
            "valign": simplify_valign(self.valign_type, self.valign_amount),
            "height": simplify_height(self.height_type, self.height_amount),
            "top": self.top,
            "bottom": self.bottom,
            "min_height": self.min_height,
        }
        return remove_defaults(attrs, Filler.__init__)

    @property
    def body(self) -> WrappedWidget:
        """backwards compatibility, widget used to be stored as body"""
        warnings.warn(
            "backwards compatibility, widget used to be stored as body",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.original_widget

    @body.setter
    def body(self, new_body: WrappedWidget) -> None:
        warnings.warn(
            "backwards compatibility, widget used to be stored as body",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        self.original_widget = new_body

    def get_body(self) -> WrappedWidget:
        """backwards compatibility, widget used to be stored as body"""
        warnings.warn(
            "backwards compatibility, widget used to be stored as body",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.original_widget

    def set_body(self, new_body: WrappedWidget) -> None:
        warnings.warn(
            "backwards compatibility, widget used to be stored as body",
            DeprecationWarning,
            stacklevel=2,
        )
        self.original_widget = new_body

    def selectable(self) -> bool:
        """Return selectable from body."""
        return self._original_widget.selectable()

    def filler_values(self, size: tuple[int, int] | tuple[int], focus: bool) -> tuple[int, int]:
        """
        Return the number of rows to pad on the top and bottom.

        Override this method to define custom padding behaviour.
        """
        maxcol, maxrow = self.pack(size, focus)

        if self.height_type == WHSettings.PACK:
            height = self._original_widget.rows((maxcol,), focus=focus)
            return calculate_top_bottom_filler(
                maxrow,
                self.valign_type,
                self.valign_amount,
                WHSettings.GIVEN,
                height,
                None,
                self.top,
                self.bottom,
            )

        return calculate_top_bottom_filler(
            maxrow,
            self.valign_type,
            self.valign_amount,
            self.height_type,
            self.height_amount,
            self.min_height,
            self.top,
            self.bottom,
        )

    def render(
        self,
        size: tuple[int, int] | tuple[int],  # type: ignore[override]
        focus: bool = False,
    ) -> CompositeCanvas:
        """Render self.original_widget with space above and/or below."""
        maxcol, maxrow = self.pack(size, focus)
        top, bottom = self.filler_values(size, focus)

        if self.height_type == WHSettings.PACK:
            canv = self._original_widget.render((maxcol,), focus)
        else:
            canv = self._original_widget.render((maxcol, maxrow - top - bottom), focus)
        canv = CompositeCanvas(canv)

        if maxrow and canv.rows() > maxrow and canv.cursor is not None:
            _cx, cy = canv.cursor
            if cy >= maxrow:
                canv.trim(cy - maxrow + 1, maxrow - top - bottom)
        if canv.rows() > maxrow:
            canv.trim(0, maxrow)
            return canv
        canv.pad_trim_top_bottom(top, bottom)
        return canv

    def keypress(
        self,
        size: tuple[int, int] | tuple[()],  # type: ignore[override]
        key: str,
    ) -> str | None:
        """Pass keypress to self.original_widget."""
        maxcol, maxrow = self.pack(size, True)
        if self.height_type == WHSettings.PACK:
            return self._original_widget.keypress((maxcol,), key)

        top, bottom = self.filler_values((maxcol, maxrow), True)
        return self._original_widget.keypress((maxcol, maxrow - top - bottom), key)

    def get_cursor_coords(self, size: tuple[int, int] | tuple[int]) -> tuple[int, int] | None:
        """Return cursor coords from self.original_widget if any."""
        maxcol, maxrow = self.pack(size, True)
        if not hasattr(self._original_widget, "get_cursor_coords"):
            return None

        top, bottom = self.filler_values(size, True)
        if self.height_type == WHSettings.PACK:
            coords = self._original_widget.get_cursor_coords((maxcol,))
        else:
            coords = self._original_widget.get_cursor_coords((maxcol, maxrow - top - bottom))
        if not coords:
            return None
        x, y = coords
        if y >= maxrow:
            y = maxrow - 1
        return x, y + top

    def get_pref_col(self, size: tuple[int, int] | tuple[int]) -> int | None:
        """Return pref_col from self.original_widget if any."""
        maxcol, maxrow = self.pack(size, True)
        if not hasattr(self._original_widget, "get_pref_col"):
            return None

        if self.height_type == WHSettings.PACK:
            x = self._original_widget.get_pref_col((maxcol,))
        else:
            top, bottom = self.filler_values(size, True)
            x = self._original_widget.get_pref_col((maxcol, maxrow - top - bottom))

        return x

    def move_cursor_to_coords(self, size: tuple[int, int] | tuple[int], col: int, row: int) -> bool:
        """Pass to self.original_widget."""
        maxcol, maxrow = self.pack(size, True)
        if not hasattr(self._original_widget, "move_cursor_to_coords"):
            return True

        top, bottom = self.filler_values(size, True)
        if row < top or row >= maxcol - bottom:
            return False

        if self.height_type == WHSettings.PACK:
            return self._original_widget.move_cursor_to_coords((maxcol,), col, row - top)
        return self._original_widget.move_cursor_to_coords((maxcol, maxrow - top - bottom), col, row - top)

    def mouse_event(
        self,
        size: tuple[int, int] | tuple[int],  # type: ignore[override]
        event,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        """Pass to self.original_widget."""
        maxcol, maxrow = self.pack(size, focus)
        if not hasattr(self._original_widget, "mouse_event"):
            return False

        top, bottom = self.filler_values(size, True)
        if row < top or row >= maxrow - bottom:
            return False

        if self.height_type == WHSettings.PACK:
            return self._original_widget.mouse_event((maxcol,), event, button, col, row - top, focus)
        return self._original_widget.mouse_event((maxcol, maxrow - top - bottom), event, button, col, row - top, focus)


def calculate_top_bottom_filler(
    maxrow: int,
    valign_type: Literal["top", "middle", "bottom", "relative", WHSettings.RELATIVE] | VAlign,
    valign_amount: int,
    height_type: Literal["given", "relative", "clip", WHSettings.GIVEN, WHSettings.RELATIVE, WHSettings.CLIP],
    height_amount: int,
    min_height: int | None,
    top: int,
    bottom: int,
) -> tuple[int, int]:
    """
    Return the amount of filler (or clipping) on the top and
    bottom part of maxrow rows to satisfy the following:

    valign_type -- 'top', 'middle', 'bottom', 'relative'
    valign_amount -- a percentage when align_type=='relative'
    height_type -- 'given', 'relative', 'clip'
    height_amount -- a percentage when width_type=='relative'
        otherwise equal to the height of the widget
    min_height -- a desired minimum width for the widget or None
    top -- a fixed number of rows to fill on the top
    bottom -- a fixed number of rows to fill on the bottom

    >>> ctbf = calculate_top_bottom_filler
    >>> ctbf(15, 'top', 0, 'given', 10, None, 2, 0)
    (2, 3)
    >>> ctbf(15, 'relative', 0, 'given', 10, None, 2, 0)
    (2, 3)
    >>> ctbf(15, 'relative', 100, 'given', 10, None, 2, 0)
    (5, 0)
    >>> ctbf(15, 'middle', 0, 'given', 4, None, 2, 0)
    (6, 5)
    >>> ctbf(15, 'middle', 0, 'given', 18, None, 2, 0)
    (0, 0)
    >>> ctbf(20, 'top', 0, 'relative', 60, None, 0, 0)
    (0, 8)
    >>> ctbf(20, 'relative', 30, 'relative', 60, None, 0, 0)
    (2, 6)
    >>> ctbf(20, 'relative', 30, 'relative', 60, 14, 0, 0)
    (2, 4)
    """
    if height_type == WHSettings.RELATIVE:
        maxheight = max(maxrow - top - bottom, 0)
        height = int_scale(height_amount, 101, maxheight + 1)
        if min_height is not None:
            height = max(height, min_height)
    else:
        height = height_amount

    valign = {VAlign.TOP: 0, VAlign.MIDDLE: 50, VAlign.BOTTOM: 100}.get(valign_type, valign_amount)

    # add the remainder of top/bottom to the filler
    filler = maxrow - height - top - bottom
    bottom += int_scale(100 - valign, 101, filler + 1)
    top = maxrow - height - bottom

    # reduce filler if we are clipping an edge
    if bottom < 0 < top:
        shift = min(top, -bottom)
        top -= shift
        bottom += shift
    elif top < 0 < bottom:
        shift = min(bottom, -top)
        bottom -= shift
        top += shift

    # no negative values for filler at the moment
    top = max(top, 0)
    bottom = max(bottom, 0)

    return top, bottom
urwid-2.6.16/urwid/widget/frame.py000066400000000000000000000525121470350774000170350ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings

from urwid.canvas import CanvasCombine, CompositeCanvas
from urwid.split_repr import remove_defaults
from urwid.util import is_mouse_press

from .constants import Sizing, VAlign
from .container import WidgetContainerMixin
from .filler import Filler
from .widget import Widget, WidgetError

if typing.TYPE_CHECKING:
    from collections.abc import Iterator, MutableMapping

    from typing_extensions import Literal


BodyWidget = typing.TypeVar("BodyWidget")
HeaderWidget = typing.TypeVar("HeaderWidget")
FooterWidget = typing.TypeVar("FooterWidget")


class FrameError(WidgetError):
    pass


def _check_widget_subclass(widget: Widget | None) -> None:
    if widget is None:
        return

    if not isinstance(widget, Widget):
        obj_class_path = f"{widget.__class__.__module__}.{widget.__class__.__name__}"
        warnings.warn(
            f"{obj_class_path} is not subclass of Widget",
            DeprecationWarning,
            stacklevel=3,
        )


class Frame(Widget, WidgetContainerMixin, typing.Generic[BodyWidget, HeaderWidget, FooterWidget]):
    """
    Frame widget is a box widget with optional header and footer
    flow widgets placed above and below the box widget.

    .. note:: The main difference between a Frame and a :class:`Pile` widget
        defined as: `Pile([('pack', header), body, ('pack', footer)])` is that
        the Frame will not automatically change focus up and down in response to keystrokes.
    """

    _selectable = True
    _sizing = frozenset([Sizing.BOX])

    def __init__(
        self,
        body: BodyWidget,
        header: HeaderWidget = None,
        footer: FooterWidget = None,
        focus_part: Literal["header", "footer", "body"] | Widget = "body",
    ):
        """
        :param body: a box widget for the body of the frame
        :type body: Widget
        :param header: a flow widget for above the body (or None)
        :type header: Widget
        :param footer: a flow widget for below the body (or None)
        :type footer: Widget
        :param focus_part:  'header', 'footer' or 'body'
        :type focus_part: str | Widget
        """
        super().__init__()

        self._header = header
        self._body = body
        self._footer = footer
        if focus_part in {"header", "footer", "body"}:
            self.focus_part = focus_part
        elif focus_part == header:
            self.focus_part = "header"
        elif focus_part == footer:
            self.focus_part = "footer"
        elif focus_part == body:
            self.focus_part = "body"
        else:
            raise ValueError(f"Invalid focus part {focus_part!r}")

        _check_widget_subclass(header)
        _check_widget_subclass(body)
        _check_widget_subclass(footer)

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = {
            **super()._repr_attrs(),
            "body": self._body,
            "header": self._header,
            "footer": self._footer,
            "focus_part": self.focus_part,
        }
        return remove_defaults(attrs, Frame.__init__)

    def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
        yield "body", self._body
        yield "header", self._header
        yield "footer", self._footer
        yield "focus_part", self.focus_part

    @property
    def header(self) -> HeaderWidget:
        return self._header

    @header.setter
    def header(self, header: HeaderWidget) -> None:
        _check_widget_subclass(header)
        self._header = header
        if header is None and self.focus_part == "header":
            self.focus_part = "body"
        self._invalidate()

    def get_header(self) -> HeaderWidget:
        warnings.warn(
            f"method `{self.__class__.__name__}.get_header` is deprecated, "
            f"standard property `{self.__class__.__name__}.header` should be used instead",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.header

    def set_header(self, header: HeaderWidget) -> None:
        warnings.warn(
            f"method `{self.__class__.__name__}.set_header` is deprecated, "
            f"standard property `{self.__class__.__name__}.header` should be used instead",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        self.header = header

    @property
    def body(self) -> BodyWidget:
        return self._body

    @body.setter
    def body(self, body: BodyWidget) -> None:
        _check_widget_subclass(body)
        self._body = body
        self._invalidate()

    def get_body(self) -> BodyWidget:
        warnings.warn(
            f"method `{self.__class__.__name__}.get_body` is deprecated, "
            f"standard property {self.__class__.__name__}.body should be used instead",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.body

    def set_body(self, body: BodyWidget) -> None:
        warnings.warn(
            f"method `{self.__class__.__name__}.set_body` is deprecated, "
            f"standard property `{self.__class__.__name__}.body` should be used instead",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        self.body = body

    @property
    def footer(self) -> FooterWidget:
        return self._footer

    @footer.setter
    def footer(self, footer: FooterWidget) -> None:
        _check_widget_subclass(footer)
        self._footer = footer
        if footer is None and self.focus_part == "footer":
            self.focus_part = "body"
        self._invalidate()

    def get_footer(self) -> FooterWidget:
        warnings.warn(
            f"method `{self.__class__.__name__}.get_footer` is deprecated, "
            f"standard property `{self.__class__.__name__}.footer` should be used instead",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.footer

    def set_footer(self, footer: FooterWidget) -> None:
        warnings.warn(
            f"method `{self.__class__.__name__}.set_footer` is deprecated, "
            f"standard property `{self.__class__.__name__}.footer` should be used instead",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        self.footer = footer

    @property
    def focus_position(self) -> Literal["header", "footer", "body"]:
        """
        writeable property containing an indicator which part of the frame
        that is in focus: `'body', 'header'` or `'footer'`.

        :returns: one of 'header', 'footer' or 'body'.
        :rtype: str
        """
        return self.focus_part

    @focus_position.setter
    def focus_position(self, part: Literal["header", "footer", "body"]) -> None:
        """
        Determine which part of the frame is in focus.

        :param part: 'header', 'footer' or 'body'
        :type part: str
        """
        if part not in {"header", "footer", "body"}:
            raise IndexError(f"Invalid position for Frame: {part}")
        if (part == "header" and self._header is None) or (part == "footer" and self._footer is None):
            raise IndexError(f"This Frame has no {part}")
        self.focus_part = part
        self._invalidate()

    def get_focus(self) -> Literal["header", "footer", "body"]:
        """
        writeable property containing an indicator which part of the frame
        that is in focus: `'body', 'header'` or `'footer'`.

        .. note:: included for backwards compatibility. You should rather use
            the container property :attr:`.focus_position` to get this value.

        :returns: one of 'header', 'footer' or 'body'.
        :rtype: str
        """
        warnings.warn(
            "included for backwards compatibility."
            "You should rather use the container property `.focus_position` to get this value.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.focus_position

    def set_focus(self, part: Literal["header", "footer", "body"]) -> None:
        warnings.warn(
            "included for backwards compatibility."
            "You should rather use the container property `.focus_position` to set this value.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        self.focus_position = part

    @property
    def focus(self) -> BodyWidget | HeaderWidget | FooterWidget:
        """
        child :class:`Widget` in focus: the body, header or footer widget.
        This is a read-only property."""
        return {"header": self._header, "footer": self._footer, "body": self._body}[self.focus_part]

    def _get_focus(self) -> BodyWidget | HeaderWidget | FooterWidget:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus` is deprecated, "
            f"please use `{self.__class__.__name__}.focus` property",
            DeprecationWarning,
            stacklevel=3,
        )
        return {"header": self._header, "footer": self._footer, "body": self._body}[self.focus_part]

    @property
    def contents(
        self,
    ) -> MutableMapping[
        Literal["header", "footer", "body"],
        tuple[BodyWidget | HeaderWidget | FooterWidget, None],
    ]:
        """
        a dict-like object similar to::

            {
                'body': (body_widget, None),
                'header': (header_widget, None),  # if frame has a header
                'footer': (footer_widget, None) # if frame has a footer
            }

        This object may be used to read or update the contents of the Frame.

        The values are similar to the list-like .contents objects used
        in other containers with (:class:`Widget`, options) tuples, but are
        constrained to keys for each of the three usual parts of a Frame.
        When other keys are used a :exc:`KeyError` will be raised.

        Currently, all options are `None`, but using the :meth:`options` method
        to create the options value is recommended for forwards
        compatibility.
        """

        # noinspection PyMethodParameters
        class FrameContents(
            typing.MutableMapping[
                str,
                typing.Tuple[typing.Union[BodyWidget, HeaderWidget, FooterWidget], None],
            ]
        ):
            # pylint: disable=no-self-argument

            __slots__ = ()

            def __len__(inner_self) -> int:
                return len(inner_self.keys())

            __getitem__ = self._contents__getitem__
            __setitem__ = self._contents__setitem__
            __delitem__ = self._contents__delitem__

            def __iter__(inner_self) -> Iterator[str]:
                yield from inner_self.keys()

            def __repr__(inner_self) -> str:
                return f"<{inner_self.__class__.__name__}({dict(inner_self)}) for {self}>"

            def __rich_repr__(inner_self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
                yield from inner_self.items()

        return FrameContents()

    def _contents_keys(self) -> list[Literal["header", "footer", "body"]]:
        keys = ["body"]
        if self._header:
            keys.append("header")
        if self._footer:
            keys.append("footer")
        return keys

    @typing.overload
    def _contents__getitem__(self, key: Literal["body"]) -> tuple[BodyWidget, None]: ...

    @typing.overload
    def _contents__getitem__(self, key: Literal["header"]) -> tuple[HeaderWidget, None]: ...

    @typing.overload
    def _contents__getitem__(self, key: Literal["footer"]) -> tuple[FooterWidget, None]: ...

    def _contents__getitem__(
        self, key: Literal["body", "header", "footer"]
    ) -> tuple[BodyWidget | HeaderWidget | FooterWidget, None]:
        if key == "body":
            return (self._body, None)
        if key == "header" and self._header:
            return (self._header, None)
        if key == "footer" and self._footer:
            return (self._footer, None)
        raise KeyError(f"Frame.contents has no key: {key!r}")

    @typing.overload
    def _contents__setitem__(self, key: Literal["body"], value: tuple[BodyWidget, None]) -> None: ...

    @typing.overload
    def _contents__setitem__(self, key: Literal["header"], value: tuple[HeaderWidget, None]) -> None: ...

    @typing.overload
    def _contents__setitem__(self, key: Literal["footer"], value: tuple[FooterWidget, None]) -> None: ...

    def _contents__setitem__(
        self,
        key: Literal["body", "header", "footer"],
        value: tuple[BodyWidget | HeaderWidget | FooterWidget, None],
    ) -> None:
        if key not in {"body", "header", "footer"}:
            raise KeyError(f"Frame.contents has no key: {key!r}")
        try:
            value_w, value_options = value
            if value_options is not None:
                raise FrameError(f"added content invalid: {value!r}")
        except (ValueError, TypeError) as exc:
            raise FrameError(f"added content invalid: {value!r}").with_traceback(exc.__traceback__) from exc
        if key == "body":
            self.body = value_w
        elif key == "footer":
            self.footer = value_w
        else:
            self.header = value_w

    def _contents__delitem__(self, key: Literal["header", "footer"]) -> None:
        if key not in {"header", "footer"}:
            raise KeyError(f"Frame.contents can't remove key: {key!r}")
        if (key == "header" and self._header is None) or (key == "footer" and self._footer is None):
            raise KeyError(f"Frame.contents has no key: {key!r}")
        if key == "header":
            self.header = None
        else:
            self.footer = None

    def _contents(self):
        warnings.warn(
            f"method `{self.__class__.__name__}._contents` is deprecated, "
            f"please use property `{self.__class__.__name__}.contents`",
            DeprecationWarning,
            stacklevel=3,
        )
        return self.contents

    def options(self) -> None:
        """
        There are currently no options for Frame contents.

        Return None as a placeholder for future options.
        """

    def frame_top_bottom(self, size: tuple[int, int], focus: bool) -> tuple[tuple[int, int], tuple[int, int]]:
        """
        Calculate the number of rows for the header and footer.

        :param size: See :meth:`Widget.render` for details
        :type size: widget size
        :param focus: ``True`` if this widget is in focus
        :type focus: bool
        :returns: `(head rows, foot rows),(orig head, orig foot)`
                  orig head/foot are from rows() calls.
        :rtype: (int, int), (int, int)
        """
        (maxcol, maxrow) = size
        frows = hrows = 0

        if self.header:
            hrows = self.header.rows((maxcol,), self.focus_part == "header" and focus)

        if self.footer:
            frows = self.footer.rows((maxcol,), self.focus_part == "footer" and focus)

        remaining = maxrow

        if self.focus_part == "footer":
            if frows >= remaining:
                return (0, remaining), (hrows, frows)

            remaining -= frows
            if hrows >= remaining:
                return (remaining, frows), (hrows, frows)

        elif self.focus_part == "header":
            if hrows >= maxrow:
                return (remaining, 0), (hrows, frows)

            remaining -= hrows
            if frows >= remaining:
                return (hrows, remaining), (hrows, frows)

        elif hrows + frows >= remaining:
            # self.focus_part == 'body'
            rless1 = max(0, remaining - 1)
            if frows >= remaining - 1:
                return (0, rless1), (hrows, frows)

            remaining -= frows
            rless1 = max(0, remaining - 1)
            return (rless1, frows), (hrows, frows)

        return (hrows, frows), (hrows, frows)

    def render(
        self,
        size: tuple[int, int],  # type: ignore[override]
        focus: bool = False,
    ) -> CompositeCanvas:
        (maxcol, maxrow) = size
        (htrim, ftrim), (hrows, frows) = self.frame_top_bottom((maxcol, maxrow), focus)

        combinelist = []
        depends_on = []

        head = None
        if htrim and htrim < hrows:
            head = Filler(self.header, VAlign.TOP).render((maxcol, htrim), focus and self.focus_part == "header")
        elif htrim:
            head = self.header.render((maxcol,), focus and self.focus_part == "header")
            if head.rows() != hrows:
                raise RuntimeError("rows, render mismatch")
        if head:
            combinelist.append((head, "header", self.focus_part == "header"))
            depends_on.append(self.header)

        if ftrim + htrim < maxrow:
            body = self.body.render((maxcol, maxrow - ftrim - htrim), focus and self.focus_part == "body")
            combinelist.append((body, "body", self.focus_part == "body"))
            depends_on.append(self.body)

        foot = None
        if ftrim and ftrim < frows:
            foot = Filler(self.footer, VAlign.BOTTOM).render((maxcol, ftrim), focus and self.focus_part == "footer")
        elif ftrim:
            foot = self.footer.render((maxcol,), focus and self.focus_part == "footer")
            if foot.rows() != frows:
                raise RuntimeError("rows, render mismatch")
        if foot:
            combinelist.append((foot, "footer", self.focus_part == "footer"))
            depends_on.append(self.footer)

        return CanvasCombine(combinelist)

    def keypress(
        self,
        size: tuple[int, int],  # type: ignore[override]
        key: str,
    ) -> str | None:
        """Pass keypress to widget in focus."""
        (maxcol, maxrow) = size

        if self.focus_part == "header" and self.header is not None:
            if not self.header.selectable():
                return key
            return self.header.keypress((maxcol,), key)
        if self.focus_part == "footer" and self.footer is not None:
            if not self.footer.selectable():
                return key
            return self.footer.keypress((maxcol,), key)
        if self.focus_part != "body":
            return key
        remaining = maxrow
        if self.header is not None:
            remaining -= self.header.rows((maxcol,))
        if self.footer is not None:
            remaining -= self.footer.rows((maxcol,))
        if remaining <= 0:
            return key

        if not self.body.selectable():
            return key
        return self.body.keypress((maxcol, remaining), key)

    def mouse_event(
        self,
        size: tuple[int, int],  # type: ignore[override]
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        """
        Pass mouse event to appropriate part of frame.
        Focus may be changed on button 1 press.
        """
        (maxcol, maxrow) = size
        (htrim, ftrim), (_hrows, _frows) = self.frame_top_bottom((maxcol, maxrow), focus)

        if row < htrim:  # within header
            focus = focus and self.focus_part == "header"
            if is_mouse_press(event) and button == 1 and self.header.selectable():
                self.focus_position = "header"
            if not hasattr(self.header, "mouse_event"):
                return False
            return self.header.mouse_event((maxcol,), event, button, col, row, focus)

        if row >= maxrow - ftrim:  # within footer
            focus = focus and self.focus_part == "footer"
            if is_mouse_press(event) and button == 1 and self.footer.selectable():
                self.focus_position = "footer"
            if not hasattr(self.footer, "mouse_event"):
                return False
            return self.footer.mouse_event((maxcol,), event, button, col, row - maxrow + ftrim, focus)

        # within body
        focus = focus and self.focus_part == "body"
        if is_mouse_press(event) and button == 1 and self.body.selectable():
            self.focus_position = "body"

        if not hasattr(self.body, "mouse_event"):
            return False
        return self.body.mouse_event((maxcol, maxrow - htrim - ftrim), event, button, col, row - htrim, focus)

    def get_cursor_coords(self, size: tuple[int, int]) -> tuple[int, int] | None:
        """Return the cursor coordinates of the focus widget."""
        if not self.focus.selectable():
            return None
        if not hasattr(self.focus, "get_cursor_coords"):
            return None

        fp = self.focus_position
        (maxcol, maxrow) = size
        (hrows, frows), _ = self.frame_top_bottom(size, True)

        if fp == "header":
            row_adjust = 0
            coords = self.header.get_cursor_coords((maxcol,))
        elif fp == "body":
            row_adjust = hrows
            coords = self.body.get_cursor_coords((maxcol, maxrow - hrows - frows))
        else:
            row_adjust = maxrow - frows
            coords = self.footer.get_cursor_coords((maxcol,))

        if coords is None:
            return None

        x, y = coords
        return x, y + row_adjust

    def __iter__(self) -> Iterator[Literal["header", "body", "footer"]]:
        """
        Return an iterator over the positions in this Frame top to bottom.
        """
        if self._header:
            yield "header"
        yield "body"
        if self._footer:
            yield "footer"

    def __reversed__(self) -> Iterator[Literal["footer", "body", "header"]]:
        """
        Return an iterator over the positions in this Frame bottom to top.
        """
        if self._footer:
            yield "footer"
        yield "body"
        if self._header:
            yield "header"
urwid-2.6.16/urwid/widget/grid_flow.py000066400000000000000000000523751470350774000177260ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings

from urwid.split_repr import remove_defaults

from .columns import Columns
from .constants import Align, Sizing, WHSettings
from .container import WidgetContainerListContentsMixin, WidgetContainerMixin
from .divider import Divider
from .monitored_list import MonitoredFocusList, MonitoredList
from .padding import Padding
from .pile import Pile
from .widget import Widget, WidgetError, WidgetWarning, WidgetWrap

if typing.TYPE_CHECKING:
    from collections.abc import Iterable, Iterator, Sequence

    from typing_extensions import Literal


class GridFlowError(WidgetError):
    """GridFlow specific error."""


class GridFlowWarning(WidgetWarning):
    """GridFlow specific warning."""


class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListContentsMixin):
    """
    The GridFlow widget is a flow widget that renders all the widgets it contains the same width,
    and it arranges them from left to right and top to bottom.
    """

    def sizing(self) -> frozenset[Sizing]:
        """Widget sizing.

        ..note:: Empty widget sizing is limited to the FLOW due to no data for width.
        """
        if self:
            return frozenset((Sizing.FLOW, Sizing.FIXED))
        return frozenset((Sizing.FLOW,))

    def __init__(
        self,
        cells: Iterable[Widget],
        cell_width: int,
        h_sep: int,
        v_sep: int,
        align: Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int],
        focus: int | Widget | None = None,
    ) -> None:
        """
        :param cells: iterable of flow widgets to display
        :param cell_width: column width for each cell
        :param h_sep: blank columns between each cell horizontally
        :param v_sep: blank rows between cells vertically
            (if more than one row is required to display all the cells)
        :param align: horizontal alignment of cells, one of:
            'left', 'center', 'right', ('relative', percentage 0=left 100=right)
        :param focus: widget index or widget instance to focus on
        """
        prepared_contents: list[tuple[Widget, tuple[Literal[WHSettings.GIVEN], int]]] = []
        focus_position: int = -1

        for idx, widget in enumerate(cells):
            prepared_contents.append((widget, (WHSettings.GIVEN, cell_width)))
            if focus_position < 0 and (focus in {widget, idx} or (focus is None and widget.selectable())):
                focus_position = idx

        focus_position = max(focus_position, 0)

        self._contents: MonitoredFocusList[tuple[Widget, tuple[Literal[WHSettings.GIVEN], int]]] = MonitoredFocusList(
            prepared_contents, focus=focus_position
        )
        self._contents.set_modified_callback(self._invalidate)
        self._contents.set_focus_changed_callback(lambda f: self._invalidate())
        self._contents.set_validate_contents_modified(self._contents_modified)
        self._cell_width = cell_width
        self.h_sep = h_sep
        self.v_sep = v_sep
        self.align = align
        self._cache_maxcol = self._get_maxcol(())
        super().__init__(self.generate_display_widget((self._cache_maxcol,)))

    def _repr_words(self) -> list[str]:
        if len(self.contents) > 1:
            contents_string = f"({len(self.contents)} items)"
        elif self.contents:
            contents_string = "(1 item)"
        else:
            contents_string = "()"
        return [*super()._repr_words(), contents_string]

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = {
            **super()._repr_attrs(),
            "cell_width": self.cell_width,
            "h_sep": self.h_sep,
            "v_sep": self.v_sep,
            "align": self.align,
            "focus": self.focus_position if len(self._contents) > 1 else None,
        }
        return remove_defaults(attrs, GridFlow.__init__)

    def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
        yield "cells", [widget for widget, _ in self.contents]
        yield "cell_width", self.cell_width
        yield "h_sep", self.h_sep
        yield "v_sep", self.v_sep
        yield "align", self.align
        yield "focus", self.focus_position

    def __len__(self) -> int:
        return len(self._contents)

    def _invalidate(self) -> None:
        self._cache_maxcol = None
        super()._invalidate()

    def _contents_modified(
        self,
        _slc: tuple[int, int, int],
        new_items: Iterable[tuple[Widget, tuple[Literal["given", WHSettings.GIVEN], int]]],
    ) -> None:
        for item in new_items:
            try:
                _w, (t, _n) = item
                if t != WHSettings.GIVEN:
                    raise GridFlowError(f"added content invalid {item!r}")
            except (TypeError, ValueError) as exc:  # noqa: PERF203
                raise GridFlowError(f"added content invalid {item!r}").with_traceback(exc.__traceback__) from exc

    @property
    def cells(self):
        """
        A list of the widgets in this GridFlow

        .. note:: only for backwards compatibility. You should use the new
            standard container property :attr:`contents` to modify GridFlow
            contents.
        """
        warnings.warn(
            "only for backwards compatibility."
            "You should use the new standard container property `contents` to modify GridFlow",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        ml = MonitoredList(w for w, t in self.contents)

        def user_modified():
            self.cells = ml

        ml.set_modified_callback(user_modified)
        return ml

    @cells.setter
    def cells(self, widgets: Sequence[Widget]):
        warnings.warn(
            "only for backwards compatibility."
            "You should use the new standard container property `contents` to modify GridFlow",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        focus_position = self.focus_position
        self.contents = [(new, (WHSettings.GIVEN, self._cell_width)) for new in widgets]
        if focus_position < len(widgets):
            self.focus_position = focus_position

    def _get_cells(self):
        warnings.warn(
            "only for backwards compatibility."
            "You should use the new standard container property `contents` to modify GridFlow",
            DeprecationWarning,
            stacklevel=3,
        )
        return self.cells

    def _set_cells(self, widgets: Sequence[Widget]):
        warnings.warn(
            "only for backwards compatibility."
            "You should use the new standard container property `contents` to modify GridFlow",
            DeprecationWarning,
            stacklevel=3,
        )
        self.cells = widgets

    @property
    def cell_width(self) -> int:
        """
        The width of each cell in the GridFlow. Setting this value affects
        all cells.
        """
        return self._cell_width

    @cell_width.setter
    def cell_width(self, width: int) -> None:
        focus_position = self.focus_position
        self.contents = [(w, (WHSettings.GIVEN, width)) for (w, options) in self.contents]
        self.focus_position = focus_position
        self._cell_width = width

    def _get_cell_width(self) -> int:
        warnings.warn(
            f"Method `{self.__class__.__name__}._get_cell_width` is deprecated, "
            f"please use property `{self.__class__.__name__}.cell_width`",
            DeprecationWarning,
            stacklevel=3,
        )
        return self.cell_width

    def _set_cell_width(self, width: int) -> None:
        warnings.warn(
            f"Method `{self.__class__.__name__}._set_cell_width` is deprecated, "
            f"please use property `{self.__class__.__name__}.cell_width`",
            DeprecationWarning,
            stacklevel=3,
        )
        self.cell_width = width

    @property
    def contents(self) -> MonitoredFocusList[tuple[Widget, tuple[Literal[WHSettings.GIVEN], int]]]:
        """
        The contents of this GridFlow as a list of (widget, options)
        tuples.

        options is currently a tuple in the form `('fixed', number)`.
        number is the number of screen columns to allocate to this cell.
        'fixed' is the only type accepted at this time.

        This list may be modified like a normal list and the GridFlow
        widget will update automatically.

        .. seealso:: Create new options tuples with the :meth:`options` method.
        """
        return self._contents

    @contents.setter
    def contents(self, c):
        self._contents[:] = c

    def options(
        self,
        width_type: Literal["given", WHSettings.GIVEN] = WHSettings.GIVEN,
        width_amount: int | None = None,
    ) -> tuple[Literal[WHSettings.GIVEN], int]:
        """
        Return a new options tuple for use in a GridFlow's .contents list.

        width_type -- 'given' is the only value accepted
        width_amount -- None to use the default cell_width for this GridFlow
        """
        if width_type != WHSettings.GIVEN:
            raise GridFlowError(f"invalid width_type: {width_type!r}")
        if width_amount is None:
            width_amount = self._cell_width
        return (WHSettings(width_type), width_amount)

    def set_focus(self, cell: Widget | int) -> None:
        """
        Set the cell in focus, for backwards compatibility.

        .. note:: only for backwards compatibility. You may also use the new
            standard container property :attr:`focus_position` to get the focus.

        :param cell: contained element to focus
        :type cell: Widget or int
        """
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property `focus_position` to set the focus.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        if isinstance(cell, int):
            try:
                if cell < 0 or cell >= len(self.contents):
                    raise IndexError(f"No GridFlow child widget at position {cell}")
            except TypeError as exc:
                raise IndexError(f"No GridFlow child widget at position {cell}").with_traceback(
                    exc.__traceback__
                ) from exc
            self.contents.focus = cell
            return

        for i, (w, _options) in enumerate(self.contents):
            if cell == w:
                self.focus_position = i
                return
        raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")

    @property
    def focus(self) -> Widget | None:
        """the child widget in focus or None when GridFlow is empty"""
        if not self.contents:
            return None
        return self.contents[self.focus_position][0]

    def _get_focus(self) -> Widget | None:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus` is deprecated, "
            f"please use `{self.__class__.__name__}.focus` property",
            DeprecationWarning,
            stacklevel=3,
        )
        if not self.contents:
            return None
        return self.contents[self.focus_position][0]

    def get_focus(self):
        """
        Return the widget in focus, for backwards compatibility.

        .. note:: only for backwards compatibility. You may also use the new
            standard container property :attr:`focus` to get the focus.
        """
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property `focus` to get the focus.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        if not self.contents:
            return None
        return self.contents[self.focus_position][0]

    @property
    def focus_cell(self):
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property"
            "`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self.focus

    @focus_cell.setter
    def focus_cell(self, cell: Widget) -> None:
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property"
            "`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        for i, (w, _options) in enumerate(self.contents):
            if cell == w:
                self.focus_position = i
                return
        raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")

    def _set_focus_cell(self, cell: Widget) -> None:
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property"
            "`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
            DeprecationWarning,
            stacklevel=3,
        )
        for i, (w, _options) in enumerate(self.contents):
            if cell == w:
                self.focus_position = i
                return
        raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")

    @property
    def focus_position(self) -> int | None:
        """
        index of child widget in focus.
        Raises :exc:`IndexError` if read when GridFlow is empty, or when set to an invalid index.
        """
        if not self.contents:
            raise IndexError("No focus_position, GridFlow is empty")
        return self.contents.focus

    @focus_position.setter
    def focus_position(self, position: int) -> None:
        """
        Set the widget in focus.

        position -- index of child widget to be made focus
        """
        try:
            if position < 0 or position >= len(self.contents):
                raise IndexError(f"No GridFlow child widget at position {position}")
        except TypeError as exc:
            raise IndexError(f"No GridFlow child widget at position {position}").with_traceback(
                exc.__traceback__
            ) from exc
        self.contents.focus = position

    def _get_focus_position(self) -> int | None:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        if not self.contents:
            raise IndexError("No focus_position, GridFlow is empty")
        return self.contents.focus

    def _set_focus_position(self, position: int) -> None:
        """
        Set the widget in focus.

        position -- index of child widget to be made focus
        """
        warnings.warn(
            f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        try:
            if position < 0 or position >= len(self.contents):
                raise IndexError(f"No GridFlow child widget at position {position}")
        except TypeError as exc:
            raise IndexError(f"No GridFlow child widget at position {position}").with_traceback(
                exc.__traceback__
            ) from exc
        self.contents.focus = position

    def _get_maxcol(self, size: tuple[int] | tuple[()]) -> int:
        if size:
            (maxcol,) = size
            if self and maxcol < self.cell_width:
                warnings.warn(
                    f"Size is smaller than cell width ({maxcol!r} < {self.cell_width!r})",
                    GridFlowWarning,
                    stacklevel=3,
                )
        elif self:
            maxcol = len(self) * self.cell_width + (len(self) - 1) * self.h_sep
        else:
            maxcol = 0
        return maxcol

    def get_display_widget(self, size: tuple[int] | tuple[()]) -> Divider | Pile:
        """
        Arrange the cells into columns (and possibly a pile) for
        display, input or to calculate rows, and update the display
        widget.
        """
        maxcol = self._get_maxcol(size)

        # use cache if possible
        if self._cache_maxcol == maxcol:
            return self._w

        self._cache_maxcol = maxcol
        self._w = self.generate_display_widget((maxcol,))

        return self._w

    def generate_display_widget(self, size: tuple[int] | tuple[()]) -> Divider | Pile:
        """
        Actually generate display widget (ignoring cache)
        """
        maxcol = self._get_maxcol(size)

        divider = Divider()
        if not self.contents:
            return divider

        if self.v_sep > 1:
            # increase size of divider
            divider.top = self.v_sep - 1

        c = None
        p = Pile([])
        used_space = 0

        for i, (w, (_width_type, width_amount)) in enumerate(self.contents):
            if c is None or maxcol - used_space < width_amount:
                # starting a new row
                if self.v_sep:
                    p.contents.append((divider, p.options()))
                c = Columns([], self.h_sep)
                column_focused = False
                pad = Padding(c, self.align)
                # extra attribute to reference contents position
                pad.first_position = i
                p.contents.append((pad, p.options()))

            # Use width == maxcol in case of maxcol < width amount
            # Columns will use empty widget in case of GIVEN width > maxcol
            c.contents.append((w, c.options(WHSettings.GIVEN, min(width_amount, maxcol))))
            if (i == self.focus_position) or (not column_focused and w.selectable()):
                c.focus_position = len(c.contents) - 1
                column_focused = True
            if i == self.focus_position:
                p.focus_position = len(p.contents) - 1
            used_space = sum(x[1][1] for x in c.contents) + self.h_sep * len(c.contents)
            pad.width = used_space - self.h_sep

        if self.v_sep:
            # remove first divider
            del p.contents[:1]
        else:
            # Ensure p __selectable is updated
            p._contents_modified()  # pylint: disable=protected-access

        return p

    def _set_focus_from_display_widget(self) -> None:
        """
        Set the focus to the item in focus in the display widget.
        """
        # display widget (self._w) is always built as:
        #
        # Pile([
        #     Padding(
        #         Columns([ # possibly
        #         cell, ...])),
        #     Divider(), # possibly
        #     ...])

        pile_focus = self._w.focus
        if not pile_focus:
            return
        c = pile_focus.base_widget
        if c.focus:
            col_focus_position = c.focus_position
        else:
            col_focus_position = 0
        # pad.first_position was set by generate_display_widget() above
        self.focus_position = pile_focus.first_position + col_focus_position

    def keypress(
        self,
        size: tuple[int] | tuple[()],  # type: ignore[override]
        key: str,
    ) -> str | None:
        """
        Pass keypress to display widget for handling.
        Captures focus changes.
        """
        self.get_display_widget(size)
        key = super().keypress(size, key)
        if key is None:
            self._set_focus_from_display_widget()
        return key

    def pack(
        self,
        size: tuple[int] | tuple[()] = (),  # type: ignore[override]
        focus: bool = False,
    ) -> tuple[int, int]:
        if size:
            return super().pack(size, focus)
        if self:
            cols = len(self) * self.cell_width + (len(self) - 1) * self.h_sep
        else:
            cols = 0
        return cols, self.rows((cols,), focus)

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        self.get_display_widget(size)
        return super().rows(size, focus=focus)

    def render(
        self,
        size: tuple[int] | tuple[()],  # type: ignore[override]
        focus: bool = False,
    ):
        self.get_display_widget(size)
        return super().render(size, focus)

    def get_cursor_coords(self, size: tuple[int] | tuple[()]) -> tuple[int, int]:
        """Get cursor from display widget."""
        self.get_display_widget(size)
        return super().get_cursor_coords(size)

    def move_cursor_to_coords(self, size: tuple[int] | tuple[()], col: int, row: int):
        """Set the widget in focus based on the col + row."""
        self.get_display_widget(size)
        rval = super().move_cursor_to_coords(size, col, row)
        self._set_focus_from_display_widget()
        return rval

    def mouse_event(
        self,
        size: tuple[int] | tuple[()],  # type: ignore[override]
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> Literal[True]:
        self.get_display_widget(size)
        super().mouse_event(size, event, button, col, row, focus)
        self._set_focus_from_display_widget()
        return True  # at a minimum we adjusted our focus

    def get_pref_col(self, size: tuple[int] | tuple[()]):
        """Return pref col from display widget."""
        self.get_display_widget(size)
        return super().get_pref_col(size)
urwid-2.6.16/urwid/widget/line_box.py000066400000000000000000000153441470350774000175440ustar00rootroot00000000000000from __future__ import annotations

import typing

from .columns import Columns
from .constants import BOX_SYMBOLS, Align, WHSettings
from .divider import Divider
from .pile import Pile
from .solid_fill import SolidFill
from .text import Text
from .widget_decoration import WidgetDecoration, delegate_to_widget_mixin

if typing.TYPE_CHECKING:
    from typing_extensions import Literal

    from .widget import Widget

WrappedWidget = typing.TypeVar("WrappedWidget")


class LineBox(WidgetDecoration[WrappedWidget], delegate_to_widget_mixin("_wrapped_widget")):
    Symbols = BOX_SYMBOLS

    def __init__(
        self,
        original_widget: WrappedWidget,
        title: str = "",
        title_align: Literal["left", "center", "right"] | Align = Align.CENTER,
        title_attr=None,
        tlcorner: str = BOX_SYMBOLS.LIGHT.TOP_LEFT,
        tline: str = BOX_SYMBOLS.LIGHT.HORIZONTAL,
        lline: str = BOX_SYMBOLS.LIGHT.VERTICAL,
        trcorner: str = BOX_SYMBOLS.LIGHT.TOP_RIGHT,
        blcorner: str = BOX_SYMBOLS.LIGHT.BOTTOM_LEFT,
        rline: str = BOX_SYMBOLS.LIGHT.VERTICAL,
        bline: str = BOX_SYMBOLS.LIGHT.HORIZONTAL,
        brcorner: str = BOX_SYMBOLS.LIGHT.BOTTOM_RIGHT,
    ) -> None:
        """
        Draw a line around original_widget.

        Use 'title' to set an initial title text with will be centered
        on top of the box.

        Use `title_attr` to apply a specific attribute to the title text.

        Use `title_align` to align the title to the 'left', 'right', or 'center'.
        The default is 'center'.

        You can also override the widgets used for the lines/corners:
            tline: top line
            bline: bottom line
            lline: left line
            rline: right line
            tlcorner: top left corner
            trcorner: top right corner
            blcorner: bottom left corner
            brcorner: bottom right corner

        If empty string is specified for one of the lines/corners, then no character will be output there.
        If no top/bottom/left/right lines - whole lines will be omitted.
        This allows for seamless use of adjoining LineBoxes.

        Class attribute `Symbols` can be used as source for standard lines:

        >>> print(LineBox(Text("Some text")).render(()))
        ┌─────────â”
        │Some text│
        └─────────┘
        >>> print(
        ...   LineBox(
        ...     Text("Some text"),
        ...     tlcorner=LineBox.Symbols.LIGHT.TOP_LEFT_ROUNDED,
        ...     trcorner=LineBox.Symbols.LIGHT.TOP_RIGHT_ROUNDED,
        ...     blcorner=LineBox.Symbols.LIGHT.BOTTOM_LEFT_ROUNDED,
        ...     brcorner=LineBox.Symbols.LIGHT.BOTTOM_RIGHT_ROUNDED,
        ...   ).render(())
        ... )
        ╭─────────╮
        │Some text│
        ╰─────────╯
        >>> print(
        ...   LineBox(
        ...     Text("Some text"),
        ...     tline=LineBox.Symbols.HEAVY.HORIZONTAL,
        ...     bline=LineBox.Symbols.HEAVY.HORIZONTAL,
        ...     lline=LineBox.Symbols.HEAVY.VERTICAL,
        ...     rline=LineBox.Symbols.HEAVY.VERTICAL,
        ...     tlcorner=LineBox.Symbols.HEAVY.TOP_LEFT,
        ...     trcorner=LineBox.Symbols.HEAVY.TOP_RIGHT,
        ...     blcorner=LineBox.Symbols.HEAVY.BOTTOM_LEFT,
        ...     brcorner=LineBox.Symbols.HEAVY.BOTTOM_RIGHT,
        ...   ).render(())
        ... )
        â”â”â”â”â”â”â”â”â”â”┓
        ┃Some text┃
        â”—â”â”â”â”â”â”â”â”â”â”›

        To make Table constructions, some lineboxes need to be drawn without sides
        and T or CROSS symbols used for corners of cells.
        """

        w_lline = SolidFill(lline)
        w_rline = SolidFill(rline)

        w_tlcorner, w_tline, w_trcorner = Text(tlcorner), Divider(tline), Text(trcorner)
        w_blcorner, w_bline, w_brcorner = Text(blcorner), Divider(bline), Text(brcorner)

        if not tline and title:
            raise ValueError("Cannot have a title when tline is empty string")

        if title_attr:
            self.title_widget = Text((title_attr, self.format_title(title)))
        else:
            self.title_widget = Text(self.format_title(title))

        if tline:
            if title_align not in {Align.LEFT, Align.CENTER, Align.RIGHT}:
                raise ValueError('title_align must be one of "left", "right", or "center"')
            if title_align == Align.LEFT:
                tline_widgets = [(WHSettings.PACK, self.title_widget), w_tline]
            else:
                tline_widgets = [w_tline, (WHSettings.PACK, self.title_widget)]
                if title_align == Align.CENTER:
                    tline_widgets.append(w_tline)

            self.tline_widget = Columns(tline_widgets)
            top = Columns(
                (
                    (int(bool(tlcorner and lline)), w_tlcorner),
                    self.tline_widget,
                    (int(bool(trcorner and rline)), w_trcorner),
                )
            )

        else:
            self.tline_widget = None
            top = None

        # Note: We need to define a fixed first widget (even if it's 0 width) so that the other
        # widgets have something to anchor onto
        middle = Columns(
            ((int(bool(lline)), w_lline), original_widget, (int(bool(rline)), w_rline)),
            box_columns=[0, 2],
            focus_column=original_widget,
        )

        if bline:
            bottom = Columns(
                (
                    (int(bool(blcorner and lline)), w_blcorner),
                    w_bline,
                    (int(bool(brcorner and rline)), w_brcorner),
                )
            )
        else:
            bottom = None

        pile_widgets = []
        if top:
            pile_widgets.append((WHSettings.PACK, top))
        pile_widgets.append(middle)
        if bottom:
            pile_widgets.append((WHSettings.PACK, bottom))

        self._wrapped_widget = Pile(pile_widgets, focus_item=middle)

        super().__init__(original_widget)

    @property
    def _w(self) -> Pile:
        return self._wrapped_widget

    def format_title(self, text: str) -> str:
        if text:
            return f" {text} "

        return ""

    def set_title(self, text: str) -> None:
        if not self.tline_widget:
            raise ValueError("Cannot set title when tline is unset")
        self.title_widget.set_text(self.format_title(text))
        self.tline_widget._invalidate()

    @property
    def focus(self) -> Widget | None:
        """LineBox is partially container.

        While focus position is a bit hacky
        (formally it's not container and only position 0 available),
        focus widget is always provided by original widget.
        """
        return self._original_widget.focus
urwid-2.6.16/urwid/widget/listbox.py000066400000000000000000002213261470350774000174300ustar00rootroot00000000000000# Urwid listbox class
#    Copyright (C) 2004-2012  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import operator
import typing
import warnings
from collections.abc import Iterable, Sized
from contextlib import suppress

from typing_extensions import Protocol, runtime_checkable

from urwid import signals
from urwid.canvas import CanvasCombine, SolidCanvas

from .constants import Sizing, VAlign, WHSettings, normalize_valign
from .container import WidgetContainerMixin
from .filler import calculate_top_bottom_filler
from .monitored_list import MonitoredFocusList, MonitoredList
from .widget import Widget, nocache_widget_render_instance

if typing.TYPE_CHECKING:
    from collections.abc import Callable, Hashable

    from typing_extensions import Literal, Self

    from urwid.canvas import Canvas, CompositeCanvas

__all__ = (
    "ListBox",
    "ListBoxError",
    "ListWalker",
    "ListWalkerError",
    "SimpleFocusListWalker",
    "SimpleListWalker",
    "VisibleInfo",
    "VisibleInfoFillItem",
    "VisibleInfoMiddle",
    "VisibleInfoTopBottom",
)

_T = typing.TypeVar("_T")
_K = typing.TypeVar("_K")


class ListWalkerError(Exception):
    pass


@runtime_checkable
class ScrollSupportingBody(Protocol):
    """Protocol for ListWalkers."""

    def get_focus(self) -> tuple[Widget, _K]: ...

    def set_focus(self, position: _K) -> None: ...

    def get_next(self, position: _K) -> tuple[Widget, _K] | tuple[None, None]: ...

    def get_prev(self, position: _K) -> tuple[Widget, _K] | tuple[None, None]: ...


@runtime_checkable
class EstimatedSized(Protocol):
    """Widget can estimate it's size.

    PEP 424 defines API for memory-efficiency.
    For the ListBox it's a sign of the limited body length.
    The main use-case is lazy-load, where real length calculation is expensive.
    """

    def __length_hint__(self) -> int: ...


class ListWalker(metaclass=signals.MetaSignals):  # pylint: disable=no-member, unsubscriptable-object
    # mixin not named as mixin
    signals: typing.ClassVar[list[str]] = ["modified"]

    def _modified(self) -> None:
        signals.emit_signal(self, "modified")

    def get_focus(self):
        """
        This default implementation relies on a focus attribute and a
        __getitem__() method defined in a subclass.

        Override and don't call this method if these are not defined.
        """
        try:
            focus = self.focus
            return self[focus], focus
        except (IndexError, KeyError, TypeError):
            return None, None

    def get_next(self, position):
        """
        This default implementation relies on a next_position() method and a
        __getitem__() method defined in a subclass.

        Override and don't call this method if these are not defined.
        """
        try:
            position = self.next_position(position)
            return self[position], position
        except (IndexError, KeyError):
            return None, None

    def get_prev(self, position):
        """
        This default implementation relies on a prev_position() method and a
        __getitem__() method defined in a subclass.

        Override and don't call this method if these are not defined.
        """
        try:
            position = self.prev_position(position)
            return self[position], position
        except (IndexError, KeyError):
            return None, None


class SimpleListWalker(MonitoredList[_T], ListWalker):
    def __init__(self, contents: Iterable[_T], wrap_around: bool = False) -> None:
        """
        contents -- list to copy into this object

        wrap_around -- if true, jumps to beginning/end of list on move

        This class inherits :class:`MonitoredList` which means
        it can be treated as a list.

        Changes made to this object (when it is treated as a list) are
        detected automatically and will cause ListBox objects using
        this list walker to be updated.
        """
        if not isinstance(contents, Iterable):
            raise ListWalkerError(f"SimpleListWalker expecting list like object, got: {contents!r}")
        MonitoredList.__init__(self, contents)
        self.focus = 0
        self.wrap_around = wrap_around

    @property
    def contents(self) -> Self:
        """
        Return self.

        Provides compatibility with old SimpleListWalker class.
        """
        return self

    def _get_contents(self) -> Self:
        warnings.warn(
            f"Method `{self.__class__.__name__}._get_contents` is deprecated, "
            f"please use property`{self.__class__.__name__}.contents`",
            DeprecationWarning,
            stacklevel=3,
        )
        return self

    def _modified(self) -> None:
        if self.focus >= len(self):
            self.focus = max(0, len(self) - 1)
        ListWalker._modified(self)

    def set_modified_callback(self, callback: Callable[[], typing.Any]) -> typing.NoReturn:
        """
        This function inherited from MonitoredList is not implemented in SimpleListWalker.

        Use connect_signal(list_walker, "modified", ...) instead.
        """
        raise NotImplementedError('Use connect_signal(list_walker, "modified", ...) instead.')

    def set_focus(self, position: int) -> None:
        """Set focus position."""

        if not 0 <= position < len(self):
            raise IndexError(f"No widget at position {position}")

        self.focus = position
        self._modified()

    def next_position(self, position: int) -> int:
        """
        Return position after start_from.
        """
        if len(self) - 1 <= position:
            if self.wrap_around:
                return 0
            raise IndexError
        return position + 1

    def prev_position(self, position: int) -> int:
        """
        Return position before start_from.
        """
        if position <= 0:
            if self.wrap_around:
                return len(self) - 1
            raise IndexError
        return position - 1

    def positions(self, reverse: bool = False) -> Iterable[int]:
        """
        Optional method for returning an iterable of positions.
        """
        if reverse:
            return range(len(self) - 1, -1, -1)
        return range(len(self))


class SimpleFocusListWalker(ListWalker, MonitoredFocusList[_T]):
    def __init__(self, contents: Iterable[_T], wrap_around: bool = False) -> None:
        """
        contents -- list to copy into this object

        wrap_around -- if true, jumps to beginning/end of list on move

        This class inherits :class:`MonitoredList` which means
        it can be treated as a list.

        Changes made to this object (when it is treated as a list) are
        detected automatically and will cause ListBox objects using
        this list walker to be updated.

        Also, items added or removed before the widget in focus with
        normal list methods will cause the focus to be updated
        intelligently.
        """
        if not isinstance(contents, Iterable):
            raise ListWalkerError(f"SimpleFocusListWalker expecting iterable object, got: {contents!r}")
        MonitoredFocusList.__init__(self, contents)
        self.wrap_around = wrap_around

    def set_modified_callback(self, callback: typing.Any) -> typing.NoReturn:
        """
        This function inherited from MonitoredList is not
        implemented in SimpleFocusListWalker.

        Use connect_signal(list_walker, "modified", ...) instead.
        """
        raise NotImplementedError('Use connect_signal(list_walker, "modified", ...) instead.')

    def set_focus(self, position: int) -> None:
        """Set focus position."""
        self.focus = position
        self._modified()

    def next_position(self, position: int) -> int:
        """
        Return position after start_from.
        """
        if len(self) - 1 <= position:
            if self.wrap_around:
                return 0
            raise IndexError
        return position + 1

    def prev_position(self, position: int) -> int:
        """
        Return position before start_from.
        """
        if position <= 0:
            if self.wrap_around:
                return len(self) - 1
            raise IndexError
        return position - 1

    def positions(self, reverse: bool = False) -> Iterable[int]:
        """
        Optional method for returning an iterable of positions.
        """
        if reverse:
            return range(len(self) - 1, -1, -1)
        return range(len(self))


class ListBoxError(Exception):
    pass


class VisibleInfoMiddle(typing.NamedTuple):
    """Named tuple for ListBox internals."""

    offset: int
    focus_widget: Widget
    focus_pos: Hashable
    focus_rows: int
    cursor: tuple[int, int] | tuple[int] | None


class VisibleInfoFillItem(typing.NamedTuple):
    """Named tuple for ListBox internals."""

    widget: Widget
    position: Hashable
    rows: int


class VisibleInfoTopBottom(typing.NamedTuple):
    """Named tuple for ListBox internals."""

    trim: int
    fill: list[VisibleInfoFillItem]

    @classmethod
    def from_raw_data(
        cls,
        trim: int,
        fill: Iterable[tuple[Widget, Hashable, int]],
    ) -> Self:
        """Construct from not typed data.

        Useful for overridden cases."""
        return cls(trim=trim, fill=[VisibleInfoFillItem(*item) for item in fill])  # pragma: no cover


class VisibleInfo(typing.NamedTuple):
    middle: VisibleInfoMiddle
    top: VisibleInfoTopBottom
    bottom: VisibleInfoTopBottom

    @classmethod
    def from_raw_data(
        cls,
        middle: tuple[int, Widget, Hashable, int, tuple[int, int] | tuple[int] | None],
        top: tuple[int, Iterable[tuple[Widget, Hashable, int]]],
        bottom: tuple[int, Iterable[tuple[Widget, Hashable, int]]],
    ) -> Self:
        """Construct from not typed data.

        Useful for overridden cases.
        """
        return cls(  # pragma: no cover
            middle=VisibleInfoMiddle(*middle),
            top=VisibleInfoTopBottom.from_raw_data(*top),
            bottom=VisibleInfoTopBottom.from_raw_data(*bottom),
        )


class ListBox(Widget, WidgetContainerMixin):
    """
    Vertically stacked list of widgets
    """

    _selectable = True
    _sizing = frozenset([Sizing.BOX])

    def __init__(self, body: ListWalker | Iterable[Widget]) -> None:
        """
        :param body: a ListWalker subclass such as :class:`SimpleFocusListWalker`
            that contains widgets to be displayed inside the list box
        :type body: ListWalker
        """
        super().__init__()
        if getattr(body, "get_focus", None):
            self._body: ListWalker = body
        else:
            self._body = SimpleListWalker(body)

        self.body = self._body  # Initialization hack

        # offset_rows is the number of rows between the top of the view
        # and the top of the focused item
        self.offset_rows = 0
        # inset_fraction is used when the focused widget is off the
        # top of the view.  it is the fraction of the widget cut off
        # at the top.  (numerator, denominator)
        self.inset_fraction = (0, 1)

        # pref_col is the preferred column for the cursor when moving
        # between widgets that use the cursor (edit boxes etc.)
        self.pref_col = "left"

        # variable for delayed focus change used by set_focus
        self.set_focus_pending = "first selectable"

        # variable for delayed valign change used by set_focus_valign
        self.set_focus_valign_pending = None

        # used for scrollable protocol
        self._rows_max_cached = 0
        self._rendered_size = 0, 0

    @property
    def body(self) -> ListWalker:
        """
        a ListWalker subclass such as :class:`SimpleFocusListWalker` that contains
        widgets to be displayed inside the list box
        """
        return self._body

    @body.setter
    def body(self, body: Iterable[Widget] | ListWalker) -> None:
        with suppress(AttributeError):
            signals.disconnect_signal(self._body, "modified", self._invalidate)
            # _body may be not yet assigned

        if getattr(body, "get_focus", None):
            self._body = body
        else:
            self._body = SimpleListWalker(body)
        try:
            signals.connect_signal(self._body, "modified", self._invalidate)
        except NameError:
            # our list walker has no modified signal so we must not
            # cache our canvases because we don't know when our
            # content has changed
            self.render = nocache_widget_render_instance(self)
        self._invalidate()

    def _get_body(self):
        warnings.warn(
            f"Method `{self.__class__.__name__}._get_body` is deprecated, "
            f"please use property `{self.__class__.__name__}.body`",
            DeprecationWarning,
            stacklevel=3,
        )
        return self.body

    def _set_body(self, body):
        warnings.warn(
            f"Method `{self.__class__.__name__}._set_body` is deprecated, "
            f"please use property `{self.__class__.__name__}.body`",
            DeprecationWarning,
            stacklevel=3,
        )
        self.body = body

    @property
    def __len__(self) -> Callable[[], int]:
        if isinstance(self._body, Sized):
            return self._body.__len__
        raise AttributeError(f"{self._body.__class__.__name__} is not Sized")

    @property
    def __length_hint__(self) -> Callable[[], int]:  # pylint: disable=invalid-length-hint-returned
        if isinstance(self._body, (Sized, EstimatedSized)):
            return lambda: operator.length_hint(self._body)
        raise AttributeError(f'{self._body.__class__.__name__} is not Sized and do not implement "__length_hint__"')

    def calculate_visible(
        self,
        size: tuple[int, int],
        focus: bool = False,
    ) -> VisibleInfo | tuple[None, None, None]:
        """
        Returns the widgets that would be displayed in
        the ListBox given the current *size* and *focus*.

        see :meth:`Widget.render` for parameter details

        :returns: (*middle*, *top*, *bottom*) or (``None``, ``None``, ``None``)

        *middle*
            (*row offset*(when +ve) or *inset*(when -ve),
            *focus widget*, *focus position*, *focus rows*,
            *cursor coords* or ``None``)
        *top*
            (*# lines to trim off top*,
            list of (*widget*, *position*, *rows*) tuples above focus in order from bottom to top)
        *bottom*
            (*# lines to trim off bottom*,
            list of (*widget*, *position*, *rows*) tuples below focus in order from top to bottom)
        """
        (maxcol, maxrow) = size

        # 0. set the focus if a change is pending
        if self.set_focus_pending or self.set_focus_valign_pending:
            self._set_focus_complete((maxcol, maxrow), focus)

        # 1. start with the focus widget
        focus_widget, focus_pos = self._body.get_focus()
        if focus_widget is None:  # list box is empty?
            return None, None, None
        top_pos = focus_pos

        offset_rows, inset_rows = self.get_focus_offset_inset((maxcol, maxrow))
        #    force at least one line of focus to be visible
        if maxrow and offset_rows >= maxrow:
            offset_rows = maxrow - 1

        #    adjust position so cursor remains visible
        cursor = None
        if maxrow and focus_widget.selectable() and focus and hasattr(focus_widget, "get_cursor_coords"):
            cursor = focus_widget.get_cursor_coords((maxcol,))

        if cursor is not None:
            _cx, cy = cursor
            effective_cy = cy + offset_rows - inset_rows

            if effective_cy < 0:  # cursor above top?
                inset_rows = cy
            elif effective_cy >= maxrow:  # cursor below bottom?
                offset_rows = maxrow - cy - 1
                if offset_rows < 0:  # need to trim the top
                    inset_rows, offset_rows = -offset_rows, 0

        #    set trim_top by focus trimmimg
        trim_top = inset_rows
        focus_rows = focus_widget.rows((maxcol,), True)

        # 2. collect the widgets above the focus
        pos = focus_pos
        fill_lines = offset_rows
        fill_above = []
        top_pos = pos
        while fill_lines > 0:
            prev, pos = self._body.get_prev(pos)
            if prev is None:  # run out of widgets above?
                offset_rows -= fill_lines
                break
            top_pos = pos

            p_rows = prev.rows((maxcol,))
            if p_rows:  # filter out 0-height widgets
                fill_above.append(VisibleInfoFillItem(prev, pos, p_rows))
            if p_rows > fill_lines:  # crosses top edge?
                trim_top = p_rows - fill_lines
                break
            fill_lines -= p_rows

        trim_bottom = max(focus_rows + offset_rows - inset_rows - maxrow, 0)

        # 3. collect the widgets below the focus
        pos = focus_pos
        fill_lines = maxrow - focus_rows - offset_rows + inset_rows
        fill_below = []
        while fill_lines > 0:
            next_pos, pos = self._body.get_next(pos)
            if next_pos is None:  # run out of widgets below?
                break

            n_rows = next_pos.rows((maxcol,))
            if n_rows:  # filter out 0-height widgets
                fill_below.append(VisibleInfoFillItem(next_pos, pos, n_rows))
            if n_rows > fill_lines:  # crosses bottom edge?
                trim_bottom = n_rows - fill_lines
                fill_lines -= n_rows
                break
            fill_lines -= n_rows

        # 4. fill from top again if necessary & possible
        fill_lines = max(0, fill_lines)

        if fill_lines > 0 and trim_top > 0:
            if fill_lines <= trim_top:
                trim_top -= fill_lines
                offset_rows += fill_lines
                fill_lines = 0
            else:
                fill_lines -= trim_top
                offset_rows += trim_top
                trim_top = 0
        pos = top_pos
        while fill_lines > 0:
            prev, pos = self._body.get_prev(pos)
            if prev is None:
                break

            p_rows = prev.rows((maxcol,))
            fill_above.append(VisibleInfoFillItem(prev, pos, p_rows))
            if p_rows > fill_lines:  # more than required
                trim_top = p_rows - fill_lines
                offset_rows += fill_lines
                break
            fill_lines -= p_rows
            offset_rows += p_rows

        # 5. return the interesting bits
        return VisibleInfo(
            VisibleInfoMiddle(offset_rows - inset_rows, focus_widget, focus_pos, focus_rows, cursor),
            VisibleInfoTopBottom(trim_top, fill_above),
            VisibleInfoTopBottom(trim_bottom, fill_below),
        )

    def _check_support_scrolling(self) -> None:
        from .treetools import TreeWalker

        if not isinstance(self._body, ScrollSupportingBody):
            raise ListBoxError(f"{self} body do not implement methods required for scrolling protocol")

        if not isinstance(self._body, (Sized, EstimatedSized, TreeWalker)):
            raise ListBoxError(
                f"{self} body is not a Sized, can not estimate it's size and not a TreeWalker."
                f"Scroll is not allowed due to risk of infinite cycle of widgets load."
            )

        if getattr(self._body, "wrap_around", False):
            raise ListBoxError("Body is wrapped around. Scroll position calculation is undefined.")

    def get_scrollpos(self, size: tuple[int, int] | None = None, focus: bool = False) -> int:
        """Current scrolling position."""
        self._check_support_scrolling()

        if not self._body:
            return 0

        if size is not None:
            self._rendered_size = size

        mid, top, _bottom = self.calculate_visible(self._rendered_size, focus)

        start_row = top.trim
        maxcol = self._rendered_size[0]

        if top.fill:
            pos = top.fill[-1].position
        else:
            pos = mid.focus_pos

        prev, pos = self._body.get_prev(pos)
        while prev is not None:
            start_row += prev.rows((maxcol,))
            prev, pos = self._body.get_prev(pos)

        return start_row

    def rows_max(self, size: tuple[int, int] | None = None, focus: bool = False) -> int:
        """Scrollable protocol for sized iterable and not wrapped around contents."""
        self._check_support_scrolling()

        if size is not None:
            self._rendered_size = size

        if size or not self._rows_max_cached:
            cols = self._rendered_size[0]
            rows = 0

            focused_w, idx = self.body.get_focus()
            if focused_w:
                rows += focused_w.rows((cols,), focus)

                prev, pos = self._body.get_prev(idx)
                while prev is not None:
                    rows += prev.rows((cols,), False)
                    prev, pos = self._body.get_prev(pos)

                next_, pos = self.body.get_next(idx)
                while next_ is not None:
                    rows += next_.rows((cols,), True)
                    next_, pos = self._body.get_next(pos)

            self._rows_max_cached = rows

        return self._rows_max_cached

    def require_relative_scroll(self, size: tuple[int, int], focus: bool = False) -> bool:
        """Widget require relative scroll due to performance limitations of real lines count calculation."""
        return isinstance(self._body, (Sized, EstimatedSized)) and (size[1] * 3 < operator.length_hint(self.body))

    def get_first_visible_pos(self, size: tuple[int, int], focus: bool = False) -> int:
        self._check_support_scrolling()

        if not self._body:
            return 0

        _mid, top, _bottom = self.calculate_visible(size, focus)
        if top.fill:
            first_pos = top.fill[-1].position
        else:
            first_pos = self.focus_position

        over = 0
        _widget, first_pos = self.body.get_prev(first_pos)
        while first_pos is not None:
            over += 1
            _widget, first_pos = self.body.get_prev(first_pos)

        return over

    def get_visible_amount(self, size: tuple[int, int], focus: bool = False) -> int:
        self._check_support_scrolling()

        if not self._body:
            return 1

        _mid, top, bottom = self.calculate_visible(size, focus)
        return 1 + len(top.fill) + len(bottom.fill)

    def render(
        self,
        size: tuple[int, int],  # type: ignore[override]
        focus: bool = False,
    ) -> CompositeCanvas | SolidCanvas:
        """
        Render ListBox and return canvas.

        see :meth:`Widget.render` for details
        """
        (maxcol, maxrow) = size

        self._rendered_size = size

        middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus=focus)
        if middle is None:
            return SolidCanvas(" ", maxcol, maxrow)

        _ignore, focus_widget, focus_pos, focus_rows, cursor = middle  # pylint: disable=unpacking-non-sequence
        trim_top, fill_above = top  # pylint: disable=unpacking-non-sequence
        trim_bottom, fill_below = bottom  # pylint: disable=unpacking-non-sequence

        combinelist: list[tuple[Canvas, int, bool]] = []
        rows = 0
        fill_above.reverse()  # fill_above is in bottom-up order
        for widget, w_pos, w_rows in fill_above:
            canvas = widget.render((maxcol,))
            if w_rows != canvas.rows():
                raise ListBoxError(
                    f"Widget {widget!r} at position {w_pos!r} "
                    f"within listbox calculated {w_rows:d} rows "
                    f"but rendered {canvas.rows():d}!"
                )
            rows += w_rows
            combinelist.append((canvas, w_pos, False))

        focus_canvas = focus_widget.render((maxcol,), focus=focus)

        if focus_canvas.rows() != focus_rows:
            raise ListBoxError(
                f"Focus Widget {focus_widget!r} at position {focus_pos!r} "
                f"within listbox calculated {focus_rows:d} rows "
                f"but rendered {focus_canvas.rows():d}!"
            )
        c_cursor = focus_canvas.cursor
        if cursor is not None and cursor != c_cursor:
            raise ListBoxError(
                f"Focus Widget {focus_widget!r} at position {focus_pos!r} "
                f"within listbox calculated cursor coords {cursor!r} "
                f"but rendered cursor coords {c_cursor!r}!"
            )

        rows += focus_rows
        combinelist.append((focus_canvas, focus_pos, True))

        for widget, w_pos, w_rows in fill_below:
            canvas = widget.render((maxcol,))
            if w_rows != canvas.rows():
                raise ListBoxError(
                    f"Widget {widget!r} at position {w_pos!r} "
                    f"within listbox calculated {w_rows:d} "
                    f"rows but rendered {canvas.rows():d}!"
                )
            rows += w_rows
            combinelist.append((canvas, w_pos, False))

        final_canvas = CanvasCombine(combinelist)

        if trim_top:
            final_canvas.trim(trim_top)
            rows -= trim_top
        if trim_bottom:
            final_canvas.trim_end(trim_bottom)
            rows -= trim_bottom

        if rows > maxrow:
            raise ListBoxError(
                f"Listbox contents too long!\nRender top={top!r}, middle={middle!r}, bottom={bottom!r}\n"
            )

        if rows < maxrow:
            if trim_bottom != 0:
                raise ListBoxError(
                    f"Listbox contents too short!\n"
                    f"Render top={top!r}, middle={middle!r}, bottom={bottom!r}\n"
                    f"Trim bottom={trim_bottom!r}"
                )

            bottom_pos = focus_pos
            if fill_below:
                bottom_pos = fill_below[-1][1]

            rendered_positions = frozenset(idx for _, idx, _ in combinelist)
            widget, next_pos = self._body.get_next(bottom_pos)
            while all(
                (
                    widget is not None,
                    next_pos is not None,
                    next_pos not in rendered_positions,
                )
            ):
                if widget.rows((maxcol,), False):
                    raise ListBoxError(
                        f"Listbox contents too short!\n"
                        f"Render top={top!r}, middle={middle!r}, bottom={bottom!r}\n"
                        f"Not rendered not empty widgets available (first is {widget!r} with position {next_pos!r})"
                    )

                widget, next_next_pos = self._body.get_next(next_pos)
                if next_pos == next_next_pos:
                    raise ListBoxError(
                        f"Next position after {next_pos!r} is invalid (points to itself)\n"
                        f"Looks like bug with {self._body!r}"
                    )
                next_pos = next_next_pos

            final_canvas.pad_trim_top_bottom(0, maxrow - rows)

        return final_canvas

    def get_cursor_coords(self, size: tuple[int, int]) -> tuple[int, int] | None:
        """
        See :meth:`Widget.get_cursor_coords` for details
        """
        (maxcol, maxrow) = size

        middle, _top, _bottom = self.calculate_visible((maxcol, maxrow), True)
        if middle is None:
            return None

        offset_inset, _ignore1, _ignore2, _ignore3, cursor = middle  # pylint: disable=unpacking-non-sequence
        if not cursor:
            return None

        x, y = cursor
        y += offset_inset
        if y < 0 or y >= maxrow:
            return None
        return (x, y)

    def set_focus_valign(
        self,
        valign: Literal["top", "middle", "bottom"] | VAlign | tuple[Literal["relative", WHSettings.RELATIVE], int],
    ):
        """Set the focus widget's display offset and inset.

        :param valign: one of: 'top', 'middle', 'bottom' ('relative', percentage 0=top 100=bottom)
        """
        vt, va = normalize_valign(valign, ListBoxError)
        self.set_focus_valign_pending = vt, va

    def set_focus(self, position, coming_from: Literal["above", "below"] | None = None) -> None:
        """
        Set the focus position and try to keep the old focus in view.

        :param position: a position compatible with :meth:`self._body.set_focus`
        :param coming_from: set to 'above' or 'below' if you know that
                            old position is above or below the new position.
        :type coming_from: str
        """
        if coming_from not in {"above", "below", None}:
            raise ListBoxError(f"coming_from value invalid: {coming_from!r}")
        focus_widget, focus_pos = self._body.get_focus()
        if focus_widget is None:
            raise IndexError("Can't set focus, ListBox is empty")

        self.set_focus_pending = coming_from, focus_widget, focus_pos
        self._body.set_focus(position)

    def get_focus(self):
        """
        Return a `(focus widget, focus position)` tuple, for backwards
        compatibility. You may also use the new standard container
        properties :attr:`focus` and :attr:`focus_position` to read these values.
        """
        warnings.warn(
            "only for backwards compatibility."
            "You may also use the new standard container property `focus` to get the focus "
            "and property `focus_position` to read these values.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        return self._body.get_focus()

    @property
    def focus(self) -> Widget | None:
        """
        the child widget in focus or None when ListBox is empty.

        Return the widget in focus according to our :obj:`list walker `.
        """
        return self._body.get_focus()[0]

    def _get_focus(self) -> Widget:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus` is deprecated, "
            f"please use `{self.__class__.__name__}.focus` property",
            DeprecationWarning,
            stacklevel=3,
        )
        return self.focus

    def _get_focus_position(self):
        """
        Return the list walker position of the widget in focus. The type
        of value returned depends on the :obj:`list walker `.

        """
        w, pos = self._body.get_focus()
        if w is None:
            raise IndexError("No focus_position, ListBox is empty")
        return pos

    focus_position = property(
        _get_focus_position,
        set_focus,
        doc="""
        the position of child widget in focus. The valid values for this
        position depend on the list walker in use.
        :exc:`IndexError` will be raised by reading this property when the
        ListBox is empty or setting this property to an invalid position.
        """,
    )

    def _contents(self):
        # noinspection PyMethodParameters
        class ListBoxContents:
            # pylint: disable=no-self-argument

            __getitem__ = self._contents__getitem__

            __len__ = self.__len__

            def __repr__(inner_self) -> str:
                return f"<{inner_self.__class__.__name__} for {self!r} at 0x{id(inner_self):X}>"

            def __call__(inner_self) -> Self:
                warnings.warn(
                    "ListBox.contents is a property, not a method",
                    DeprecationWarning,
                    stacklevel=3,
                )
                return inner_self

        return ListBoxContents()

    def _contents__getitem__(self, key):
        # try list walker protocol v2 first
        if hasattr(self._body, "__getitem__"):
            try:
                return (self._body[key], None)
            except (IndexError, KeyError) as exc:
                raise KeyError(f"ListBox.contents key not found: {key!r}").with_traceback(exc.__traceback__) from exc
        # fall back to v1
        _w, old_focus = self._body.get_focus()

        try:
            self._body.set_focus(key)
            return self._body.get_focus()[0]
        except (IndexError, KeyError) as exc:
            raise KeyError(f"ListBox.contents key not found: {key!r}").with_traceback(exc.__traceback__) from exc
        finally:
            self._body.set_focus(old_focus)

    @property
    def contents(self):
        """
        An object that allows reading widgets from the ListBox's list
        walker as a `(widget, options)` tuple. `None` is currently the only
        value for options.

        .. warning::

            This object may not be used to set or iterate over contents.

            You must use the list walker stored as
            :attr:`.body` to perform manipulation and iteration, if supported.
        """
        return self._contents()

    def options(self):
        """
        There are currently no options for ListBox contents.

        Return None as a placeholder for future options.
        """

    def _set_focus_valign_complete(self, size: tuple[int, int], focus: bool) -> None:
        """Finish setting the offset and inset now that we have have a maxcol & maxrow."""
        (maxcol, maxrow) = size
        vt, va = self.set_focus_valign_pending
        self.set_focus_valign_pending = None
        self.set_focus_pending = None

        focus_widget, _focus_pos = self._body.get_focus()
        if focus_widget is None:
            return

        rows = focus_widget.rows((maxcol,), focus)
        rtop, _rbot = calculate_top_bottom_filler(
            maxrow,
            vt,
            va,
            WHSettings.GIVEN,
            rows,
            None,
            0,
            0,
        )

        self.shift_focus((maxcol, maxrow), rtop)

    def _set_focus_first_selectable(self, size: tuple[int, int], focus: bool) -> None:
        """Choose the first visible, selectable widget below the current focus as the focus widget."""
        (maxcol, maxrow) = size
        self.set_focus_valign_pending = None
        self.set_focus_pending = None
        middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus=focus)
        if middle is None:
            return

        row_offset, focus_widget, _focus_pos, focus_rows, _cursor = middle  # pylint: disable=unpacking-non-sequence
        _trim_top, _fill_above = top  # pylint: disable=unpacking-non-sequence
        trim_bottom, fill_below = bottom  # pylint: disable=unpacking-non-sequence

        if focus_widget.selectable():
            return

        if trim_bottom:
            fill_below = fill_below[:-1]
        new_row_offset = row_offset + focus_rows
        for widget, pos, rows in fill_below:
            if widget.selectable():
                self._body.set_focus(pos)
                self.shift_focus((maxcol, maxrow), new_row_offset)
                return
            new_row_offset += rows

    def _set_focus_complete(self, size: tuple[int, int], focus: bool) -> None:
        """Finish setting the position now that we have maxcol & maxrow."""
        (maxcol, maxrow) = size
        self._invalidate()
        if self.set_focus_pending == "first selectable":
            return self._set_focus_first_selectable((maxcol, maxrow), focus)
        if self.set_focus_valign_pending is not None:
            return self._set_focus_valign_complete((maxcol, maxrow), focus)
        coming_from, _focus_widget, focus_pos = self.set_focus_pending
        self.set_focus_pending = None

        # new position
        _new_focus_widget, position = self._body.get_focus()
        if focus_pos == position:
            # do nothing
            return None

        # restore old focus temporarily
        self._body.set_focus(focus_pos)

        middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus)
        focus_offset, _focus_widget, focus_pos, focus_rows, _cursor = middle  # pylint: disable=unpacking-non-sequence
        _trim_top, fill_above = top  # pylint: disable=unpacking-non-sequence
        _trim_bottom, fill_below = bottom  # pylint: disable=unpacking-non-sequence

        offset = focus_offset
        for _widget, pos, rows in fill_above:
            offset -= rows
            if pos == position:
                self.change_focus((maxcol, maxrow), pos, offset, "below")
                return None

        offset = focus_offset + focus_rows
        for _widget, pos, rows in fill_below:
            if pos == position:
                self.change_focus((maxcol, maxrow), pos, offset, "above")
                return None
            offset += rows

        # failed to find widget among visible widgets
        self._body.set_focus(position)
        widget, position = self._body.get_focus()
        rows = widget.rows((maxcol,), focus)

        if coming_from == "below":
            offset = 0
        elif coming_from == "above":
            offset = maxrow - rows
        else:
            offset = (maxrow - rows) // 2
        self.shift_focus((maxcol, maxrow), offset)
        return None

    def shift_focus(self, size: tuple[int, int], offset_inset: int) -> None:
        """
        Move the location of the current focus relative to the top.
        This is used internally by methods that know the widget's *size*.

        See also :meth:`.set_focus_valign`.

        :param size: see :meth:`Widget.render` for details
        :param offset_inset: either the number of rows between the
            top of the listbox and the start of the focus widget (+ve
            value) or the number of lines of the focus widget hidden off
            the top edge of the listbox (-ve value) or ``0`` if the top edge
            of the focus widget is aligned with the top edge of the
            listbox.
        :type offset_inset: int
        """
        (maxcol, maxrow) = size

        if offset_inset >= 0:
            if offset_inset >= maxrow:
                raise ListBoxError(f"Invalid offset_inset: {offset_inset!r}, only {maxrow!r} rows in list box")
            self.offset_rows = offset_inset
            self.inset_fraction = (0, 1)
        else:
            target, _ignore = self._body.get_focus()
            tgt_rows = target.rows((maxcol,), True)
            if offset_inset + tgt_rows <= 0:
                raise ListBoxError(f"Invalid offset_inset: {offset_inset!r}, only {tgt_rows!r} rows in target!")
            self.offset_rows = 0
            self.inset_fraction = (-offset_inset, tgt_rows)
        self._invalidate()

    def update_pref_col_from_focus(self, size: tuple[int, int]) -> None:
        """Update self.pref_col from the focus widget."""
        # TODO: should this not be private?
        (maxcol, _maxrow) = size

        widget, _old_pos = self._body.get_focus()
        if widget is None:
            return

        pref_col = None
        if hasattr(widget, "get_pref_col"):
            pref_col = widget.get_pref_col((maxcol,))
        if pref_col is None and hasattr(widget, "get_cursor_coords"):
            coords = widget.get_cursor_coords((maxcol,))
            if isinstance(coords, tuple):
                pref_col, _y = coords
        if pref_col is not None:
            self.pref_col = pref_col

    def change_focus(
        self,
        size: tuple[int, int],
        position,
        offset_inset: int = 0,
        coming_from: Literal["above", "below"] | None = None,
        cursor_coords: tuple[int, int] | None = None,
        snap_rows: int | None = None,
    ) -> None:
        """
        Change the current focus widget.
        This is used internally by methods that know the widget's *size*.

        See also :meth:`.set_focus`.

        :param size: see :meth:`Widget.render` for details
        :param position: a position compatible with :meth:`self._body.set_focus`
        :param offset_inset: either the number of rows between the
            top of the listbox and the start of the focus widget (+ve
            value) or the number of lines of the focus widget hidden off
            the top edge of the listbox (-ve value) or 0 if the top edge
            of the focus widget is aligned with the top edge of the
            listbox (default if unspecified)
        :type offset_inset: int
        :param coming_from: either 'above', 'below' or unspecified `None`
        :type coming_from: str
        :param cursor_coords: (x, y) tuple indicating the desired
            column and row for the cursor, a (x,) tuple indicating only
            the column for the cursor, or unspecified
        :type cursor_coords: (int, int)
        :param snap_rows: the maximum number of extra rows to scroll
            when trying to "snap" a selectable focus into the view
        :type snap_rows: int
        """
        (maxcol, maxrow) = size

        # update pref_col before change
        if cursor_coords:
            self.pref_col = cursor_coords[0]
        else:
            self.update_pref_col_from_focus((maxcol, maxrow))

        self._invalidate()
        self._body.set_focus(position)
        target, _ignore = self._body.get_focus()
        tgt_rows = target.rows((maxcol,), True)
        if snap_rows is None:
            snap_rows = maxrow - 1

        # "snap" to selectable widgets
        align_top = 0
        align_bottom = maxrow - tgt_rows

        if coming_from == "above" and target.selectable() and offset_inset > align_bottom:
            if snap_rows >= offset_inset - align_bottom:
                offset_inset = align_bottom
            elif snap_rows >= offset_inset - align_top:
                offset_inset = align_top
            else:
                offset_inset -= snap_rows

        if coming_from == "below" and target.selectable() and offset_inset < align_top:
            if snap_rows >= align_top - offset_inset:
                offset_inset = align_top
            elif snap_rows >= align_bottom - offset_inset:
                offset_inset = align_bottom
            else:
                offset_inset += snap_rows

        # convert offset_inset to offset_rows or inset_fraction
        if offset_inset >= 0:
            self.offset_rows = offset_inset
            self.inset_fraction = (0, 1)
        else:
            if offset_inset + tgt_rows <= 0:
                raise ListBoxError(f"Invalid offset_inset: {offset_inset}, only {tgt_rows} rows in target!")
            self.offset_rows = 0
            self.inset_fraction = (-offset_inset, tgt_rows)

        if cursor_coords is None:
            if coming_from is None:
                return  # must either know row or coming_from
            cursor_coords = (self.pref_col,)

        if not hasattr(target, "move_cursor_to_coords"):
            return

        attempt_rows = []

        if len(cursor_coords) == 1:
            # only column (not row) specified
            # start from closest edge and move inwards
            (pref_col,) = cursor_coords
            if coming_from == "above":
                attempt_rows = range(0, tgt_rows)
            else:
                if coming_from != "below":
                    raise ValueError("must specify coming_from ('above' or 'below') if cursor row is not specified")
                attempt_rows = range(tgt_rows, -1, -1)
        else:
            # both column and row specified
            # start from preferred row and move back to closest edge
            (pref_col, pref_row) = cursor_coords
            if pref_row < 0 or pref_row >= tgt_rows:
                raise ListBoxError(
                    f"cursor_coords row outside valid range for target. pref_row:{pref_row!r} target_rows:{tgt_rows!r}"
                )

            if coming_from == "above":
                attempt_rows = range(pref_row, -1, -1)
            elif coming_from == "below":
                attempt_rows = range(pref_row, tgt_rows)
            else:
                attempt_rows = [pref_row]

        for row in attempt_rows:
            if target.move_cursor_to_coords((maxcol,), pref_col, row):
                break

    def get_focus_offset_inset(self, size: tuple[int, int]) -> tuple[int, int]:
        """Return (offset rows, inset rows) for focus widget."""
        (maxcol, _maxrow) = size
        focus_widget, _pos = self._body.get_focus()
        focus_rows = focus_widget.rows((maxcol,), True)
        offset_rows = self.offset_rows
        inset_rows = 0
        if offset_rows == 0:
            inum, iden = self.inset_fraction
            if inum < 0 or iden < 0 or inum >= iden:
                raise ListBoxError(f"Invalid inset_fraction: {self.inset_fraction!r}")
            inset_rows = focus_rows * inum // iden
            if inset_rows and inset_rows >= focus_rows:
                raise ListBoxError("urwid inset_fraction error (please report)")
        return offset_rows, inset_rows

    def make_cursor_visible(self, size: tuple[int, int]) -> None:
        """Shift the focus widget so that its cursor is visible."""
        (maxcol, maxrow) = size

        focus_widget, _pos = self._body.get_focus()
        if focus_widget is None:
            return
        if not focus_widget.selectable():
            return
        if not hasattr(focus_widget, "get_cursor_coords"):
            return
        cursor = focus_widget.get_cursor_coords((maxcol,))
        if cursor is None:
            return
        _cx, cy = cursor
        offset_rows, inset_rows = self.get_focus_offset_inset((maxcol, maxrow))

        if cy < inset_rows:
            self.shift_focus((maxcol, maxrow), -(cy))
            return

        if offset_rows - inset_rows + cy >= maxrow:
            self.shift_focus((maxcol, maxrow), maxrow - cy - 1)
            return

    def keypress(
        self,
        size: tuple[int, int],  # type: ignore[override]
        key: str,
    ) -> str | None:
        """Move selection through the list elements scrolling when
        necessary. Keystrokes are first passed to widget in focus
        in case that widget can handle them.

        Keystrokes handled by this widget are:
         'up'        up one line (or widget)
         'down'      down one line (or widget)
         'page up'   move cursor up one listbox length (or widget)
         'page down' move cursor down one listbox length (or widget)
        """
        from urwid.command_map import Command

        (maxcol, maxrow) = size

        if self.set_focus_pending or self.set_focus_valign_pending:
            self._set_focus_complete((maxcol, maxrow), focus=True)

        focus_widget, _pos = self._body.get_focus()
        if focus_widget is None:  # empty listbox, can't do anything
            return key

        if focus_widget.selectable():
            key = focus_widget.keypress((maxcol,), key)
            if key is None:
                self.make_cursor_visible((maxcol, maxrow))
                return None

        def actual_key(unhandled) -> str | None:
            if unhandled:
                return key
            return None

        # pass off the heavy lifting
        if self._command_map[key] == Command.UP:
            return actual_key(self._keypress_up((maxcol, maxrow)))

        if self._command_map[key] == Command.DOWN:
            return actual_key(self._keypress_down((maxcol, maxrow)))

        if self._command_map[key] == Command.PAGE_UP:
            return actual_key(self._keypress_page_up((maxcol, maxrow)))

        if self._command_map[key] == Command.PAGE_DOWN:
            return actual_key(self._keypress_page_down((maxcol, maxrow)))

        if self._command_map[key] == Command.MAX_LEFT:
            return actual_key(self._keypress_max_left((maxcol, maxrow)))

        if self._command_map[key] == Command.MAX_RIGHT:
            return actual_key(self._keypress_max_right((maxcol, maxrow)))

        return key

    def _keypress_max_left(self, size: tuple[int, int]) -> None:
        self.focus_position = next(iter(self.body.positions()))
        self.set_focus_valign(VAlign.TOP)

    def _keypress_max_right(self, size: tuple[int, int]) -> None:
        self.focus_position = next(iter(self.body.positions(reverse=True)))
        self.set_focus_valign(VAlign.BOTTOM)

    def _keypress_up(self, size: tuple[int, int]) -> bool | None:
        (maxcol, maxrow) = size

        middle, top, _bottom = self.calculate_visible((maxcol, maxrow), True)
        if middle is None:
            return True

        focus_row_offset, focus_widget, focus_pos, _ignore, cursor = middle  # pylint: disable=unpacking-non-sequence
        _trim_top, fill_above = top  # pylint: disable=unpacking-non-sequence

        row_offset = focus_row_offset

        # look for selectable widget above
        pos = focus_pos
        widget = None
        for widget, pos, rows in fill_above:
            row_offset -= rows
            if rows and widget.selectable():
                # this one will do
                self.change_focus((maxcol, maxrow), pos, row_offset, "below")
                return None

        # at this point we must scroll
        row_offset += 1
        self._invalidate()

        while row_offset > 0:
            # need to scroll in another candidate widget
            widget, pos = self._body.get_prev(pos)
            if widget is None:
                # cannot scroll any further
                return True  # keypress not handled
            rows = widget.rows((maxcol,), True)
            row_offset -= rows
            if rows and widget.selectable():
                # this one will do
                self.change_focus((maxcol, maxrow), pos, row_offset, "below")
                return None

        if not focus_widget.selectable() or focus_row_offset + 1 >= maxrow:
            # just take top one if focus is not selectable
            # or if focus has moved out of view
            if widget is None:
                self.shift_focus((maxcol, maxrow), row_offset)
                return None
            self.change_focus((maxcol, maxrow), pos, row_offset, "below")
            return None

        # check if cursor will stop scroll from taking effect
        if cursor is not None:
            _x, y = cursor
            if y + focus_row_offset + 1 >= maxrow:
                # cursor position is a problem,
                # choose another focus
                if widget is None:
                    # try harder to get prev widget
                    widget, pos = self._body.get_prev(pos)
                    if widget is None:
                        return None  # can't do anything
                    rows = widget.rows((maxcol,), True)
                    row_offset -= rows

                if -row_offset >= rows:
                    # must scroll further than 1 line
                    row_offset = -(rows - 1)

                self.change_focus((maxcol, maxrow), pos, row_offset, "below")
                return None

        # if all else fails, just shift the current focus.
        self.shift_focus((maxcol, maxrow), focus_row_offset + 1)
        return None

    def _keypress_down(self, size: tuple[int, int]) -> bool | None:
        (maxcol, maxrow) = size

        middle, _top, bottom = self.calculate_visible((maxcol, maxrow), True)
        if middle is None:
            return True

        focus_row_offset, focus_widget, focus_pos, focus_rows, cursor = middle  # pylint: disable=unpacking-non-sequence
        _trim_bottom, fill_below = bottom  # pylint: disable=unpacking-non-sequence

        row_offset = focus_row_offset + focus_rows
        rows = focus_rows

        # look for selectable widget below
        pos = focus_pos
        widget = None
        for widget, pos, rows in fill_below:
            if rows and widget.selectable():
                # this one will do
                self.change_focus((maxcol, maxrow), pos, row_offset, "above")
                return None
            row_offset += rows

        # at this point we must scroll
        row_offset -= 1
        self._invalidate()

        while row_offset < maxrow:
            # need to scroll in another candidate widget
            widget, pos = self._body.get_next(pos)
            if widget is None:
                # cannot scroll any further
                return True  # keypress not handled
            rows = widget.rows((maxcol,))
            if rows and widget.selectable():
                # this one will do
                self.change_focus((maxcol, maxrow), pos, row_offset, "above")
                return None
            row_offset += rows

        if not focus_widget.selectable() or focus_row_offset + focus_rows - 1 <= 0:
            # just take bottom one if current is not selectable
            # or if focus has moved out of view
            if widget is None:
                self.shift_focus((maxcol, maxrow), row_offset - rows)
                return None
            self.change_focus((maxcol, maxrow), pos, row_offset - rows, "above")
            return None

        # check if cursor will stop scroll from taking effect
        if cursor is not None:
            _x, y = cursor
            if y + focus_row_offset - 1 < 0:
                # cursor position is a problem,
                # choose another focus
                if widget is None:
                    # try harder to get next widget
                    widget, pos = self._body.get_next(pos)
                    if widget is None:
                        return None  # can't do anything
                else:
                    row_offset -= rows

                if row_offset >= maxrow:
                    # must scroll further than 1 line
                    row_offset = maxrow - 1

                self.change_focus(
                    (maxcol, maxrow),
                    pos,
                    row_offset,
                    "above",
                )
                return None

        # if all else fails, keep the current focus.
        self.shift_focus((maxcol, maxrow), focus_row_offset - 1)
        return None

    def _keypress_page_up(self, size: tuple[int, int]) -> bool | None:
        (maxcol, maxrow) = size

        middle, top, _bottom = self.calculate_visible((maxcol, maxrow), True)
        if middle is None:
            return True

        row_offset, focus_widget, focus_pos, focus_rows, cursor = middle  # pylint: disable=unpacking-non-sequence
        _trim_top, fill_above = top  # pylint: disable=unpacking-non-sequence

        # topmost_visible is row_offset rows above top row of
        # focus (+ve) or -row_offset rows below top row of focus (-ve)
        topmost_visible = row_offset

        # scroll_from_row is (first match)
        # 1. topmost visible row if focus is not selectable
        # 2. row containing cursor if focus has a cursor
        # 3. top row of focus widget if it is visible
        # 4. topmost visible row otherwise
        if not focus_widget.selectable():
            scroll_from_row = topmost_visible
        elif cursor is not None:
            _x, y = cursor
            scroll_from_row = -y
        elif row_offset >= 0:
            scroll_from_row = 0
        else:
            scroll_from_row = topmost_visible

        # snap_rows is maximum extra rows to scroll when
        # snapping to new a focus
        snap_rows = topmost_visible - scroll_from_row

        # move row_offset to the new desired value (1 "page" up)
        row_offset = scroll_from_row + maxrow

        # not used below:
        scroll_from_row = topmost_visible = None

        # gather potential target widgets and add current focus
        t = [(row_offset, focus_widget, focus_pos, focus_rows)]
        pos = focus_pos
        # include widgets from calculate_visible(..)
        for widget, pos, rows in fill_above:
            row_offset -= rows
            t.append((row_offset, widget, pos, rows))
        # add newly visible ones, including within snap_rows
        snap_region_start = len(t)
        while row_offset > -snap_rows:
            widget, pos = self._body.get_prev(pos)
            if widget is None:
                break
            rows = widget.rows((maxcol,))
            row_offset -= rows
            # determine if one below puts current one into snap rgn
            if row_offset > 0:
                snap_region_start += 1
            t.append((row_offset, widget, pos, rows))

        # if we can't fill the top we need to adjust the row offsets
        row_offset, _w, _p, _r = t[-1]
        if row_offset > 0:
            adjust = -row_offset
            t = [(ro + adjust, w, p, r) for (ro, w, p, r) in t]

        # if focus_widget (first in t) is off edge, remove it
        row_offset, _w, _p, _r = t[0]
        if row_offset >= maxrow:
            del t[0]
            snap_region_start -= 1

        # we'll need this soon
        self.update_pref_col_from_focus((maxcol, maxrow))

        # choose the topmost selectable and (newly) visible widget
        # search within snap_rows then visible region
        search_order = list(range(snap_region_start, len(t))) + list(range(snap_region_start - 1, -1, -1))
        # assert 0, repr((t, search_order))
        bad_choices = []
        cut_off_selectable_chosen = 0
        for i in search_order:
            row_offset, widget, pos, rows = t[i]
            if not widget.selectable():
                continue

            if not rows:
                continue

            # try selecting this widget
            pref_row = max(0, -row_offset)

            # if completely within snap region, adjust row_offset
            if rows + row_offset <= 0:
                self.change_focus(
                    (maxcol, maxrow),
                    pos,
                    -(rows - 1),
                    "below",
                    (self.pref_col, rows - 1),
                    snap_rows - ((-row_offset) - (rows - 1)),
                )
            else:
                self.change_focus(
                    (maxcol, maxrow),
                    pos,
                    row_offset,
                    "below",
                    (self.pref_col, pref_row),
                    snap_rows,
                )

            # if we're as far up as we can scroll, take this one
            if fill_above and self._body.get_prev(fill_above[-1][1]) == (None, None):
                pass  # return

            # find out where that actually puts us
            middle, top, _bottom = self.calculate_visible((maxcol, maxrow), True)
            act_row_offset, _ign1, _ign2, _ign3, _ign4 = middle  # pylint: disable=unpacking-non-sequence

            # discard chosen widget if it will reduce scroll amount
            # because of a fixed cursor (absolute last resort)
            if act_row_offset > row_offset + snap_rows:
                bad_choices.append(i)
                continue
            if act_row_offset < row_offset:
                bad_choices.append(i)
                continue

            # also discard if off top edge (second last resort)
            if act_row_offset < 0:
                bad_choices.append(i)
                cut_off_selectable_chosen = 1
                continue

            return None

        # anything selectable is better than what follows:
        if cut_off_selectable_chosen:
            return None

        if fill_above and focus_widget.selectable() and self._body.get_prev(fill_above[-1][1]) == (None, None):
            # if we're at the top and have a selectable, return
            pass  # return

        # if still none found choose the topmost widget
        good_choices = [j for j in search_order if j not in bad_choices]
        for i in good_choices + search_order:
            row_offset, widget, pos, rows = t[i]
            if pos == focus_pos:
                continue

            if not rows:  # never focus a 0-height widget
                continue

            # if completely within snap region, adjust row_offset
            if rows + row_offset <= 0:
                snap_rows -= (-row_offset) - (rows - 1)
                row_offset = -(rows - 1)

            self.change_focus((maxcol, maxrow), pos, row_offset, "below", None, snap_rows)
            return None

        # no choices available, just shift current one
        self.shift_focus((maxcol, maxrow), min(maxrow - 1, row_offset))

        # final check for pathological case where we may fall short
        middle, top, _bottom = self.calculate_visible((maxcol, maxrow), True)
        act_row_offset, _ign1, pos, _ign2, _ign3 = middle  # pylint: disable=unpacking-non-sequence
        if act_row_offset >= row_offset:
            # no problem
            return None

        # fell short, try to select anything else above
        if not t:
            return None
        _ign1, _ign2, pos, _ign3 = t[-1]
        widget, pos = self._body.get_prev(pos)
        if widget is None:
            # no dice, we're stuck here
            return None
        # bring in only one row if possible
        rows = widget.rows((maxcol,), True)
        self.change_focus(
            (maxcol, maxrow),
            pos,
            -(rows - 1),
            "below",
            (self.pref_col, rows - 1),
            0,
        )
        return None

    def _keypress_page_down(self, size: tuple[int, int]) -> bool | None:
        (maxcol, maxrow) = size

        middle, _top, bottom = self.calculate_visible((maxcol, maxrow), True)
        if middle is None:
            return True

        row_offset, focus_widget, focus_pos, focus_rows, cursor = middle  # pylint: disable=unpacking-non-sequence
        _trim_bottom, fill_below = bottom  # pylint: disable=unpacking-non-sequence

        # bottom_edge is maxrow-focus_pos rows below top row of focus
        bottom_edge = maxrow - row_offset

        # scroll_from_row is (first match)
        # 1. bottom edge if focus is not selectable
        # 2. row containing cursor + 1 if focus has a cursor
        # 3. bottom edge of focus widget if it is visible
        # 4. bottom edge otherwise
        if not focus_widget.selectable():
            scroll_from_row = bottom_edge
        elif cursor is not None:
            _x, y = cursor
            scroll_from_row = y + 1
        elif bottom_edge >= focus_rows:
            scroll_from_row = focus_rows
        else:
            scroll_from_row = bottom_edge

        # snap_rows is maximum extra rows to scroll when
        # snapping to new a focus
        snap_rows = bottom_edge - scroll_from_row

        # move row_offset to the new desired value (1 "page" down)
        row_offset = -scroll_from_row

        # not used below:
        scroll_from_row = bottom_edge = None

        # gather potential target widgets and add current focus
        t = [(row_offset, focus_widget, focus_pos, focus_rows)]
        pos = focus_pos
        row_offset += focus_rows
        # include widgets from calculate_visible(..)
        for widget, pos, rows in fill_below:
            t.append((row_offset, widget, pos, rows))
            row_offset += rows
        # add newly visible ones, including within snap_rows
        snap_region_start = len(t)
        while row_offset < maxrow + snap_rows:
            widget, pos = self._body.get_next(pos)
            if widget is None:
                break
            rows = widget.rows((maxcol,))
            t.append((row_offset, widget, pos, rows))
            row_offset += rows
            # determine if one above puts current one into snap rgn
            if row_offset < maxrow:
                snap_region_start += 1

        # if we can't fill the bottom we need to adjust the row offsets
        row_offset, _w, _p, rows = t[-1]
        if row_offset + rows < maxrow:
            adjust = maxrow - (row_offset + rows)
            t = [(ro + adjust, w, p, r) for (ro, w, p, r) in t]

        # if focus_widget (first in t) is off edge, remove it
        row_offset, _w, _p, rows = t[0]
        if row_offset + rows <= 0:
            del t[0]
            snap_region_start -= 1

        # we'll need this soon
        self.update_pref_col_from_focus((maxcol, maxrow))

        # choose the bottommost selectable and (newly) visible widget
        # search within snap_rows then visible region
        search_order = list(range(snap_region_start, len(t))) + list(range(snap_region_start - 1, -1, -1))
        # assert 0, repr((t, search_order))
        bad_choices = []
        cut_off_selectable_chosen = 0
        for i in search_order:
            row_offset, widget, pos, rows = t[i]
            if not widget.selectable():
                continue

            if not rows:
                continue

            # try selecting this widget
            pref_row = min(maxrow - row_offset - 1, rows - 1)

            # if completely within snap region, adjust row_offset
            if row_offset >= maxrow:
                self.change_focus(
                    (maxcol, maxrow),
                    pos,
                    maxrow - 1,
                    "above",
                    (self.pref_col, 0),
                    snap_rows + maxrow - row_offset - 1,
                )
            else:
                self.change_focus(
                    (maxcol, maxrow),
                    pos,
                    row_offset,
                    "above",
                    (self.pref_col, pref_row),
                    snap_rows,
                )

            # find out where that actually puts us
            middle, _top, bottom = self.calculate_visible((maxcol, maxrow), True)
            act_row_offset, _ign1, _ign2, _ign3, _ign4 = middle  # pylint: disable=unpacking-non-sequence

            # discard chosen widget if it will reduce scroll amount
            # because of a fixed cursor (absolute last resort)
            if act_row_offset < row_offset - snap_rows:
                bad_choices.append(i)
                continue
            if act_row_offset > row_offset:
                bad_choices.append(i)
                continue

            # also discard if off top edge (second last resort)
            if act_row_offset + rows > maxrow:
                bad_choices.append(i)
                cut_off_selectable_chosen = 1
                continue

            return None

        # anything selectable is better than what follows:
        if cut_off_selectable_chosen:
            return None

        # if still none found choose the bottommost widget
        good_choices = [j for j in search_order if j not in bad_choices]
        for i in good_choices + search_order:
            row_offset, widget, pos, rows = t[i]
            if pos == focus_pos:
                continue

            if not rows:  # never focus a 0-height widget
                continue

            # if completely within snap region, adjust row_offset
            if row_offset >= maxrow:
                snap_rows -= snap_rows + maxrow - row_offset - 1
                row_offset = maxrow - 1

            self.change_focus((maxcol, maxrow), pos, row_offset, "above", None, snap_rows)
            return None

        # no choices available, just shift current one
        self.shift_focus((maxcol, maxrow), max(1 - focus_rows, row_offset))

        # final check for pathological case where we may fall short
        middle, _top, bottom = self.calculate_visible((maxcol, maxrow), True)
        act_row_offset, _ign1, pos, _ign2, _ign3 = middle  # pylint: disable=unpacking-non-sequence
        if act_row_offset <= row_offset:
            # no problem
            return None

        # fell short, try to select anything else below
        if not t:
            return None
        _ign1, _ign2, pos, _ign3 = t[-1]
        widget, pos = self._body.get_next(pos)
        if widget is None:
            # no dice, we're stuck here
            return None
        # bring in only one row if possible
        rows = widget.rows((maxcol,), True)
        self.change_focus(
            (maxcol, maxrow),
            pos,
            maxrow - 1,
            "above",
            (self.pref_col, 0),
            0,
        )
        return None

    def mouse_event(
        self,
        size: tuple[int, int],  # type: ignore[override]
        event,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        """
        Pass the event to the contained widgets.
        May change focus on button 1 press.
        """
        from urwid.util import is_mouse_press

        (maxcol, maxrow) = size
        middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus=True)
        if middle is None:
            return False

        _ignore, focus_widget, focus_pos, focus_rows, _cursor = middle  # pylint: disable=unpacking-non-sequence
        trim_top, fill_above = top  # pylint: disable=unpacking-non-sequence
        _ignore, fill_below = bottom  # pylint: disable=unpacking-non-sequence

        fill_above.reverse()  # fill_above is in bottom-up order
        w_list = [*fill_above, (focus_widget, focus_pos, focus_rows), *fill_below]

        wrow = -trim_top
        for w, w_pos, w_rows in w_list:  # noqa: B007  # magic with scope
            if wrow + w_rows > row:
                break
            wrow += w_rows
        else:
            return False

        focus = focus and w == focus_widget
        if is_mouse_press(event) and button == 1 and w.selectable():
            self.change_focus((maxcol, maxrow), w_pos, wrow)

        if not hasattr(w, "mouse_event"):
            warnings.warn(
                f"{w.__class__.__module__}.{w.__class__.__name__} is not subclass of Widget",
                DeprecationWarning,
                stacklevel=2,
            )
            return False

        handled = w.mouse_event((maxcol,), event, button, col, row - wrow, focus)
        if handled:
            return True

        if is_mouse_press(event):
            if button == 4:
                return not self._keypress_up((maxcol, maxrow))

            if button == 5:
                return not self._keypress_down((maxcol, maxrow))

        return False

    def ends_visible(self, size: tuple[int, int], focus: bool = False) -> list[Literal["top", "bottom"]]:
        """
        Return a list that may contain ``'top'`` and/or ``'bottom'``.

        i.e. this function will return one of: [], [``'top'``],
        [``'bottom'``] or [``'top'``, ``'bottom'``].

        convenience function for checking whether the top and bottom
        of the list are visible
        """
        (maxcol, maxrow) = size
        result = []
        middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus=focus)
        if middle is None:  # empty listbox
            return ["top", "bottom"]
        trim_top, above = top  # pylint: disable=unpacking-non-sequence
        trim_bottom, below = bottom  # pylint: disable=unpacking-non-sequence

        if trim_bottom == 0:
            row_offset, _w, pos, rows, _c = middle  # pylint: disable=unpacking-non-sequence
            row_offset += rows
            for _w, pos, rows in below:  # noqa: B007  # magic with scope
                row_offset += rows
            if row_offset < maxrow or (self._body.get_next(pos) == (None, None)):
                result.append("bottom")

        if trim_top == 0:
            row_offset, _w, pos, _rows, _c = middle  # pylint: disable=unpacking-non-sequence
            for _w, pos, rows in above:  # noqa: B007  # magic with scope
                row_offset -= rows
            if self._body.get_prev(pos) == (None, None):
                result.insert(0, "top")

        return result

    def __iter__(self):
        """
        Return an iterator over the positions in this ListBox.

        If self._body does not implement positions() then iterate
        from the focus widget down to the bottom, then from above
        the focus up to the top.  This is the best we can do with
        a minimal list walker implementation.
        """
        positions_fn = getattr(self._body, "positions", None)
        if positions_fn:
            yield from positions_fn()
            return

        focus_widget, focus_pos = self._body.get_focus()
        if focus_widget is None:
            return
        pos = focus_pos
        while True:
            yield pos
            w, pos = self._body.get_next(pos)
            if not w:
                break
        pos = focus_pos
        while True:
            w, pos = self._body.get_prev(pos)
            if not w:
                break
            yield pos

    def __reversed__(self):
        """
        Return a reversed iterator over the positions in this ListBox.

        If :attr:`body` does not implement :meth:`positions` then iterate
        from above the focus widget up to the top, then from the focus
        widget down to the bottom.  Note that this is not actually the
        reverse of what `__iter__()` produces, but this is the best we can
        do with a minimal list walker implementation.
        """
        positions_fn = getattr(self._body, "positions", None)
        if positions_fn:
            yield from positions_fn(reverse=True)
            return

        focus_widget, focus_pos = self._body.get_focus()
        if focus_widget is None:
            return
        pos = focus_pos
        while True:
            w, pos = self._body.get_prev(pos)
            if not w:
                break
            yield pos
        pos = focus_pos
        while True:
            yield pos
            w, pos = self._body.get_next(pos)
            if not w:
                break
urwid-2.6.16/urwid/widget/monitored_list.py000066400000000000000000000455001470350774000207750ustar00rootroot00000000000000# Urwid MonitoredList class
#    Copyright (C) 2004-2011  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/

from __future__ import annotations

import functools
import typing
import warnings

if typing.TYPE_CHECKING:
    from collections.abc import Callable, Collection, Iterable, Iterator

    from typing_extensions import Concatenate, ParamSpec, Self

    ArgSpec = ParamSpec("ArgSpec")
    Ret = typing.TypeVar("Ret")

__all__ = ("MonitoredFocusList", "MonitoredList")

_T = typing.TypeVar("_T")


def _call_modified(
    fn: Callable[Concatenate[MonitoredList, ArgSpec], Ret]
) -> Callable[Concatenate[MonitoredList, ArgSpec], Ret]:
    @functools.wraps(fn)
    def call_modified_wrapper(self: MonitoredList, *args: ArgSpec.args, **kwargs: ArgSpec.kwargs) -> Ret:
        rval = fn(self, *args, **kwargs)
        self._modified()  # pylint: disable=protected-access
        return rval

    return call_modified_wrapper


class MonitoredList(typing.List[_T], typing.Generic[_T]):
    """
    This class can trigger a callback any time its contents are changed
    with the usual list operations append, extend, etc.
    """

    def _modified(self) -> None:  # pylint: disable=method-hidden  # monkeypatch used
        pass

    def set_modified_callback(self, callback: Callable[[], typing.Any]) -> None:
        """
        Assign a callback function with no parameters that is called any
        time the list is modified.  Callback's return value is ignored.

        >>> import sys
        >>> ml = MonitoredList([1,2,3])
        >>> ml.set_modified_callback(lambda: sys.stdout.write("modified\\n"))
        >>> ml
        MonitoredList([1, 2, 3])
        >>> ml.append(10)
        modified
        >>> len(ml)
        4
        >>> ml += [11, 12, 13]
        modified
        >>> ml[:] = ml[:2] + ml[-2:]
        modified
        >>> ml
        MonitoredList([1, 2, 12, 13])
        """
        self._modified = callback  # monkeypatch

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({list(self)!r})"

    # noinspection PyMethodParameters
    def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
        for item in self:
            yield None, item

    @_call_modified
    def __add__(self, __value: list[_T]) -> Self:
        return super().__add__(__value)

    @_call_modified
    def __delitem__(self, __key: typing.SupportsIndex | slice) -> None:
        super().__delitem__(__key)

    @_call_modified
    def __iadd__(self, __value: Iterable[_T]) -> Self:
        return super().__iadd__(__value)

    @_call_modified
    def __rmul__(self, __value: typing.SupportsIndex) -> Self:
        return super().__rmul__(__value)

    @_call_modified
    def __imul__(self, __value: typing.SupportsIndex) -> Self:
        return super().__imul__(__value)

    @typing.overload
    @_call_modified
    def __setitem__(self, __key: typing.SupportsIndex, __value: _T) -> None: ...

    @typing.overload
    @_call_modified
    def __setitem__(self, __key: slice, __value: Iterable[_T]) -> None: ...

    @_call_modified
    def __setitem__(self, __key: typing.SupportsIndex | slice, __value: _T | Iterable[_T]) -> None:
        super().__setitem__(__key, __value)

    @_call_modified
    def append(self, __object: _T) -> None:
        super().append(__object)

    @_call_modified
    def extend(self, __iterable: Iterable[_T]) -> None:
        super().extend(__iterable)

    @_call_modified
    def pop(self, __index: typing.SupportsIndex = -1) -> _T:
        return super().pop(__index)

    @_call_modified
    def insert(self, __index: typing.SupportsIndex, __object: _T) -> None:
        super().insert(__index, __object)

    @_call_modified
    def remove(self, __value: _T) -> None:
        super().remove(__value)

    @_call_modified
    def reverse(self) -> None:
        super().reverse()

    @_call_modified
    def sort(self, *, key: Callable[[_T], typing.Any] | None = None, reverse: bool = False) -> None:
        super().sort(key=key, reverse=reverse)

    @_call_modified
    def clear(self) -> None:
        super().clear()


class MonitoredFocusList(MonitoredList[_T], typing.Generic[_T]):
    """
    This class can trigger a callback any time its contents are modified,
    before and/or after modification, and any time the focus index is changed.
    """

    def __init__(self, *args, focus: int = 0, **kwargs) -> None:
        """
        This is a list that tracks one item as the focus item.  If items
        are inserted or removed it will update the focus.

        >>> ml = MonitoredFocusList([10, 11, 12, 13, 14], focus=3)
        >>> ml
        MonitoredFocusList([10, 11, 12, 13, 14], focus=3)
        >>> del(ml[1])
        >>> ml
        MonitoredFocusList([10, 12, 13, 14], focus=2)
        >>> ml[:2] = [50, 51, 52, 53]
        >>> ml
        MonitoredFocusList([50, 51, 52, 53, 13, 14], focus=4)
        >>> ml[4] = 99
        >>> ml
        MonitoredFocusList([50, 51, 52, 53, 99, 14], focus=4)
        >>> ml[:] = []
        >>> ml
        MonitoredFocusList([], focus=None)
        """

        super().__init__(*args, **kwargs)

        self._focus = focus
        self._focus_modified = lambda ml, indices, new_items: None

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({list(self)!r}, focus={self.focus!r})"

    @property
    def focus(self) -> int | None:
        """
        Get/set the focus index.  This value is read as None when the list
        is empty, and may only be set to a value between 0 and len(self)-1
        or an IndexError will be raised.

        Return the index of the item "in focus" or None if
        the list is empty.

        >>> MonitoredFocusList([1,2,3], focus=2).focus
        2
        >>> MonitoredFocusList().focus
        """
        if not self:
            return None
        return self._focus

    @focus.setter
    def focus(self, index: int) -> None:
        """
        index -- index into this list, any index out of range will
            raise an IndexError, except when the list is empty and
            the index passed is ignored.

        This function may call self._focus_changed when the focus
        is modified, passing the new focus position to the
        callback just before changing the old focus setting.
        That method may be overridden on the
        instance with set_focus_changed_callback().

        >>> ml = MonitoredFocusList([9, 10, 11])
        >>> ml.focus = 2; ml.focus
        2
        >>> ml.focus = 0; ml.focus
        0
        >>> ml.focus = -2
        Traceback (most recent call last):
        ...
        IndexError: focus index is out of range: -2
        """
        if not self:
            self._focus = 0
            return
        if not isinstance(index, int):
            raise TypeError("index must be an integer")
        if index < 0 or index >= len(self):
            raise IndexError(f"focus index is out of range: {index}")

        if index != self._focus:
            self._focus_changed(index)
        self._focus = index

    def _get_focus(self) -> int | None:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus` is deprecated, "
            f"please use `{self.__class__.__name__}.focus` property",
            DeprecationWarning,
            stacklevel=3,
        )
        return self.focus

    def _set_focus(self, index: int) -> None:
        warnings.warn(
            f"method `{self.__class__.__name__}._set_focus` is deprecated, "
            f"please use `{self.__class__.__name__}.focus` property",
            DeprecationWarning,
            stacklevel=3,
        )
        self.focus = index

    def _focus_changed(self, new_focus: int) -> None:  # pylint: disable=method-hidden  # monkeypatch used
        pass

    def set_focus_changed_callback(self, callback: Callable[[int], typing.Any]) -> None:
        """
        Assign a callback to be called when the focus index changes
        for any reason.  The callback is in the form:

        callback(new_focus)
        new_focus -- new focus index

        >>> import sys
        >>> ml = MonitoredFocusList([1,2,3], focus=1)
        >>> ml.set_focus_changed_callback(lambda f: sys.stdout.write("focus: %d\\n" % (f,)))
        >>> ml
        MonitoredFocusList([1, 2, 3], focus=1)
        >>> ml.append(10)
        >>> ml.insert(1, 11)
        focus: 2
        >>> ml
        MonitoredFocusList([1, 11, 2, 3, 10], focus=2)
        >>> del ml[:2]
        focus: 0
        >>> ml[:0] = [12, 13, 14]
        focus: 3
        >>> ml.focus = 5
        focus: 5
        >>> ml
        MonitoredFocusList([12, 13, 14, 2, 3, 10], focus=5)
        """
        self._focus_changed = callback  # Monkeypatch

    def _validate_contents_modified(  # pylint: disable=method-hidden  # monkeypatch used
        self,
        indices: tuple[int, int, int],
        new_items: Collection[_T],
    ) -> int | None:
        return None

    def set_validate_contents_modified(self, callback: Callable[[tuple[int, int, int], Collection[_T]], int | None]):
        """
        Assign a callback function to handle validating changes to the list.
        This may raise an exception if the change should not be performed.
        It may also return an integer position to be the new focus after the
        list is modified, or None to use the default behaviour.

        The callback is in the form:

        callback(indices, new_items)
        indices -- a (start, stop, step) tuple whose range covers the
            items being modified
        new_items -- an iterable of items replacing those at range(*indices),
            empty if items are being removed, if step==1 this list may
            contain any number of items
        """
        self._validate_contents_modified = callback  # Monkeypatch

    def _adjust_focus_on_contents_modified(self, slc: slice, new_items: Collection[_T] = ()) -> int:
        """
        Default behaviour is to move the focus to the item following
        any removed items, unless that item was simply replaced.

        Failing that choose the last item in the list.

        returns focus position for after change is applied
        """
        num_new_items = len(new_items)
        start, stop, step = indices = slc.indices(len(self))
        num_removed = len(list(range(*indices)))

        focus = self._validate_contents_modified(indices, new_items)
        if focus is not None:
            return focus

        focus = self._focus
        if step == 1:
            if start + num_new_items <= focus < stop:
                focus = stop
            # adjust for added/removed items
            if stop <= focus:
                focus += num_new_items - (stop - start)

        else:  # noqa: PLR5501  # pylint: disable=else-if-used  # readability
            if not num_new_items:
                # extended slice being removed
                if focus in range(start, stop, step):
                    focus += 1

                # adjust for removed items
                focus -= len(list(range(start, min(focus, stop), step)))

        return min(focus, len(self) + num_new_items - num_removed - 1)

    # override all the list methods that modify the list

    def __delitem__(self, y: int | slice) -> None:
        """
        >>> ml = MonitoredFocusList([0,1,2,3,4], focus=2)
        >>> del ml[3]; ml
        MonitoredFocusList([0, 1, 2, 4], focus=2)
        >>> del ml[-1]; ml
        MonitoredFocusList([0, 1, 2], focus=2)
        >>> del ml[0]; ml
        MonitoredFocusList([1, 2], focus=1)
        >>> del ml[1]; ml
        MonitoredFocusList([1], focus=0)
        >>> del ml[0]; ml
        MonitoredFocusList([], focus=None)
        >>> ml = MonitoredFocusList([5,4,6,4,5,4,6,4,5], focus=4)
        >>> del ml[1::2]; ml
        MonitoredFocusList([5, 6, 5, 6, 5], focus=2)
        >>> del ml[::2]; ml
        MonitoredFocusList([6, 6], focus=1)
        >>> ml = MonitoredFocusList([0,1,2,3,4,6,7], focus=2)
        >>> del ml[-2:]; ml
        MonitoredFocusList([0, 1, 2, 3, 4], focus=2)
        >>> del ml[-4:-2]; ml
        MonitoredFocusList([0, 3, 4], focus=1)
        >>> del ml[:]; ml
        MonitoredFocusList([], focus=None)
        """
        if isinstance(y, slice):
            focus = self._adjust_focus_on_contents_modified(y)
        else:
            focus = self._adjust_focus_on_contents_modified(slice(y, y + 1 or None))
        super().__delitem__(y)
        self.focus = focus

    @typing.overload
    def __setitem__(self, i: int, y: _T) -> None: ...

    @typing.overload
    def __setitem__(self, i: slice, y: Collection[_T]) -> None: ...

    def __setitem__(self, i: int | slice, y: _T | Collection[_T]) -> None:
        """
        >>> def modified(indices, new_items):
        ...     print(f"range{indices!r} <- {new_items!r}" )
        >>> ml = MonitoredFocusList([0,1,2,3], focus=2)
        >>> ml.set_validate_contents_modified(modified)
        >>> ml[0] = 9
        range(0, 1, 1) <- [9]
        >>> ml[2] = 6
        range(2, 3, 1) <- [6]
        >>> ml.focus
        2
        >>> ml[-1] = 8
        range(3, 4, 1) <- [8]
        >>> ml
        MonitoredFocusList([9, 1, 6, 8], focus=2)
        >>> ml[1::2] = [12, 13]
        range(1, 4, 2) <- [12, 13]
        >>> ml[::2] = [10, 11]
        range(0, 4, 2) <- [10, 11]
        >>> ml[-3:-1] = [21, 22, 23]
        range(1, 3, 1) <- [21, 22, 23]
        >>> ml
        MonitoredFocusList([10, 21, 22, 23, 13], focus=2)
        >>> ml[:] = []
        range(0, 5, 1) <- []
        >>> ml
        MonitoredFocusList([], focus=None)
        """
        if isinstance(i, slice):
            focus = self._adjust_focus_on_contents_modified(i, y)
        else:
            focus = self._adjust_focus_on_contents_modified(slice(i, i + 1 or None), [y])
        super().__setitem__(i, y)
        self.focus = focus

    def __imul__(self, n: int):
        """
        >>> def modified(indices, new_items):
        ...     print(f"range{indices!r} <- {list(new_items)!r}" )
        >>> ml = MonitoredFocusList([0,1,2], focus=2)
        >>> ml.set_validate_contents_modified(modified)
        >>> ml *= 3
        range(3, 3, 1) <- [0, 1, 2, 0, 1, 2]
        >>> ml
        MonitoredFocusList([0, 1, 2, 0, 1, 2, 0, 1, 2], focus=2)
        >>> ml *= 0
        range(0, 9, 1) <- []
        >>> print(ml.focus)
        None
        """
        if n > 0:
            focus = self._adjust_focus_on_contents_modified(slice(len(self), len(self)), list(self) * (n - 1))
        else:  # all contents are being removed
            focus = self._adjust_focus_on_contents_modified(slice(0, len(self)))
        rval = super().__imul__(n)
        self.focus = focus
        return rval

    def append(self, item: _T) -> None:
        """
        >>> def modified(indices, new_items):
        ...     print(f"range{indices!r} <- {new_items!r}" )
        >>> ml = MonitoredFocusList([0,1,2], focus=2)
        >>> ml.set_validate_contents_modified(modified)
        >>> ml.append(6)
        range(3, 3, 1) <- [6]
        """
        focus = self._adjust_focus_on_contents_modified(slice(len(self), len(self)), [item])
        super().append(item)
        self.focus = focus

    def extend(self, items: Collection[_T]) -> None:
        """
        >>> def modified(indices, new_items):
        ...     print(f"range{indices!r} <- {list(new_items)!r}" )
        >>> ml = MonitoredFocusList([0,1,2], focus=2)
        >>> ml.set_validate_contents_modified(modified)
        >>> ml.extend((6,7,8))
        range(3, 3, 1) <- [6, 7, 8]
        """
        focus = self._adjust_focus_on_contents_modified(slice(len(self), len(self)), items)
        super().extend(items)
        self.focus = focus

    def insert(self, index: int, item: _T) -> None:
        """
        >>> ml = MonitoredFocusList([0,1,2,3], focus=2)
        >>> ml.insert(-1, -1); ml
        MonitoredFocusList([0, 1, 2, -1, 3], focus=2)
        >>> ml.insert(0, -2); ml
        MonitoredFocusList([-2, 0, 1, 2, -1, 3], focus=3)
        >>> ml.insert(3, -3); ml
        MonitoredFocusList([-2, 0, 1, -3, 2, -1, 3], focus=4)
        """
        focus = self._adjust_focus_on_contents_modified(slice(index, index), [item])
        super().insert(index, item)
        self.focus = focus

    def pop(self, index: int = -1) -> _T:
        """
        >>> ml = MonitoredFocusList([-2,0,1,-3,2,3], focus=4)
        >>> ml.pop(3); ml
        -3
        MonitoredFocusList([-2, 0, 1, 2, 3], focus=3)
        >>> ml.pop(0); ml
        -2
        MonitoredFocusList([0, 1, 2, 3], focus=2)
        >>> ml.pop(-1); ml
        3
        MonitoredFocusList([0, 1, 2], focus=2)
        >>> ml.pop(2); ml
        2
        MonitoredFocusList([0, 1], focus=1)
        """
        focus = self._adjust_focus_on_contents_modified(slice(index, index + 1 or None))
        rval = super().pop(index)
        self.focus = focus
        return rval

    def remove(self, value: _T) -> None:
        """
        >>> ml = MonitoredFocusList([-2,0,1,-3,2,-1,3], focus=4)
        >>> ml.remove(-3); ml
        MonitoredFocusList([-2, 0, 1, 2, -1, 3], focus=3)
        >>> ml.remove(-2); ml
        MonitoredFocusList([0, 1, 2, -1, 3], focus=2)
        >>> ml.remove(3); ml
        MonitoredFocusList([0, 1, 2, -1], focus=2)
        """
        index = self.index(value)
        focus = self._adjust_focus_on_contents_modified(slice(index, index + 1 or None))
        super().remove(value)
        self.focus = focus

    def reverse(self) -> None:
        """
        >>> ml = MonitoredFocusList([0,1,2,3,4], focus=1)
        >>> ml.reverse(); ml
        MonitoredFocusList([4, 3, 2, 1, 0], focus=3)
        """
        rval = super().reverse()
        self.focus = max(0, len(self) - self._focus - 1)
        return rval

    def sort(self, **kwargs) -> None:
        """
        >>> ml = MonitoredFocusList([-2,0,1,-3,2,-1,3], focus=4)
        >>> ml.sort(); ml
        MonitoredFocusList([-3, -2, -1, 0, 1, 2, 3], focus=5)
        """
        if not self:
            return None
        value = self[self._focus]
        rval = super().sort(**kwargs)
        self.focus = self.index(value)
        return rval

    if hasattr(list, "clear"):

        def clear(self) -> None:
            focus = self._adjust_focus_on_contents_modified(slice(0, 0))
            super().clear()
            self.focus = focus


def _test():
    import doctest

    doctest.testmod()


if __name__ == "__main__":
    _test()
urwid-2.6.16/urwid/widget/overlay.py000066400000000000000000001033111470350774000174160ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings

from urwid.canvas import CanvasOverlay, CompositeCanvas
from urwid.split_repr import remove_defaults

from .constants import (
    RELATIVE_100,
    Align,
    Sizing,
    VAlign,
    WHSettings,
    WrapMode,
    normalize_align,
    normalize_height,
    normalize_valign,
    normalize_width,
    simplify_align,
    simplify_height,
    simplify_valign,
    simplify_width,
)
from .container import WidgetContainerListContentsMixin, WidgetContainerMixin
from .filler import calculate_top_bottom_filler
from .padding import calculate_left_right_padding
from .widget import Widget, WidgetError, WidgetWarning

if typing.TYPE_CHECKING:
    from collections.abc import Iterator, MutableSequence, Sequence

    from typing_extensions import Literal


TopWidget = typing.TypeVar("TopWidget")
BottomWidget = typing.TypeVar("BottomWidget")


class OverlayError(WidgetError):
    """Overlay specific errors."""


class OverlayWarning(WidgetWarning):
    """Overlay specific warnings."""


def _check_widget_subclass(widget: Widget) -> None:
    if not isinstance(widget, Widget):
        obj_class_path = f"{widget.__class__.__module__}.{widget.__class__.__name__}"
        warnings.warn(
            f"{obj_class_path} is not subclass of Widget",
            DeprecationWarning,
            stacklevel=3,
        )


class OverlayOptions(typing.NamedTuple):
    align: Align | Literal[WHSettings.RELATIVE]
    align_amount: int | None
    width_type: WHSettings
    width_amount: int | None
    min_width: int | None
    left: int
    right: int
    valign_type: VAlign | Literal[WHSettings.RELATIVE]
    valign_amount: int | None
    height_type: WHSettings
    height_amount: int | None
    min_height: int | None
    top: int
    bottom: int


class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin, typing.Generic[TopWidget, BottomWidget]):
    """Overlay contains two widgets and renders one on top of the other.

    Top widget can be Box, Flow or Fixed.
    Bottom widget should be Box.
    """

    _selectable = True

    _DEFAULT_BOTTOM_OPTIONS = OverlayOptions(
        align=Align.LEFT,
        align_amount=None,
        width_type=WHSettings.RELATIVE,
        width_amount=100,
        min_width=None,
        left=0,
        right=0,
        valign_type=VAlign.TOP,
        valign_amount=None,
        height_type=WHSettings.RELATIVE,
        height_amount=100,
        min_height=None,
        top=0,
        bottom=0,
    )

    def __init__(
        self,
        top_w: TopWidget,
        bottom_w: BottomWidget,
        align: (
            Literal["left", "center", "right"]
            | Align
            | tuple[Literal["relative", "fixed left", "fixed right", WHSettings.RELATIVE], int]
        ),
        width: Literal["pack", WHSettings.PACK] | int | tuple[Literal["relative", WHSettings.RELATIVE], int] | None,
        valign: (
            Literal["top", "middle", "bottom"]
            | VAlign
            | tuple[Literal["relative", "fixed top", "fixed bottom", WHSettings.RELATIVE], int]
        ),
        height: Literal["pack", WHSettings.PACK] | int | tuple[Literal["relative", WHSettings.RELATIVE], int] | None,
        min_width: int | None = None,
        min_height: int | None = None,
        left: int = 0,
        right: int = 0,
        top: int = 0,
        bottom: int = 0,
    ) -> None:
        """
        :param top_w: a flow, box or fixed widget to overlay "on top".
        :type top_w: Widget
        :param bottom_w: a box widget to appear "below" previous widget.
        :type bottom_w: Widget
        :param align: alignment, one of ``'left'``, ``'center'``, ``'right'`` or
            (``'relative'``, *percentage* 0=left 100=right)
        :type align: Literal["left", "center", "right"] | tuple[Literal["relative"], int]
        :param width: width type, one of:
            ``'pack'``
              if *top_w* is a fixed widget
            *given width*
              integer number of columns wide
            (``'relative'``, *percentage of total width*)
              make *top_w* width related to container width
        :type width: Literal["pack"] | int | tuple[Literal["relative"], int]
        :param valign: alignment mode, one of ``'top'``, ``'middle'``, ``'bottom'`` or
            (``'relative'``, *percentage* 0=top 100=bottom)
        :type valign: Literal["top", "middle", "bottom"] | tuple[Literal["relative"], int]
        :param height: one of:
            ``'pack'``
              if *top_w* is a flow or fixed widget
            *given height*
              integer number of rows high
            (``'relative'``, *percentage of total height*)
              make *top_w* height related to container height
        :type height: Literal["pack"] | int | tuple[Literal["relative"], int]
        :param min_width: the minimum number of columns for *top_w* when width is not fixed.
        :type min_width: int
        :param min_height: minimum number of rows for *top_w* when height is not fixed.
        :type min_height: int
        :param left: a fixed number of columns to add on the left.
        :type left: int
        :param right: a fixed number of columns to add on the right.
        :type right: int
        :param top: a fixed number of rows to add on the top.
        :type top: int
        :param bottom: a fixed number of rows to add on the bottom.
        :type bottom: int

        Overlay widgets behave similarly to :class:`Padding` and :class:`Filler`
        widgets when determining the size and position of *top_w*. *bottom_w* is
        always rendered the full size available "below" *top_w*.
        """
        super().__init__()

        self.top_w = top_w
        self.bottom_w = bottom_w

        self.set_overlay_parameters(align, width, valign, height, min_width, min_height, left, right, top, bottom)

        _check_widget_subclass(top_w)
        _check_widget_subclass(bottom_w)

    def sizing(self) -> frozenset[Sizing]:
        """Actual widget sizing.

        :returns: Sizing information depends on the top widget sizing and sizing parameters.
        :rtype: frozenset[Sizing]

        Rules:
        * BOX sizing is always supported provided by the bottom widget
        * FLOW sizing is supported if top widget has:
        * * PACK height type and FLOW supported by the TOP widget
        * * BOX supported by TOP widget AND height amount AND height type GIVEN of min_height
        * FIXED sizing is supported if top widget has:
        * * PACK width type and FIXED supported by the TOP widget
        * * width amount and GIVEN width or min_width AND:
        * * * FLOW supported by the TOP widget AND PACK height type
        * * * BOX supported by the TOP widget AND height_amount and GIVEN height or min height
        """
        sizing = {Sizing.BOX}
        top_sizing = self.top_w.sizing()
        if self.width_type == WHSettings.PACK:
            if Sizing.FIXED in top_sizing:
                sizing.add(Sizing.FIXED)
            else:
                warnings.warn(
                    f"Top widget {self.top_w} should support sizing {Sizing.FIXED.upper()} "
                    f"for width type {WHSettings.PACK.upper()}",
                    OverlayWarning,
                    stacklevel=3,
                )

        elif self.height_type == WHSettings.PACK:
            if Sizing.FLOW in top_sizing:
                sizing.add(Sizing.FLOW)

                if self.width_amount and (self.width_type == WHSettings.GIVEN or self.min_width):
                    sizing.add(Sizing.FIXED)
            else:
                warnings.warn(
                    f"Top widget {self.top_w} should support sizing {Sizing.FLOW.upper()} "
                    f"for height type {self.height_type.upper()}",
                    OverlayWarning,
                    stacklevel=3,
                )

        elif self.height_amount and (self.height_type == WHSettings.GIVEN or self.min_height):
            if Sizing.BOX in top_sizing:
                sizing.add(Sizing.FLOW)
                if self.width_amount and (self.width_type == WHSettings.GIVEN or self.min_width):
                    sizing.add(Sizing.FIXED)
            else:
                warnings.warn(
                    f"Top widget {self.top_w} should support sizing {Sizing.BOX.upper()} "
                    f"for height type {self.height_type.upper()}",
                    OverlayWarning,
                    stacklevel=3,
                )

        return frozenset(sizing)

    def pack(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int] = (),
        focus: bool = False,
    ) -> tuple[int, int]:
        if size:
            return super().pack(size, focus)

        extra_cols = (self.left or 0) + (self.right or 0)
        extra_rows = (self.top or 0) + (self.bottom or 0)

        if self.width_type == WHSettings.PACK:
            cols, rows = self.top_w.pack((), focus)
            return cols + extra_cols, rows + extra_rows

        if not self.width_amount:
            raise OverlayError(
                f"Requested FIXED render for {self.top_w} with "
                f"width_type={self.width_type.upper()}, "
                f"width_amount={self.width_amount!r}, "
                f"height_type={self.height_type.upper()}, "
                f"height_amount={self.height_amount!r}"
                f"min_width={self.min_width!r}, "
                f"min_height={self.min_height!r}"
            )

        if self.width_type == WHSettings.GIVEN:
            w_cols = self.width_amount
            cols = w_cols + extra_cols
        elif self.width_type == WHSettings.RELATIVE and self.min_width:
            w_cols = self.min_width
            cols = int(w_cols * 100 / self.width_amount + 0.5)
        else:
            raise OverlayError(
                f"Requested FIXED render for {self.top_w} with "
                f"width_type={self.width_type.upper()}, "
                f"width_amount={self.width_amount!r}, "
                f"height_type={self.height_type.upper()}, "
                f"height_amount={self.height_amount!r}"
                f"min_width={self.min_width!r}, "
                f"min_height={self.min_height!r}"
            )

        if self.height_type == WHSettings.PACK:
            return cols, self.top_w.rows((w_cols,), focus) + extra_rows

        if not self.height_amount:
            raise OverlayError(
                f"Requested FIXED render for {self.top_w} with "
                f"width_type={self.width_type.upper()}, "
                f"width_amount={self.width_amount!r}, "
                f"height_type={self.height_type.upper()}, "
                f"height_amount={self.height_amount!r}"
                f"min_width={self.min_width!r}, "
                f"min_height={self.min_height!r}"
            )

        if self.height_type == WHSettings.GIVEN:
            return cols, self.height_amount + extra_rows

        if self.height_type == WHSettings.RELATIVE and self.min_height:
            return cols, int(self.min_height * 100 / self.height_amount + 0.5)

        raise OverlayError(
            f"Requested FIXED render for {self.top_w} with "
            f"width_type={self.width_type.upper()}, "
            f"width_amount={self.width_amount!r}, "
            f"height_type={self.height_type.upper()}, "
            f"height_amount={self.height_amount!r}"
            f"min_width={self.min_width!r}, "
            f"min_height={self.min_height!r}"
        )

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        """Widget rows amount for FLOW sizing."""
        extra_height = (self.top or 0) + (self.bottom or 0)
        if self.height_type == WHSettings.GIVEN:
            return self.height_amount + extra_height
        if self.height_type == WHSettings.RELATIVE and self.min_height:
            return int(self.min_height * 100 / self.height_amount + 0.5)

        if self.height_type == WHSettings.PACK:
            extra_height = (self.top or 0) + (self.bottom or 0)
            if self.width_type == WHSettings.GIVEN and self.width_amount:
                return self.top_w.rows((self.width_amount,), focus) + extra_height
            if self.width_type == WHSettings.RELATIVE:
                width = max(int(size[0] * self.width_amount / 100 + 0.5), (self.min_width or 0))
                return self.top_w.rows((width,), focus) + extra_height

        raise OverlayError(
            f"Requested rows for {self.top_w} with size {size!r}"
            f"width_type={self.width_type.upper()}, "
            f"width_amount={self.width_amount!r}, "
            f"height_type={self.height_type.upper()}, "
            f"height_amount={self.height_amount!r}"
            f"min_width={self.min_width!r}, "
            f"min_height={self.min_height!r}"
        )

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = {
            **super()._repr_attrs(),
            "top_w": self.top_w,
            "bottom_w": self.bottom_w,
            "align": self.align,
            "width": self.width,
            "valign": self.valign,
            "height": self.height,
            "min_width": self.min_width,
            "min_height": self.min_height,
            "left": self.left,
            "right": self.right,
            "top": self.top,
            "bottom": self.bottom,
        }
        return remove_defaults(attrs, Overlay.__init__)

    def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
        yield "top", self.top_w
        yield "bottom", self.bottom_w
        yield "align", self.align
        yield "width", self.width
        yield "valign", self.valign
        yield "height", self.height
        yield "min_width", self.min_width
        yield "min_height", self.min_height
        yield "left", self.left
        yield "right", self.right
        yield "top", self.top
        yield "bottom", self.bottom

    @property
    def align(self) -> Align | tuple[Literal[WHSettings.RELATIVE], int]:
        return simplify_align(self.align_type, self.align_amount)

    @property
    def width(
        self,
    ) -> (
        Literal[WHSettings.CLIP, WHSettings.PACK]
        | int
        | tuple[Literal[WHSettings.RELATIVE], int]
        | tuple[Literal[WHSettings.WEIGHT], int | float]
    ):
        return simplify_width(self.width_type, self.width_amount)

    @property
    def valign(self) -> VAlign | tuple[Literal[WHSettings.RELATIVE], int]:
        return simplify_valign(self.valign_type, self.valign_amount)

    @property
    def height(
        self,
    ) -> (
        int
        | Literal[WHSettings.FLOW, WHSettings.PACK]
        | tuple[Literal[WHSettings.RELATIVE], int]
        | tuple[Literal[WHSettings.WEIGHT], int | float]
    ):
        return simplify_height(self.height_type, self.height_amount)

    @staticmethod
    def options(
        align_type: Literal["left", "center", "right", "relative", WHSettings.RELATIVE] | Align,
        align_amount: int | None,
        width_type: Literal["clip", "pack", "relative", "given"] | WHSettings,
        width_amount: int | None,
        valign_type: Literal["top", "middle", "bottom", "relative", WHSettings.RELATIVE] | VAlign,
        valign_amount: int | None,
        height_type: Literal["flow", "pack", "relative", "given"] | WHSettings,
        height_amount: int | None,
        min_width: int | None = None,
        min_height: int | None = None,
        left: int = 0,
        right: int = 0,
        top: int = 0,
        bottom: int = 0,
    ) -> OverlayOptions:
        """
        Return a new options tuple for use in this Overlay's .contents mapping.

        This is the common container API to create options for replacing the
        top widget of this Overlay.  It is provided for completeness
        but is not necessarily the easiest way to change the overlay parameters.
        See also :meth:`.set_overlay_parameters`
        """
        if align_type in {Align.LEFT, Align.CENTER, Align.RIGHT}:
            align = Align(align_type)
        elif align_type == WHSettings.RELATIVE:
            align = WHSettings.RELATIVE
        else:
            raise ValueError(f"Unknown alignment type {align_type!r}")

        if valign_type in {VAlign.TOP, VAlign.MIDDLE, VAlign.BOTTOM}:
            valign = VAlign(valign_type)
        elif valign_type == WHSettings.RELATIVE:
            valign = WHSettings.RELATIVE
        else:
            raise ValueError(f"Unknown vertical alignment type {valign_type!r}")

        return OverlayOptions(
            align,
            align_amount,
            WHSettings(width_type),
            width_amount,
            min_width,
            left,
            right,
            valign,
            valign_amount,
            WHSettings(height_type),
            height_amount,
            min_height,
            top,
            bottom,
        )

    def set_overlay_parameters(
        self,
        align: (
            Literal["left", "center", "right"]
            | Align
            | tuple[Literal["relative", "fixed left", "fixed right", WHSettings.RELATIVE], int]
        ),
        width: Literal["pack", WHSettings.PACK] | int | tuple[Literal["relative", WHSettings.RELATIVE], int] | None,
        valign: (
            Literal["top", "middle", "bottom"]
            | VAlign
            | tuple[Literal["relative", "fixed top", "fixed bottom", WHSettings.RELATIVE], int]
        ),
        height: Literal["pack", WHSettings.PACK] | int | tuple[Literal["relative", WHSettings.RELATIVE], int] | None,
        min_width: int | None = None,
        min_height: int | None = None,
        left: int = 0,
        right: int = 0,
        top: int = 0,
        bottom: int = 0,
    ) -> None:
        """
        Adjust the overlay size and position parameters.

        See :class:`__init__() ` for a description of the parameters.
        """

        # convert obsolete parameters 'fixed ...':
        if isinstance(align, tuple):
            if align[0] == "fixed left":
                left = align[1]
                normalized_align = Align.LEFT
            elif align[0] == "fixed right":
                right = align[1]
                normalized_align = Align.RIGHT
            else:
                normalized_align = align
        else:
            normalized_align = Align(align)

        if isinstance(width, tuple):
            if width[0] == "fixed left":
                left = width[1]
                width = RELATIVE_100
            elif width[0] == "fixed right":
                right = width[1]
                width = RELATIVE_100

        if isinstance(valign, tuple):
            if valign[0] == "fixed top":
                top = valign[1]
                normalized_valign = VAlign.TOP
            elif valign[0] == "fixed bottom":
                bottom = valign[1]
                normalized_valign = VAlign.BOTTOM
            else:
                normalized_valign = valign

        elif not isinstance(valign, (VAlign, str)):
            raise OverlayError(f"invalid valign: {valign!r}")

        else:
            normalized_valign = VAlign(valign)

        if isinstance(height, tuple):
            if height[0] == "fixed bottom":
                bottom = height[1]
                height = RELATIVE_100
            elif height[0] == "fixed top":
                top = height[1]
                height = RELATIVE_100

        if width is None:  # more obsolete values accepted
            width = WHSettings.PACK
        if height is None:
            height = WHSettings.PACK

        align_type, align_amount = normalize_align(normalized_align, OverlayError)
        width_type, width_amount = normalize_width(width, OverlayError)
        valign_type, valign_amount = normalize_valign(normalized_valign, OverlayError)
        height_type, height_amount = normalize_height(height, OverlayError)

        if height_type in {WHSettings.GIVEN, WHSettings.PACK}:
            min_height = None

        # use container API to set the parameters
        self.contents[1] = (
            self.top_w,
            self.options(
                align_type,
                align_amount,
                width_type,
                width_amount,
                valign_type,
                valign_amount,
                height_type,
                height_amount,
                min_width,
                min_height,
                left,
                right,
                top,
                bottom,
            ),
        )

    def selectable(self) -> bool:
        """Return selectable from top_w."""
        return self.top_w.selectable()

    def keypress(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        key: str,
    ) -> str | None:
        """Pass keypress to top_w."""
        real_size = self.pack(size, True)
        return self.top_w.keypress(
            self.top_w_size(real_size, *self.calculate_padding_filler(real_size, True)),
            key,
        )

    @property
    def focus(self) -> TopWidget:
        """
        Read-only property returning the child widget in focus for
        container widgets.  This default implementation
        always returns ``None``, indicating that this widget has no children.
        """
        return self.top_w

    def _get_focus(self) -> TopWidget:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus` is deprecated, "
            f"please use `{self.__class__.__name__}.focus` property",
            DeprecationWarning,
            stacklevel=3,
        )
        return self.top_w

    @property
    def focus_position(self) -> Literal[1]:
        """
        Return the top widget position (currently always 1).
        """
        return 1

    @focus_position.setter
    def focus_position(self, position: int) -> None:
        """
        Set the widget in focus.  Currently only position 0 is accepted.

        position -- index of child widget to be made focus
        """
        if position != 1:
            raise IndexError(f"Overlay widget focus_position currently must always be set to 1, not {position}")

    def _get_focus_position(self) -> int | None:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        return 1

    def _set_focus_position(self, position: int) -> None:
        """
        Set the widget in focus.

        position -- index of child widget to be made focus
        """
        warnings.warn(
            f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        if position != 1:
            raise IndexError(f"Overlay widget focus_position currently must always be set to 1, not {position}")

    @property
    def contents(self) -> MutableSequence[tuple[TopWidget | BottomWidget, OverlayOptions]]:
        """
        a list-like object similar to::

            [(bottom_w, bottom_options)),
             (top_w, top_options)]

        This object may be used to read or update top and bottom widgets and
        top widgets's options, but no widgets may be added or removed.

        `top_options` takes the form
        `(align_type, align_amount, width_type, width_amount, min_width, left,
        right, valign_type, valign_amount, height_type, height_amount,
        min_height, top, bottom)`

        bottom_options is always
        `('left', None, 'relative', 100, None, 0, 0,
        'top', None, 'relative', 100, None, 0, 0)`
        which means that bottom widget always covers the full area of the Overlay.
        writing a different value for `bottom_options` raises an
        :exc:`OverlayError`.
        """

        # noinspection PyMethodParameters
        class OverlayContents(
            typing.MutableSequence[
                typing.Tuple[
                    typing.Union[TopWidget, BottomWidget],
                    OverlayOptions,
                ]
            ]
        ):

            # pylint: disable=no-self-argument
            def __len__(inner_self) -> int:
                return 2

            __getitem__ = self._contents__getitem__
            __setitem__ = self._contents__setitem__

            def __delitem__(self, index: int | slice) -> typing.NoReturn:
                raise TypeError("OverlayContents is fixed-sized sequence")

            def insert(self, index: int | slice, value: typing.Any) -> typing.NoReturn:
                raise TypeError("OverlayContents is fixed-sized sequence")

            def __repr__(inner_self) -> str:
                return repr(f"<{inner_self.__class__.__name__}({[inner_self[0], inner_self[1]]})> for {self}")

            def __rich_repr__(inner_self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
                for val in inner_self:
                    yield None, val

            def __iter__(inner_self) -> Iterator[tuple[Widget, OverlayOptions]]:
                for idx in range(2):
                    yield inner_self[idx]

        return OverlayContents()

    @contents.setter
    def contents(self, new_contents: Sequence[tuple[width, OverlayOptions]]) -> None:
        if len(new_contents) != 2:
            raise ValueError("Contents length for overlay should be only 2")
        self.contents[0] = new_contents[0]
        self.contents[1] = new_contents[1]

    def _contents__getitem__(
        self,
        index: Literal[0, 1],
    ) -> tuple[TopWidget | BottomWidget, OverlayOptions]:
        if index == 0:
            return (self.bottom_w, self._DEFAULT_BOTTOM_OPTIONS)

        if index == 1:
            return (
                self.top_w,
                OverlayOptions(
                    self.align_type,
                    self.align_amount,
                    self.width_type,
                    self.width_amount,
                    self.min_width,
                    self.left,
                    self.right,
                    self.valign_type,
                    self.valign_amount,
                    self.height_type,
                    self.height_amount,
                    self.min_height,
                    self.top,
                    self.bottom,
                ),
            )
        raise IndexError(f"Overlay.contents has no position {index!r}")

    def _contents__setitem__(
        self,
        index: Literal[0, 1],
        value: tuple[TopWidget | BottomWidget, OverlayOptions],
    ) -> None:
        try:
            value_w, value_options = value
        except (ValueError, TypeError) as exc:
            raise OverlayError(f"added content invalid: {value!r}").with_traceback(exc.__traceback__) from exc
        if index == 0:
            if value_options != self._DEFAULT_BOTTOM_OPTIONS:
                raise OverlayError(f"bottom_options must be set to {self._DEFAULT_BOTTOM_OPTIONS!r}")
            self.bottom_w = value_w
        elif index == 1:
            try:
                (
                    align_type,
                    align_amount,
                    width_type,
                    width_amount,
                    min_width,
                    left,
                    right,
                    valign_type,
                    valign_amount,
                    height_type,
                    height_amount,
                    min_height,
                    top,
                    bottom,
                ) = value_options
            except (ValueError, TypeError) as exc:
                raise OverlayError(f"top_options is invalid: {value_options!r}").with_traceback(
                    exc.__traceback__
                ) from exc
            # normalize first, this is where errors are raised
            align_type, align_amount = normalize_align(simplify_align(align_type, align_amount), OverlayError)
            width_type, width_amount = normalize_width(simplify_width(width_type, width_amount), OverlayError)
            valign_type, valign_amount = normalize_valign(simplify_valign(valign_type, valign_amount), OverlayError)
            height_type, height_amount = normalize_height(simplify_height(height_type, height_amount), OverlayError)
            self.align_type = align_type
            self.align_amount = align_amount
            self.width_type = width_type
            self.width_amount = width_amount
            self.valign_type = valign_type
            self.valign_amount = valign_amount
            self.height_type = height_type
            self.height_amount = height_amount
            self.left = left
            self.right = right
            self.top = top
            self.bottom = bottom
            self.min_width = min_width
            self.min_height = min_height
        else:
            raise IndexError(f"Overlay.contents has no position {index!r}")
        self._invalidate()

    def get_cursor_coords(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
    ) -> tuple[int, int] | None:
        """Return cursor coords from top_w, if any."""
        if not hasattr(self.top_w, "get_cursor_coords"):
            return None
        real_size = self.pack(size, True)
        (maxcol, maxrow) = real_size
        left, right, top, bottom = self.calculate_padding_filler(real_size, True)
        x, y = self.top_w.get_cursor_coords((maxcol - left - right, maxrow - top - bottom))
        if y >= maxrow:  # required??
            y = maxrow - 1
        return x + left, y + top

    def calculate_padding_filler(
        self,
        size: tuple[int, int],
        focus: bool,
    ) -> tuple[int, int, int, int]:
        """Return (padding left, right, filler top, bottom)."""
        (maxcol, maxrow) = size
        height = None
        if self.width_type == WHSettings.PACK:
            width, height = self.top_w.pack((), focus=focus)
            if not height:
                raise OverlayError("fixed widget must have a height")
            left, right = calculate_left_right_padding(
                maxcol,
                self.align_type,
                self.align_amount,
                WrapMode.CLIP,
                width,
                None,
                self.left,
                self.right,
            )
        else:
            left, right = calculate_left_right_padding(
                maxcol,
                self.align_type,
                self.align_amount,
                self.width_type,
                self.width_amount,
                self.min_width,
                self.left,
                self.right,
            )

        if height:
            # top_w is a fixed widget
            top, bottom = calculate_top_bottom_filler(
                maxrow,
                self.valign_type,
                self.valign_amount,
                WHSettings.GIVEN,
                height,
                None,
                self.top,
                self.bottom,
            )
            if maxrow - top - bottom < height:
                bottom = maxrow - top - height
        elif self.height_type == WHSettings.PACK:
            # top_w is a flow widget
            height = self.top_w.rows((maxcol,), focus=focus)
            top, bottom = calculate_top_bottom_filler(
                maxrow,
                self.valign_type,
                self.valign_amount,
                WHSettings.GIVEN,
                height,
                None,
                self.top,
                self.bottom,
            )
            if height > maxrow:  # flow widget rendered too large
                bottom = maxrow - height
        else:
            top, bottom = calculate_top_bottom_filler(
                maxrow,
                self.valign_type,
                self.valign_amount,
                self.height_type,
                self.height_amount,
                self.min_height,
                self.top,
                self.bottom,
            )
        return left, right, top, bottom

    def top_w_size(
        self,
        size: tuple[int, int],
        left: int,
        right: int,
        top: int,
        bottom: int,
    ) -> tuple[()] | tuple[int] | tuple[int, int]:
        """Return the size to pass to top_w."""
        if self.width_type == WHSettings.PACK:
            # top_w is a fixed widget
            return ()
        maxcol, maxrow = size
        if self.width_type != WHSettings.PACK and self.height_type == WHSettings.PACK:
            # top_w is a flow widget
            return (maxcol - left - right,)
        return (maxcol - left - right, maxrow - top - bottom)

    def render(self, size: tuple[()] | tuple[int] | tuple[int, int], focus: bool = False) -> CompositeCanvas:
        """Render top_w overlayed on bottom_w."""
        real_size = self.pack(size, focus)

        left, right, top, bottom = self.calculate_padding_filler(real_size, focus)
        bottom_c = self.bottom_w.render(real_size)
        if not bottom_c.cols() or not bottom_c.rows():
            return CompositeCanvas(bottom_c)

        top_c = self.top_w.render(self.top_w_size(real_size, left, right, top, bottom), focus)
        top_c = CompositeCanvas(top_c)
        if left < 0 or right < 0:
            top_c.pad_trim_left_right(min(0, left), min(0, right))
        if top < 0 or bottom < 0:
            top_c.pad_trim_top_bottom(min(0, top), min(0, bottom))

        return CanvasOverlay(top_c, bottom_c, left, top)

    def mouse_event(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        """Pass event to top_w, ignore if outside of top_w."""
        if not hasattr(self.top_w, "mouse_event"):
            return False

        real_size = self.pack(size, focus)

        left, right, top, bottom = self.calculate_padding_filler(real_size, focus)
        maxcol, maxrow = real_size
        if col < left or col >= maxcol - right or row < top or row >= maxrow - bottom:
            return False

        return self.top_w.mouse_event(
            self.top_w_size(real_size, left, right, top, bottom),
            event,
            button,
            col - left,
            row - top,
            focus,
        )
urwid-2.6.16/urwid/widget/padding.py000066400000000000000000000515261470350774000173550ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings

from urwid.canvas import CompositeCanvas, SolidCanvas
from urwid.split_repr import remove_defaults
from urwid.util import int_scale

from .constants import (
    RELATIVE_100,
    Align,
    Sizing,
    WHSettings,
    normalize_align,
    normalize_width,
    simplify_align,
    simplify_width,
)
from .widget_decoration import WidgetDecoration, WidgetError, WidgetWarning

if typing.TYPE_CHECKING:
    from collections.abc import Iterator

    from typing_extensions import Literal

WrappedWidget = typing.TypeVar("WrappedWidget")


class PaddingError(WidgetError):
    """Padding related errors."""


class PaddingWarning(WidgetWarning):
    """Padding related warnings."""


class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
    def __init__(
        self,
        w: WrappedWidget,
        align: (
            Literal["left", "center", "right"]
            | Align
            | tuple[Literal["relative", WHSettings.RELATIVE, "fixed left", "fixed right"], int]
        ) = Align.LEFT,
        width: (
            int
            | Literal["pack", "clip", WHSettings.PACK, WHSettings.CLIP]
            | tuple[Literal["relative", WHSettings.RELATIVE, "fixed left", "fixed right"], int]
        ) = RELATIVE_100,
        min_width: int | None = None,
        left: int = 0,
        right: int = 0,
    ) -> None:
        """
        :param w: a box, flow or fixed widget to pad on the left and/or right
            this widget is stored as self.original_widget
        :type w: Widget

        :param align: one of: ``'left'``, ``'center'``, ``'right'``
            (``'relative'``, *percentage* 0=left 100=right)

        :param width: one of:

            *given width*
              integer number of columns for self.original_widget

            ``'pack'``
              try to pack self.original_widget to its ideal size

            (``'relative'``, *percentage of total width*)
              make width depend on the container's width

            ``'clip'``
              to enable clipping mode for a fixed widget

        :param min_width: the minimum number of columns for
            self.original_widget or ``None``
        :type min_width: int | None

        :param left: a fixed number of columns to pad on the left
        :type left: int

        :param right: a fixed number of columns to pad on the right
        :type right: int

        Clipping Mode: (width= ``'clip'``)
        In clipping mode this padding widget will behave as a flow
        widget and self.original_widget will be treated as a fixed widget.
        self.original_widget will be clipped to fit the available number of columns.
        For example if align is ``'left'`` then self.original_widget may be clipped on the right.

        Pack Mode: (width= ``'pack'``)
        In pack mode is supported FIXED operation if it is supported by the original widget.

        >>> from urwid import Divider, Text, BigText, FontRegistry
        >>> from urwid.util import set_temporary_encoding
        >>> size = (7,)
        >>> def pr(w):
        ...     with set_temporary_encoding("utf-8"):
        ...         for t in w.render(size).text:
        ...             print(f"|{t.decode('utf-8')}|" )
        >>> pr(Padding(Text(u"Head"), ('relative', 20), 'pack'))
        | Head  |
        >>> pr(Padding(Divider(u"-"), left=2, right=1))
        |  ---- |
        >>> pr(Padding(Divider(u"*"), 'center', 3))
        |  ***  |
        >>> p=Padding(Text(u"1234"), 'left', 2, None, 1, 1)
        >>> p
         left=1 right=1 width=2>
        >>> pr(p)   # align against left
        | 12    |
        | 34    |
        >>> p.align = 'right'
        >>> pr(p)   # align against right
        |    12 |
        |    34 |
        >>> pr(Padding(Text(u"hi\\nthere"), 'right', 'pack')) # pack text first
        |  hi   |
        |  there|
        >>> pr(Padding(BigText("1,2,3", FontRegistry['Thin 3x3']()), width="clip"))
        | â”  ┌─â”|
        | │  ┌─┘|
        | ┴ ,└─ |
        """
        super().__init__(w)

        # convert obsolete parameters 'fixed left' and 'fixed right':
        if isinstance(align, tuple) and align[0] in {"fixed left", "fixed right"}:
            if align[0] == "fixed left":
                left = align[1]
                align = Align.LEFT
            else:
                right = align[1]
                align = Align.RIGHT
        if isinstance(width, tuple) and width[0] in {"fixed left", "fixed right"}:
            if width[0] == "fixed left":
                left = width[1]
            else:
                right = width[1]
            width = RELATIVE_100

        # convert old clipping mode width=None to width='clip'
        if width is None:
            width = WHSettings.CLIP

        self.left = left
        self.right = right
        self._align_type, self._align_amount = normalize_align(align, PaddingError)
        self._width_type, self._width_amount = normalize_width(width, PaddingError)
        self.min_width = min_width

    def sizing(self) -> frozenset[Sizing]:
        """Widget sizing.

        Rules:
        * width == CLIP: only FLOW is supported, and wrapped widget should support FIXED
        * width == GIVEN: FIXED is supported, and wrapped widget should support FLOW
        * All other cases: use sizing of target widget
        """
        if self._width_type == WHSettings.CLIP:
            return frozenset((Sizing.FLOW,))

        sizing = set(self.original_widget.sizing())
        if self._width_type == WHSettings.GIVEN:
            if Sizing.FLOW in sizing:
                sizing.add(Sizing.FIXED)

            elif Sizing.BOX not in sizing:
                warnings.warn(
                    f"WHSettings.GIVEN expect BOX or FLOW widget to be used, but received {self.original_widget}",
                    PaddingWarning,
                    stacklevel=3,
                )

        return frozenset(sizing)

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = {
            **super()._repr_attrs(),
            "align": self.align,
            "width": self.width,
            "left": self.left,
            "right": self.right,
            "min_width": self.min_width,
        }
        return remove_defaults(attrs, Padding.__init__)

    def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
        yield "w", self.original_widget
        yield "align", self.align
        yield "width", self.width
        yield "min_width", self.min_width
        yield "left", self.left
        yield "right", self.right

    @property
    def align(
        self,
    ) -> Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int]:
        """
        Return the padding alignment setting.
        """
        return simplify_align(self._align_type, self._align_amount)

    @align.setter
    def align(
        self, align: Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int]
    ) -> None:
        """
        Set the padding alignment.
        """
        self._align_type, self._align_amount = normalize_align(align, PaddingError)
        self._invalidate()

    def _get_align(self) -> Literal["left", "center", "right"] | tuple[Literal["relative"], int]:
        warnings.warn(
            f"Method `{self.__class__.__name__}._get_align` is deprecated, "
            f"please use property `{self.__class__.__name__}.align`",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.align

    def _set_align(self, align: Literal["left", "center", "right"] | tuple[Literal["relative"], int]) -> None:
        warnings.warn(
            f"Method `{self.__class__.__name__}._set_align` is deprecated, "
            f"please use property `{self.__class__.__name__}.align`",
            DeprecationWarning,
            stacklevel=2,
        )
        self.align = align

    @property
    def width(
        self,
    ) -> (
        Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK]
        | int
        | tuple[Literal["relative", WHSettings.RELATIVE], int]
    ):
        """
        Return the padding width.
        """
        return simplify_width(self._width_type, self._width_amount)

    @width.setter
    def width(
        self,
        width: (
            Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK]
            | int
            | tuple[Literal["relative", WHSettings.RELATIVE], int]
        ),
    ) -> None:
        """
        Set the padding width.
        """
        self._width_type, self._width_amount = normalize_width(width, PaddingError)
        self._invalidate()

    def _get_width(self) -> Literal["clip", "pack"] | int | tuple[Literal["relative"], int]:
        warnings.warn(
            f"Method `{self.__class__.__name__}._get_width` is deprecated, "
            f"please use property `{self.__class__.__name__}.width`",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.width

    def _set_width(self, width: Literal["clip", "pack"] | int | tuple[Literal["relative"], int]) -> None:
        warnings.warn(
            f"Method `{self.__class__.__name__}._set_width` is deprecated, "
            f"please use property `{self.__class__.__name__}.width`",
            DeprecationWarning,
            stacklevel=2,
        )
        self.width = width

    def pack(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int] = (),
        focus: bool = False,
    ) -> tuple[int, int]:
        if size:
            return super().pack(size, focus)
        if self._width_type == WHSettings.CLIP:
            raise PaddingError("WHSettings.CLIP makes Padding FLOW-only widget")

        expand = self.left + self.right
        w_sizing = self.original_widget.sizing()

        if self._width_type == WHSettings.GIVEN:
            if Sizing.FLOW not in w_sizing:
                warnings.warn(
                    f"WHSettings.GIVEN expect FLOW widget to be used for FIXED pack/render, "
                    f"but received {self.original_widget}",
                    PaddingWarning,
                    stacklevel=3,
                )

            return (
                max(self._width_amount, self.min_width or 1) + expand,
                self.original_widget.rows((self._width_amount,), focus),
            )

        if Sizing.FIXED not in w_sizing:
            warnings.warn(
                f"Padded widget should support FIXED sizing for FIXED render, but received {self.original_widget}",
                PaddingWarning,
                stacklevel=3,
            )
        width, height = self.original_widget.pack(size, focus)

        if self._width_type == WHSettings.PACK:
            return max(width, self.min_width or 1) + expand, height

        if self._width_type == WHSettings.RELATIVE:
            return max(int(width * 100 / self._width_amount + 0.5), self.min_width or 1) + expand, height

        raise PaddingError(f"Unexpected width type: {self._width_type.upper()})")

    def render(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        focus: bool = False,
    ) -> CompositeCanvas:
        left, right = self.padding_values(size, focus)

        if self._width_type == WHSettings.CLIP:
            canv = self._original_widget.render((), focus)
        elif size:
            maxcol = size[0] - (left + right)
            if self._width_type == WHSettings.GIVEN and maxcol < self._width_amount:
                warnings.warn(
                    f"{self}.render(size={size}, focus={focus}): too narrow size ({maxcol!r} < {self._width_amount!r})",
                    PaddingWarning,
                    stacklevel=3,
                )
            canv = self._original_widget.render((maxcol,) + size[1:], focus)
        elif self._width_type == WHSettings.GIVEN:
            canv = self._original_widget.render((self._width_amount,) + size[1:], focus)
        else:
            canv = self._original_widget.render((), focus)

        if canv.cols() == 0:
            canv = SolidCanvas(" ", size[0], canv.rows())
            canv = CompositeCanvas(canv)
            canv.set_depends([self._original_widget])
            return canv

        canv = CompositeCanvas(canv)
        canv.set_depends([self._original_widget])
        if left != 0 or right != 0:
            canv.pad_trim_left_right(left, right)

        return canv

    def padding_values(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        focus: bool,
    ) -> tuple[int, int]:
        """Return the number of columns to pad on the left and right.

        Override this method to define custom padding behaviour."""
        if self._width_type == WHSettings.CLIP:
            width, _ignore = self._original_widget.pack((), focus=focus)
            if not size:
                raise PaddingError("WHSettings.CLIP makes Padding FLOW-only widget")
            return calculate_left_right_padding(
                size[0],
                self._align_type,
                self._align_amount,
                WHSettings.CLIP,
                width,
                None,
                self.left,
                self.right,
            )

        if self._width_type == WHSettings.PACK:
            if size:
                maxcol = size[0]
                maxwidth = max(maxcol - self.left - self.right, self.min_width or 0)
                (width, _ignore) = self._original_widget.pack((maxwidth,), focus=focus)
            else:
                (width, _ignore) = self._original_widget.pack((), focus=focus)
                maxcol = width + self.left + self.right

            return calculate_left_right_padding(
                maxcol,
                self._align_type,
                self._align_amount,
                WHSettings.GIVEN,
                width,
                self.min_width,
                self.left,
                self.right,
            )

        if size:
            maxcol = size[0]
        elif self._width_type == WHSettings.GIVEN:
            maxcol = self._width_amount + self.left + self.right
        else:
            maxcol = (
                max(self._original_widget.pack((), focus=focus)[0] * 100 // self._width_amount, self.min_width or 1)
                + self.left
                + self.right
            )

        return calculate_left_right_padding(
            maxcol,
            self._align_type,
            self._align_amount,
            self._width_type,
            self._width_amount,
            self.min_width,
            self.left,
            self.right,
        )

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        """Return the rows needed for self.original_widget."""
        (maxcol,) = size
        left, right = self.padding_values(size, focus)
        if self._width_type == WHSettings.PACK:
            _pcols, prows = self._original_widget.pack((maxcol - left - right,), focus)
            return prows
        if self._width_type == WHSettings.CLIP:
            _fcols, frows = self._original_widget.pack((), focus)
            return frows
        return self._original_widget.rows((maxcol - left - right,), focus=focus)

    def keypress(self, size: tuple[()] | tuple[int] | tuple[int, int], key: str) -> str | None:
        """Pass keypress to self._original_widget."""
        left, right = self.padding_values(size, True)
        if size:
            maxvals = (size[0] - left - right,) + size[1:]
            return self._original_widget.keypress(maxvals, key)
        return self._original_widget.keypress((), key)

    def get_cursor_coords(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> tuple[int, int] | None:
        """Return the (x,y) coordinates of cursor within self._original_widget."""
        if not hasattr(self._original_widget, "get_cursor_coords"):
            return None

        left, right = self.padding_values(size, True)
        if size:
            maxvals = (size[0] - left - right,) + size[1:]
            if maxvals[0] == 0:
                return None
        else:
            maxvals = ()

        coords = self._original_widget.get_cursor_coords(maxvals)
        if coords is None:
            return None

        x, y = coords
        return x + left, y

    def move_cursor_to_coords(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        x: int,
        y: int,
    ) -> bool:
        """Set the cursor position with (x,y) coordinates of self._original_widget.

        Returns True if move succeeded, False otherwise.
        """
        if not hasattr(self._original_widget, "move_cursor_to_coords"):
            return True

        left, right = self.padding_values(size, True)
        if size:
            maxcol = size[0]
            maxvals = (maxcol - left - right,) + size[1:]
        else:
            maxcol = self.pack((), True)[0]
            maxvals = ()

        if isinstance(x, int):
            if x < left:
                x = left
            elif x >= maxcol - right:
                x = maxcol - right - 1
            x -= left

        return self._original_widget.move_cursor_to_coords(maxvals, x, y)

    def mouse_event(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        """Send mouse event if position is within self._original_widget."""
        if not hasattr(self._original_widget, "mouse_event"):
            return False

        left, right = self.padding_values(size, focus)
        if size:
            maxcol = size[0]
            if col < left or col >= maxcol - right:
                return False
            maxvals = (maxcol - left - right,) + size[1:]
        else:
            maxvals = ()

        return self._original_widget.mouse_event(maxvals, event, button, col - left, row, focus)

    def get_pref_col(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> int | None:
        """Return the preferred column from self._original_widget, or None."""
        if not hasattr(self._original_widget, "get_pref_col"):
            return None

        left, right = self.padding_values(size, True)
        if size:
            maxvals = (size[0] - left - right,) + size[1:]
        else:
            maxvals = ()

        x = self._original_widget.get_pref_col(maxvals)
        if isinstance(x, int):
            return x + left
        return x


def calculate_left_right_padding(
    maxcol: int,
    align_type: Literal["left", "center", "right"] | Align,
    align_amount: int,
    width_type: Literal["fixed", "relative", "clip", "given", WHSettings.RELATIVE, WHSettings.CLIP, WHSettings.GIVEN],
    width_amount: int,
    min_width: int | None,
    left: int,
    right: int,
) -> tuple[int, int]:
    """
    Return the amount of padding (or clipping) on the left and
    right part of maxcol columns to satisfy the following:

    align_type -- 'left', 'center', 'right', 'relative'
    align_amount -- a percentage when align_type=='relative'
    width_type -- 'fixed', 'relative', 'clip'
    width_amount -- a percentage when width_type=='relative'
        otherwise equal to the width of the widget
    min_width -- a desired minimum width for the widget or None
    left -- a fixed number of columns to pad on the left
    right -- a fixed number of columns to pad on the right

    >>> clrp = calculate_left_right_padding
    >>> clrp(15, 'left', 0, 'given', 10, None, 2, 0)
    (2, 3)
    >>> clrp(15, 'relative', 0, 'given', 10, None, 2, 0)
    (2, 3)
    >>> clrp(15, 'relative', 100, 'given', 10, None, 2, 0)
    (5, 0)
    >>> clrp(15, 'center', 0, 'given', 4, None, 2, 0)
    (6, 5)
    >>> clrp(15, 'left', 0, 'clip', 18, None, 0, 0)
    (0, -3)
    >>> clrp(15, 'right', 0, 'clip', 18, None, 0, -1)
    (-2, -1)
    >>> clrp(15, 'center', 0, 'given', 18, None, 2, 0)
    (0, 0)
    >>> clrp(20, 'left', 0, 'relative', 60, None, 0, 0)
    (0, 8)
    >>> clrp(20, 'relative', 30, 'relative', 60, None, 0, 0)
    (2, 6)
    >>> clrp(20, 'relative', 30, 'relative', 60, 14, 0, 0)
    (2, 4)
    """
    if width_type == WHSettings.RELATIVE:
        maxwidth = max(maxcol - left - right, 0)
        width = int(maxwidth * width_amount / 100 + 0.5)
        if min_width is not None:
            width = max(width, min_width)
    else:
        width = width_amount

    align = {Align.LEFT: 0, Align.CENTER: 50, Align.RIGHT: 100}.get(align_type, align_amount)

    # add the remainder of left/right the padding
    padding = maxcol - width - left - right
    right += int_scale(100 - align, 101, padding + 1)
    left = maxcol - width - right

    # reduce padding if we are clipping an edge
    if right < 0 < left:
        shift = min(left, -right)
        left -= shift
        right += shift
    elif left < 0 < right:
        shift = min(right, -left)
        right -= shift
        left += shift

    # only clip if width_type == 'clip'
    if width_type != WHSettings.CLIP and (left < 0 or right < 0):
        left = max(left, 0)
        right = max(right, 0)

    return left, right
urwid-2.6.16/urwid/widget/pile.py000066400000000000000000001131541470350774000166740ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings
from itertools import chain, repeat

from urwid.canvas import CanvasCombine, CompositeCanvas, SolidCanvas
from urwid.command_map import Command
from urwid.split_repr import remove_defaults
from urwid.util import is_mouse_press

from .constants import Sizing, WHSettings
from .container import WidgetContainerListContentsMixin, WidgetContainerMixin, _ContainerElementSizingFlag
from .monitored_list import MonitoredFocusList, MonitoredList
from .widget import Widget, WidgetError, WidgetWarning

if typing.TYPE_CHECKING:
    from collections.abc import Iterable, Iterator, Sequence

    from typing_extensions import Literal


class PileError(WidgetError):
    """Pile related errors."""


class PileWarning(WidgetWarning):
    """Pile related warnings."""


class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
    """
    A pile of widgets stacked vertically from top to bottom
    """

    def sizing(self) -> frozenset[Sizing]:
        """Sizing supported by widget.

        :return: Calculated widget sizing
        :rtype: frozenset[Sizing]

        Due to the nature of container with mutable contents, this method cannot be cached.

        Rules:
        * WEIGHT BOX -> BOX
        * GIVEN BOX -> FLOW (height is known) & BOX (can be shrinked/padded)
        * PACK BOX -> Unsupported

        * WEIGHT FLOW -> FLOW
        * GIVEN FLOW -> Unsupported
        * PACK FLOW -> FLOW

        * WEIGHT FIXED -> Need also FLOW or/and BOX to properly render due to width calculation
        * GIVEN FIXED -> Unsupported
        * PACK FIXED -> FIXED (widget knows its size)

        >>> from urwid import BigText, ProgressBar, SolidFill, Text, Thin3x3Font
        >>> font = Thin3x3Font()

        # BOX-only widget
        >>> Pile((SolidFill("#"),))
        

        # GIVEN BOX -> BOX/FLOW
        >>> Pile(((10, SolidFill("#")),))
        

        # FLOW-only
        >>> Pile((ProgressBar(None, None),))
        

        # FIXED -> FIXED
        >>> Pile(((WHSettings.PACK, BigText("0", font)),))
        

        # FLOW/FIXED -> FLOW/FIXED
        >>> Pile(((WHSettings.PACK, Text("text")),))
        

        # FLOW + FIXED widgets -> FLOW/FIXED
        >>> Pile((ProgressBar(None, None), (WHSettings.PACK, BigText("0", font))))
        

        # GIVEN BOX + FIXED widgets -> BOX/FLOW/FIXED (GIVEN BOX allows overriding its height & allows any width)
        >>> Pile(((10, SolidFill("#")), (WHSettings.PACK, BigText("0", font))))
        

        # Invalid sizing combination -> use fallback settings (and produce warning)
        >>> Pile(((WHSettings.WEIGHT, 1, BigText("0", font)),))
        

        # Special case: empty pile widget sizing is impossible to calculate
        >>> Pile(())
        
        """
        if not self.contents:
            return frozenset((Sizing.BOX, Sizing.FLOW))
        strict_box = False
        has_flow = False

        has_fixed = False
        supported: set[Sizing] = set()

        box_flow_fixed = (
            _ContainerElementSizingFlag.BOX | _ContainerElementSizingFlag.FLOW | _ContainerElementSizingFlag.FIXED
        )
        flow_fixed = _ContainerElementSizingFlag.FLOW | _ContainerElementSizingFlag.FIXED

        for idx, (widget, (size_kind, _size_weight)) in enumerate(self.contents):
            w_sizing = widget.sizing()

            flag = _ContainerElementSizingFlag.NONE

            if size_kind == WHSettings.WEIGHT:
                flag |= _ContainerElementSizingFlag.WH_WEIGHT
                if Sizing.BOX in w_sizing:
                    flag |= _ContainerElementSizingFlag.BOX
                if Sizing.FLOW in w_sizing:
                    flag |= _ContainerElementSizingFlag.FLOW
                if Sizing.FIXED in w_sizing and w_sizing & {Sizing.BOX, Sizing.FLOW}:
                    flag |= _ContainerElementSizingFlag.FIXED

            elif size_kind == WHSettings.GIVEN:
                flag |= _ContainerElementSizingFlag.WH_GIVEN
                if Sizing.BOX in w_sizing:
                    flag |= _ContainerElementSizingFlag.BOX
                    flag |= _ContainerElementSizingFlag.FLOW

            else:
                flag = _ContainerElementSizingFlag.WH_PACK
                if Sizing.FLOW in w_sizing:
                    flag |= _ContainerElementSizingFlag.FLOW
                if Sizing.FIXED in w_sizing:
                    flag |= _ContainerElementSizingFlag.FIXED

            if not flag & box_flow_fixed:
                warnings.warn(
                    f"Sizing combination of widget {idx} not supported: "
                    f"{size_kind.name} {'|'.join(w_sizing).upper()}",
                    PileWarning,
                    stacklevel=3,
                )
                return frozenset((Sizing.BOX, Sizing.FLOW))

            if flag & _ContainerElementSizingFlag.BOX:
                supported.add(Sizing.BOX)
                if not flag & flow_fixed:
                    strict_box = True
                    break

            if flag & _ContainerElementSizingFlag.FLOW:
                has_flow = True
            if flag & _ContainerElementSizingFlag.FIXED:
                has_fixed = True

        if not strict_box:
            if has_flow:
                supported.add(Sizing.FLOW)
            if has_fixed:
                supported.add(Sizing.FIXED)

        return frozenset(supported)

    def __init__(
        self,
        widget_list: Iterable[
            Widget
            | tuple[Literal["pack", WHSettings.PACK] | int, Widget]
            | tuple[Literal["given", WHSettings.GIVEN], int, Widget]
            | tuple[Literal["weight", WHSettings.WEIGHT], int | float, Widget]
        ],
        focus_item: Widget | int | None = None,
    ) -> None:
        """
        :param widget_list: child widgets
        :type widget_list: iterable
        :param focus_item: child widget that gets the focus initially.
            Chooses the first selectable widget if unset.
        :type focus_item: Widget or int

        *widget_list* may also contain tuples such as:

        (*given_height*, *widget*)
            always treat *widget* as a box widget and give it *given_height* rows, where given_height is an int
        (``'pack'``, *widget*)
            allow *widget* to calculate its own height by calling its :meth:`rows` method,
            i.e. treat it as a flow widget.
        (``'weight'``, *weight*, *widget*)
            if the pile is treated as a box widget then treat widget as a box
            widget with a height based on its relative weight value, otherwise treat the same as (``'pack'``, *widget*).

        Widgets not in a tuple are the same as (``'weight'``, ``1``, *widget*)`

        .. note:: If the Pile is treated as a box widget there must be at least
            one ``'weight'`` tuple in :attr:`widget_list`.
        """
        self._selectable = False
        super().__init__()
        self._contents: MonitoredFocusList[
            Widget,
            tuple[Literal[WHSettings.PACK], None]
            | tuple[Literal[WHSettings.GIVEN], int]
            | tuple[Literal[WHSettings.WEIGHT], int | float],
        ] = MonitoredFocusList()
        self._contents.set_modified_callback(self._contents_modified)
        self._contents.set_focus_changed_callback(lambda f: self._invalidate())
        self._contents.set_validate_contents_modified(self._validate_contents_modified)

        for i, original in enumerate(widget_list):
            w = original
            if not isinstance(w, tuple):
                self.contents.append((w, (WHSettings.WEIGHT, 1)))
            elif w[0] in {Sizing.FLOW, WHSettings.PACK}:  # 'pack' used to be called 'flow'
                f, w = w
                self.contents.append((w, (WHSettings.PACK, None)))
            elif len(w) == 2 or w[0] in {Sizing.FIXED, WHSettings.GIVEN}:  # backwards compatibility
                height, w = w[-2:]
                self.contents.append((w, (WHSettings.GIVEN, height)))
            elif w[0] == WHSettings.WEIGHT:
                f, height, w = w
                self.contents.append((w, (f, height)))
            else:
                raise PileError(f"initial widget list item invalid {original!r}")
            if focus_item is None and w.selectable():
                focus_item = i

            if not isinstance(w, Widget):
                warnings.warn(f"{w!r} is not a Widget", PileWarning, stacklevel=3)

        if self.contents and focus_item is not None:
            self.focus = focus_item

        self.pref_col = 0

    def _repr_words(self) -> list[str]:
        if len(self.contents) > 1:
            contents_string = f"({len(self.contents)} items)"
        elif self.contents:
            contents_string = "(1 item)"
        else:
            contents_string = "()"
        return [*super()._repr_words(), contents_string]

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = {**super()._repr_attrs(), "focus_item": self.focus_position if len(self._contents) > 1 else None}
        return remove_defaults(attrs, Pile.__init__)

    def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
        widget_list: list[
            Widget
            | tuple[Literal[WHSettings.PACK] | int, Widget]
            | tuple[Literal[WHSettings.WEIGHT], int | float, Widget]
        ] = []

        for w_instance, (sizing, amount) in self._contents:
            if sizing == WHSettings.GIVEN:
                widget_list.append((amount, w_instance))
            elif sizing == WHSettings.PACK:
                widget_list.append((WHSettings.PACK, w_instance))
            elif sizing == WHSettings.WEIGHT:
                if amount == 1:
                    widget_list.append(w_instance)
                else:
                    widget_list.append((WHSettings.WEIGHT, amount, w_instance))

        yield "widget_list", widget_list
        yield "focus_item", self.focus_position if self._contents else None

    def __len__(self) -> int:
        return len(self._contents)

    def _contents_modified(self) -> None:
        """Recalculate whether this widget should be selectable whenever the contents has been changed."""
        self._selectable = any(w.selectable() for w, o in self.contents)
        self._invalidate()

    def _validate_contents_modified(self, slc, new_items) -> None:
        invalid_items: list[tuple[Widget, tuple[typing.Any, typing.Any]]] = []
        try:
            for item in new_items:
                _w, (t, n) = item
                if any(
                    (
                        t not in {WHSettings.PACK, WHSettings.GIVEN, WHSettings.WEIGHT},
                        (n is not None and (not isinstance(n, (int, float)) or n < 0)),
                    )
                ):
                    invalid_items.append(item)

        except (TypeError, ValueError) as exc:
            raise PileError(f"added content invalid: {exc}").with_traceback(exc.__traceback__) from exc

        if invalid_items:
            raise PileError(f"added content invalid: {invalid_items!r}")

    @property
    def widget_list(self):
        """
        A list of the widgets in this Pile

        .. note:: only for backwards compatibility. You should use the new
            standard container property :attr:`contents`.
        """
        warnings.warn(
            "only for backwards compatibility. You should use the new standard container property `contents`",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        ml = MonitoredList(w for w, t in self.contents)

        def user_modified():
            self.widget_list = ml

        ml.set_modified_callback(user_modified)
        return ml

    @widget_list.setter
    def widget_list(self, widgets):
        focus_position = self.focus_position
        self.contents = [
            (new, options)
            for (new, (w, options)) in zip(
                widgets,
                # need to grow contents list if widgets is longer
                chain(self.contents, repeat((None, (WHSettings.WEIGHT, 1)))),
            )
        ]
        if focus_position < len(widgets):
            self.focus_position = focus_position

    @property
    def item_types(self):
        """
        A list of the options values for widgets in this Pile.

        .. note:: only for backwards compatibility. You should use the new
            standard container property :attr:`contents`.
        """
        warnings.warn(
            "only for backwards compatibility. You should use the new standard container property `contents`",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        ml = MonitoredList(
            # return the old item type names
            ({WHSettings.GIVEN: Sizing.FIXED, WHSettings.PACK: Sizing.FLOW}.get(f, f), height)
            for w, (f, height) in self.contents
        )

        def user_modified():
            self.item_types = ml

        ml.set_modified_callback(user_modified)
        return ml

    @item_types.setter
    def item_types(self, item_types):
        warnings.warn(
            "only for backwards compatibility. You should use the new standard container property `contents`",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        focus_position = self.focus_position
        self.contents = [
            (w, ({Sizing.FIXED: WHSettings.GIVEN, Sizing.FLOW: WHSettings.PACK}.get(new_t, new_t), new_height))
            for ((new_t, new_height), (w, options)) in zip(item_types, self.contents)
        ]
        if focus_position < len(item_types):
            self.focus_position = focus_position

    @property
    def contents(
        self,
    ) -> MonitoredFocusList[
        Widget,
        tuple[Literal[WHSettings.PACK], None]
        | tuple[Literal[WHSettings.GIVEN], int]
        | tuple[Literal[WHSettings.WEIGHT], int | float],
    ]:
        """
        The contents of this Pile as a list of (widget, options) tuples.

        options currently may be one of

        (``'pack'``, ``None``)
            allow widget to calculate its own height by calling its
            :meth:`rows ` method, i.e. treat it as a flow widget.
        (``'given'``, *n*)
            Always treat widget as a box widget with a given height of *n* rows.
        (``'weight'``, *w*)
            If the Pile itself is treated as a box widget then
            the value *w* will be used as a relative weight for assigning rows
            to this box widget. If the Pile is being treated as a flow
            widget then this is the same as (``'pack'``, ``None``) and the *w*
            value is ignored.

        If the Pile itself is treated as a box widget then at least one
        widget must have a (``'weight'``, *w*) options value, or the Pile will
        not be able to grow to fill the required number of rows.

        This list may be modified like a normal list and the Pile widget
        will updated automatically.

        .. seealso:: Create new options tuples with the :meth:`options` method
        """
        return self._contents

    @contents.setter
    def contents(
        self,
        c: Sequence[
            Widget,
            tuple[Literal[WHSettings.PACK], None]
            | tuple[Literal[WHSettings.GIVEN], int]
            | tuple[Literal[WHSettings.WEIGHT], int | float],
        ],
    ) -> None:
        self._contents[:] = c

    @staticmethod
    def options(
        height_type: Literal["pack", "given", "weight"] | WHSettings = WHSettings.WEIGHT,
        height_amount: int | float | None = 1,  # noqa: PYI041  # provide explicit for IDEs
    ) -> (
        tuple[Literal[WHSettings.PACK], None]
        | tuple[Literal[WHSettings.GIVEN], int]
        | tuple[Literal[WHSettings.WEIGHT], int | float]
    ):
        """
        Return a new options tuple for use in a Pile's :attr:`contents` list.

        :param height_type: ``'pack'``, ``'given'`` or ``'weight'``
        :param height_amount: ``None`` for ``'pack'``, a number of rows for
            ``'fixed'`` or a weight value (number) for ``'weight'``
        """

        if height_type == WHSettings.PACK:
            return (WHSettings.PACK, None)
        if height_type in {WHSettings.GIVEN, WHSettings.WEIGHT} and height_amount is not None:
            return (height_type, height_amount)
        raise PileError(f"invalid combination: height_type={height_type!r}, height_amount={height_amount!r}")

    @property
    def focus(self) -> Widget | None:
        """the child widget in focus or None when Pile is empty"""
        if not self.contents:
            return None
        return self.contents[self.focus_position][0]

    @focus.setter
    def focus(self, item: Widget | int) -> None:
        """
        Set the item in focus, for backwards compatibility.

        .. note:: only for backwards compatibility. You should use the new
            standard container property :attr:`focus_position`.
            to set the position by integer index instead.

        :param item: element to focus
        :type item: Widget or int
        """
        if isinstance(item, int):
            self.focus_position = item
            return
        for i, (w, _options) in enumerate(self.contents):
            if item == w:
                self.focus_position = i
                return
        raise ValueError(f"Widget not found in Pile contents: {item!r}")

    def _get_focus(self) -> Widget:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus` is deprecated, "
            f"please use `{self.__class__.__name__}.focus` property",
            DeprecationWarning,
            stacklevel=3,
        )
        if not self.contents:
            return None
        return self.contents[self.focus_position][0]

    def get_focus(self) -> Widget | None:
        """
        Return the widget in focus, for backwards compatibility.  You may
        also use the new standard container property .focus to get the
        child widget in focus.
        """
        warnings.warn(
            "for backwards compatibility."
            "You may also use the new standard container property .focus to get the child widget in focus.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        if not self.contents:
            return None
        return self.contents[self.focus_position][0]

    def set_focus(self, item: Widget | int) -> None:
        warnings.warn(
            "for backwards compatibility."
            "You may also use the new standard container property .focus to get the child widget in focus.",
            PendingDeprecationWarning,
            stacklevel=2,
        )
        if isinstance(item, int):
            self.focus_position = item
            return
        for i, (w, _options) in enumerate(self.contents):
            if item == w:
                self.focus_position = i
                return
        raise ValueError(f"Widget not found in Pile contents: {item!r}")

    @property
    def focus_item(self):
        warnings.warn(
            "only for backwards compatibility."
            "You should use the new standard container properties "
            "`focus` and `focus_position` to get the child widget in focus or modify the focus position.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.focus

    @focus_item.setter
    def focus_item(self, new_item):
        warnings.warn(
            "only for backwards compatibility."
            "You should use the new standard container properties "
            "`focus` and `focus_position` to get the child widget in focus or modify the focus position.",
            DeprecationWarning,
            stacklevel=2,
        )
        self.focus = new_item

    @property
    def focus_position(self) -> int:
        """
        index of child widget in focus.
        Raises :exc:`IndexError` if read when Pile is empty, or when set to an invalid index.
        """
        if not self.contents:
            raise IndexError("No focus_position, Pile is empty")
        return self.contents.focus

    @focus_position.setter
    def focus_position(self, position: int) -> None:
        """
        Set the widget in focus.

        position -- index of child widget to be made focus
        """
        try:
            if position < 0 or position >= len(self.contents):
                raise IndexError(f"No Pile child widget at position {position}")
        except TypeError as exc:
            raise IndexError(f"No Pile child widget at position {position}").with_traceback(exc.__traceback__) from exc
        self.contents.focus = position

    def _get_focus_position(self) -> int | None:
        warnings.warn(
            f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        if not self.contents:
            raise IndexError("No focus_position, Pile is empty")
        return self.contents.focus

    def _set_focus_position(self, position: int) -> None:
        """
        Set the widget in focus.

        position -- index of child widget to be made focus
        """
        warnings.warn(
            f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
            f"please use `{self.__class__.__name__}.focus_position` property",
            DeprecationWarning,
            stacklevel=3,
        )
        try:
            if position < 0 or position >= len(self.contents):
                raise IndexError(f"No Pile child widget at position {position}")
        except TypeError as exc:
            raise IndexError(f"No Pile child widget at position {position}").with_traceback(exc.__traceback__) from exc
        self.contents.focus = position

    def get_pref_col(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> int | None:
        """Return the preferred column for the cursor, or None."""
        if not self.selectable():
            return None

        if not self.contents:
            return None

        _, _, size_args = self.get_rows_sizes(size, focus=self.selectable())
        self._update_pref_col_from_focus(size_args[self.focus_position])
        return self.pref_col

    def get_item_size(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        i: int,
        focus: bool,
        item_rows: list[int] | None = None,
    ) -> tuple[()] | tuple[int] | tuple[int, int]:
        """
        Return a size appropriate for passing to self.contents[i][0].render
        """
        _w, (f, height) = self.contents[i]
        if f == WHSettings.PACK:
            if not size:
                return ()
            return (size[0],)

        if not size:
            raise PileError(f"Element {i} using parameters {f} and do not have full size information")

        maxcol = size[0]

        if f == WHSettings.GIVEN:
            return (maxcol, height)

        if f == WHSettings.WEIGHT:
            if len(size) == 2:
                if not item_rows:
                    item_rows = self.get_item_rows(size, focus)
                return (maxcol, item_rows[i])

            return (maxcol,)

        raise PileError(f"Unsupported item height rules: {f}")

    def _get_fixed_rows_sizes(
        self,
        focus: bool = False,
    ) -> tuple[Sequence[int], Sequence[int], Sequence[tuple[int] | tuple[()]]]:
        if not self.contents:
            return (), (), ()

        widths: dict[int, int] = {}
        heights: dict[int, int] = {}
        w_h_args: dict[int, tuple[int, int] | tuple[int] | tuple[()]] = {}

        flow: list[tuple[Widget, int, bool]] = []
        box: list[int] = []
        weighted: dict[int, list[int]] = {}
        weights: list[int] = []
        weight_max_sizes: dict[int, int] = {}

        for idx, (widget, (size_kind, size_weight)) in enumerate(self.contents):
            w_sizing = widget.sizing()
            focused = focus and self.focus == widget
            if size_kind == WHSettings.PACK:
                if Sizing.FIXED in w_sizing:
                    widths[idx], heights[idx] = widget.pack((), focused)
                    w_h_args[idx] = ()
                if Sizing.FLOW in w_sizing:
                    # re-calculate height at the end
                    flow.append((widget, idx, focused))
                if not w_sizing & {Sizing.FIXED, Sizing.FLOW}:
                    raise PileError(f"Unsupported sizing {w_sizing} for {size_kind.upper()}")

            elif size_kind == WHSettings.GIVEN:
                heights[idx] = size_weight
                if Sizing.BOX in w_sizing:
                    box.append(idx)
                else:
                    raise PileError(f"Unsupported sizing {w_sizing} for {size_kind.upper()}")

            elif size_weight <= 0:
                widths[idx] = 0
                heights[idx] = 0
                if Sizing.FLOW in w_sizing:
                    w_h_args[idx] = (0,)
                else:
                    w_sizing[idx] = (0, 0)

            elif Sizing.FIXED in w_sizing and w_sizing & {Sizing.BOX, Sizing.FLOW}:
                width, height = widget.pack((), focused)
                widths[idx] = width  # We're fitting everything in case of FIXED

                if Sizing.BOX in w_sizing:
                    weighted.setdefault(size_weight, []).append(idx)
                    weights.append(size_weight)

                    weight_max_sizes.setdefault(size_weight, height)
                    weight_max_sizes[size_weight] = max(weight_max_sizes[size_weight], height)

                else:
                    # width replace is allowed
                    flow.append((widget, idx, focused))

            elif Sizing.FLOW in w_sizing:
                # FLOW WEIGHT widgets are rendered the same as PACK WEIGHT
                flow.append((widget, idx, focused))
            else:
                raise PileError(f"Unsupported combination of {size_kind}, {w_sizing}")

        if not widths:
            raise PileError("No widgets providing width information")

        max_width = max(widths.values())

        for widget, idx, focused in flow:
            widths[idx] = max_width
            heights[idx] = widget.rows((max_width,), focused)
            w_h_args[idx] = (max_width,)

        if weight_max_sizes:
            max_weighted_coefficient = max(height / weight for weight, height in weight_max_sizes.items())

            for weight in weight_max_sizes:
                height = max(int(max_weighted_coefficient * weight + 0.5), 1)
                for idx in weighted[weight]:
                    heights[idx] = height
                    w_h_args[idx] = (max_width, height)

        for idx in box:
            widths[idx] = max_width
            w_h_args[idx] = (max_width, heights[idx])

        return (
            tuple(widths[idx] for idx in range(len(widths))),
            tuple(heights[idx] for idx in range(len(heights))),
            tuple(w_h_args[idx] for idx in range(len(w_h_args))),
        )

    def get_rows_sizes(
        self,
        size: tuple[int, int] | tuple[int] | tuple[()],
        focus: bool = False,
    ) -> tuple[Sequence[int], Sequence[int], Sequence[tuple[int, int] | tuple[int] | tuple[()]]]:
        """Get rows widths, heights and render size parameters"""
        if not size:
            return self._get_fixed_rows_sizes(focus=focus)

        maxcol = size[0]
        item_rows = None

        widths: list[int] = []
        heights: list[int] = []
        w_h_args: list[tuple[int, int] | tuple[int] | tuple[()]] = []

        for i, (w, (f, height)) in enumerate(self.contents):
            if isinstance(w, Widget):
                w_sizing = w.sizing()
            else:
                warnings.warn(f"{w!r} is not a Widget", PileWarning, stacklevel=3)
                w_sizing = frozenset((Sizing.FLOW, Sizing.BOX))

            item_focus = focus and self.focus == w
            widths.append(maxcol)

            if f == WHSettings.GIVEN:
                heights.append(height)
                w_h_args.append((maxcol, height))
            elif f == WHSettings.PACK or len(size) == 1:
                if Sizing.FLOW in w_sizing:
                    w_h_arg: tuple[int] | tuple[()] = (maxcol,)
                elif Sizing.FIXED in w_sizing and f == WHSettings.PACK:
                    w_h_arg = ()
                else:
                    warnings.warn(
                        f"Unusual widget {i} sizing {w_sizing} for {f.upper()}). "
                        f"Assuming wrong sizing and using {Sizing.FLOW.upper()} for height calculation",
                        PileWarning,
                        stacklevel=3,
                    )
                    w_h_arg = (maxcol,)

                heights.append(w.pack(w_h_arg, item_focus)[1])
                w_h_args.append(w_h_arg)
            else:
                if item_rows is None:
                    item_rows = self.get_item_rows(size, focus)
                rows = item_rows[i]
                heights.append(rows)
                w_h_args.append((maxcol, rows))

        return (tuple(widths), tuple(heights), tuple(w_h_args))

    def pack(self, size: tuple[()] | tuple[int] | tuple[int, int] = (), focus: bool = False) -> tuple[int, int]:
        """Get packed sized for widget."""
        if size:
            return super().pack(size, focus)
        widths, heights, _ = self.get_rows_sizes(size, focus)
        return (max(widths), sum(heights))

    def get_item_rows(self, size: tuple[()] | tuple[int] | tuple[int, int], focus: bool) -> list[int]:
        """
        Return a list of the number of rows used by each widget in self.contents
        """
        remaining = None
        maxcol = size[0]
        if len(size) == 2:
            remaining = size[1]

        rows_numbers = []

        if remaining is None:
            # pile is a flow widget
            for i, (w, (f, height)) in enumerate(self.contents):
                if isinstance(w, Widget):
                    w_sizing = w.sizing()
                else:
                    warnings.warn(f"{w!r} is not a Widget", PileWarning, stacklevel=3)
                    w_sizing = frozenset((Sizing.FLOW, Sizing.BOX))

                focused = focus and self.focus == w

                if f == WHSettings.GIVEN:
                    rows_numbers.append(height)
                elif Sizing.FLOW in w_sizing:
                    rows_numbers.append(w.rows((maxcol,), focus=focused))
                elif Sizing.FIXED in w_sizing and f == WHSettings.PACK:
                    rows_numbers.append(w.pack((), focused)[0])
                else:
                    warnings.warn(
                        f"Unusual widget {i} sizing {w_sizing} for {f.upper()}). "
                        f"Assuming wrong sizing and using {Sizing.FLOW.upper()} for height calculation",
                        PileWarning,
                        stacklevel=3,
                    )
                    rows_numbers.append(w.rows((maxcol,), focus=focused))
            return rows_numbers

        # pile is a box widget
        # do an extra pass to calculate rows for each widget
        wtotal = 0
        for w, (f, height) in self.contents:
            if f == WHSettings.PACK:
                rows = w.rows((maxcol,), focus=focus and self.focus == w)
                rows_numbers.append(rows)
                remaining -= rows
            elif f == WHSettings.GIVEN:
                rows_numbers.append(height)
                remaining -= height
            elif height:
                rows_numbers.append(None)
                wtotal += height
            else:
                rows_numbers.append(0)  # zero-weighted items treated as ('given', 0)

        if wtotal == 0:
            raise PileError("No weighted widgets found for Pile treated as a box widget")

        remaining = max(remaining, 0)

        for i, (_w, (_f, height)) in enumerate(self.contents):
            li = rows_numbers[i]
            if li is None:
                rows = int(float(remaining) * height / wtotal + 0.5)
                rows_numbers[i] = rows
                remaining -= rows
                wtotal -= height
        return rows_numbers

    def render(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        focus: bool = False,
    ) -> SolidCanvas | CompositeCanvas:
        _widths, heights, size_args = self.get_rows_sizes(size, focus)

        combinelist = []
        for i, (height, w_size, (w, _)) in enumerate(zip(heights, size_args, self.contents)):
            item_focus = self.focus == w
            canv = None
            if height > 0:
                canv = w.render(w_size, focus=focus and item_focus)

            if canv:
                combinelist.append((canv, i, item_focus))

        if not combinelist:
            return SolidCanvas(" ", size[0], (size[1:] + (0,))[0])

        out = CanvasCombine(combinelist)
        if len(size) == 2 and size[1] != out.rows():
            # flow/fixed widgets rendered too large/small
            out = CompositeCanvas(out)
            out.pad_trim_top_bottom(0, size[1] - out.rows())
        return out

    def get_cursor_coords(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> tuple[int, int] | None:
        """Return the cursor coordinates of the focus widget."""
        if not self.selectable():
            return None
        if not hasattr(self.focus, "get_cursor_coords"):
            return None

        i = self.focus_position
        _widths, heights, size_args = self.get_rows_sizes(size, focus=True)
        coords = self.focus.get_cursor_coords(size_args[i])

        if coords is None:
            return None
        x, y = coords
        if i > 0:
            for r in heights[:i]:
                y += r
        return x, y

    def rows(self, size: tuple[int] | tuple[int, int], focus: bool = False) -> int:
        return sum(self.get_item_rows(size, focus))

    def keypress(self, size: tuple[()] | tuple[int] | tuple[int, int], key: str) -> str | None:
        """Pass the keypress to the widget in focus.

        Unhandled 'up' and 'down' keys may cause a focus change.
        """
        if not self.contents:
            return key

        i = self.focus_position
        _widths, heights, size_args = self.get_rows_sizes(size, focus=self.selectable())
        if self.selectable():
            key = self.focus.keypress(size_args[i], key)
            if self._command_map[key] not in {Command.UP, Command.DOWN}:
                return key

        if self._command_map[key] == Command.UP:
            candidates = tuple(range(i - 1, -1, -1))  # count backwards to 0
        else:  # self._command_map[key] == 'cursor down'
            candidates = tuple(range(i + 1, len(self.contents)))

        for j in candidates:
            if not self.contents[j][0].selectable():
                continue

            self._update_pref_col_from_focus(size_args[self.focus_position])
            self.focus_position = j
            if not hasattr(self.focus, "move_cursor_to_coords"):
                return None

            rows = heights[j]
            if self._command_map[key] == Command.UP:
                rowlist = tuple(range(rows - 1, -1, -1))
            else:  # self._command_map[key] == 'cursor down'
                rowlist = tuple(range(rows))
            for row in rowlist:
                if self.focus.move_cursor_to_coords(size_args[self.focus_position], self.pref_col, row):
                    break
            return None

        # nothing to select
        return key

    def _update_pref_col_from_focus(self, w_size: tuple[()] | tuple[int] | tuple[int, int]) -> None:
        """Update self.pref_col from the focus widget."""

        if not hasattr(self.focus, "get_pref_col"):
            return

        pref_col = self.focus.get_pref_col(w_size)
        if pref_col is not None:
            self.pref_col = pref_col

    def move_cursor_to_coords(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        col: int,
        row: int,
    ) -> bool:
        """Capture pref col and set new focus."""
        self.pref_col = col

        # FIXME guessing focus==True
        focus = True
        wrow = 0
        _widths, heights, size_args = self.get_rows_sizes(size, focus=focus)
        for i, (r, w_size, (w, _)) in enumerate(zip(heights, size_args, self.contents)):  # noqa: B007
            if wrow + r > row:
                break
            wrow += r
        else:
            return False

        if not w.selectable():
            return False

        if hasattr(w, "move_cursor_to_coords"):
            rval = w.move_cursor_to_coords(w_size, col, row - wrow)
            if rval is False:
                return False

        self.focus_position = i
        return True

    def mouse_event(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        """Pass the event to the contained widget.

        May change focus on button 1 press.
        """
        wrow = 0
        _widths, heights, size_args = self.get_rows_sizes(size, focus=focus)

        for i, (height, w_size, (w, _)) in enumerate(zip(heights, size_args, self.contents)):  # noqa: B007
            if wrow + height > row:
                target_row = row - wrow
                break
            wrow += height
        else:
            return False

        if is_mouse_press(event) and button == 1 and w.selectable():
            self.focus_position = i

        if not hasattr(w, "mouse_event"):
            warnings.warn(
                f"{w.__class__.__module__}.{w.__class__.__name__} is not subclass of Widget",
                DeprecationWarning,
                stacklevel=2,
            )
            return False

        return w.mouse_event(w_size, event, button, col, target_row, focus and self.focus == w)
urwid-2.6.16/urwid/widget/popup.py000066400000000000000000000135261470350774000171100ustar00rootroot00000000000000# Urwid Window-Icon-Menu-Pointer-style widget classes
#    Copyright (C) 2004-2011  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import typing

from urwid.canvas import CompositeCanvas

from .constants import Align, Sizing, VAlign
from .overlay import Overlay
from .widget import delegate_to_widget_mixin
from .widget_decoration import WidgetDecoration

if typing.TYPE_CHECKING:
    from typing_extensions import TypedDict

    from urwid.canvas import Canvas

    from .widget import Widget

    class PopUpParametersModel(TypedDict):
        left: int
        top: int
        overlay_width: int
        overlay_height: int


WrappedWidget = typing.TypeVar("WrappedWidget")


class PopUpLauncher(delegate_to_widget_mixin("_original_widget"), WidgetDecoration[WrappedWidget]):
    def __init__(self, original_widget: [WrappedWidget]) -> None:
        super().__init__(original_widget)
        self._pop_up_widget = None

    def create_pop_up(self) -> Widget:
        """
        Subclass must override this method and return a widget
        to be used for the pop-up.  This method is called once each time
        the pop-up is opened.
        """
        raise NotImplementedError("Subclass must override this method")

    def get_pop_up_parameters(self) -> PopUpParametersModel:
        """
        Subclass must override this method and have it return a dict, eg:

        {'left':0, 'top':1, 'overlay_width':30, 'overlay_height':4}

        This method is called each time this widget is rendered.
        """
        raise NotImplementedError("Subclass must override this method")

    def open_pop_up(self) -> None:
        self._pop_up_widget = self.create_pop_up()
        self._invalidate()

    def close_pop_up(self) -> None:
        self._pop_up_widget = None
        self._invalidate()

    def render(self, size, focus: bool = False) -> CompositeCanvas | Canvas:
        canv = super().render(size, focus)
        if self._pop_up_widget:
            canv = CompositeCanvas(canv)
            canv.set_pop_up(self._pop_up_widget, **self.get_pop_up_parameters())
        return canv


class PopUpTarget(WidgetDecoration[WrappedWidget]):
    # FIXME: this whole class is a terrible hack and must be fixed when layout and rendering are separated
    _sizing = frozenset((Sizing.BOX,))
    _selectable = True

    def __init__(self, original_widget: WrappedWidget) -> None:
        super().__init__(original_widget)
        self._pop_up = None
        self._current_widget = self._original_widget

    def _update_overlay(self, size: tuple[int, int], focus: bool) -> None:
        canv = self._original_widget.render(size, focus=focus)
        self._cache_original_canvas = canv  # imperfect performance hack
        pop_up = canv.get_pop_up()
        if pop_up:
            left, top, (w, overlay_width, overlay_height) = pop_up
            if self._pop_up != w:
                self._pop_up = w
                self._current_widget = Overlay(
                    top_w=w,
                    bottom_w=self._original_widget,
                    align=Align.LEFT,
                    width=overlay_width,
                    valign=VAlign.TOP,
                    height=overlay_height,
                    left=left,
                    top=top,
                )
            else:
                self._current_widget.set_overlay_parameters(
                    align=Align.LEFT,
                    width=overlay_width,
                    valign=VAlign.TOP,
                    height=overlay_height,
                    left=left,
                    top=top,
                )
        else:
            self._pop_up = None
            self._current_widget = self._original_widget

    def render(self, size: tuple[int, int], focus: bool = False) -> Canvas:
        self._update_overlay(size, focus)
        return self._current_widget.render(size, focus=focus)

    def get_cursor_coords(self, size: tuple[int, int]) -> tuple[int, int] | None:
        self._update_overlay(size, True)
        return self._current_widget.get_cursor_coords(size)

    def get_pref_col(self, size: tuple[int, int]) -> int:
        self._update_overlay(size, True)
        return self._current_widget.get_pref_col(size)

    def keypress(self, size: tuple[int, int], key: str) -> str | None:
        self._update_overlay(size, True)
        return self._current_widget.keypress(size, key)

    def move_cursor_to_coords(self, size: tuple[int, int], x: int, y: int):
        self._update_overlay(size, True)
        return self._current_widget.move_cursor_to_coords(size, x, y)

    def mouse_event(
        self,
        size: tuple[int, int],
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        self._update_overlay(size, focus)
        return self._current_widget.mouse_event(size, event, button, col, row, focus)

    def pack(self, size: tuple[int, int] | None = None, focus: bool = False) -> tuple[int, int]:
        self._update_overlay(size, focus)
        return self._current_widget.pack(size)


def _test():
    import doctest

    doctest.testmod()


if __name__ == "__main__":
    _test()
urwid-2.6.16/urwid/widget/progress_bar.py000066400000000000000000000114751470350774000204360ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings

from .constants import BAR_SYMBOLS, Align, Sizing, WrapMode
from .text import Text
from .widget import Widget

if typing.TYPE_CHECKING:
    from collections.abc import Hashable

    from urwid.canvas import TextCanvas


class ProgressBar(Widget):
    _sizing = frozenset([Sizing.FLOW])

    eighths = BAR_SYMBOLS.HORISONTAL[:8]  # Full width line is made by style

    text_align = Align.CENTER

    def __init__(
        self,
        normal: Hashable | None,
        complete: Hashable | None,
        current: int = 0,
        done: int = 100,
        satt: Hashable | None = None,
    ) -> None:
        """
        :param normal: display attribute for incomplete part of progress bar
        :param complete: display attribute for complete part of progress bar
        :param current: current progress
        :param done: progress amount at 100%
        :param satt: display attribute for smoothed part of bar where the
                     foreground of satt corresponds to the normal part and the
                     background corresponds to the complete part.
                     If satt is ``None`` then no smoothing will be done.

        >>> from urwid import LineBox
        >>> pb = ProgressBar('a', 'b')
        >>> pb
        
        >>> print(pb.get_text())
        0 %
        >>> pb.set_completion(34.42)
        >>> print(pb.get_text())
        34 %
        >>> class CustomProgressBar(ProgressBar):
        ...     def get_text(self):
        ...         return u'Foobar'
        >>> cpb = CustomProgressBar('a', 'b')
        >>> print(cpb.get_text())
        Foobar
        >>> for x in range(101):
        ...     cpb.set_completion(x)
        ...     s = cpb.render((10, ))
        >>> cpb2 = CustomProgressBar('a', 'b', satt='c')
        >>> for x in range(101):
        ...     cpb2.set_completion(x)
        ...     s = cpb2.render((10, ))
        >>> pb = ProgressBar('a', 'b', satt='c')
        >>> pb.set_completion(34.56)
        >>> print(LineBox(pb).render((20,)))
        ┌──────────────────â”
        │      â–34 %       │
        └──────────────────┘
        """
        super().__init__()
        self.normal = normal
        self.complete = complete
        self._current = current
        self._done = done
        self.satt = satt

    def set_completion(self, current: int) -> None:
        """
        current -- current progress
        """
        self._current = current
        self._invalidate()

    current = property(lambda self: self._current, set_completion)

    @property
    def done(self):
        return self._done

    @done.setter
    def done(self, done):
        """
        done -- progress amount at 100%
        """
        self._done = done
        self._invalidate()

    def _set_done(self, done):
        warnings.warn(
            f"Method `{self.__class__.__name__}._set_done` is deprecated, "
            f"please use property `{self.__class__.__name__}.done`",
            DeprecationWarning,
            stacklevel=2,
        )
        self.done = done

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        return 1

    def get_text(self) -> str:
        """
        Return the progress bar percentage text.
        You can override this method to display custom text.
        """
        percent = min(100, max(0, int(self.current * 100 / self.done)))
        return f"{percent!s} %"

    def render(
        self,
        size: tuple[int],  # type: ignore[override]
        focus: bool = False,
    ) -> TextCanvas:
        """
        Render the progress bar.
        """
        # pylint: disable=protected-access
        (maxcol,) = size
        c = Text(self.get_text(), self.text_align, WrapMode.CLIP).render((maxcol,))

        cf = float(self.current) * maxcol / self.done
        ccol_dirty = int(cf)
        ccol = len(c._text[0][:ccol_dirty].decode("utf-8", "ignore").encode("utf-8"))
        cs = 0
        if self.satt is not None:
            cs = int((cf - ccol) * 8)
        if ccol < 0 or (ccol == cs == 0):
            c._attr = [[(self.normal, maxcol)]]
        elif ccol >= maxcol:
            c._attr = [[(self.complete, maxcol)]]
        elif cs and c._text[0][ccol] == 32:
            t = c._text[0]
            cenc = self.eighths[cs].encode("utf-8")
            c._text[0] = t[:ccol] + cenc + t[ccol + 1 :]
            a = []
            if ccol > 0:
                a.append((self.complete, ccol))
            a.append((self.satt, len(cenc)))
            if maxcol - ccol - 1 > 0:
                a.append((self.normal, maxcol - ccol - 1))
            c._attr = [a]
            c._cs = [[(None, len(c._text[0]))]]
        else:
            c._attr = [[(self.complete, ccol), (self.normal, maxcol - ccol)]]
        return c
urwid-2.6.16/urwid/widget/scrollable.py000066400000000000000000000577301470350774000200740ustar00rootroot00000000000000# Copyright (C) 2024 Urwid developers
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/
#
# Copyright (C) 2017-2024 rndusr (https://github.com/rndusr)
# Re-licensed from gpl-3.0 with author permission.
# Permission comment link: https://github.com/markqvist/NomadNet/pull/46#issuecomment-1892712616

from __future__ import annotations

import contextlib
import enum
import typing

from typing_extensions import Protocol, runtime_checkable

from .constants import BOX_SYMBOLS, SHADE_SYMBOLS, Sizing
from .widget_decoration import WidgetDecoration, WidgetError

if typing.TYPE_CHECKING:
    from collections.abc import Iterator

    from typing_extensions import Literal

    from urwid import Canvas, CompositeCanvas

    from .widget import Widget


__all__ = ("ScrollBar", "Scrollable", "ScrollableError", "ScrollbarSymbols")


WrappedWidget = typing.TypeVar("WrappedWidget", bound="SupportsScroll")


class ScrollableError(WidgetError):
    """Scrollable specific widget errors."""


# Scroll actions
SCROLL_LINE_UP = "line up"
SCROLL_LINE_DOWN = "line down"
SCROLL_PAGE_UP = "page up"
SCROLL_PAGE_DOWN = "page down"
SCROLL_TO_TOP = "to top"
SCROLL_TO_END = "to end"

# Scrollbar positions
SCROLLBAR_LEFT = "left"
SCROLLBAR_RIGHT = "right"


class ScrollbarSymbols(str, enum.Enum):
    """Common symbols suitable for scrollbar."""

    FULL_BLOCK = SHADE_SYMBOLS.FULL_BLOCK
    DARK_SHADE = SHADE_SYMBOLS.DARK_SHADE
    MEDIUM_SHADE = SHADE_SYMBOLS.MEDIUM_SHADE
    LITE_SHADE = SHADE_SYMBOLS.LITE_SHADE

    DRAWING_LIGHT = BOX_SYMBOLS.LIGHT.VERTICAL
    DRAWING_LIGHT_2_DASH = BOX_SYMBOLS.LIGHT.VERTICAL_2_DASH
    DRAWING_LIGHT_3_DASH = BOX_SYMBOLS.LIGHT.VERTICAL_3_DASH
    DRAWING_LIGHT_4_DASH = BOX_SYMBOLS.LIGHT.VERTICAL_4_DASH

    DRAWING_HEAVY = BOX_SYMBOLS.HEAVY.VERTICAL
    DRAWING_HEAVY_2_DASH = BOX_SYMBOLS.HEAVY.VERTICAL_2_DASH
    DRAWING_HEAVY_3_DASH = BOX_SYMBOLS.HEAVY.VERTICAL_3_DASH
    DRAWING_HEAVY_4_DASH = BOX_SYMBOLS.HEAVY.VERTICAL_4_DASH

    DRAWING_DOUBLE = BOX_SYMBOLS.DOUBLE.VERTICAL


@runtime_checkable
class WidgetProto(Protocol):
    """Protocol for widget.

    Due to protocol cannot inherit non-protocol bases, define several obligatory Widget methods.
    """

    # Base widget methods (from Widget)
    def sizing(self) -> frozenset[Sizing]: ...

    def selectable(self) -> bool: ...

    def pack(self, size: tuple[int, int], focus: bool = False) -> tuple[int, int]: ...

    @property
    def base_widget(self) -> Widget:
        raise NotImplementedError

    def keypress(self, size: tuple[int, int], key: str) -> str | None: ...

    def mouse_event(
        self,
        size: tuple[int, int],
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None: ...

    def render(self, size: tuple[int, int], focus: bool = False) -> Canvas: ...


@runtime_checkable
class SupportsScroll(WidgetProto, Protocol):
    """Scroll specific methods."""

    def get_scrollpos(self, size: tuple[int, int], focus: bool = False) -> int: ...

    def rows_max(self, size: tuple[int, int] | None = None, focus: bool = False) -> int: ...


@runtime_checkable
class SupportsRelativeScroll(WidgetProto, Protocol):
    """Relative scroll-specific methods."""

    def require_relative_scroll(self, size: tuple[int, int], focus: bool = False) -> bool: ...

    def get_first_visible_pos(self, size: tuple[int, int], focus: bool = False) -> int: ...

    def get_visible_amount(self, size: tuple[int, int], focus: bool = False) -> int: ...


def orig_iter(w: Widget) -> Iterator[Widget]:
    visited = {w}
    yield w
    while hasattr(w, "original_widget"):
        w = w.original_widget
        if w in visited:
            break
        visited.add(w)
        yield w


class Scrollable(WidgetDecoration[WrappedWidget]):
    def sizing(self) -> frozenset[Sizing]:
        return frozenset((Sizing.BOX,))

    def selectable(self) -> bool:
        return True

    def __init__(self, widget: WrappedWidget, force_forward_keypress: bool = False) -> None:
        """Box widget that makes a fixed or flow widget vertically scrollable

        .. note::
            Focusable widgets are handled, including switching focus, but possibly not intuitively,
            depending on the arrangement of widgets.

            When switching focus to a widget that is ouside of the visible part of the original widget,
            the canvas scrolls up/down to the focused widget.

            It would be better to scroll until the next focusable widget is in sight first.
            But for that to work we must somehow obtain a list of focusable rows in the original canvas.
        """
        if not widget.sizing() & frozenset((Sizing.FIXED, Sizing.FLOW)):
            raise ValueError(f"Not a fixed or flow widget: {widget!r}")

        self._trim_top = 0
        self._scroll_action = None
        self._forward_keypress = None
        self._old_cursor_coords = None
        self._rows_max_cached = 0
        self.force_forward_keypress = force_forward_keypress
        super().__init__(widget)

    def render(
        self,
        size: tuple[int, int],  # type: ignore[override]
        focus: bool = False,
    ) -> CompositeCanvas:
        from urwid import canvas

        maxcol, maxrow = size

        def automove_cursor() -> None:
            ch = 0
            last_hidden = False
            first_visible = False
            for pwi, (w, _o) in enumerate(ow.contents):
                wcanv = w.render((maxcol,))
                wh = wcanv.rows()
                if wh:
                    ch += wh

                if not last_hidden and ch >= self._trim_top:
                    last_hidden = True

                elif last_hidden:
                    if not first_visible:
                        first_visible = True

                    if not w.selectable():
                        continue

                    ow.focus_item = pwi

                    st = None
                    nf = ow.get_focus()
                    if hasattr(nf, "key_timeout"):
                        st = nf
                    elif hasattr(nf, "original_widget"):
                        no = nf.original_widget
                        if hasattr(no, "original_widget"):
                            st = no.original_widget
                        elif hasattr(no, "key_timeout"):
                            st = no

                    if st and hasattr(st, "key_timeout") and callable(getattr(st, "keypress", None)):
                        st.keypress(None, None)

                    break

        # Render complete original widget
        ow = self._original_widget
        ow_size = self._get_original_widget_size(size)
        canv_full = ow.render(ow_size, focus)

        # Make full canvas editable
        canv = canvas.CompositeCanvas(canv_full)
        canv_cols, canv_rows = canv.cols(), canv.rows()

        if canv_cols <= maxcol:
            pad_width = maxcol - canv_cols
            if pad_width > 0:
                # Canvas is narrower than available horizontal space
                canv.pad_trim_left_right(0, pad_width)

        if canv_rows <= maxrow:
            fill_height = maxrow - canv_rows
            if fill_height > 0:
                # Canvas is lower than available vertical space
                canv.pad_trim_top_bottom(0, fill_height)

        if canv_cols <= maxcol and canv_rows <= maxrow:
            # Canvas is small enough to fit without trimming
            return canv

        self._adjust_trim_top(canv, size)

        # Trim canvas if necessary
        trim_top = self._trim_top
        trim_end = canv_rows - maxrow - trim_top
        trim_right = canv_cols - maxcol
        if trim_top > 0:
            canv.trim(trim_top)
        if trim_end > 0:
            canv.trim_end(trim_end)
        if trim_right > 0:
            canv.pad_trim_left_right(0, -trim_right)

        # Disable cursor display if cursor is outside of visible canvas parts
        if canv.cursor is not None:
            # Pylint check acts here a bit weird.
            _curscol, cursrow = canv.cursor  # pylint: disable=unpacking-non-sequence,useless-suppression
            if cursrow >= maxrow or cursrow < 0:
                canv.cursor = None

        # Figure out whether we should forward keypresses to original widget
        if canv.cursor is not None:
            # Trimmed canvas contains the cursor, e.g. in an Edit widget
            self._forward_keypress = True
        elif canv_full.cursor is not None:
            # Full canvas contains the cursor, but scrolled out of view
            self._forward_keypress = False

            # Reset cursor position on page/up down scrolling
            if getattr(ow, "automove_cursor_on_scroll", False):
                with contextlib.suppress(Exception):
                    automove_cursor()

        else:
            # Original widget does not have a cursor, but may be selectable

            # FIXME: Using ow.selectable() is bad because the original
            # widget may be selectable because it's a container widget with
            # a key-grabbing widget that is scrolled out of view.
            # ow.selectable() returns True anyway because it doesn't know
            # how we trimmed our canvas.
            #
            # To fix this, we need to resolve ow.focus and somehow
            # ask canv whether it contains bits of the focused widget.  I
            # can't see a way to do that.
            self._forward_keypress = ow.selectable()

        return canv

    def keypress(
        self,
        size: tuple[int, int],  # type: ignore[override]
        key: str,
    ) -> str | None:
        from urwid.command_map import Command

        # Maybe offer key to original widget
        if self._forward_keypress or self.force_forward_keypress:
            ow = self._original_widget
            ow_size = self._get_original_widget_size(size)

            # Remember the previous cursor position if possible
            if hasattr(ow, "get_cursor_coords"):
                self._old_cursor_coords = ow.get_cursor_coords(ow_size)

            key = ow.keypress(ow_size, key)
            if key is None:
                return None

        # Handle up/down, page up/down, etc.
        command_map = self._command_map
        if command_map[key] == Command.UP:
            self._scroll_action = SCROLL_LINE_UP
        elif command_map[key] == Command.DOWN:
            self._scroll_action = SCROLL_LINE_DOWN

        elif command_map[key] == Command.PAGE_UP:
            self._scroll_action = SCROLL_PAGE_UP
        elif command_map[key] == Command.PAGE_DOWN:
            self._scroll_action = SCROLL_PAGE_DOWN

        elif command_map[key] == Command.MAX_LEFT:  # 'home'
            self._scroll_action = SCROLL_TO_TOP
        elif command_map[key] == Command.MAX_RIGHT:  # 'end'
            self._scroll_action = SCROLL_TO_END

        else:
            return key

        self._invalidate()
        return None

    def mouse_event(
        self,
        size: tuple[int, int],  # type: ignore[override]
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        ow = self._original_widget
        if hasattr(ow, "mouse_event"):
            ow_size = self._get_original_widget_size(size)
            row += self._trim_top
            return ow.mouse_event(ow_size, event, button, col, row, focus)

        return False

    def _adjust_trim_top(self, canv: Canvas, size: tuple[int, int]) -> None:
        """Adjust self._trim_top according to self._scroll_action"""
        action = self._scroll_action
        self._scroll_action = None

        _maxcol, maxrow = size
        trim_top = self._trim_top
        canv_rows = canv.rows()

        if trim_top < 0:
            # Negative trim_top values use bottom of canvas as reference
            trim_top = canv_rows - maxrow + trim_top + 1

        if canv_rows <= maxrow:
            self._trim_top = 0  # Reset scroll position
            return

        def ensure_bounds(new_trim_top: int) -> int:
            return max(0, min(canv_rows - maxrow, new_trim_top))

        if action == SCROLL_LINE_UP:
            self._trim_top = ensure_bounds(trim_top - 1)
        elif action == SCROLL_LINE_DOWN:
            self._trim_top = ensure_bounds(trim_top + 1)

        elif action == SCROLL_PAGE_UP:
            self._trim_top = ensure_bounds(trim_top - maxrow + 1)
        elif action == SCROLL_PAGE_DOWN:
            self._trim_top = ensure_bounds(trim_top + maxrow - 1)

        elif action == SCROLL_TO_TOP:
            self._trim_top = 0
        elif action == SCROLL_TO_END:
            self._trim_top = canv_rows - maxrow

        else:
            self._trim_top = ensure_bounds(trim_top)

        # If the cursor was moved by the most recent keypress, adjust trim_top
        # so that the new cursor position is within the displayed canvas part.
        # But don't do this if the cursor is at the top/bottom edge so we can still scroll out
        if self._old_cursor_coords is not None and self._old_cursor_coords != canv.cursor and canv.cursor is not None:
            self._old_cursor_coords = None
            _curscol, cursrow = canv.cursor
            if cursrow < self._trim_top:
                self._trim_top = cursrow
            elif cursrow >= self._trim_top + maxrow:
                self._trim_top = max(0, cursrow - maxrow + 1)

    def _get_original_widget_size(
        self,
        size: tuple[int, int],  # type: ignore[override]
    ) -> tuple[int] | tuple[()]:
        ow = self._original_widget
        sizing = ow.sizing()
        if Sizing.FLOW in sizing:
            return (size[0],)
        if Sizing.FIXED in sizing:
            return ()
        raise ScrollableError(f"{ow!r} sizing is not supported")

    def get_scrollpos(self, size: tuple[int, int] | None = None, focus: bool = False) -> int:
        """Current scrolling position.

        Lower limit is 0, upper limit is the maximum number of rows with the given maxcol minus maxrow.

        ..note::
            The returned value may be too low or too high if the position has
            changed but the widget wasn't rendered yet.
        """
        return self._trim_top

    def set_scrollpos(self, position: typing.SupportsInt) -> None:
        """Set scrolling position

        If `position` is positive it is interpreted as lines from the top.
        If `position` is negative it is interpreted as lines from the bottom.

        Values that are too high or too low values are automatically adjusted during rendering.
        """
        self._trim_top = int(position)
        self._invalidate()

    def rows_max(self, size: tuple[int, int] | None = None, focus: bool = False) -> int:
        """Return the number of rows for `size`

        If `size` is not given, the currently rendered number of rows is returned.
        """
        if size is not None:
            ow = self._original_widget
            ow_size = self._get_original_widget_size(size)
            sizing = ow.sizing()
            if Sizing.FIXED in sizing:
                self._rows_max_cached = ow.pack(ow_size, focus)[1]
            elif Sizing.FLOW in sizing:
                self._rows_max_cached = ow.rows(ow_size, focus)
            else:
                raise ScrollableError(f"Not a flow/box widget: {self._original_widget!r}")
        return self._rows_max_cached


class ScrollBar(WidgetDecoration[WrappedWidget]):
    Symbols = ScrollbarSymbols

    def sizing(self) -> frozenset[Sizing]:
        return frozenset((Sizing.BOX,))

    def selectable(self) -> bool:
        return True

    def __init__(
        self,
        widget: WrappedWidget,
        thumb_char: str = ScrollbarSymbols.FULL_BLOCK,
        trough_char: str = " ",
        side: Literal["left", "right"] = SCROLLBAR_RIGHT,
        width: int = 1,
    ) -> None:
        """Box widget that adds a scrollbar to `widget`

        `widget` must be a box widget with the following methods:
          - `get_scrollpos` takes the arguments `size` and `focus` and returns the index of the first visible row.
          - `set_scrollpos` (optional; needed for mouse click support) takes the index of the first visible row.
          - `rows_max` takes `size` and `focus` and returns the total number of rows `widget` can render.

        `thumb_char` is the character used for the scrollbar handle.
        `trough_char` is used for the space above and below the handle.
        `side` must be 'left' or 'right'.
        `width` specifies the number of columns the scrollbar uses.
        """
        if Sizing.BOX not in widget.sizing():
            raise ValueError(f"Not a box widget: {widget!r}")

        if not any(isinstance(w, SupportsScroll) for w in orig_iter(widget)):
            raise TypeError(f"Not a scrollable widget: {widget!r}")

        super().__init__(widget)
        self._thumb_char = thumb_char
        self._trough_char = trough_char
        self.scrollbar_side = side
        self.scrollbar_width = max(1, width)
        self._original_widget_size = (0, 0)

    def render(
        self,
        size: tuple[int, int],  # type: ignore[override]
        focus: bool = False,
    ) -> Canvas:
        from urwid import canvas

        def render_no_scrollbar() -> Canvas:
            self._original_widget_size = size
            return ow.render(size, focus)

        def render_for_scrollbar() -> Canvas:
            self._original_widget_size = ow_size
            return ow.render(ow_size, focus)

        maxcol, maxrow = size

        ow_size = (max(0, maxcol - self._scrollbar_width), maxrow)
        sb_width = maxcol - ow_size[0]

        ow = self._original_widget
        ow_base = self.scrolling_base_widget

        # Use hasattr instead of protocol: hasattr will return False in case of getattr raise AttributeError
        # Use __length_hint__ first since it's less resource intensive
        use_relative = (
            isinstance(ow_base, SupportsRelativeScroll)
            and any(hasattr(ow_base, attrib) for attrib in ("__length_hint__", "__len__"))
            and ow_base.require_relative_scroll(size, focus)
        )

        if use_relative:
            # `operator.length_hint` is Protocol (Spec) over class based and can end false-negative on the instance
            # use length_hint-like approach with safe `AttributeError` handling
            ow_len = getattr(ow_base, "__len__", getattr(ow_base, "__length_hint__", int))()
            ow_canv = render_for_scrollbar()
            visible_amount = ow_base.get_visible_amount(ow_size, focus)
            pos = ow_base.get_first_visible_pos(ow_size, focus)

            # in the case of estimated length, it can be smaller than real widget length
            ow_len = max(ow_len, visible_amount, pos)
            posmax = ow_len - visible_amount
            thumb_weight = min(1.0, visible_amount / max(1, ow_len))

            if ow_len == visible_amount:
                # Corner case: formally all contents indexes should be visible, but this does not mean all rows
                use_relative = False

        if not use_relative:
            ow_rows_max = ow_base.rows_max(size, focus)
            if ow_rows_max <= maxrow:
                # Canvas fits without scrolling - no scrollbar needed
                return render_no_scrollbar()

            ow_canv = render_for_scrollbar()
            ow_rows_max = ow_base.rows_max(ow_size, focus)
            pos = ow_base.get_scrollpos(ow_size, focus)
            posmax = ow_rows_max - maxrow
            thumb_weight = min(1.0, maxrow / max(1, ow_rows_max))

        # Thumb shrinks/grows according to the ratio of  / 
        thumb_height = max(1, round(thumb_weight * maxrow))  # pylint: disable=possibly-used-before-assignment

        # Thumb may only touch top/bottom if the first/last row is visible
        top_weight = float(pos) / max(1, posmax)  # pylint: disable=possibly-used-before-assignment
        top_height = int((maxrow - thumb_height) * top_weight)
        if top_height == 0 and top_weight > 0:
            top_height = 1

        # Bottom part is remaining space
        bottom_height = maxrow - thumb_height - top_height

        # Create scrollbar canvas
        # Creating SolidCanvases of correct height may result in
        # "cviews do not fill gaps in shard_tail!" or "cviews overflow gaps in shard_tail!" exceptions.
        # Stacking the same SolidCanvas is a workaround.
        # https://github.com/urwid/urwid/issues/226#issuecomment-437176837
        top = canvas.SolidCanvas(self._trough_char, sb_width, 1)
        thumb = canvas.SolidCanvas(self._thumb_char, sb_width, 1)
        bottom = canvas.SolidCanvas(self._trough_char, sb_width, 1)
        sb_canv = canvas.CanvasCombine(
            (
                *((top, None, False) for _ in range(top_height)),
                *((thumb, None, False) for _ in range(thumb_height)),
                *((bottom, None, False) for _ in range(bottom_height)),
            ),
        )

        combinelist = [
            (ow_canv, None, True, ow_size[0]),  # pylint: disable=possibly-used-before-assignment
            (sb_canv, None, False, sb_width),
        ]

        if self._scrollbar_side != SCROLLBAR_LEFT:
            return canvas.CanvasJoin(combinelist)

        return canvas.CanvasJoin(reversed(combinelist))

    @property
    def scrollbar_width(self) -> int:
        """Columns the scrollbar uses"""
        return max(1, self._scrollbar_width)

    @scrollbar_width.setter
    def scrollbar_width(self, width: typing.SupportsInt) -> None:
        self._scrollbar_width = max(1, int(width))
        self._invalidate()

    @property
    def scrollbar_side(self) -> Literal["left", "right"]:
        """Where to display the scrollbar; must be 'left' or 'right'"""
        return self._scrollbar_side

    @scrollbar_side.setter
    def scrollbar_side(self, side: Literal["left", "right"]) -> None:
        if side not in {SCROLLBAR_LEFT, SCROLLBAR_RIGHT}:
            raise ValueError(f'scrollbar_side must be "left" or "right", not {side!r}')
        self._scrollbar_side = side
        self._invalidate()

    @property
    def scrolling_base_widget(self) -> SupportsScroll | SupportsRelativeScroll:
        """Nearest `original_widget` that is compatible with the scrolling API"""

        w = self

        for w in orig_iter(self):
            if isinstance(w, SupportsScroll):
                return w

        raise ScrollableError(f"Not compatible to be wrapped by ScrollBar: {w!r}")

    def keypress(
        self,
        size: tuple[int, int],  # type: ignore[override]
        key: str,
    ) -> str | None:
        return self._original_widget.keypress(self._original_widget_size, key)

    def mouse_event(
        self,
        size: tuple[int, int],  # type: ignore[override]
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        ow = self._original_widget
        ow_size = self._original_widget_size
        handled: bool | None = False
        if hasattr(ow, "mouse_event"):
            handled = ow.mouse_event(ow_size, event, button, col, row, focus)

        if not handled and hasattr(ow, "set_scrollpos"):
            if button == 4:  # scroll wheel up
                pos = ow.get_scrollpos(ow_size)
                newpos = max(pos - 1, 0)
                ow.set_scrollpos(newpos)
                return True
            if button == 5:  # scroll wheel down
                pos = ow.get_scrollpos(ow_size)
                ow.set_scrollpos(pos + 1)
                return True

        return handled
urwid-2.6.16/urwid/widget/solid_fill.py000066400000000000000000000023541470350774000200620ustar00rootroot00000000000000from __future__ import annotations

from urwid.canvas import SolidCanvas

from .constants import SHADE_SYMBOLS, Sizing
from .widget import Widget


class SolidFill(Widget):
    """
    A box widget that fills an area with a single character
    """

    _selectable = False
    ignore_focus = True
    _sizing = frozenset([Sizing.BOX])

    Symbols = SHADE_SYMBOLS

    def __init__(self, fill_char: str = " ") -> None:
        """
        :param fill_char: character to fill area with
        :type fill_char: bytes or unicode

        >>> SolidFill(u'8')
        
        """
        super().__init__()
        self.fill_char = fill_char

    def _repr_words(self) -> list[str]:
        return [*super()._repr_words(), repr(self.fill_char)]

    def render(
        self,
        size: tuple[int, int],  # type: ignore[override]
        focus: bool = False,
    ) -> SolidCanvas:
        """
        Render the Fill as a canvas and return it.

        >>> SolidFill().render((4,2)).text # ... = b in Python 3
        [...'    ', ...'    ']
        >>> SolidFill('#').render((5,3)).text
        [...'#####', ...'#####', ...'#####']
        """
        maxcol, maxrow = size
        return SolidCanvas(self.fill_char, maxcol, maxrow)
urwid-2.6.16/urwid/widget/text.py000066400000000000000000000276331470350774000167350ustar00rootroot00000000000000from __future__ import annotations

import typing

from urwid import text_layout
from urwid.canvas import apply_text_layout
from urwid.split_repr import remove_defaults
from urwid.str_util import calc_width
from urwid.util import decompose_tagmarkup, get_encoding

from .constants import Align, Sizing, WrapMode
from .widget import Widget, WidgetError

if typing.TYPE_CHECKING:
    from collections.abc import Hashable

    from typing_extensions import Literal

    from urwid.canvas import TextCanvas


class TextError(WidgetError):
    pass


class Text(Widget):
    """
    a horizontally resizeable text widget
    """

    _sizing = frozenset([Sizing.FLOW, Sizing.FIXED])

    ignore_focus = True
    _repr_content_length_max = 140

    def __init__(
        self,
        markup: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        align: Literal["left", "center", "right"] | Align = Align.LEFT,
        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = WrapMode.SPACE,
        layout: text_layout.TextLayout | None = None,
    ) -> None:
        """
        :param markup: content of text widget, one of:

            bytes or unicode
              text to be displayed

            (*display attribute*, *text markup*)
              *text markup* with *display attribute* applied to all parts
              of *text markup* with no display attribute already applied

            [*text markup*, *text markup*, ... ]
              all *text markup* in the list joined together

        :type markup: :ref:`text-markup`
        :param align: typically ``'left'``, ``'center'`` or ``'right'``
        :type align: text alignment mode
        :param wrap: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``
        :type wrap: text wrapping mode
        :param layout: defaults to a shared :class:`StandardTextLayout` instance
        :type layout: text layout instance

        >>> Text(u"Hello")
        
        >>> t = Text(('bold', u"stuff"), 'right', 'any')
        >>> t
        
        >>> print(t.text)
        stuff
        >>> t.attrib
        [('bold', 5)]
        """
        super().__init__()
        self._cache_maxcol: int | None = None
        self.set_text(markup)
        self.set_layout(align, wrap, layout)

    def _repr_words(self) -> list[str]:
        """
        Show the text in the repr in python3 format (b prefix for byte strings) and truncate if it's too long
        """
        first = super()._repr_words()
        text = self.get_text()[0]
        rest = repr(text)
        if len(rest) > self._repr_content_length_max:
            rest = (
                rest[: self._repr_content_length_max * 2 // 3 - 3] + "..." + rest[-self._repr_content_length_max // 3 :]
            )
        return [*first, rest]

    def _repr_attrs(self) -> dict[str, typing.Any]:
        attrs = {
            **super()._repr_attrs(),
            "align": self._align_mode,
            "wrap": self._wrap_mode,
        }
        return remove_defaults(attrs, Text.__init__)

    def _invalidate(self) -> None:
        self._cache_maxcol = None
        super()._invalidate()

    def set_text(self, markup: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]) -> None:
        """
        Set content of text widget.

        :param markup: see :class:`Text` for description.
        :type markup: text markup

        >>> t = Text(u"foo")
        >>> print(t.text)
        foo
        >>> t.set_text(u"bar")
        >>> print(t.text)
        bar
        >>> t.text = u"baz"  # not supported because text stores text but set_text() takes markup
        Traceback (most recent call last):
        AttributeError: can't set attribute
        """
        self._text, self._attrib = decompose_tagmarkup(markup)
        self._invalidate()

    def get_text(self) -> tuple[str | bytes, list[tuple[Hashable, int]]]:
        """
        :returns: (*text*, *display attributes*)

            *text*
              complete bytes/unicode content of text widget

            *display attributes*
              run length encoded display attributes for *text*, eg.
              ``[('attr1', 10), ('attr2', 5)]``

        >>> Text(u"Hello").get_text() # ... = u in Python 2
        (...'Hello', [])
        >>> Text(('bright', u"Headline")).get_text()
        (...'Headline', [('bright', 8)])
        >>> Text([('a', u"one"), u"two", ('b', u"three")]).get_text()
        (...'onetwothree', [('a', 3), (None, 3), ('b', 5)])
        """
        return self._text, self._attrib

    @property
    def text(self) -> str | bytes:
        """
        Read-only property returning the complete bytes/unicode content
        of this widget
        """
        return self.get_text()[0]

    @property
    def attrib(self) -> list[tuple[Hashable, int]]:
        """
        Read-only property returning the run-length encoded display
        attributes of this widget
        """
        return self.get_text()[1]

    def set_align_mode(self, mode: Literal["left", "center", "right"] | Align) -> None:
        """
        Set text alignment mode. Supported modes depend on text layout
        object in use but defaults to a :class:`StandardTextLayout` instance

        :param mode: typically ``'left'``, ``'center'`` or ``'right'``
        :type mode: text alignment mode

        >>> t = Text(u"word")
        >>> t.set_align_mode('right')
        >>> t.align
        'right'
        >>> t.render((10,)).text # ... = b in Python 3
        [...'      word']
        >>> t.align = 'center'
        >>> t.render((10,)).text
        [...'   word   ']
        >>> t.align = 'somewhere'
        Traceback (most recent call last):
        TextError: Alignment mode 'somewhere' not supported.
        """
        if not self.layout.supports_align_mode(mode):
            raise TextError(f"Alignment mode {mode!r} not supported.")
        self._align_mode = mode
        self._invalidate()

    def set_wrap_mode(self, mode: Literal["space", "any", "clip", "ellipsis"] | WrapMode) -> None:
        """
        Set text wrapping mode. Supported modes depend on text layout
        object in use but defaults to a :class:`StandardTextLayout` instance

        :param mode: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``
        :type mode: text wrapping mode

        >>> t = Text(u"some words")
        >>> t.render((6,)).text # ... = b in Python 3
        [...'some  ', ...'words ']
        >>> t.set_wrap_mode('clip')
        >>> t.wrap
        'clip'
        >>> t.render((6,)).text
        [...'some w']
        >>> t.wrap = 'any'  # Urwid 0.9.9 or later
        >>> t.render((6,)).text
        [...'some w', ...'ords  ']
        >>> t.wrap = 'somehow'
        Traceback (most recent call last):
        TextError: Wrap mode 'somehow' not supported.
        """
        if not self.layout.supports_wrap_mode(mode):
            raise TextError(f"Wrap mode {mode!r} not supported.")
        self._wrap_mode = mode
        self._invalidate()

    def set_layout(
        self,
        align: Literal["left", "center", "right"] | Align,
        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode,
        layout: text_layout.TextLayout | None = None,
    ) -> None:
        """
        Set the text layout object, alignment and wrapping modes at
        the same time.

        :type align: text alignment mode
        :param wrap: typically 'space', 'any', 'clip' or 'ellipsis'
        :type wrap: text wrapping mode
        :param layout: defaults to a shared :class:`StandardTextLayout` instance
        :type layout: text layout instance

        >>> t = Text(u"hi")
        >>> t.set_layout('right', 'clip')
        >>> t
        
        """
        if layout is None:
            layout = text_layout.default_layout
        self._layout = layout
        self.set_align_mode(align)
        self.set_wrap_mode(wrap)

    align = property(lambda self: self._align_mode, set_align_mode)
    wrap = property(lambda self: self._wrap_mode, set_wrap_mode)

    @property
    def layout(self):
        return self._layout

    def render(
        self,
        size: tuple[int] | tuple[()],  # type: ignore[override]
        focus: bool = False,
    ) -> TextCanvas:
        """
        Render contents with wrapping and alignment.  Return canvas.

        See :meth:`Widget.render` for parameter details.

        >>> Text(u"important things").render((18,)).text
        [b'important things  ']
        >>> Text(u"important things").render((11,)).text
        [b'important  ', b'things     ']
        >>> Text("demo text").render(()).text
        [b'demo text']
        """
        text, attr = self.get_text()
        if size:
            (maxcol,) = size
        else:
            maxcol, _ = self.pack(focus=focus)

        trans = self.get_line_translation(maxcol, (text, attr))
        return apply_text_layout(text, attr, trans, maxcol)

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        """
        Return the number of rows the rendered text requires.

        See :meth:`Widget.rows` for parameter details.

        >>> Text(u"important things").rows((18,))
        1
        >>> Text(u"important things").rows((11,))
        2
        """
        (maxcol,) = size
        return len(self.get_line_translation(maxcol))

    def get_line_translation(
        self,
        maxcol: int,
        ta: tuple[str | bytes, list[tuple[Hashable, int]]] | None = None,
    ) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]:
        """
        Return layout structure used to map self.text to a canvas.
        This method is used internally, but may be useful for debugging custom layout classes.

        :param maxcol: columns available for display
        :type maxcol: int
        :param ta: ``None`` or the (*text*, *display attributes*) tuple
                   returned from :meth:`.get_text`
        :type ta: text and display attributes
        """
        if not self._cache_maxcol or self._cache_maxcol != maxcol:
            self._update_cache_translation(maxcol, ta)
        return self._cache_translation

    def _update_cache_translation(
        self,
        maxcol: int,
        ta: tuple[str | bytes, list[tuple[Hashable, int]]] | None,
    ) -> None:
        if ta:
            text, _attr = ta
        else:
            text, _attr = self.get_text()
        self._cache_maxcol = maxcol
        self._cache_translation = self.layout.layout(text, maxcol, self._align_mode, self._wrap_mode)

    def pack(
        self,
        size: tuple[()] | tuple[int] | None = None,  # type: ignore[override]
        focus: bool = False,
    ) -> tuple[int, int]:
        """
        Return the number of screen columns and rows required for
        this Text widget to be displayed without wrapping or
        clipping, as a single element tuple.

        :param size: ``None`` or ``()`` for unlimited screen columns (like FIXED sizing)
                     or (*maxcol*,) to specify a maximum column size
        :type size: widget size
        :param focus: widget is focused on
        :type focus: bool

        >>> Text(u"important things").pack()
        (16, 1)
        >>> Text(u"important things").pack((15,))
        (9, 2)
        >>> Text(u"important things").pack((8,))
        (8, 2)
        >>> Text(u"important things").pack(())
        (16, 1)
        """
        text, attr = self.get_text()

        if size:
            (maxcol,) = size
            if not hasattr(self.layout, "pack"):
                return maxcol, self.rows(size, focus)

            trans = self.get_line_translation(maxcol, (text, attr))
            cols = self.layout.pack(maxcol, trans)
            return (cols, len(trans))

        if text:
            if isinstance(text, bytes):
                text = text.decode(get_encoding())

            return (
                max(calc_width(line, 0, len(line)) for line in text.splitlines(keepends=False)),
                text.count("\n") + 1,
            )
        return 0, 1
urwid-2.6.16/urwid/widget/treetools.py000066400000000000000000000417531470350774000177700ustar00rootroot00000000000000# Generic TreeWidget/TreeWalker class
#    Copyright (c) 2010  Rob Lanphier
#    Copyright (C) 2004-2010  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


"""
Urwid tree view

Features:
- custom selectable widgets for trees
- custom list walker for displaying widgets in a tree fashion
"""


from __future__ import annotations

import typing

from .columns import Columns
from .constants import WHSettings
from .listbox import ListBox, ListWalker
from .padding import Padding
from .text import Text
from .widget import WidgetWrap
from .wimp import SelectableIcon

if typing.TYPE_CHECKING:
    from collections.abc import Hashable, Sequence

__all__ = ("ParentNode", "TreeListBox", "TreeNode", "TreeWalker", "TreeWidget", "TreeWidgetError")


class TreeWidgetError(RuntimeError):
    pass


class TreeWidget(WidgetWrap[Padding[typing.Union[Text, Columns]]]):
    """A widget representing something in a nested tree display."""

    indent_cols = 3
    unexpanded_icon = SelectableIcon("+", 0)
    expanded_icon = SelectableIcon("-", 0)

    def __init__(self, node: TreeNode) -> None:
        self._node = node
        self._innerwidget: Text | None = None
        self.is_leaf = not hasattr(node, "get_first_child")
        self.expanded = True
        widget = self.get_indented_widget()
        super().__init__(widget)

    def selectable(self) -> bool:
        """
        Allow selection of non-leaf nodes so children may be (un)expanded
        """
        return not self.is_leaf

    def get_indented_widget(self) -> Padding[Text | Columns]:
        widget = self.get_inner_widget()
        if not self.is_leaf:
            widget = Columns(
                [(1, [self.unexpanded_icon, self.expanded_icon][self.expanded]), widget],
                dividechars=1,
            )
        indent_cols = self.get_indent_cols()
        return Padding(widget, width=(WHSettings.RELATIVE, 100), left=indent_cols)

    def update_expanded_icon(self) -> None:
        """Update display widget text for parent widgets"""
        # icon is first element in columns indented widget
        icon = [self.unexpanded_icon, self.expanded_icon][self.expanded]
        self._w.base_widget.contents[0] = (icon, (WHSettings.GIVEN, 1, False))

    def get_indent_cols(self) -> int:
        return self.indent_cols * self.get_node().get_depth()

    def get_inner_widget(self) -> Text:
        if self._innerwidget is None:
            self._innerwidget = self.load_inner_widget()
        return self._innerwidget

    def load_inner_widget(self) -> Text:
        return Text(self.get_display_text())

    def get_node(self) -> TreeNode:
        return self._node

    def get_display_text(self) -> str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]:
        return f"{self.get_node().get_key()}: {self.get_node().get_value()!s}"

    def next_inorder(self) -> TreeWidget | None:
        """Return the next TreeWidget depth first from this one."""
        # first check if there's a child widget
        first_child = self.first_child()
        if first_child is not None:
            return first_child

        # now we need to hunt for the next sibling
        this_node = self.get_node()
        next_node = this_node.next_sibling()
        depth = this_node.get_depth()
        while next_node is None and depth > 0:
            # keep going up the tree until we find an ancestor next sibling
            this_node = this_node.get_parent()
            next_node = this_node.next_sibling()
            depth -= 1
            if depth != this_node.get_depth():
                raise ValueError(depth)
        if next_node is None:
            # we're at the end of the tree
            return None

        return next_node.get_widget()

    def prev_inorder(self) -> TreeWidget | None:
        """Return the previous TreeWidget depth first from this one."""
        this_node = self._node
        prev_node = this_node.prev_sibling()
        if prev_node is not None:
            # we need to find the last child of the previous widget if its
            # expanded
            prev_widget = prev_node.get_widget()
            last_child = prev_widget.last_child()
            if last_child is None:
                return prev_widget

            return last_child

        # need to hunt for the parent
        depth = this_node.get_depth()
        if prev_node is None and depth == 0:
            return None
        if prev_node is None:
            prev_node = this_node.get_parent()
        return prev_node.get_widget()

    def keypress(
        self,
        size: tuple[int] | tuple[()],
        key: str,
    ) -> str | None:
        """Handle expand & collapse requests (non-leaf nodes)"""
        if self.is_leaf:
            return key

        if key in {"+", "right"}:
            self.expanded = True
            self.update_expanded_icon()
            return None
        if key == "-":
            self.expanded = False
            self.update_expanded_icon()
            return None
        if self._w.selectable():
            return super().keypress(size, key)

        return key

    def mouse_event(
        self,
        size: tuple[int] | tuple[()],
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool:
        if self.is_leaf or event != "mouse press" or button != 1:
            return False

        if row == 0 and col == self.get_indent_cols():
            self.expanded = not self.expanded
            self.update_expanded_icon()
            return True

        return False

    def first_child(self) -> TreeWidget | None:
        """Return first child if expanded."""
        if self.is_leaf or not self.expanded:
            return None

        if self._node.has_children():
            first_node = self._node.get_first_child()
            return first_node.get_widget()

        return None

    def last_child(self) -> TreeWidget | None:
        """Return last child if expanded."""
        if self.is_leaf or not self.expanded:
            return None

        if self._node.has_children():
            last_child = self._node.get_last_child().get_widget()
        else:
            return None
        # recursively search down for the last descendant
        last_descendant = last_child.last_child()
        if last_descendant is None:
            return last_child

        return last_descendant


class TreeNode:
    """
    Store tree contents and cache TreeWidget objects.
    A TreeNode consists of the following elements:
    *  key: accessor token for parent nodes
    *  value: subclass-specific data
    *  parent: a TreeNode which contains a pointer back to this object
    *  widget: The widget used to render the object
    """

    def __init__(
        self,
        value: typing.Any,
        parent: ParentNode | None = None,
        key: Hashable | None = None,
        depth: int | None = None,
    ) -> None:
        self._key = key
        self._parent = parent
        self._value = value
        self._depth = depth
        self._widget: TreeWidget | None = None

    def get_widget(self, reload: bool = False) -> TreeWidget:
        """Return the widget for this node."""
        if self._widget is None or reload:
            self._widget = self.load_widget()
        return self._widget

    def load_widget(self) -> TreeWidget:
        return TreeWidget(self)

    def get_depth(self) -> int:
        if self._depth is self._parent is None:
            self._depth = 0
        elif self._depth is None:
            self._depth = self._parent.get_depth() + 1
        return self._depth

    def get_index(self) -> int | None:
        if self.get_depth() == 0:
            return None

        return self.get_parent().get_child_index(self.get_key())

    def get_key(self) -> Hashable | None:
        return self._key

    def set_key(self, key: Hashable | None) -> None:
        self._key = key

    def change_key(self, key: Hashable | None) -> None:
        self.get_parent().change_child_key(self._key, key)

    def get_parent(self) -> ParentNode:
        if self._parent is None and self.get_depth() > 0:
            self._parent = self.load_parent()
        return self._parent

    def load_parent(self):
        """Provide TreeNode with a parent for the current node.  This function
        is only required if the tree was instantiated from a child node
        (virtual function)"""
        raise TreeWidgetError("virtual function.  Implement in subclass")

    def get_value(self):
        return self._value

    def is_root(self) -> bool:
        return self.get_depth() == 0

    def next_sibling(self) -> TreeNode | None:
        if self.get_depth() > 0:
            return self.get_parent().next_child(self.get_key())

        return None

    def prev_sibling(self) -> TreeNode | None:
        if self.get_depth() > 0:
            return self.get_parent().prev_child(self.get_key())

        return None

    def get_root(self) -> ParentNode:
        root = self
        while root.get_parent() is not None:
            root = root.get_parent()
        return root


class ParentNode(TreeNode):
    """Maintain sort order for TreeNodes."""

    def __init__(
        self,
        value: typing.Any,
        parent: ParentNode | None = None,
        key: Hashable = None,
        depth: int | None = None,
    ) -> None:
        super().__init__(value, parent=parent, key=key, depth=depth)

        self._child_keys: Sequence[Hashable] | None = None
        self._children: dict[Hashable, TreeNode] = {}

    def get_child_keys(self, reload: bool = False) -> Sequence[Hashable]:
        """Return a possibly ordered list of child keys"""
        if self._child_keys is None or reload:
            self._child_keys = self.load_child_keys()
        return self._child_keys

    def load_child_keys(self) -> Sequence[Hashable]:
        """Provide ParentNode with an ordered list of child keys (virtual function)"""
        raise TreeWidgetError("virtual function.  Implement in subclass")

    def get_child_widget(self, key) -> TreeWidget:
        """Return the widget for a given key.  Create if necessary."""

        return self.get_child_node(key).get_widget()

    def get_child_node(self, key, reload: bool = False) -> TreeNode:
        """Return the child node for a given key. Create if necessary."""
        if key not in self._children or reload:
            self._children[key] = self.load_child_node(key)
        return self._children[key]

    def load_child_node(self, key: Hashable) -> TreeNode:
        """Load the child node for a given key (virtual function)"""
        raise TreeWidgetError("virtual function.  Implement in subclass")

    def set_child_node(self, key: Hashable, node: TreeNode) -> None:
        """Set the child node for a given key.

        Useful for bottom-up, lazy population of a tree.
        """
        self._children[key] = node

    def change_child_key(self, oldkey: Hashable, newkey: Hashable) -> None:
        if newkey in self._children:
            raise TreeWidgetError(f"{newkey} is already in use")
        self._children[newkey] = self._children.pop(oldkey)
        self._children[newkey].set_key(newkey)

    def get_child_index(self, key: Hashable) -> int:
        try:
            return self.get_child_keys().index(key)
        except ValueError as exc:
            raise TreeWidgetError(
                f"Can't find key {key} in ParentNode {self.get_key()}\nParentNode items: {self.get_child_keys()!s}"
            ).with_traceback(exc.__traceback__) from exc

    def next_child(self, key: Hashable) -> TreeNode | None:
        """Return the next child node in index order from the given key."""

        index = self.get_child_index(key)
        # the given node may have just been deleted
        if index is None:
            return None
        index += 1

        child_keys = self.get_child_keys()
        if index < len(child_keys):
            # get the next item at same level
            return self.get_child_node(child_keys[index])

        return None

    def prev_child(self, key: Hashable) -> TreeNode | None:
        """Return the previous child node in index order from the given key."""
        index = self.get_child_index(key)
        if index is None:
            return None

        child_keys = self.get_child_keys()
        index -= 1

        if index >= 0:
            # get the previous item at same level
            return self.get_child_node(child_keys[index])

        return None

    def get_first_child(self) -> TreeNode:
        """Return the first TreeNode in the directory."""
        child_keys = self.get_child_keys()
        return self.get_child_node(child_keys[0])

    def get_last_child(self) -> TreeNode:
        """Return the last TreeNode in the directory."""
        child_keys = self.get_child_keys()
        return self.get_child_node(child_keys[-1])

    def has_children(self) -> bool:
        """Does this node have any children?"""
        return len(self.get_child_keys()) > 0


class TreeWalker(ListWalker):
    """ListWalker-compatible class for displaying TreeWidgets

    positions are TreeNodes."""

    def __init__(self, start_from) -> None:
        """start_from: TreeNode with the initial focus."""
        self.focus = start_from

    def get_focus(self):
        widget = self.focus.get_widget()
        return widget, self.focus

    def set_focus(self, focus) -> None:
        self.focus = focus
        self._modified()

    # pylint: disable=arguments-renamed  # its bad, but we should not change API
    def get_next(self, start_from) -> tuple[TreeWidget, TreeNode] | tuple[None, None]:
        target = start_from.get_widget().next_inorder()
        if target is None:
            return None, None

        return target, target.get_node()

    def get_prev(self, start_from) -> tuple[TreeWidget, TreeNode] | tuple[None, None]:
        target = start_from.get_widget().prev_inorder()
        if target is None:
            return None, None

        return target, target.get_node()

    # pylint: enable=arguments-renamed


class TreeListBox(ListBox):
    """A ListBox with special handling for navigation and
    collapsing of TreeWidgets"""

    def keypress(
        self,
        size: tuple[int, int],  # type: ignore[override]
        key: str,
    ) -> str | None:
        key: str | None = super().keypress(size, key)
        return self.unhandled_input(size, key)

    def unhandled_input(self, size: tuple[int, int], data: str) -> str | None:
        """Handle macro-navigation keys"""
        if data == "left":
            self.move_focus_to_parent(size)
            return None
        if data == "-":
            self.collapse_focus_parent(size)
            return None

        return data

    def collapse_focus_parent(self, size: tuple[int, int]) -> None:
        """Collapse parent directory."""

        _widget, pos = self.body.get_focus()
        self.move_focus_to_parent(size)

        _pwidget, ppos = self.body.get_focus()
        if pos != ppos:
            self.keypress(size, "-")

    def move_focus_to_parent(self, size: tuple[int, int]) -> None:
        """Move focus to parent of widget in focus."""

        _widget, pos = self.body.get_focus()

        parentpos = pos.get_parent()

        if parentpos is None:
            return

        middle, top, _bottom = self.calculate_visible(size)

        row_offset, _focus_widget, _focus_pos, _focus_rows, _cursor = middle  # pylint: disable=unpacking-non-sequence
        _trim_top, fill_above = top  # pylint: disable=unpacking-non-sequence

        for _widget, pos, rows in fill_above:
            row_offset -= rows
            if pos == parentpos:
                self.change_focus(size, pos, row_offset)
                return

        self.change_focus(size, pos.get_parent())

    def _keypress_max_left(self, size: tuple[int, int]) -> None:
        self.focus_home(size)

    def _keypress_max_right(self, size: tuple[int, int]) -> None:
        self.focus_end(size)

    def focus_home(self, size: tuple[int, int]) -> None:
        """Move focus to very top."""

        _widget, pos = self.body.get_focus()
        rootnode = pos.get_root()
        self.change_focus(size, rootnode)

    def focus_end(self, size: tuple[int, int]) -> None:
        """Move focus to far bottom."""

        maxrow, _maxcol = size
        _widget, pos = self.body.get_focus()
        lastwidget = pos.get_root().get_widget().last_child()
        if lastwidget:
            lastnode = lastwidget.get_node()

            self.change_focus(size, lastnode, maxrow - 1)
urwid-2.6.16/urwid/widget/widget.py000066400000000000000000000715651470350774000172370ustar00rootroot00000000000000# Urwid basic widget classes
#    Copyright (C) 2004-2012  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import functools
import logging
import typing
import warnings
from operator import attrgetter

from urwid import signals
from urwid.canvas import Canvas, CanvasCache, CompositeCanvas
from urwid.command_map import command_map
from urwid.split_repr import split_repr
from urwid.util import MetaSuper

from .constants import Sizing

if typing.TYPE_CHECKING:
    from collections.abc import Callable, Hashable

WrappedWidget = typing.TypeVar("WrappedWidget")
LOGGER = logging.getLogger(__name__)


class WidgetMeta(MetaSuper, signals.MetaSignals):
    """
    Bases: :class:`MetaSuper`, :class:`MetaSignals`

    Automatic caching of render and rows methods.

    Class variable *no_cache* is a list of names of methods to not cache
    automatically.  Valid method names for *no_cache* are ``'render'`` and
    ``'rows'``.

    Class variable *ignore_focus* if defined and set to ``True`` indicates
    that the canvas this widget renders is not affected by the focus
    parameter, so it may be ignored when caching.
    """

    def __init__(cls, name, bases, d):
        no_cache = d.get("no_cache", [])

        super().__init__(name, bases, d)

        if "render" in d:
            if "render" not in no_cache:
                render_fn = cache_widget_render(cls)
            else:
                render_fn = nocache_widget_render(cls)
            cls.render = render_fn

        if "rows" in d and "rows" not in no_cache:
            cls.rows = cache_widget_rows(cls)
        if "no_cache" in d:
            del cls.no_cache
        if "ignore_focus" in d:
            del cls.ignore_focus


class WidgetError(Exception):
    """Widget specific errors."""


class WidgetWarning(Warning):
    """Widget specific warnings."""


def validate_size(widget, size, canv):
    """
    Raise a WidgetError if a canv does not match size.
    """
    if (size and size[1:] != (0,) and size[0] != canv.cols()) or (len(size) > 1 and size[1] != canv.rows()):
        raise WidgetError(
            f"Widget {widget!r} rendered ({canv.cols():d} x {canv.rows():d}) canvas when passed size {size!r}!"
        )


def cache_widget_render(cls):
    """
    Return a function that wraps the cls.render() method
    and fetches and stores canvases with CanvasCache.
    """
    ignore_focus = bool(getattr(cls, "ignore_focus", False))
    fn = cls.render

    @functools.wraps(fn)
    def cached_render(self, size, focus=False):
        focus = focus and not ignore_focus
        canv = CanvasCache.fetch(self, cls, size, focus)
        if canv:
            return canv

        canv = fn(self, size, focus=focus)
        validate_size(self, size, canv)
        if canv.widget_info:
            canv = CompositeCanvas(canv)
        canv.finalize(self, size, focus)
        CanvasCache.store(cls, canv)
        return canv

    cached_render.original_fn = fn
    return cached_render


def nocache_widget_render(cls):
    """
    Return a function that wraps the cls.render() method
    and finalizes the canvas that it returns.
    """
    fn = cls.render
    if hasattr(fn, "original_fn"):
        fn = fn.original_fn

    @functools.wraps(fn)
    def finalize_render(self, size, focus=False):
        canv = fn(self, size, focus=focus)
        if canv.widget_info:
            canv = CompositeCanvas(canv)
        validate_size(self, size, canv)
        canv.finalize(self, size, focus)
        return canv

    finalize_render.original_fn = fn
    return finalize_render


def nocache_widget_render_instance(self):
    """
    Return a function that wraps the cls.render() method
    and finalizes the canvas that it returns, but does not
    cache the canvas.
    """
    fn = self.render.original_fn

    @functools.wraps(fn)
    def finalize_render(size, focus=False):
        canv = fn(self, size, focus=focus)
        if canv.widget_info:
            canv = CompositeCanvas(canv)
        canv.finalize(self, size, focus)
        return canv

    finalize_render.original_fn = fn
    return finalize_render


def cache_widget_rows(cls):
    """
    Return a function that wraps the cls.rows() method
    and returns rows from the CanvasCache if available.
    """
    ignore_focus = bool(getattr(cls, "ignore_focus", False))
    fn = cls.rows

    @functools.wraps(fn)
    def cached_rows(self, size: tuple[int], focus: bool = False) -> int:
        focus = focus and not ignore_focus
        canv = CanvasCache.fetch(self, cls, size, focus)
        if canv:
            return canv.rows()

        return fn(self, size, focus)

    return cached_rows


class Widget(metaclass=WidgetMeta):
    """
    Widget base class

    .. attribute:: _selectable
       :annotation: = False

       The default :meth:`.selectable` method returns this value.

    .. attribute:: _sizing
       :annotation: = frozenset(['flow', 'box', 'fixed'])

       The default :meth:`.sizing` method returns this value.

    .. attribute:: _command_map
       :annotation: = urwid.command_map

       A shared :class:`CommandMap` instance. May be redefined in subclasses or widget instances.


    .. method:: rows(size, focus=False)

       .. note::

          This method is not implemented in :class:`.Widget` but
          must be implemented by any flow widget.  See :meth:`.sizing`.

       See :meth:`Widget.render` for parameter details.

       :returns: The number of rows required for this widget given a number of columns in *size*

       This is the method flow widgets use to communicate their size to other
       widgets without having to render a canvas. This should be a quick
       calculation as this function may be called a number of times in normal
       operation. If your implementation may take a long time you should add
       your own caching here.

       There is some metaclass magic defined in the :class:`Widget`
       metaclass :class:`WidgetMeta` that causes the
       result of this function to be retrieved from any
       canvas cached by :class:`CanvasCache`, so if your widget
       has been rendered you may not receive calls to this function. The class
       variable :attr:`ignore_focus` may be defined and set to ``True`` if this
       widget renders the same size regardless of the value of the *focus*
       parameter.

    .. method:: get_cursor_coords(size)

       .. note::

          This method is not implemented in :class:`.Widget` but
          must be implemented by any widget that may return cursor
          coordinates as part of the canvas that :meth:`render` returns.

       :param size: See :meth:`Widget.render` for details.
       :type size: widget size

       :returns: (*col*, *row*) if this widget has a cursor, ``None`` otherwise

       Return the cursor coordinates (*col*, *row*) of a cursor that will appear
       as part of the canvas rendered by this widget when in focus, or ``None``
       if no cursor is displayed.

       The :class:`ListBox` widget
       uses this method to make sure a cursor in the focus widget is not scrolled out of view.
       It is a separate method to avoid having to render the whole widget while calculating layout.

       Container widgets will typically call the :meth:`.get_cursor_coords` method on their focus widget.


    .. method:: get_pref_col(size)

       .. note::

          This method is not implemented in :class:`.Widget` but may be implemented by a subclass.

       :param size: See :meth:`Widget.render` for details.
       :type size: widget size

       :returns: a column number or ``'left'`` for the leftmost available
                 column or ``'right'`` for the rightmost available column

       Return the preferred column for the cursor to be displayed in this
       widget. This value might not be the same as the column returned from
       :meth:`get_cursor_coords`.

       The :class:`ListBox` and :class:`Pile`
       widgets call this method on a widget losing focus and use the value
       returned to call :meth:`.move_cursor_to_coords` on the widget becoming
       the focus. This allows the focus to move up and down through widgets
       while keeping the cursor in approximately the same column on screen.


    .. method:: move_cursor_to_coords(size, col, row)

       .. note::

          This method is not implemented in :class:`.Widget` but may be implemented by a subclass.
          Not implementing this method is equivalent to having a method that always returns
          ``False``.

       :param size: See :meth:`Widget.render` for details.
       :type size: widget size
       :param col: new column for the cursor, 0 is the left edge of this widget
       :type col: int
       :param row: new row for the cursor, 0 it the top row of this widget
       :type row: int

       :returns: ``True`` if the position was set successfully anywhere on *row*, ``False`` otherwise
    """

    _selectable = False
    _sizing = frozenset([Sizing.FLOW, Sizing.BOX, Sizing.FIXED])
    _command_map = command_map

    def __init__(self) -> None:
        self.logger = logging.getLogger(f"{self.__class__.__module__}.{self.__class__.__name__}")

    def _invalidate(self) -> None:
        """Mark cached canvases rendered by this widget as dirty so that they will not be used again."""
        CanvasCache.invalidate(self)

    def _emit(self, name: Hashable, *args) -> None:
        """Convenience function to emit signals with self as first argument."""
        signals.emit_signal(self, name, self, *args)

    def selectable(self) -> bool:
        """
        :returns: ``True`` if this is a widget that is designed to take the
                  focus, i.e. it contains something the user might want to
                  interact with, ``False`` otherwise,

        This default implementation returns :attr:`._selectable`.
        Subclasses may leave these is if the are not selectable,
        or if they are always selectable they may
        set the :attr:`_selectable` class variable to ``True``.

        If this method returns ``True`` then the :meth:`.keypress` method
        must be implemented.

        Returning ``False`` does not guarantee that this widget will never be in
        focus, only that this widget will usually be skipped over when changing
        focus. It is still possible for non selectable widgets to have the focus
        (typically when there are no other selectable widgets visible).
        """
        return self._selectable

    def sizing(self) -> frozenset[Sizing]:
        """
        :returns: A frozenset including one or more of ``'box'``, ``'flow'`` and
                  ``'fixed'``.  Default implementation returns the value of
                  :attr:`._sizing`, which for this class includes all three.

        The sizing modes returned indicate the modes that may be
        supported by this widget, but is not sufficient to know
        that using that sizing mode will work.  Subclasses should
        make an effort to remove sizing modes they know will not
        work given the state of the widget, but many do not yet
        do this.

        If a sizing mode is missing from the set then the widget
        should fail when used in that mode.

        If ``'flow'`` is among the values returned then the other
        methods in this widget must be able to accept a
        single-element tuple (*maxcol*,) to their ``size``
        parameter, and the :meth:`rows` method must be defined.

        If ``'box'`` is among the values returned then the other
        methods must be able to accept a two-element tuple
        (*maxcol*, *maxrow*) to their size parameter.

        If ``'fixed'`` is among the values returned then the other
        methods must be able to accept an empty tuple () to
        their size parameter, and the :meth:`pack` method must
        be defined.
        """
        return self._sizing

    def pack(self, size: tuple[()] | tuple[int] | tuple[int, int], focus: bool = False) -> tuple[int, int]:
        """
        See :meth:`Widget.render` for parameter details.

        :returns: A "packed" size (*maxcol*, *maxrow*) for this widget

        Calculate and return a minimum
        size where all content could still be displayed. Fixed widgets must
        implement this method and return their size when ``()`` is passed as the
        *size* parameter.

        This default implementation returns the *size* passed, or the *maxcol*
        passed and the value of :meth:`rows` as the *maxrow* when (*maxcol*,)
        is passed as the *size* parameter.

        .. note::

           This is a new method that hasn't been fully implemented across the
           standard widget types. In particular it has not yet been
           implemented for container widgets.

        :class:`Text` widgets have implemented this method.
        You can use :meth:`Text.pack` to calculate the minimum
        columns and rows required to display a text widget without wrapping,
        or call it iteratively to calculate the minimum number of columns
        required to display the text wrapped into a target number of rows.
        """
        if not size:
            if Sizing.FIXED in self.sizing():
                raise NotImplementedError(f"{self!r} must override Widget.pack()")
            raise WidgetError(f"Cannot pack () size, this is not a fixed widget: {self!r}")

        if len(size) == 1:
            if Sizing.FLOW in self.sizing():
                return (*size, self.rows(size, focus))  # pylint: disable=no-member  # can not announce abstract

            raise WidgetError(f"Cannot pack (maxcol,) size, this is not a flow widget: {self!r}")

        return size

    @property
    def base_widget(self) -> Widget:
        """Read-only property that steps through decoration widgets and returns the one at the base.

        This default implementation returns self.
        """
        return self

    @property
    def focus(self) -> Widget | None:
        """
        Read-only property returning the child widget in focus for container widgets.

        This default implementation always returns ``None``, indicating that this widget has no children.
        """
        return None

    def _not_a_container(self, val=None):
        raise IndexError(f"No focus_position, {self!r} is not a container widget")

    focus_position = property(
        _not_a_container,
        _not_a_container,
        doc="""
        Property for reading and setting the focus position for
        container widgets. This default implementation raises
        :exc:`IndexError`, making normal widgets fail the same way
        accessing :attr:`.focus_position` on an empty container widget would.
        """,
    )

    def __repr__(self):
        """A friendly __repr__ for widgets.

        Designed to be extended by subclasses with _repr_words and _repr_attr methods.
        """
        return split_repr(self)

    def _repr_words(self) -> list[str]:
        words = []
        if self.selectable():
            words = ["selectable", *words]
        if self.sizing() and self.sizing() != frozenset([Sizing.FLOW, Sizing.BOX, Sizing.FIXED]):
            words.append("/".join(sorted(self.sizing())))
        return [*words, "widget"]

    def _repr_attrs(self) -> dict[str, typing.Any]:
        return {}

    def keypress(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        key: str,
    ) -> str | None:
        """Keyboard input handler.

        :param size: See :meth:`Widget.render` for details
        :type size: tuple[()] | tuple[int] | tuple[int, int]
        :param key: a single keystroke value; see :ref:`keyboard-input`
        :type key: str
        :return: ``None`` if *key* was handled by *key* (the same value passed) if *key* was not handled
        :rtype: str | None
        """
        if not self.selectable():
            if hasattr(self, "logger"):
                self.logger.debug(f"keypress sent to non selectable widget {self!r}")
            else:
                warnings.warn(
                    f"Widget {self.__class__.__name__} did not call 'super().__init__()",
                    WidgetWarning,
                    stacklevel=3,
                )
                LOGGER.debug(f"Widget {self!r} is not selectable")
        return key

    def mouse_event(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        event: str,
        button: int,
        col: int,
        row: int,
        focus: bool,
    ) -> bool | None:
        """Mouse event handler.

        :param size: See :meth:`Widget.render` for details.
        :type size: tuple[()] | tuple[int] | tuple[int, int]
        :param event: Values such as ``'mouse press'``, ``'ctrl mouse press'``,
                     ``'mouse release'``, ``'meta mouse release'``,
                     ``'mouse drag'``; see :ref:`mouse-input`
        :type event: str
        :param button: 1 through 5 for press events, often 0 for release events
                      (which button was released is often not known)
        :type button: int
        :param col: Column of the event, 0 is the left edge of this widget
        :type col: int
        :param row: Row of the event, 0 it the top row of this widget
        :type row: int
        :param focus: Set to ``True`` if this widget or one of its children is in focus
        :type focus: bool
        :return: ``True`` if the event was handled by this widget, ``False`` otherwise
        :rtype: bool | None
        """
        if not self.selectable():
            if hasattr(self, "logger"):
                self.logger.debug(f"Widget {self!r} is not selectable")
            else:
                warnings.warn(
                    f"Widget {self.__class__.__name__} not called 'super().__init__()",
                    WidgetWarning,
                    stacklevel=3,
                )
                LOGGER.debug(f"Widget {self!r} is not selectable")
        return False

    def render(
        self,
        size: tuple[()] | tuple[int] | tuple[int, int],
        focus: bool = False,
    ) -> Canvas:
        """Render widget and produce canvas

        :param size: One of the following, *maxcol* and *maxrow* are integers > 0:

            (*maxcol*, *maxrow*)
              for box sizing -- the parent chooses the exact
              size of this widget

            (*maxcol*,)
              for flow sizing -- the parent chooses only the
              number of columns for this widget

            ()
              for fixed sizing -- this widget is a fixed size
              which can't be adjusted by the parent
        :type size: widget size
        :param focus: set to ``True`` if this widget or one of its children is in focus
        :type focus: bool

        :returns: A :class:`Canvas` subclass instance containing the rendered content of this widget

        :class:`Text` widgets return a :class:`TextCanvas` (arbitrary text and display attributes),
        :class:`SolidFill` widgets return a :class:`SolidCanvas` (a single character repeated across the whole surface)
        and container widgets return a :class:`CompositeCanvas` (one or more other canvases arranged arbitrarily).

        If *focus* is ``False``, the returned canvas may not have a cursor position set.

        There is some metaclass magic defined in the :class:`Widget` metaclass :class:`WidgetMeta`
        that causes the result of this method to be cached by :class:`CanvasCache`.
        Later calls will automatically look up the value in the cache first.

        As a small optimization the class variable :attr:`ignore_focus`
        may be defined and set to ``True`` if this widget renders the same
        canvas regardless of the value of the *focus* parameter.

        Any time the content of a widget changes it should call
        :meth:`_invalidate` to remove any cached canvases, or the widget
        may render the cached canvas instead of creating a new one.
        """
        raise NotImplementedError


class FlowWidget(Widget):
    """
    Deprecated.  Inherit from Widget and add:

        _sizing = frozenset(['flow'])

    at the top of your class definition instead.

    Base class of widgets that determine their rows from the number of
    columns available.
    """

    _sizing = frozenset([Sizing.FLOW])

    def __init__(self, *args, **kwargs):
        warnings.warn(
            """
            FlowWidget is deprecated. Inherit from Widget and add:

                _sizing = frozenset(['flow'])

            at the top of your class definition instead.""",
            DeprecationWarning,
            stacklevel=3,
        )
        super().__init__()

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        """
        All flow widgets must implement this function.
        """
        raise NotImplementedError()

    def render(self, size: tuple[int], focus: bool = False) -> Canvas:  # type: ignore[override]
        """
        All widgets must implement this function.
        """
        raise NotImplementedError()


class BoxWidget(Widget):
    """
    Deprecated.  Inherit from Widget and add:

        _sizing = frozenset(['box'])
        _selectable = True

    at the top of your class definition instead.

    Base class of width and height constrained widgets such as
    the top level widget attached to the display object
    """

    _selectable = True
    _sizing = frozenset([Sizing.BOX])

    def __init__(self, *args, **kwargs):
        warnings.warn(
            """
            BoxWidget is deprecated. Inherit from Widget and add:

                _sizing = frozenset(['box'])
                _selectable = True

            at the top of your class definition instead.""",
            DeprecationWarning,
            stacklevel=3,
        )
        super().__init__()

    def render(self, size: tuple[int, int], focus: bool = False) -> Canvas:  # type: ignore[override]
        """
        All widgets must implement this function.
        """
        raise NotImplementedError()


def fixed_size(size: tuple[()]) -> None:
    """
    raise ValueError if size != ().

    Used by FixedWidgets to test size parameter.
    """
    if size != ():
        raise ValueError(f"FixedWidget takes only () for size.passed: {size!r}")


class FixedWidget(Widget):
    """
    Deprecated.  Inherit from Widget and add:

        _sizing = frozenset(['fixed'])

    at the top of your class definition instead.

    Base class of widgets that know their width and height and
    cannot be resized
    """

    _sizing = frozenset([Sizing.FIXED])

    def __init__(self, *args, **kwargs):
        warnings.warn(
            """
            FixedWidget is deprecated. Inherit from Widget and add:

                _sizing = frozenset(['fixed'])

            at the top of your class definition instead.""",
            DeprecationWarning,
            stacklevel=3,
        )
        super().__init__()

    def render(self, size: tuple[()], focus: bool = False) -> Canvas:  # type: ignore[override]
        """
        All widgets must implement this function.
        """
        raise NotImplementedError()

    def pack(self, size: tuple[()] = (), focus: bool = False) -> tuple[int, int]:  # type: ignore[override]
        """
        All fixed widgets must implement this function.
        """
        raise NotImplementedError()


def delegate_to_widget_mixin(attribute_name: str) -> type[Widget]:
    """
    Return a mixin class that delegates all standard widget methods
    to an attribute given by attribute_name.

    This mixin is designed to be used as a superclass of another widget.
    """
    # FIXME: this is so common, let's add proper support for it
    # when layout and rendering are separated

    get_delegate = attrgetter(attribute_name)

    class DelegateToWidgetMixin(Widget):
        no_cache: typing.ClassVar[list[str]] = ["rows"]  # crufty metaclass work-around

        def render(self, size, focus: bool = False) -> CompositeCanvas:
            canv = get_delegate(self).render(size, focus=focus)
            return CompositeCanvas(canv)

        @property
        def selectable(self) -> Callable[[], bool]:
            return get_delegate(self).selectable

        @property
        def get_cursor_coords(self) -> Callable[[tuple[()] | tuple[int] | tuple[int, int]], tuple[int, int] | None]:
            # TODO(Aleksei):  Get rid of property usage after getting rid of "if getattr"
            return get_delegate(self).get_cursor_coords

        @property
        def get_pref_col(self) -> Callable[[tuple[()] | tuple[int] | tuple[int, int]], int | None]:
            # TODO(Aleksei):  Get rid of property usage after getting rid of "if getattr"
            return get_delegate(self).get_pref_col

        def keypress(self, size: tuple[()] | tuple[int] | tuple[int, int], key: str) -> str | None:
            return get_delegate(self).keypress(size, key)

        @property
        def move_cursor_to_coords(self) -> Callable[[[tuple[()] | tuple[int] | tuple[int, int], int, int]], bool]:
            # TODO(Aleksei):  Get rid of property usage after getting rid of "if getattr"
            return get_delegate(self).move_cursor_to_coords

        @property
        def rows(self) -> Callable[[tuple[int], bool], int]:
            return get_delegate(self).rows

        @property
        def mouse_event(
            self,
        ) -> Callable[[tuple[()] | tuple[int] | tuple[int, int], str, int, int, int, bool], bool | None]:
            # TODO(Aleksei):  Get rid of property usage after getting rid of "if getattr"
            return get_delegate(self).mouse_event

        @property
        def sizing(self) -> Callable[[], frozenset[Sizing]]:
            return get_delegate(self).sizing

        @property
        def pack(self) -> Callable[[tuple[()] | tuple[int] | tuple[int, int], bool], tuple[int, int]]:
            return get_delegate(self).pack

    return DelegateToWidgetMixin


class WidgetWrapError(Exception):
    pass


class WidgetWrap(delegate_to_widget_mixin("_wrapped_widget"), typing.Generic[WrappedWidget]):
    def __init__(self, w: WrappedWidget) -> None:
        """
        w -- widget to wrap, stored as self._w

        This object will pass the functions defined in Widget interface
        definition to self._w.

        The purpose of this widget is to provide a base class for
        widgets that compose other widgets for their display and
        behaviour.  The details of that composition should not affect
        users of the subclass.  The subclass may decide to expose some
        of the wrapped widgets by behaving like a ContainerWidget or
        WidgetDecoration, or it may hide them from outside access.
        """
        super().__init__()
        if not isinstance(w, Widget):
            obj_class_path = f"{w.__class__.__module__}.{w.__class__.__name__}"
            warnings.warn(
                f"{obj_class_path} is not subclass of Widget",
                DeprecationWarning,
                stacklevel=2,
            )
        self._wrapped_widget = w

    @property
    def _w(self) -> WrappedWidget:
        return self._wrapped_widget

    @_w.setter
    def _w(self, new_widget: WrappedWidget) -> None:
        """
        Change the wrapped widget.  This is meant to be called
        only by subclasses.

        >>> size = (10,)
        >>> ww = WidgetWrap(Edit("hello? ","hi"))
        >>> ww.render(size).text # ... = b in Python 3
        [...'hello? hi ']
        >>> ww.selectable()
        True
        >>> ww._w = Text("goodbye") # calls _set_w()
        >>> ww.render(size).text
        [...'goodbye   ']
        >>> ww.selectable()
        False
        """
        self._wrapped_widget = new_widget
        self._invalidate()

    def _set_w(self, w: WrappedWidget) -> None:
        """
        Change the wrapped widget.  This is meant to be called
        only by subclasses.
        >>> from urwid import Edit, Text
        >>> size = (10,)
        >>> ww = WidgetWrap(Edit("hello? ","hi"))
        >>> ww.render(size).text # ... = b in Python 3
        [...'hello? hi ']
        >>> ww.selectable()
        True
        >>> ww._w = Text("goodbye") # calls _set_w()
        >>> ww.render(size).text
        [...'goodbye   ']
        >>> ww.selectable()
        False
        """
        warnings.warn(
            "_set_w is deprecated. Please use 'WidgetWrap._w' property directly",
            DeprecationWarning,
            stacklevel=2,
        )
        self._wrapped_widget = w
        self._invalidate()


def _test():
    import doctest

    doctest.testmod()


if __name__ == "__main__":
    _test()
urwid-2.6.16/urwid/widget/widget_decoration.py000066400000000000000000000131501470350774000214300ustar00rootroot00000000000000from __future__ import annotations

import typing
import warnings

from urwid.canvas import CompositeCanvas

from .widget import Widget, WidgetError, WidgetWarning, delegate_to_widget_mixin

if typing.TYPE_CHECKING:
    from typing_extensions import Literal

    from .constants import Sizing


__all__ = (
    "WidgetDecoration",
    "WidgetDisable",
    "WidgetError",
    "WidgetPlaceholder",
    "WidgetWarning",
    "delegate_to_widget_mixin",
)

WrappedWidget = typing.TypeVar("WrappedWidget")


class WidgetDecoration(Widget, typing.Generic[WrappedWidget]):  # pylint: disable=abstract-method
    """
    original_widget -- the widget being decorated

    This is a base class for decoration widgets,
    widgets that contain one or more widgets and only ever have a single focus.
    This type of widget will affect the display or behaviour of the original_widget,
    but it is not part of determining a chain of focus.

    Don't actually do this -- use a WidgetDecoration subclass instead, these are not real widgets:

    >>> from urwid import Text
    >>> WidgetDecoration(Text(u"hi"))
    >

    .. Warning:
        WidgetDecoration do not implement ``render`` method.
        Implement it or forward to the widget in the subclass.
    """

    def __init__(self, original_widget: WrappedWidget) -> None:
        # TODO(Aleksei): reduce amount of multiple inheritance usage
        # Special case: subclasses with multiple inheritance causes `super` call wrong way
        # Call parent __init__ explicit
        Widget.__init__(self)
        if not isinstance(original_widget, Widget):
            obj_class_path = f"{original_widget.__class__.__module__}.{original_widget.__class__.__name__}"
            warnings.warn(
                f"{obj_class_path} is not subclass of Widget",
                DeprecationWarning,
                stacklevel=2,
            )
        self._original_widget = original_widget

    def _repr_words(self) -> list[str]:
        return [*super()._repr_words(), repr(self._original_widget)]

    @property
    def original_widget(self) -> WrappedWidget:
        return self._original_widget

    @original_widget.setter
    def original_widget(self, original_widget: WrappedWidget) -> None:
        self._original_widget = original_widget
        self._invalidate()

    def _get_original_widget(self) -> WrappedWidget:
        warnings.warn(
            f"Method `{self.__class__.__name__}._get_original_widget` is deprecated, "
            f"please use property `{self.__class__.__name__}.original_widget`",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.original_widget

    def _set_original_widget(self, original_widget: WrappedWidget) -> None:
        warnings.warn(
            f"Method `{self.__class__.__name__}._set_original_widget` is deprecated, "
            f"please use property `{self.__class__.__name__}.original_widget`",
            DeprecationWarning,
            stacklevel=2,
        )
        self.original_widget = original_widget

    @property
    def base_widget(self) -> Widget:
        """
        Return the widget without decorations.  If there is only one
        Decoration then this is the same as original_widget.

        >>> from urwid import Text
        >>> t = Text('hello')
        >>> wd1 = WidgetDecoration(t)
        >>> wd2 = WidgetDecoration(wd1)
        >>> wd3 = WidgetDecoration(wd2)
        >>> wd3.original_widget is wd2
        True
        >>> wd3.base_widget is t
        True
        """
        visited = {self}
        w = self
        while hasattr(w, "_original_widget"):
            w = w._original_widget
            if w in visited:
                break
            visited.add(w)
        return w

    def _get_base_widget(self) -> Widget:
        warnings.warn(
            f"Method `{self.__class__.__name__}._get_base_widget` is deprecated, "
            f"please use property `{self.__class__.__name__}.base_widget`",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.base_widget

    def selectable(self) -> bool:
        return self._original_widget.selectable()

    def sizing(self) -> frozenset[Sizing]:
        return self._original_widget.sizing()


class WidgetPlaceholder(delegate_to_widget_mixin("_original_widget"), WidgetDecoration[WrappedWidget]):
    """
    This is a do-nothing decoration widget that can be used for swapping
    between widgets without modifying the container of this widget.

    This can be useful for making an interface with a number of distinct
    pages or for showing and hiding menu or status bars.

    The widget displayed is stored as the self.original_widget property and
    can be changed by assigning a new widget to it.
    """


class WidgetDisable(WidgetDecoration[WrappedWidget]):
    """
    A decoration widget that disables interaction with the widget it
    wraps.  This widget always passes focus=False to the wrapped widget,
    even if it somehow does become the focus.
    """

    no_cache: typing.ClassVar[list[str]] = ["rows"]
    ignore_focus = True

    def selectable(self) -> Literal[False]:
        return False

    def rows(self, size: tuple[int], focus: bool = False) -> int:
        return self._original_widget.rows(size, False)

    def sizing(self) -> frozenset[Sizing]:
        return self._original_widget.sizing()

    def pack(self, size, focus: bool = False) -> tuple[int, int]:
        return self._original_widget.pack(size, False)

    def render(self, size, focus: bool = False) -> CompositeCanvas:
        canv = self._original_widget.render(size, False)
        return CompositeCanvas(canv)
urwid-2.6.16/urwid/widget/wimp.py000066400000000000000000000652511470350774000167230ustar00rootroot00000000000000# Urwid Window-Icon-Menu-Pointer-style widget classes
#    Copyright (C) 2004-2011  Ian Ward
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: https://urwid.org/


from __future__ import annotations

import typing

from urwid.canvas import CompositeCanvas
from urwid.command_map import Command
from urwid.signals import connect_signal
from urwid.text_layout import calc_coords
from urwid.util import is_mouse_press

from .columns import Columns
from .constants import Align, WrapMode
from .text import Text
from .widget import WidgetError, WidgetWrap

if typing.TYPE_CHECKING:
    from collections.abc import Callable, Hashable, MutableSequence

    from typing_extensions import Literal, Self

    from urwid.canvas import TextCanvas
    from urwid.text_layout import TextLayout

    _T = typing.TypeVar("_T")


class SelectableIcon(Text):
    ignore_focus = False
    _selectable = True

    def __init__(
        self,
        text: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        cursor_position: int = 0,
        align: Literal["left", "center", "right"] | Align = Align.LEFT,
        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = WrapMode.SPACE,
        layout: TextLayout | None = None,
    ) -> None:
        """
        :param text: markup for this widget; see :class:`Text` for
                     description of text markup
        :param cursor_position: position the cursor will appear in the
                                text when this widget is in focus
        :param align: typically ``'left'``, ``'center'`` or ``'right'``
        :type align: text alignment mode
        :param wrap: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``
        :type wrap: text wrapping mode
        :param layout: defaults to a shared :class:`StandardTextLayout` instance
        :type layout: text layout instance

        This is a text widget that is selectable.  A cursor
        displayed at a fixed location in the text when in focus.
        This widget has no special handling of keyboard or mouse input.
        """
        super().__init__(text, align=align, wrap=wrap, layout=layout)
        self._cursor_position = cursor_position

    def render(  # type: ignore[override]
        self,
        size: tuple[int] | tuple[()],  # type: ignore[override]
        focus: bool = False,
    ) -> TextCanvas | CompositeCanvas:  # type: ignore[override]
        """
        Render the text content of this widget with a cursor when
        in focus.

        >>> si = SelectableIcon(u"[!]")
        >>> si
        
        >>> si.render((4,), focus=True).cursor
        (0, 0)
        >>> si = SelectableIcon("((*))", 2)
        >>> si.render((8,), focus=True).cursor
        (2, 0)
        >>> si.render((2,), focus=True).cursor
        (0, 1)
        >>> si.render(()).cursor
        >>> si.render(()).text
        [b'((*))']
        >>> si.render((), focus=True).cursor
        (2, 0)
        """
        c: TextCanvas | CompositeCanvas = super().render(size, focus)
        if focus:
            # create a new canvas so we can add a cursor
            c = CompositeCanvas(c)
            c.cursor = self.get_cursor_coords(size)
        return c

    def get_cursor_coords(self, size: tuple[int] | tuple[()]) -> tuple[int, int] | None:
        """
        Return the position of the cursor if visible.  This method
        is required for widgets that display a cursor.
        """
        if self._cursor_position > len(self.text):
            return None
        # find out where the cursor will be displayed based on
        # the text layout
        if size:
            (maxcol,) = size
        else:
            maxcol, _ = self.pack()
        trans = self.get_line_translation(maxcol)
        x, y = calc_coords(self.text, trans, self._cursor_position)
        if maxcol <= x:
            return None
        return x, y

    def keypress(
        self,
        size: tuple[int] | tuple[()],  # type: ignore[override]
        key: str,
    ) -> str:
        """
        No keys are handled by this widget.  This method is
        required for selectable widgets.
        """
        return key


class CheckBoxError(WidgetError):
    pass


class CheckBox(WidgetWrap[Columns]):
    states: typing.ClassVar[dict[bool | Literal["mixed"], SelectableIcon]] = {
        True: SelectableIcon("[X]", 1),
        False: SelectableIcon("[ ]", 1),
        "mixed": SelectableIcon("[#]", 1),
    }
    reserve_columns = 4

    # allow users of this class to listen for change events
    # sent when the state of this widget is modified
    # (this variable is picked up by the MetaSignals metaclass)
    signals: typing.ClassVar[list[str]] = ["change", "postchange"]

    @typing.overload
    def __init__(
        self,
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        state: bool = False,
        has_mixed: typing.Literal[False] = False,
        on_state_change: Callable[[Self, bool, _T], typing.Any] | None = None,
        user_data: _T = ...,
        checked_symbol: str | None = ...,
    ) -> None: ...

    @typing.overload
    def __init__(
        self,
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        state: bool = False,
        has_mixed: typing.Literal[False] = False,
        on_state_change: Callable[[Self, bool], typing.Any] | None = None,
        user_data: None = None,
        checked_symbol: str | None = ...,
    ) -> None: ...

    @typing.overload
    def __init__(
        self,
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        state: typing.Literal["mixed"] | bool = False,
        has_mixed: typing.Literal[True] = True,
        on_state_change: Callable[[Self, bool | typing.Literal["mixed"], _T], typing.Any] | None = None,
        user_data: _T = ...,
        checked_symbol: str | None = ...,
    ) -> None: ...

    @typing.overload
    def __init__(
        self,
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        state: typing.Literal["mixed"] | bool = False,
        has_mixed: typing.Literal[True] = True,
        on_state_change: Callable[[Self, bool | typing.Literal["mixed"]], typing.Any] | None = None,
        user_data: None = None,
        checked_symbol: str | None = ...,
    ) -> None: ...

    def __init__(
        self,
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        state: bool | Literal["mixed"] = False,
        has_mixed: typing.Literal[False, True] = False,  # MyPy issue: Literal[True, False] is not equal `bool`
        on_state_change: (
            Callable[[Self, bool, _T], typing.Any]
            | Callable[[Self, bool], typing.Any]
            | Callable[[Self, bool | typing.Literal["mixed"], _T], typing.Any]
            | Callable[[Self, bool | typing.Literal["mixed"]], typing.Any]
            | None
        ) = None,
        user_data: _T | None = None,
        checked_symbol: str | None = None,
    ):
        """
        :param label: markup for check box label
        :param state: False, True or "mixed"
        :param has_mixed: True if "mixed" is a state to cycle through
        :param on_state_change: shorthand for connect_signal()
                                function call for a single callback
        :param user_data: user_data for on_state_change

        ..note:: `pack` method expect, that `Columns` backend widget is not modified from outside

        Signals supported: ``'change'``, ``"postchange"``

        Register signal handler with::

          urwid.connect_signal(check_box, 'change', callback, user_data)

        where callback is callback(check_box, new_state [,user_data])
        Unregister signal handlers with::

          urwid.disconnect_signal(check_box, 'change', callback, user_data)

        >>> CheckBox("Confirm")
        
        >>> CheckBox("Yogourt", "mixed", True)
        
        >>> cb = CheckBox("Extra onions", True)
        >>> cb
        
        >>> cb.render((20,), focus=True).text
        [b'[X] Extra onions    ']
        >>> CheckBox("Test", None)
        Traceback (most recent call last):
        ...
        ValueError: None not in (True, False, 'mixed')
        """
        if state not in self.states:
            raise ValueError(f"{state!r} not in {tuple(self.states.keys())}")

        self._label = Text(label)
        self.has_mixed = has_mixed

        self._state = state
        if checked_symbol:
            self.states[True] = SelectableIcon(f"[{checked_symbol}]", 1)
        # The old way of listening for a change was to pass the callback
        # in to the constructor.  Just convert it to the new way:
        if on_state_change:
            connect_signal(self, "change", on_state_change, user_data)

        # Initial create expect no callbacks call, create explicit
        super().__init__(
            Columns(
                [(self.reserve_columns, self.states[state]), self._label],
                focus_column=0,
            ),
        )

    def pack(self, size: tuple[()] | tuple[int] | None = None, focus: bool = False) -> tuple[str, str]:
        """Pack for widget.

        :param size: size data. Special case: None - get minimal widget size to fit
        :param focus: widget is focused

        >>> cb = CheckBox("test")
        >>> cb.pack((10,))
        (10, 1)
        >>> cb.pack()
        (8, 1)
        >>> ml_cb = CheckBox("Multi\\nline\\ncheckbox")
        >>> ml_cb.pack()
        (12, 3)
        >>> ml_cb.pack((), True)
        (12, 3)
        """
        return super().pack(size or (), focus)

    def _repr_words(self) -> list[str]:
        return [*super()._repr_words(), repr(self.label)]

    def _repr_attrs(self) -> dict[str, typing.Any]:
        return {**super()._repr_attrs(), "state": self.state}

    def set_label(self, label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]):
        """
        Change the check box label.

        label -- markup for label.  See Text widget for description
        of text markup.

        >>> cb = CheckBox(u"foo")
        >>> cb
        
        >>> cb.set_label(('bright_attr', u"bar"))
        >>> cb
        
        """
        self._label.set_text(label)
        # no need to call self._invalidate(). WidgetWrap takes care of
        # that when self.w changes

    def get_label(self):
        """
        Return label text.

        >>> cb = CheckBox(u"Seriously")
        >>> print(cb.get_label())
        Seriously
        >>> print(cb.label)
        Seriously
        >>> cb.set_label([('bright_attr', u"flashy"), u" normal"])
        >>> print(cb.label)  #  only text is returned
        flashy normal
        """
        return self._label.text

    label = property(get_label)

    def set_state(
        self,
        state: bool | Literal["mixed"],
        do_callback: bool = True,
    ) -> None:
        """
        Set the CheckBox state.

        state -- True, False or "mixed"
        do_callback -- False to suppress signal from this change

        >>> from urwid import disconnect_signal
        >>> changes = []
        >>> def callback_a(user_data, cb, state):
        ...     changes.append("A %r %r" % (state, user_data))
        >>> def callback_b(cb, state):
        ...     changes.append("B %r" % state)
        >>> cb = CheckBox('test', False, False)
        >>> key1 = connect_signal(cb, 'change', callback_a, user_args=("user_a",))
        >>> key2 = connect_signal(cb, 'change', callback_b)
        >>> cb.set_state(True) # both callbacks will be triggered
        >>> cb.state
        True
        >>> disconnect_signal(cb, 'change', callback_a, user_args=("user_a",))
        >>> cb.state = False
        >>> cb.state
        False
        >>> cb.set_state(True)
        >>> cb.state
        True
        >>> cb.set_state(False, False) # don't send signal
        >>> changes
        ["A True 'user_a'", 'B True', 'B False', 'B True']
        """
        if self._state == state:
            return

        if state not in self.states:
            raise CheckBoxError(f"{self!r} Invalid state: {state!r}")

        # self._state is None is a special case when the CheckBox
        # has just been created
        old_state = self._state
        if do_callback:
            self._emit("change", state)
        self._state = state
        # rebuild the display widget with the new state
        self._w = Columns([(self.reserve_columns, self.states[state]), self._label], focus_column=0)
        if do_callback:
            self._emit("postchange", old_state)

    def get_state(self) -> bool | Literal["mixed"]:
        """Return the state of the checkbox."""
        return self._state

    state = property(get_state, set_state)

    def keypress(self, size: tuple[int], key: str) -> str | None:
        """
        Toggle state on 'activate' command.

        >>> assert CheckBox._command_map[' '] == 'activate'
        >>> assert CheckBox._command_map['enter'] == 'activate'
        >>> size = (10,)
        >>> cb = CheckBox('press me')
        >>> cb.state
        False
        >>> cb.keypress(size, ' ')
        >>> cb.state
        True
        >>> cb.keypress(size, ' ')
        >>> cb.state
        False
        """
        if self._command_map[key] != Command.ACTIVATE:
            return key

        self.toggle_state()
        return None

    def toggle_state(self) -> None:
        """
        Cycle to the next valid state.

        >>> cb = CheckBox("3-state", has_mixed=True)
        >>> cb.state
        False
        >>> cb.toggle_state()
        >>> cb.state
        True
        >>> cb.toggle_state()
        >>> cb.state
        'mixed'
        >>> cb.toggle_state()
        >>> cb.state
        False
        """
        if self.state is False:
            self.set_state(True)
        elif self.state is True:
            if self.has_mixed:
                self.set_state("mixed")
            else:
                self.set_state(False)
        elif self.state == "mixed":
            self.set_state(False)

    def mouse_event(self, size: tuple[int], event: str, button: int, x: int, y: int, focus: bool) -> bool:
        """
        Toggle state on button 1 press.

        >>> size = (20,)
        >>> cb = CheckBox("clickme")
        >>> cb.state
        False
        >>> cb.mouse_event(size, 'mouse press', 1, 2, 0, True)
        True
        >>> cb.state
        True
        """
        if button != 1 or not is_mouse_press(event):
            return False
        self.toggle_state()
        return True


class RadioButton(CheckBox):
    states: typing.ClassVar[dict[bool | Literal["mixed"], SelectableIcon]] = {
        True: SelectableIcon("(X)", 1),
        False: SelectableIcon("( )", 1),
        "mixed": SelectableIcon("(#)", 1),
    }
    reserve_columns = 4

    @typing.overload
    def __init__(
        self,
        group: MutableSequence[RadioButton],
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        state: bool | Literal["first True"] = ...,
        on_state_change: Callable[[Self, bool, _T], typing.Any] | None = None,
        user_data: _T = ...,
    ) -> None: ...

    @typing.overload
    def __init__(
        self,
        group: MutableSequence[RadioButton],
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        state: bool | Literal["first True"] = ...,
        on_state_change: Callable[[Self, bool], typing.Any] | None = None,
        user_data: None = None,
    ) -> None: ...

    def __init__(
        self,
        group: MutableSequence[RadioButton],
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        state: bool | Literal["first True"] = "first True",
        on_state_change: Callable[[Self, bool, _T], typing.Any] | Callable[[Self, bool], typing.Any] | None = None,
        user_data: _T | None = None,
    ) -> None:
        """
        :param group: list for radio buttons in same group
        :param label: markup for radio button label
        :param state: False, True, "mixed" or "first True"
        :param on_state_change: shorthand for connect_signal()
                                function call for a single 'change' callback
        :param user_data: user_data for on_state_change

        ..note:: `pack` method expect, that `Columns` backend widget is not modified from outside

        This function will append the new radio button to group.
        "first True" will set to True if group is empty.

        Signals supported: ``'change'``, ``"postchange"``

        Register signal handler with::

          urwid.connect_signal(radio_button, 'change', callback, user_data)

        where callback is callback(radio_button, new_state [,user_data])
        Unregister signal handlers with::

          urwid.disconnect_signal(radio_button, 'change', callback, user_data)

        >>> bgroup = [] # button group
        >>> b1 = RadioButton(bgroup, u"Agree")
        >>> b2 = RadioButton(bgroup, u"Disagree")
        >>> len(bgroup)
        2
        >>> b1
        
        >>> b2
        
        >>> b2.render((15,), focus=True).text # ... = b in Python 3
        [...'( ) Disagree   ']
        """
        if state == "first True":
            state = not group

        self.group = group
        super().__init__(label, state, False, on_state_change, user_data)  # type: ignore[call-overload]
        group.append(self)

    def set_state(self, state: bool | Literal["mixed"], do_callback: bool = True) -> None:
        """
        Set the RadioButton state.

        state -- True, False or "mixed"

        do_callback -- False to suppress signal from this change

        If state is True all other radio buttons in the same button
        group will be set to False.

        >>> bgroup = [] # button group
        >>> b1 = RadioButton(bgroup, u"Agree")
        >>> b2 = RadioButton(bgroup, u"Disagree")
        >>> b3 = RadioButton(bgroup, u"Unsure")
        >>> b1.state, b2.state, b3.state
        (True, False, False)
        >>> b2.set_state(True)
        >>> b1.state, b2.state, b3.state
        (False, True, False)
        >>> def relabel_button(radio_button, new_state):
        ...     radio_button.set_label(u"Think Harder!")
        >>> key = connect_signal(b3, 'change', relabel_button)
        >>> b3
        
        >>> b3.set_state(True) # this will trigger the callback
        >>> b3
        
        """
        if self._state == state:
            return

        super().set_state(state, do_callback)

        # if we're clearing the state we don't have to worry about
        # other buttons in the button group
        if state is not True:
            return

        # clear the state of each other radio button
        for cb in self.group:
            if cb is self:
                continue
            if cb.state:
                cb.state = False

    def toggle_state(self) -> None:
        """
        Set state to True.

        >>> bgroup = [] # button group
        >>> b1 = RadioButton(bgroup, "Agree")
        >>> b2 = RadioButton(bgroup, "Disagree")
        >>> b1.state, b2.state
        (True, False)
        >>> b2.toggle_state()
        >>> b1.state, b2.state
        (False, True)
        >>> b2.toggle_state()
        >>> b1.state, b2.state
        (False, True)
        """
        self.set_state(True)


class Button(WidgetWrap[Columns]):
    button_left = Text("<")
    button_right = Text(">")

    signals: typing.ClassVar[list[str]] = ["click"]

    @typing.overload
    def __init__(
        self,
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        on_press: Callable[[Self, _T], typing.Any] | None = None,
        user_data: _T = ...,
        *,
        align: Literal["left", "center", "right"] | Align = ...,
        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = ...,
        layout: TextLayout | None = ...,
    ) -> None: ...

    @typing.overload
    def __init__(
        self,
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        on_press: Callable[[Self], typing.Any] | None = None,
        user_data: None = None,
        *,
        align: Literal["left", "center", "right"] | Align = ...,
        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = ...,
        layout: TextLayout | None = ...,
    ) -> None: ...

    def __init__(
        self,
        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
        on_press: Callable[[Self, _T], typing.Any] | Callable[[Self], typing.Any] | None = None,
        user_data: _T | None = None,
        *,
        align: Literal["left", "center", "right"] | Align = Align.LEFT,
        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = WrapMode.SPACE,
        layout: TextLayout | None = None,
    ) -> None:
        """
        :param label: markup for button label
        :param on_press: shorthand for connect_signal()
                         function call for a single callback
        :param user_data: user_data for on_press
        :param align: typically ``'left'``, ``'center'`` or ``'right'``
        :type align: label alignment mode
        :param wrap: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``
        :type wrap: label wrapping mode
        :param layout: defaults to a shared :class:`StandardTextLayout` instance
        :type layout: text layout instance

        ..note:: `pack` method expect, that `Columns` backend widget is not modified from outside

        Signals supported: ``'click'``

        Register signal handler with::

          urwid.connect_signal(button, 'click', callback, user_data)

        where callback is callback(button [,user_data])
        Unregister signal handlers with::

          urwid.disconnect_signal(button, 'click', callback, user_data)

        >>> from urwid.util import set_temporary_encoding
        >>> Button(u"Ok")